(cherry picked from commit 329d14957b)
# Conflicts:
# .editorconfig
# .git-blame-ignore-revs
# .github/helper/documentation.py
# .github/helper/install.sh
# .github/stale.yml
# .github/workflows/linters.yml
# .github/workflows/patch.yml
# .github/workflows/release.yml
# .github/workflows/release_notes.yml
# .github/workflows/server-tests-mariadb.yml
# .github/workflows/server-tests-postgres.yml
# .gitignore
# .mergify.yml
# .releaserc
# CODEOWNERS
# README.md
# erpnext/__init__.py
# erpnext/accounts/deferred_revenue.py
# erpnext/accounts/doctype/account/account.json
# erpnext/accounts/doctype/account/account.py
# erpnext/accounts/doctype/account/account_tree.js
# erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
# erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04.json
# erpnext/accounts/doctype/account/chart_of_accounts/verified/hu_chart_of_accounts_for_microenterprises_with_account_number.json
# erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json
# erpnext/accounts/doctype/account/chart_of_accounts/verified/ni_catalogo_de_cuentas.json
# erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py
# erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py
# erpnext/accounts/doctype/account/test_account.py
# erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json
# erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py
# erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json
# erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
# erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json
# erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json
# erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
# erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
# erpnext/accounts/doctype/accounting_period/accounting_period.json
# erpnext/accounts/doctype/accounting_period/test_accounting_period.py
# erpnext/accounts/doctype/accounts_settings/accounts_settings.json
# erpnext/accounts/doctype/accounts_settings/test_accounts_settings.py
# erpnext/accounts/doctype/advance_payment_ledger_entry/test_advance_payment_ledger_entry.py
# erpnext/accounts/doctype/advance_tax/advance_tax.json
# erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json
# erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py
# erpnext/accounts/doctype/allowed_dimension/allowed_dimension.json
# erpnext/accounts/doctype/allowed_to_transact_with/allowed_to_transact_with.json
# erpnext/accounts/doctype/applicable_on_account/applicable_on_account.json
# erpnext/accounts/doctype/bank/bank.json
# erpnext/accounts/doctype/bank/test_bank.py
# erpnext/accounts/doctype/bank_account/bank_account.json
# erpnext/accounts/doctype/bank_account/bank_account.py
# erpnext/accounts/doctype/bank_account/test_bank_account.py
# erpnext/accounts/doctype/bank_account_subtype/bank_account_subtype.json
# erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.py
# erpnext/accounts/doctype/bank_account_type/bank_account_type.json
# erpnext/accounts/doctype/bank_account_type/test_bank_account_type.py
# erpnext/accounts/doctype/bank_clearance/bank_clearance.json
# erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py
# erpnext/accounts/doctype/bank_clearance_detail/bank_clearance_detail.json
# erpnext/accounts/doctype/bank_guarantee/bank_guarantee.json
# erpnext/accounts/doctype/bank_guarantee/bank_guarantee.py
# erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.py
# erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
# erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
# erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
# erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json
# erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
# erpnext/accounts/doctype/bank_statement_import/test_bank_statement_import.py
# erpnext/accounts/doctype/bank_transaction/auto_match_party.py
# erpnext/accounts/doctype/bank_transaction/bank_transaction.py
# erpnext/accounts/doctype/bank_transaction/test_auto_match_party.py
# erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
# erpnext/accounts/doctype/bank_transaction_mapping/bank_transaction_mapping.json
# erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json
# erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.json
# erpnext/accounts/doctype/bisect_accounting_statements/bisect_accounting_statements.py
# erpnext/accounts/doctype/bisect_accounting_statements/test_bisect_accounting_statements.py
# erpnext/accounts/doctype/bisect_nodes/bisect_nodes.json
# erpnext/accounts/doctype/bisect_nodes/test_bisect_nodes.py
# erpnext/accounts/doctype/budget/budget.json
# erpnext/accounts/doctype/budget/test_budget.py
# erpnext/accounts/doctype/budget_account/budget_account.json
# erpnext/accounts/doctype/campaign_item/campaign_item.json
# erpnext/accounts/doctype/cashier_closing/cashier_closing.json
# erpnext/accounts/doctype/cashier_closing/test_cashier_closing.py
# erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.json
# erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.json
# erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.py
# erpnext/accounts/doctype/cheque_print_template/cheque_print_template.json
# erpnext/accounts/doctype/cheque_print_template/test_cheque_print_template.py
# erpnext/accounts/doctype/closed_document/closed_document.json
# erpnext/accounts/doctype/cost_center/cost_center.json
# erpnext/accounts/doctype/cost_center/test_cost_center.py
# erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json
# erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
# erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
# erpnext/accounts/doctype/coupon_code/coupon_code.json
# erpnext/accounts/doctype/coupon_code/coupon_code.py
# erpnext/accounts/doctype/coupon_code/test_coupon_code.py
# erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
# erpnext/accounts/doctype/currency_exchange_settings/test_currency_exchange_settings.py
# erpnext/accounts/doctype/currency_exchange_settings_details/currency_exchange_settings_details.json
# erpnext/accounts/doctype/currency_exchange_settings_result/currency_exchange_settings_result.json
# erpnext/accounts/doctype/customer_group_item/customer_group_item.json
# erpnext/accounts/doctype/customer_item/customer_item.json
# erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json
# erpnext/accounts/doctype/dunning/dunning.json
# erpnext/accounts/doctype/dunning/dunning.py
# erpnext/accounts/doctype/dunning/test_dunning.py
# erpnext/accounts/doctype/dunning_letter_text/dunning_letter_text.json
# erpnext/accounts/doctype/dunning_type/dunning_type.json
# erpnext/accounts/doctype/dunning_type/test_dunning_type.py
# erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
# erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json
# erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
# erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.py
# erpnext/accounts/doctype/exchange_rate_revaluation_account/exchange_rate_revaluation_account.json
# erpnext/accounts/doctype/finance_book/finance_book.json
# erpnext/accounts/doctype/finance_book/test_finance_book.py
# erpnext/accounts/doctype/fiscal_year/fiscal_year.json
# erpnext/accounts/doctype/fiscal_year/test_fiscal_year.py
# erpnext/accounts/doctype/fiscal_year_company/fiscal_year_company.json
# erpnext/accounts/doctype/gl_entry/test_gl_entry.py
# erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
# erpnext/accounts/doctype/invoice_discounting/invoice_discounting.json
# erpnext/accounts/doctype/invoice_discounting/test_invoice_discounting.py
# erpnext/accounts/doctype/item_tax_template/item_tax_template.json
# erpnext/accounts/doctype/item_tax_template/test_item_tax_template.py
# erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.json
# erpnext/accounts/doctype/journal_entry/journal_entry.js
# erpnext/accounts/doctype/journal_entry/journal_entry.json
# erpnext/accounts/doctype/journal_entry/journal_entry.py
# erpnext/accounts/doctype/journal_entry/journal_entry_list.js
# erpnext/accounts/doctype/journal_entry/test_journal_entry.py
# erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
# erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json
# erpnext/accounts/doctype/journal_entry_template/test_journal_entry_template.py
# erpnext/accounts/doctype/journal_entry_template_account/journal_entry_template_account.json
# erpnext/accounts/doctype/ledger_health/test_ledger_health.py
# erpnext/accounts/doctype/ledger_health_monitor/test_ledger_health_monitor.py
# erpnext/accounts/doctype/ledger_merge/ledger_merge.json
# erpnext/accounts/doctype/ledger_merge/test_ledger_merge.py
# erpnext/accounts/doctype/ledger_merge_accounts/ledger_merge_accounts.json
# erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json
# erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.py
# erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.py
# erpnext/accounts/doctype/loyalty_point_entry_redemption/loyalty_point_entry_redemption.json
# erpnext/accounts/doctype/loyalty_program/loyalty_program.json
# erpnext/accounts/doctype/loyalty_program/loyalty_program.py
# erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py
# erpnext/accounts/doctype/loyalty_program_collection/loyalty_program_collection.json
# erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json
# erpnext/accounts/doctype/mode_of_payment/test_mode_of_payment.py
# erpnext/accounts/doctype/mode_of_payment_account/mode_of_payment_account.json
# erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json
# erpnext/accounts/doctype/monthly_distribution/test_monthly_distribution.py
# erpnext/accounts/doctype/monthly_distribution_percentage/monthly_distribution_percentage.json
# erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json
# erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
# erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
# erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json
# erpnext/accounts/doctype/overdue_payment/overdue_payment.json
# erpnext/accounts/doctype/party_account/party_account.json
# erpnext/accounts/doctype/party_link/party_link.json
# erpnext/accounts/doctype/party_link/test_party_link.py
# erpnext/accounts/doctype/payment_entry/payment_entry.js
# erpnext/accounts/doctype/payment_entry/payment_entry.json
# erpnext/accounts/doctype/payment_entry/payment_entry.py
# erpnext/accounts/doctype/payment_entry/test_payment_entry.py
# erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
# erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
# erpnext/accounts/doctype/payment_gateway_account/payment_gateway_account.json
# erpnext/accounts/doctype/payment_gateway_account/test_payment_gateway_account.py
# erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
# erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
# erpnext/accounts/doctype/payment_order/payment_order.json
# erpnext/accounts/doctype/payment_order/test_payment_order.py
# erpnext/accounts/doctype/payment_order_reference/payment_order_reference.json
# erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
# erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
# erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
# erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
# erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
# erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
# erpnext/accounts/doctype/payment_request/payment_request.js
# erpnext/accounts/doctype/payment_request/payment_request.json
# erpnext/accounts/doctype/payment_request/payment_request.py
# erpnext/accounts/doctype/payment_request/payment_request_list.js
# erpnext/accounts/doctype/payment_request/test_payment_request.py
# erpnext/accounts/doctype/payment_schedule/payment_schedule.json
# erpnext/accounts/doctype/payment_term/payment_term.json
# erpnext/accounts/doctype/payment_term/test_payment_term.py
# erpnext/accounts/doctype/payment_terms_template/payment_terms_template.json
# erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.py
# erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
# erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json
# erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
# erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
# erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
# erpnext/accounts/doctype/pos_closing_entry_detail/pos_closing_entry_detail.json
# erpnext/accounts/doctype/pos_closing_entry_taxes/pos_closing_entry_taxes.json
# erpnext/accounts/doctype/pos_customer_group/pos_customer_group.json
# erpnext/accounts/doctype/pos_field/pos_field.json
# erpnext/accounts/doctype/pos_invoice/pos_invoice.js
# erpnext/accounts/doctype/pos_invoice/pos_invoice.json
# erpnext/accounts/doctype/pos_invoice/pos_invoice.py
# erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
# erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json
# erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.py
# erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
# erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
# erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
# erpnext/accounts/doctype/pos_invoice_reference/pos_invoice_reference.json
# erpnext/accounts/doctype/pos_item_group/pos_item_group.json
# erpnext/accounts/doctype/pos_opening_entry/pos_opening_entry.json
# erpnext/accounts/doctype/pos_opening_entry/test_pos_opening_entry.py
# erpnext/accounts/doctype/pos_opening_entry_detail/pos_opening_entry_detail.json
# erpnext/accounts/doctype/pos_payment_method/pos_payment_method.json
# erpnext/accounts/doctype/pos_profile/pos_profile.json
# erpnext/accounts/doctype/pos_profile/pos_profile.py
# erpnext/accounts/doctype/pos_profile/test_pos_profile.py
# erpnext/accounts/doctype/pos_profile_user/pos_profile_user.json
# erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.py
# erpnext/accounts/doctype/pos_search_fields/pos_search_fields.json
# erpnext/accounts/doctype/pos_settings/pos_settings.json
# erpnext/accounts/doctype/pos_settings/test_pos_settings.py
# erpnext/accounts/doctype/pricing_rule/pricing_rule.json
# erpnext/accounts/doctype/pricing_rule/pricing_rule.py
# erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
# erpnext/accounts/doctype/pricing_rule/utils.py
# erpnext/accounts/doctype/pricing_rule_brand/pricing_rule_brand.json
# erpnext/accounts/doctype/pricing_rule_detail/pricing_rule_detail.json
# erpnext/accounts/doctype/pricing_rule_item_code/pricing_rule_item_code.json
# erpnext/accounts/doctype/pricing_rule_item_group/pricing_rule_item_group.json
# erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.json
# erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
# erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.json
# erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py
# erpnext/accounts/doctype/process_payment_reconciliation/test_process_payment_reconciliation.py
# erpnext/accounts/doctype/process_payment_reconciliation_log/process_payment_reconciliation_log.json
# erpnext/accounts/doctype/process_payment_reconciliation_log/test_process_payment_reconciliation_log.py
# erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json
# erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
# erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
# erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html
# erpnext/accounts/doctype/process_statement_of_accounts/test_process_statement_of_accounts.py
# erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
# erpnext/accounts/doctype/process_subscription/process_subscription.json
# erpnext/accounts/doctype/process_subscription/test_process_subscription.py
# erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json
# erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
# erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
# erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
# erpnext/accounts/doctype/psoa_cost_center/psoa_cost_center.json
# erpnext/accounts/doctype/psoa_project/psoa_project.json
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
# erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
# erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
# erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
# erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
# erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
# erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json
# erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py
# erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json
# erpnext/accounts/doctype/purchase_taxes_and_charges_template/test_purchase_taxes_and_charges_template.py
# erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
# erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
# erpnext/accounts/doctype/repost_accounting_ledger_items/repost_accounting_ledger_items.json
# erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json
# erpnext/accounts/doctype/repost_accounting_ledger_settings/test_repost_accounting_ledger_settings.py
# erpnext/accounts/doctype/repost_allowed_types/repost_allowed_types.json
# erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
# erpnext/accounts/doctype/repost_payment_ledger/test_repost_payment_ledger.py
# erpnext/accounts/doctype/repost_payment_ledger_items/repost_payment_ledger_items.json
# erpnext/accounts/doctype/sales_invoice/sales_invoice.js
# erpnext/accounts/doctype/sales_invoice/sales_invoice.json
# erpnext/accounts/doctype/sales_invoice/sales_invoice.py
# erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
# erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
# erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
# erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
# erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.py
# erpnext/accounts/doctype/sales_invoice_payment/sales_invoice_payment.json
# erpnext/accounts/doctype/sales_invoice_timesheet/sales_invoice_timesheet.json
# erpnext/accounts/doctype/sales_partner_item/sales_partner_item.json
# erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
# erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py
# erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
# erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
# erpnext/accounts/doctype/sales_taxes_and_charges_template/test_sales_taxes_and_charges_template.py
# erpnext/accounts/doctype/share_balance/share_balance.json
# erpnext/accounts/doctype/share_transfer/share_transfer.json
# erpnext/accounts/doctype/share_transfer/test_share_transfer.py
# erpnext/accounts/doctype/share_type/share_type.json
# erpnext/accounts/doctype/share_type/test_share_type.py
# erpnext/accounts/doctype/shareholder/shareholder.json
# erpnext/accounts/doctype/shareholder/test_shareholder.py
# erpnext/accounts/doctype/shipping_rule/shipping_rule.json
# erpnext/accounts/doctype/shipping_rule/shipping_rule.py
# erpnext/accounts/doctype/shipping_rule/test_shipping_rule.py
# erpnext/accounts/doctype/shipping_rule_condition/shipping_rule_condition.json
# erpnext/accounts/doctype/shipping_rule_country/shipping_rule_country.json
# erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json
# erpnext/accounts/doctype/subscription/subscription.json
# erpnext/accounts/doctype/subscription/subscription.py
# erpnext/accounts/doctype/subscription/subscription_list.js
# erpnext/accounts/doctype/subscription/test_subscription.py
# erpnext/accounts/doctype/subscription_invoice/subscription_invoice.json
# erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.py
# erpnext/accounts/doctype/subscription_plan/subscription_plan.json
# erpnext/accounts/doctype/subscription_plan/test_subscription_plan.py
# erpnext/accounts/doctype/subscription_plan_detail/subscription_plan_detail.json
# erpnext/accounts/doctype/subscription_settings/subscription_settings.json
# erpnext/accounts/doctype/subscription_settings/test_subscription_settings.py
# erpnext/accounts/doctype/supplier_group_item/supplier_group_item.json
# erpnext/accounts/doctype/supplier_item/supplier_item.json
# erpnext/accounts/doctype/tax_category/tax_category.json
# erpnext/accounts/doctype/tax_category/test_tax_category.py
# erpnext/accounts/doctype/tax_rule/tax_rule.json
# erpnext/accounts/doctype/tax_rule/tax_rule.py
# erpnext/accounts/doctype/tax_rule/test_tax_rule.py
# erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json
# erpnext/accounts/doctype/tax_withholding_account/tax_withholding_account.json
# erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
# erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
# erpnext/accounts/doctype/tax_withholding_rate/tax_withholding_rate.json
# erpnext/accounts/doctype/territory_item/territory_item.json
# erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json
# erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
# erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.json
# erpnext/accounts/doctype/unreconcile_payment/unreconcile_payment.py
# erpnext/accounts/doctype/unreconcile_payment_entries/unreconcile_payment_entries.json
# erpnext/accounts/general_ledger.py
# erpnext/accounts/notification/notification_for_new_fiscal_year/notification_for_new_fiscal_year.json
# erpnext/accounts/party.py
# erpnext/accounts/print_format/dunning_letter/dunning_letter.json
# erpnext/accounts/print_format/sales_invoice_return/sales_invoice_return.html
# erpnext/accounts/report/account_balance/account_balance.js
# erpnext/accounts/report/account_balance/account_balance.py
# erpnext/accounts/report/account_balance/test_account_balance.py
# erpnext/accounts/report/accounts_payable/accounts_payable.js
# erpnext/accounts/report/accounts_payable/test_accounts_payable.py
# erpnext/accounts/report/accounts_receivable/accounts_receivable.js
# erpnext/accounts/report/accounts_receivable/accounts_receivable.py
# erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
# erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
# erpnext/accounts/report/accounts_receivable_summary/test_accounts_receivable_summary.py
# erpnext/accounts/report/balance_sheet/test_balance_sheet.py
# erpnext/accounts/report/bank_reconciliation_statement/test_bank_reconciliation_statement.py
# erpnext/accounts/report/cash_flow/cash_flow.py
# erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
# erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
# erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py
# erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
# erpnext/accounts/report/general_and_payment_ledger_comparison/test_general_and_payment_ledger_comparison.py
# erpnext/accounts/report/general_ledger/general_ledger.html
# erpnext/accounts/report/general_ledger/general_ledger.py
# erpnext/accounts/report/general_ledger/test_general_ledger.py
# erpnext/accounts/report/gross_and_net_profit_report/gross_and_net_profit_report.py
# erpnext/accounts/report/gross_profit/test_gross_profit.py
# erpnext/accounts/report/item_wise_purchase_register/test_item_wise_purchase_register.py
# erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
# erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
# erpnext/accounts/report/item_wise_sales_register/test_item_wise_sales_register.py
# erpnext/accounts/report/payment_ledger/test_payment_ledger.py
# erpnext/accounts/report/profit_and_loss_statement/test_profit_and_loss_statement.py
# erpnext/accounts/report/purchase_register/test_purchase_register.py
# erpnext/accounts/report/sales_payment_summary/test_sales_payment_summary.py
# erpnext/accounts/report/sales_register/test_sales_register.py
# erpnext/accounts/report/tax_withholding_details/tax_withholding_details.py
# erpnext/accounts/report/tax_withholding_details/test_tax_withholding_details.py
# erpnext/accounts/report/trial_balance/test_trial_balance.py
# erpnext/accounts/test/accounts_mixin.py
# erpnext/accounts/test/test_reports.py
# erpnext/accounts/test/test_utils.py
# erpnext/accounts/test_party.py
# erpnext/accounts/utils.py
# erpnext/assets/doctype/asset/asset.js
# erpnext/assets/doctype/asset/asset.json
# erpnext/assets/doctype/asset/asset.py
# erpnext/assets/doctype/asset/depreciation.py
# erpnext/assets/doctype/asset/test_asset.py
# erpnext/assets/doctype/asset_activity/asset_activity.json
# erpnext/assets/doctype/asset_activity/test_asset_activity.py
# erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
# erpnext/assets/doctype/asset_capitalization/asset_capitalization.json
# erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
# erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py
# erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json
# erpnext/assets/doctype/asset_capitalization_service_item/asset_capitalization_service_item.json
# erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
# erpnext/assets/doctype/asset_category/asset_category.json
# erpnext/assets/doctype/asset_category/test_asset_category.py
# erpnext/assets/doctype/asset_category_account/asset_category_account.json
# erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
# erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py
# erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
# erpnext/assets/doctype/asset_maintenance/asset_maintenance.json
# erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
# erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.py
# erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.json
# erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.py
# erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
# erpnext/assets/doctype/asset_maintenance_team/asset_maintenance_team.json
# erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.py
# erpnext/assets/doctype/asset_movement/asset_movement.json
# erpnext/assets/doctype/asset_movement/asset_movement.py
# erpnext/assets/doctype/asset_movement/test_asset_movement.py
# erpnext/assets/doctype/asset_movement_item/asset_movement_item.json
# erpnext/assets/doctype/asset_repair/asset_repair.js
# erpnext/assets/doctype/asset_repair/asset_repair.json
# erpnext/assets/doctype/asset_repair/asset_repair.py
# erpnext/assets/doctype/asset_repair/test_asset_repair.py
# erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
# erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json
# erpnext/assets/doctype/asset_shift_allocation/test_asset_shift_allocation.py
# erpnext/assets/doctype/asset_shift_factor/asset_shift_factor.json
# erpnext/assets/doctype/asset_shift_factor/test_asset_shift_factor.py
# erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
# erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json
# erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
# erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
# erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
# erpnext/assets/doctype/linked_location/linked_location.json
# erpnext/assets/doctype/location/location.json
# erpnext/assets/doctype/location/test_location.py
# erpnext/assets/doctype/maintenance_team_member/maintenance_team_member.json
# erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.py
# erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
# erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json
# erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
# erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json
# erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/test_bulk_transaction_log_detail.py
# erpnext/buying/doctype/buying_settings/buying_settings.json
# erpnext/buying/doctype/buying_settings/test_buying_settings.py
# erpnext/buying/doctype/purchase_order/purchase_order.js
# erpnext/buying/doctype/purchase_order/purchase_order.json
# erpnext/buying/doctype/purchase_order/purchase_order.py
# erpnext/buying/doctype/purchase_order/purchase_order_list.js
# erpnext/buying/doctype/purchase_order/test_purchase_order.py
# erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
# erpnext/buying/doctype/purchase_order_item/purchase_order_item.py
# erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json
# erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
# erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
# erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
# erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
# erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
# erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json
# erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json
# erpnext/buying/doctype/supplier/supplier.json
# erpnext/buying/doctype/supplier/supplier_dashboard.py
# erpnext/buying/doctype/supplier/test_supplier.py
# erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
# erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
# erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
# erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
# erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json
# erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.py
# erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.json
# erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
# erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
# erpnext/buying/doctype/supplier_scorecard_criteria/supplier_scorecard_criteria.json
# erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
# erpnext/buying/doctype/supplier_scorecard_period/supplier_scorecard_period.json
# erpnext/buying/doctype/supplier_scorecard_period/test_supplier_scorecard_period.py
# erpnext/buying/doctype/supplier_scorecard_scoring_criteria/supplier_scorecard_scoring_criteria.json
# erpnext/buying/doctype/supplier_scorecard_scoring_standing/supplier_scorecard_scoring_standing.json
# erpnext/buying/doctype/supplier_scorecard_scoring_variable/supplier_scorecard_scoring_variable.json
# erpnext/buying/doctype/supplier_scorecard_standing/supplier_scorecard_standing.json
# erpnext/buying/doctype/supplier_scorecard_standing/test_supplier_scorecard_standing.py
# erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.json
# erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
# erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
# erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
# erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
# erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.py
# erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
# erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
# erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
# erpnext/buying/utils.py
# erpnext/communication/doctype/communication_medium/communication_medium.json
# erpnext/communication/doctype/communication_medium_timeslot/communication_medium_timeslot.json
# erpnext/controllers/accounts_controller.py
# erpnext/controllers/buying_controller.py
# erpnext/controllers/queries.py
# erpnext/controllers/sales_and_purchase_return.py
# erpnext/controllers/selling_controller.py
# erpnext/controllers/status_updater.py
# erpnext/controllers/stock_controller.py
# erpnext/controllers/subcontracting_controller.py
# erpnext/controllers/taxes_and_totals.py
# erpnext/controllers/tests/test_accounts_controller.py
# erpnext/controllers/tests/test_item_variant.py
# erpnext/controllers/tests/test_mapper.py
# erpnext/controllers/tests/test_qty_based_taxes.py
# erpnext/controllers/tests/test_queries.py
# erpnext/controllers/tests/test_subcontracting_controller.py
# erpnext/controllers/tests/test_transaction_base.py
# erpnext/controllers/website_list_for_contact.py
# erpnext/crm/dashboard_chart/lead_source/lead_source.json
# erpnext/crm/dashboard_chart/opportunities_via_campaigns/opportunities_via_campaigns.json
# erpnext/crm/doctype/appointment/appointment.json
# erpnext/crm/doctype/appointment/appointment.py
# erpnext/crm/doctype/appointment/test_appointment.py
# erpnext/crm/doctype/appointment_booking_settings/appointment_booking_settings.json
# erpnext/crm/doctype/appointment_booking_settings/test_appointment_booking_settings.py
# erpnext/crm/doctype/appointment_booking_slots/appointment_booking_slots.json
# erpnext/crm/doctype/availability_of_slots/availability_of_slots.json
# erpnext/crm/doctype/campaign/campaign.js
# erpnext/crm/doctype/campaign/campaign.json
# erpnext/crm/doctype/campaign/campaign.py
# erpnext/crm/doctype/campaign/test_campaign.py
# erpnext/crm/doctype/campaign_email_schedule/campaign_email_schedule.json
# erpnext/crm/doctype/competitor/competitor.json
# erpnext/crm/doctype/competitor/test_competitor.py
# erpnext/crm/doctype/competitor_detail/competitor_detail.json
# erpnext/crm/doctype/contract/contract.json
# erpnext/crm/doctype/contract/test_contract.py
# erpnext/crm/doctype/contract_fulfilment_checklist/contract_fulfilment_checklist.json
# erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.py
# erpnext/crm/doctype/contract_template/contract_template.json
# erpnext/crm/doctype/contract_template/test_contract_template.py
# erpnext/crm/doctype/contract_template_fulfilment_terms/contract_template_fulfilment_terms.json
# erpnext/crm/doctype/crm_note/crm_note.json
# erpnext/crm/doctype/crm_settings/crm_settings.json
# erpnext/crm/doctype/crm_settings/test_crm_settings.py
# erpnext/crm/doctype/email_campaign/email_campaign.json
# erpnext/crm/doctype/email_campaign/email_campaign.py
# erpnext/crm/doctype/email_campaign/test_email_campaign.py
# erpnext/crm/doctype/lead/lead.json
# erpnext/crm/doctype/lead/lead.py
# erpnext/crm/doctype/lead/test_lead.py
# erpnext/crm/doctype/lost_reason_detail/lost_reason_detail.json
# erpnext/crm/doctype/market_segment/market_segment.json
# erpnext/crm/doctype/market_segment/test_market_segment.py
# erpnext/crm/doctype/opportunity/opportunity.js
# erpnext/crm/doctype/opportunity/opportunity.json
# erpnext/crm/doctype/opportunity/opportunity.py
# erpnext/crm/doctype/opportunity/test_opportunity.py
# erpnext/crm/doctype/opportunity_item/opportunity_item.json
# erpnext/crm/doctype/opportunity_lost_reason/opportunity_lost_reason.json
# erpnext/crm/doctype/opportunity_lost_reason_detail/opportunity_lost_reason_detail.json
# erpnext/crm/doctype/opportunity_type/opportunity_type.json
# erpnext/crm/doctype/opportunity_type/test_opportunity_type.py
# erpnext/crm/doctype/prospect/prospect.json
# erpnext/crm/doctype/prospect/test_prospect.py
# erpnext/crm/doctype/prospect_lead/prospect_lead.json
# erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.json
# erpnext/crm/doctype/sales_stage/sales_stage.json
# erpnext/crm/doctype/sales_stage/test_sales_stage.py
# erpnext/crm/doctype/utils.py
# erpnext/crm/report/campaign_efficiency/campaign_efficiency.py
# erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
# erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
# erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py
# erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js
# erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
# erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py
# erpnext/crm/workspace/crm/crm.json
# erpnext/edi/doctype/code_list/code_list_import.py
# erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json
# erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
# erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.json
# erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.py
# erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.json
# erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
# erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.py
# erpnext/erpnext_integrations/utils.py
# erpnext/hooks.py
# erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.json
# erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
# erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
# erpnext/maintenance/doctype/maintenance_schedule_detail/maintenance_schedule_detail.json
# erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json
# erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
# erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
# erpnext/maintenance/doctype/maintenance_visit/test_maintenance_visit.py
# erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
# erpnext/manufacturing/doctype/blanket_order/blanket_order.json
# erpnext/manufacturing/doctype/blanket_order/blanket_order.py
# erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
# erpnext/manufacturing/doctype/blanket_order_item/blanket_order_item.json
# erpnext/manufacturing/doctype/bom/bom.js
# erpnext/manufacturing/doctype/bom/bom.json
# erpnext/manufacturing/doctype/bom/bom.py
# erpnext/manufacturing/doctype/bom/test_bom.py
# erpnext/manufacturing/doctype/bom_creator/bom_creator.js
# erpnext/manufacturing/doctype/bom_creator/bom_creator.json
# erpnext/manufacturing/doctype/bom_creator/bom_creator.py
# erpnext/manufacturing/doctype/bom_creator/test_bom_creator.py
# erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json
# erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py
# erpnext/manufacturing/doctype/bom_explosion_item/bom_explosion_item.json
# erpnext/manufacturing/doctype/bom_item/bom_item.json
# erpnext/manufacturing/doctype/bom_item/bom_item.py
# erpnext/manufacturing/doctype/bom_operation/bom_operation.json
# erpnext/manufacturing/doctype/bom_operation/bom_operation.py
# erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
# erpnext/manufacturing/doctype/bom_update_batch/bom_update_batch.json
# erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
# erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
# erpnext/manufacturing/doctype/bom_update_log/test_bom_update_log.py
# erpnext/manufacturing/doctype/bom_update_tool/bom_update_tool.json
# erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
# erpnext/manufacturing/doctype/bom_website_item/bom_website_item.json
# erpnext/manufacturing/doctype/bom_website_operation/bom_website_operation.json
# erpnext/manufacturing/doctype/downtime_entry/downtime_entry.json
# erpnext/manufacturing/doctype/downtime_entry/test_downtime_entry.py
# erpnext/manufacturing/doctype/job_card/job_card.js
# erpnext/manufacturing/doctype/job_card/job_card.json
# erpnext/manufacturing/doctype/job_card/job_card.py
# erpnext/manufacturing/doctype/job_card/job_card_dashboard.py
# erpnext/manufacturing/doctype/job_card/test_job_card.py
# erpnext/manufacturing/doctype/job_card_item/job_card_item.json
# erpnext/manufacturing/doctype/job_card_operation/job_card_operation.json
# erpnext/manufacturing/doctype/job_card_scheduled_time/job_card_scheduled_time.json
# erpnext/manufacturing/doctype/job_card_scrap_item/job_card_scrap_item.json
# erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json
# erpnext/manufacturing/doctype/manufacturing_settings/manufacturing_settings.json
# erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.py
# erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
# erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.py
# erpnext/manufacturing/doctype/operation/operation.json
# erpnext/manufacturing/doctype/operation/test_operation.py
# erpnext/manufacturing/doctype/plant_floor/plant_floor.js
# erpnext/manufacturing/doctype/plant_floor/plant_floor.json
# erpnext/manufacturing/doctype/plant_floor/test_plant_floor.py
# erpnext/manufacturing/doctype/production_plan/production_plan.json
# erpnext/manufacturing/doctype/production_plan/production_plan.py
# erpnext/manufacturing/doctype/production_plan/test_production_plan.py
# erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
# erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
# erpnext/manufacturing/doctype/production_plan_material_request/production_plan_material_request.json
# erpnext/manufacturing/doctype/production_plan_material_request_warehouse/production_plan_material_request_warehouse.json
# erpnext/manufacturing/doctype/production_plan_material_request_warehouse/test_production_plan_material_request_warehouse.py
# erpnext/manufacturing/doctype/production_plan_sales_order/production_plan_sales_order.json
# erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
# erpnext/manufacturing/doctype/routing/routing.json
# erpnext/manufacturing/doctype/routing/routing.py
# erpnext/manufacturing/doctype/routing/test_routing.py
# erpnext/manufacturing/doctype/sub_operation/sub_operation.json
# erpnext/manufacturing/doctype/sub_operation/test_sub_operation.py
# erpnext/manufacturing/doctype/work_order/test_work_order.py
# erpnext/manufacturing/doctype/work_order/work_order.js
# erpnext/manufacturing/doctype/work_order/work_order.json
# erpnext/manufacturing/doctype/work_order/work_order.py
# erpnext/manufacturing/doctype/work_order_item/work_order_item.json
# erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json
# erpnext/manufacturing/doctype/work_order_operation/work_order_operation.py
# erpnext/manufacturing/doctype/workstation/test_workstation.py
# erpnext/manufacturing/doctype/workstation/workstation.js
# erpnext/manufacturing/doctype/workstation/workstation.json
# erpnext/manufacturing/doctype/workstation/workstation.py
# erpnext/manufacturing/doctype/workstation/workstation_job_card.html
# erpnext/manufacturing/doctype/workstation_type/test_workstation_type.py
# erpnext/manufacturing/doctype/workstation_type/workstation_type.json
# erpnext/manufacturing/doctype/workstation_working_hour/workstation_working_hour.json
# erpnext/manufacturing/notification/material_request_receipt_notification/material_request_receipt_notification.json
# erpnext/manufacturing/report/bom_stock_calculated/test_bom_stock_calculated.py
# erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py
# erpnext/manufacturing/report/production_analytics/production_analytics.py
# erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py
# erpnext/manufacturing/report/test_reports.py
# erpnext/patches.txt
# erpnext/patches/v11_0/create_department_records_for_each_company.py
# erpnext/patches/v11_0/make_location_from_warehouse.py
# erpnext/patches/v11_0/rebuild_tree_for_company.py
# erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py
# erpnext/patches/v11_0/update_department_lft_rgt.py
# erpnext/patches/v13_0/modify_invalid_gain_loss_gl_entries.py
# erpnext/patches/v14_0/migrate_crm_settings.py
# erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
# erpnext/patches/v15_0/create_advance_payment_ledger_records.py
# erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
# erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
# erpnext/portal/doctype/website_attribute/website_attribute.json
# erpnext/portal/doctype/website_filter_field/website_filter_field.json
# erpnext/portal/utils.py
# erpnext/projects/doctype/activity_cost/activity_cost.json
# erpnext/projects/doctype/activity_cost/test_activity_cost.py
# erpnext/projects/doctype/activity_type/activity_type.json
# erpnext/projects/doctype/activity_type/test_activity_type.py
# erpnext/projects/doctype/dependent_task/dependent_task.json
# erpnext/projects/doctype/project/project.json
# erpnext/projects/doctype/project/project.py
# erpnext/projects/doctype/project/test_project.py
# erpnext/projects/doctype/project_template/project_template.json
# erpnext/projects/doctype/project_template/test_project_template.py
# erpnext/projects/doctype/project_template_task/project_template_task.json
# erpnext/projects/doctype/project_type/project_type.json
# erpnext/projects/doctype/project_type/test_project_type.py
# erpnext/projects/doctype/project_update/project_update.json
# erpnext/projects/doctype/project_update/test_project_update.py
# erpnext/projects/doctype/project_user/project_user.json
# erpnext/projects/doctype/projects_settings/projects_settings.json
# erpnext/projects/doctype/projects_settings/test_projects_settings.py
# erpnext/projects/doctype/task/task.js
# erpnext/projects/doctype/task/task.json
# erpnext/projects/doctype/task/task.py
# erpnext/projects/doctype/task/test_task.py
# erpnext/projects/doctype/task_depends_on/task_depends_on.json
# erpnext/projects/doctype/task_type/task_type.json
# erpnext/projects/doctype/task_type/test_task_type.py
# erpnext/projects/doctype/timesheet/test_timesheet.py
# erpnext/projects/doctype/timesheet/timesheet.js
# erpnext/projects/doctype/timesheet/timesheet.json
# erpnext/projects/doctype/timesheet/timesheet.py
# erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
# erpnext/projects/doctype/timesheet_detail/timesheet_detail.py
# erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
# erpnext/projects/report/delayed_tasks_summary/test_delayed_tasks_summary.py
# erpnext/projects/workspace/projects/projects.json
# erpnext/public/images/erpnext-logo.svg
# erpnext/public/js/bom_configurator/bom_configurator.bundle.js
# erpnext/public/js/bulk_transaction_processing.js
# erpnext/public/js/controllers/accounts.js
# erpnext/public/js/controllers/taxes_and_totals.js
# erpnext/public/js/controllers/transaction.js
# erpnext/public/js/financial_statements.js
# erpnext/public/js/plant_floor_visual/visual_plant.js
# erpnext/public/js/projects/timer.js
# erpnext/public/js/setup_wizard.js
# erpnext/public/js/templates/visual_plant_floor_template.html
# erpnext/public/js/utils.js
# erpnext/public/js/utils/dimension_tree_filter.js
# erpnext/public/scss/erpnext.scss
# erpnext/quality_management/doctype/non_conformance/non_conformance.json
# erpnext/quality_management/doctype/non_conformance/test_non_conformance.py
# erpnext/quality_management/doctype/quality_action/quality_action.json
# erpnext/quality_management/doctype/quality_action/test_quality_action.py
# erpnext/quality_management/doctype/quality_action_resolution/quality_action_resolution.json
# erpnext/quality_management/doctype/quality_feedback/quality_feedback.json
# erpnext/quality_management/doctype/quality_feedback/test_quality_feedback.py
# erpnext/quality_management/doctype/quality_feedback_parameter/quality_feedback_parameter.json
# erpnext/quality_management/doctype/quality_feedback_template/quality_feedback_template.json
# erpnext/quality_management/doctype/quality_feedback_template/test_quality_feedback_template.py
# erpnext/quality_management/doctype/quality_feedback_template_parameter/quality_feedback_template_parameter.json
# erpnext/quality_management/doctype/quality_goal/quality_goal.json
# erpnext/quality_management/doctype/quality_goal/test_quality_goal.py
# erpnext/quality_management/doctype/quality_goal_objective/quality_goal_objective.json
# erpnext/quality_management/doctype/quality_meeting/quality_meeting.json
# erpnext/quality_management/doctype/quality_meeting/test_quality_meeting.py
# erpnext/quality_management/doctype/quality_meeting_agenda/quality_meeting_agenda.json
# erpnext/quality_management/doctype/quality_meeting_agenda/test_quality_meeting_agenda.py
# erpnext/quality_management/doctype/quality_meeting_minutes/quality_meeting_minutes.json
# erpnext/quality_management/doctype/quality_procedure/quality_procedure.json
# erpnext/quality_management/doctype/quality_procedure/test_quality_procedure.py
# erpnext/quality_management/doctype/quality_procedure_process/quality_procedure_process.json
# erpnext/quality_management/doctype/quality_review/quality_review.json
# erpnext/quality_management/doctype/quality_review/test_quality_review.py
# erpnext/quality_management/doctype/quality_review_objective/quality_review_objective.json
# erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json
# erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.py
# erpnext/regional/doctype/import_supplier_invoice/test_import_supplier_invoice.py
# erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json
# erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.py
# erpnext/regional/doctype/lower_deduction_certificate/test_lower_deduction_certificate.py
# erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json
# erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py
# erpnext/regional/doctype/uae_vat_account/uae_vat_account.json
# erpnext/regional/doctype/uae_vat_settings/test_uae_vat_settings.py
# erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
# erpnext/regional/italy/utils.py
# erpnext/regional/print_format/detailed_tax_invoice/detailed_tax_invoice.json
# erpnext/regional/print_format/tax_invoice/tax_invoice.json
# erpnext/regional/report/uae_vat_201/test_uae_vat_201.py
# erpnext/regional/report/vat_audit_report/vat_audit_report.py
# erpnext/regional/united_states/test_united_states.py
# erpnext/selling/doctype/customer/customer.js
# erpnext/selling/doctype/customer/customer.json
# erpnext/selling/doctype/customer/customer.py
# erpnext/selling/doctype/customer/customer_dashboard.py
# erpnext/selling/doctype/customer/test_customer.py
# erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.json
# erpnext/selling/doctype/industry_type/industry_type.json
# erpnext/selling/doctype/industry_type/test_industry_type.py
# erpnext/selling/doctype/installation_note/installation_note.json
# erpnext/selling/doctype/installation_note/installation_note.py
# erpnext/selling/doctype/installation_note/test_installation_note.py
# erpnext/selling/doctype/installation_note_item/installation_note_item.json
# erpnext/selling/doctype/party_specific_item/party_specific_item.json
# erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
# erpnext/selling/doctype/product_bundle/product_bundle.js
# erpnext/selling/doctype/product_bundle/product_bundle.json
# erpnext/selling/doctype/product_bundle/test_product_bundle.py
# erpnext/selling/doctype/product_bundle_item/product_bundle_item.json
# erpnext/selling/doctype/quotation/quotation.json
# erpnext/selling/doctype/quotation/quotation.py
# erpnext/selling/doctype/quotation/test_quotation.py
# erpnext/selling/doctype/quotation_item/quotation_item.json
# erpnext/selling/doctype/quotation_item/quotation_item.py
# erpnext/selling/doctype/sales_order/sales_order.js
# erpnext/selling/doctype/sales_order/sales_order.json
# erpnext/selling/doctype/sales_order/sales_order.py
# erpnext/selling/doctype/sales_order/sales_order_list.js
# erpnext/selling/doctype/sales_order/test_sales_order.py
# erpnext/selling/doctype/sales_order_item/sales_order_item.json
# erpnext/selling/doctype/sales_order_item/sales_order_item.py
# erpnext/selling/doctype/sales_partner_type/sales_partner_type.json
# erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.py
# erpnext/selling/doctype/sales_team/sales_team.json
# erpnext/selling/doctype/selling_settings/selling_settings.json
# erpnext/selling/doctype/selling_settings/selling_settings.py
# erpnext/selling/doctype/selling_settings/test_selling_settings.py
# erpnext/selling/doctype/sms_center/sms_center.json
# erpnext/selling/page/point_of_sale/pos_controller.js
# erpnext/selling/page/point_of_sale/pos_item_cart.js
# erpnext/selling/page/point_of_sale/pos_past_order_summary.js
# erpnext/selling/page/point_of_sale/pos_payment.js
# erpnext/selling/page/sales_funnel/sales_funnel.js
# erpnext/selling/page/sales_funnel/sales_funnel.py
# erpnext/selling/report/address_and_contacts/address_and_contacts.py
# erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
# erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
# erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
# erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
# erpnext/selling/report/sales_analytics/sales_analytics.js
# erpnext/selling/report/sales_analytics/sales_analytics.py
# erpnext/selling/report/sales_analytics/test_analytics.py
# erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
# erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
# erpnext/selling/report/sales_order_analysis/test_sales_order_analysis.py
# erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py
# erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py
# erpnext/selling/workspace/selling/selling.json
# erpnext/setup/doctype/authorization_control/authorization_control.json
# erpnext/setup/doctype/authorization_control/authorization_control.py
# erpnext/setup/doctype/authorization_rule/authorization_rule.json
# erpnext/setup/doctype/authorization_rule/test_authorization_rule.py
# erpnext/setup/doctype/branch/branch.json
# erpnext/setup/doctype/branch/test_branch.py
# erpnext/setup/doctype/brand/brand.json
# erpnext/setup/doctype/brand/test_brand.py
# erpnext/setup/doctype/company/company.js
# erpnext/setup/doctype/company/company.json
# erpnext/setup/doctype/company/company.py
# erpnext/setup/doctype/company/test_company.py
# erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
# erpnext/setup/doctype/customer_group/customer_group.json
# erpnext/setup/doctype/customer_group/customer_group.py
# erpnext/setup/doctype/customer_group/test_customer_group.py
# erpnext/setup/doctype/department/department.json
# erpnext/setup/doctype/department/department.py
# erpnext/setup/doctype/department/test_department.py
# erpnext/setup/doctype/designation/designation.json
# erpnext/setup/doctype/designation/test_designation.py
# erpnext/setup/doctype/driver/driver.json
# erpnext/setup/doctype/driver/driver.py
# erpnext/setup/doctype/driver/test_driver.py
# erpnext/setup/doctype/driving_license_category/driving_license_category.json
# erpnext/setup/doctype/email_digest/email_digest.json
# erpnext/setup/doctype/email_digest/email_digest.py
# erpnext/setup/doctype/email_digest/test_email_digest.py
# erpnext/setup/doctype/email_digest_recipient/email_digest_recipient.json
# erpnext/setup/doctype/employee/employee.json
# erpnext/setup/doctype/employee/employee.py
# erpnext/setup/doctype/employee/test_employee.py
# erpnext/setup/doctype/employee_education/employee_education.json
# erpnext/setup/doctype/employee_external_work_history/employee_external_work_history.json
# erpnext/setup/doctype/employee_group/employee_group.json
# erpnext/setup/doctype/employee_group/test_employee_group.py
# erpnext/setup/doctype/employee_group_table/employee_group_table.json
# erpnext/setup/doctype/employee_internal_work_history/employee_internal_work_history.json
# erpnext/setup/doctype/global_defaults/global_defaults.json
# erpnext/setup/doctype/global_defaults/test_global_defaults.py
# erpnext/setup/doctype/holiday/holiday.json
# erpnext/setup/doctype/holiday_list/holiday_list.json
# erpnext/setup/doctype/holiday_list/test_holiday_list.py
# erpnext/setup/doctype/incoterm/test_incoterm.py
# erpnext/setup/doctype/item_group/item_group.json
# erpnext/setup/doctype/item_group/test_item_group.py
# erpnext/setup/doctype/party_type/party_type.json
# erpnext/setup/doctype/party_type/test_party_type.py
# erpnext/setup/doctype/print_heading/print_heading.json
# erpnext/setup/doctype/print_heading/test_print_heading.py
# erpnext/setup/doctype/quotation_lost_reason/quotation_lost_reason.json
# erpnext/setup/doctype/quotation_lost_reason/test_quotation_lost_reason.py
# erpnext/setup/doctype/quotation_lost_reason_detail/quotation_lost_reason_detail.json
# erpnext/setup/doctype/sales_partner/sales_partner.json
# erpnext/setup/doctype/sales_partner/sales_partner.py
# erpnext/setup/doctype/sales_partner/test_sales_partner.py
# erpnext/setup/doctype/sales_person/sales_person.json
# erpnext/setup/doctype/sales_person/test_sales_person.py
# erpnext/setup/doctype/supplier_group/supplier_group.js
# erpnext/setup/doctype/supplier_group/supplier_group.json
# erpnext/setup/doctype/supplier_group/test_supplier_group.py
# erpnext/setup/doctype/target_detail/target_detail.json
# erpnext/setup/doctype/terms_and_conditions/terms_and_conditions.json
# erpnext/setup/doctype/terms_and_conditions/test_terms_and_conditions.py
# erpnext/setup/doctype/territory/territory.json
# erpnext/setup/doctype/territory/test_territory.py
# erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
# erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
# erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
# erpnext/setup/doctype/uom/test_uom.py
# erpnext/setup/doctype/uom/uom.json
# erpnext/setup/doctype/uom/uom.py
# erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.py
# erpnext/setup/doctype/uom_conversion_factor/uom_conversion_factor.json
# erpnext/setup/doctype/vehicle/test_vehicle.py
# erpnext/setup/doctype/vehicle/vehicle.json
# erpnext/setup/doctype/website_item_group/website_item_group.json
# erpnext/setup/install.py
# erpnext/setup/setup_wizard/data/country_wise_tax.json
# erpnext/setup/setup_wizard/data/uom_data.json
# erpnext/setup/setup_wizard/operations/defaults_setup.py
# erpnext/setup/setup_wizard/operations/install_fixtures.py
# erpnext/setup/utils.py
# erpnext/startup/boot.py
# erpnext/startup/leaderboard.py
# erpnext/stock/__init__.py
# erpnext/stock/deprecated_serial_batch.py
# erpnext/stock/doctype/batch/batch.json
# erpnext/stock/doctype/batch/test_batch.py
# erpnext/stock/doctype/bin/bin.json
# erpnext/stock/doctype/bin/test_bin.py
# erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.json
# erpnext/stock/doctype/closing_stock_balance/test_closing_stock_balance.py
# erpnext/stock/doctype/customs_tariff_number/customs_tariff_number.json
# erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.py
# erpnext/stock/doctype/delivery_note/delivery_note.json
# erpnext/stock/doctype/delivery_note/delivery_note.py
# erpnext/stock/doctype/delivery_note/delivery_note_list.js
# erpnext/stock/doctype/delivery_note/test_delivery_note.py
# erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
# erpnext/stock/doctype/delivery_note_item/delivery_note_item.py
# erpnext/stock/doctype/delivery_settings/delivery_settings.json
# erpnext/stock/doctype/delivery_settings/test_delivery_settings.py
# erpnext/stock/doctype/delivery_stop/delivery_stop.json
# erpnext/stock/doctype/delivery_trip/delivery_trip.js
# erpnext/stock/doctype/delivery_trip/delivery_trip.json
# erpnext/stock/doctype/delivery_trip/delivery_trip.py
# erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
# erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
# erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
# erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
# erpnext/stock/doctype/item/item.js
# erpnext/stock/doctype/item/item.json
# erpnext/stock/doctype/item/item.py
# erpnext/stock/doctype/item/item_list.js
# erpnext/stock/doctype/item/test_item.py
# erpnext/stock/doctype/item_alternative/item_alternative.json
# erpnext/stock/doctype/item_alternative/test_item_alternative.py
# erpnext/stock/doctype/item_attribute/item_attribute.json
# erpnext/stock/doctype/item_attribute/item_attribute.py
# erpnext/stock/doctype/item_attribute/test_item_attribute.py
# erpnext/stock/doctype/item_attribute_value/item_attribute_value.json
# erpnext/stock/doctype/item_barcode/item_barcode.json
# erpnext/stock/doctype/item_customer_detail/item_customer_detail.json
# erpnext/stock/doctype/item_default/item_default.json
# erpnext/stock/doctype/item_manufacturer/item_manufacturer.json
# erpnext/stock/doctype/item_manufacturer/test_item_manufacturer.py
# erpnext/stock/doctype/item_price/item_price.json
# erpnext/stock/doctype/item_price/item_price.py
# erpnext/stock/doctype/item_price/test_item_price.py
# erpnext/stock/doctype/item_quality_inspection_parameter/item_quality_inspection_parameter.json
# erpnext/stock/doctype/item_reorder/item_reorder.json
# erpnext/stock/doctype/item_supplier/item_supplier.json
# erpnext/stock/doctype/item_tax/item_tax.json
# erpnext/stock/doctype/item_variant/item_variant.json
# erpnext/stock/doctype/item_variant_attribute/item_variant_attribute.json
# erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
# erpnext/stock/doctype/item_variant_settings/item_variant_settings.json
# erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.py
# erpnext/stock/doctype/item_website_specification/item_website_specification.json
# erpnext/stock/doctype/landed_cost_item/landed_cost_item.json
# erpnext/stock/doctype/landed_cost_purchase_receipt/landed_cost_purchase_receipt.json
# erpnext/stock/doctype/landed_cost_taxes_and_charges/landed_cost_taxes_and_charges.json
# erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.json
# erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
# erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
# erpnext/stock/doctype/manufacturer/manufacturer.json
# erpnext/stock/doctype/manufacturer/test_manufacturer.py
# erpnext/stock/doctype/material_request/material_request.js
# erpnext/stock/doctype/material_request/material_request.json
# erpnext/stock/doctype/material_request/material_request.py
# erpnext/stock/doctype/material_request/material_request_list.js
# erpnext/stock/doctype/material_request/test_material_request.py
# erpnext/stock/doctype/material_request_item/material_request_item.json
# erpnext/stock/doctype/packed_item/packed_item.json
# erpnext/stock/doctype/packed_item/packed_item.py
# erpnext/stock/doctype/packed_item/test_packed_item.py
# erpnext/stock/doctype/packing_slip/packing_slip.json
# erpnext/stock/doctype/packing_slip/test_packing_slip.py
# erpnext/stock/doctype/packing_slip_item/packing_slip_item.json
# erpnext/stock/doctype/pick_list/pick_list.json
# erpnext/stock/doctype/pick_list/pick_list_list.js
# erpnext/stock/doctype/pick_list/test_pick_list.py
# erpnext/stock/doctype/pick_list_item/pick_list_item.json
# erpnext/stock/doctype/price_list/price_list.json
# erpnext/stock/doctype/price_list/price_list.py
# erpnext/stock/doctype/price_list/test_price_list.py
# erpnext/stock/doctype/price_list_country/price_list_country.json
# erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
# erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
# erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
# erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
# erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.py
# erpnext/stock/doctype/putaway_rule/putaway_rule.json
# erpnext/stock/doctype/putaway_rule/putaway_rule.py
# erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
# erpnext/stock/doctype/quality_inspection/quality_inspection.json
# erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
# erpnext/stock/doctype/quality_inspection_parameter/quality_inspection_parameter.json
# erpnext/stock/doctype/quality_inspection_parameter/test_quality_inspection_parameter.py
# erpnext/stock/doctype/quality_inspection_parameter_group/quality_inspection_parameter_group.json
# erpnext/stock/doctype/quality_inspection_parameter_group/test_quality_inspection_parameter_group.py
# erpnext/stock/doctype/quality_inspection_reading/quality_inspection_reading.json
# erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.json
# erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.py
# erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.json
# erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
# erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
# erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
# erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
# erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
# erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py
# erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
# erpnext/stock/doctype/serial_no/serial_no.json
# erpnext/stock/doctype/serial_no/test_serial_no.py
# erpnext/stock/doctype/shipment/shipment.json
# erpnext/stock/doctype/shipment/test_shipment.py
# erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json
# erpnext/stock/doctype/shipment_parcel/shipment_parcel.json
# erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json
# erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py
# erpnext/stock/doctype/stock_entry/stock_entry.json
# erpnext/stock/doctype/stock_entry/stock_entry.py
# erpnext/stock/doctype/stock_entry/test_stock_entry.py
# erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
# erpnext/stock/doctype/stock_entry_type/stock_entry_type.json
# erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
# erpnext/stock/doctype/stock_entry_type/test_stock_entry_type.py
# erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
# erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
# erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
# erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
# erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
# erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
# erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
# erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json
# erpnext/stock/doctype/stock_reposting_settings/test_stock_reposting_settings.py
# erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
# erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
# erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
# erpnext/stock/doctype/stock_settings/stock_settings.json
# erpnext/stock/doctype/stock_settings/stock_settings.py
# erpnext/stock/doctype/stock_settings/test_stock_settings.py
# erpnext/stock/doctype/uom_category/test_uom_category.py
# erpnext/stock/doctype/uom_category/uom_category.json
# erpnext/stock/doctype/uom_conversion_detail/uom_conversion_detail.json
# erpnext/stock/doctype/variant_field/test_variant_field.py
# erpnext/stock/doctype/variant_field/variant_field.json
# erpnext/stock/doctype/warehouse/test_warehouse.py
# erpnext/stock/doctype/warehouse/warehouse.json
# erpnext/stock/doctype/warehouse/warehouse.py
# erpnext/stock/doctype/warehouse_type/test_warehouse_type.py
# erpnext/stock/doctype/warehouse_type/warehouse_type.json
# erpnext/stock/get_item_details.py
# erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
# erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary_header.html
# erpnext/stock/print_format/purchase_receipt_serial_and_batch_bundle_print/purchase_receipt_serial_and_batch_bundle_print.json
# erpnext/stock/reorder_item.py
# erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
# erpnext/stock/report/item_shortage_report/test_item_shortage_report.py
# erpnext/stock/report/reserved_stock/test_reserved_stock.py
# erpnext/stock/report/stock_ageing/test_stock_ageing.py
# erpnext/stock/report/stock_analytics/test_stock_analytics.py
# erpnext/stock/report/stock_balance/test_stock_balance.py
# erpnext/stock/report/stock_ledger/test_stock_ledger_report.py
# erpnext/stock/report/test_reports.py
# erpnext/stock/serial_batch_bundle.py
# erpnext/stock/stock_balance.py
# erpnext/stock/stock_ledger.py
# erpnext/stock/tests/test_get_item_details.py
# erpnext/stock/tests/test_utils.py
# erpnext/stock/tests/test_valuation.py
# erpnext/stock/utils.py
# erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.json
# erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.py
# erpnext/subcontracting/doctype/subcontracting_bom/test_subcontracting_bom.py
# erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
# erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
# erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order_list.js
# erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
# erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
# erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.py
# erpnext/subcontracting/doctype/subcontracting_order_service_item/subcontracting_order_service_item.json
# erpnext/subcontracting/doctype/subcontracting_order_supplied_item/subcontracting_order_supplied_item.json
# erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
# erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
# erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt_list.js
# erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
# erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
# erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.py
# erpnext/subcontracting/doctype/subcontracting_receipt_supplied_item/subcontracting_receipt_supplied_item.json
# erpnext/support/doctype/issue/issue.json
# erpnext/support/doctype/issue/issue.py
# erpnext/support/doctype/issue/test_issue.py
# erpnext/support/doctype/issue_priority/issue_priority.json
# erpnext/support/doctype/issue_priority/test_issue_priority.py
# erpnext/support/doctype/issue_type/issue_type.json
# erpnext/support/doctype/issue_type/test_issue_type.py
# erpnext/support/doctype/pause_sla_on_status/pause_sla_on_status.json
# erpnext/support/doctype/service_day/service_day.json
# erpnext/support/doctype/service_level_agreement/service_level_agreement.json
# erpnext/support/doctype/service_level_agreement/service_level_agreement.py
# erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
# erpnext/support/doctype/service_level_priority/service_level_priority.json
# erpnext/support/doctype/sla_fulfilled_on_status/sla_fulfilled_on_status.json
# erpnext/support/doctype/support_search_source/support_search_source.json
# erpnext/support/doctype/support_settings/support_settings.json
# erpnext/support/doctype/support_settings/test_support_settings.py
# erpnext/support/doctype/warranty_claim/test_warranty_claim.py
# erpnext/support/doctype/warranty_claim/warranty_claim.json
# erpnext/support/doctype/warranty_claim/warranty_claim.py
# erpnext/support/report/issue_analytics/test_issue_analytics.py
# erpnext/telephony/doctype/call_log/test_call_log.py
# erpnext/telephony/doctype/incoming_call_handling_schedule/incoming_call_handling_schedule.json
# erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.json
# erpnext/telephony/doctype/incoming_call_settings/test_incoming_call_settings.py
# erpnext/telephony/doctype/telephony_call_type/telephony_call_type.json
# erpnext/telephony/doctype/telephony_call_type/test_telephony_call_type.py
# erpnext/telephony/doctype/voice_call_settings/test_voice_call_settings.py
# erpnext/telephony/doctype/voice_call_settings/voice_call_settings.json
# erpnext/templates/form_grid/includes/visible_cols.html
# erpnext/templates/generators/sales_partner.html
# erpnext/templates/includes/issue_row.html
# erpnext/templates/includes/macros.html
# erpnext/templates/includes/projects/project_row.html
# erpnext/templates/includes/transaction_row.html
# erpnext/templates/pages/order.html
# erpnext/templates/pages/order.py
# erpnext/templates/pages/timelog_info.html
# erpnext/templates/print_formats/includes/total.html
# erpnext/tests/test_activation.py
# erpnext/tests/test_init.py
# erpnext/tests/test_notifications.py
# erpnext/tests/test_perf.py
# erpnext/tests/test_point_of_sale.py
# erpnext/tests/test_regional.py
# erpnext/tests/test_webform.py
# erpnext/tests/test_zform_loads.py
# erpnext/utilities/activation.py
# erpnext/utilities/bulk_transaction.py
# erpnext/utilities/doctype/portal_user/portal_user.json
# erpnext/utilities/doctype/rename_tool/rename_tool.json
# erpnext/utilities/doctype/video/test_video.py
# erpnext/utilities/doctype/video/video.json
# erpnext/utilities/doctype/video/video.py
# erpnext/utilities/doctype/video_settings/test_video_settings.py
# erpnext/utilities/doctype/video_settings/video_settings.json
# erpnext/www/support/index.py
# pyproject.toml
1311 lines
42 KiB
Python
1311 lines
42 KiB
Python
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
|
# For license information, please see license.txt
|
|
|
|
import copy
|
|
import json
|
|
from collections import defaultdict
|
|
|
|
import frappe
|
|
from frappe import _
|
|
from frappe.model.mapper import get_mapped_doc
|
|
from frappe.utils import cint, flt, get_link_to_form
|
|
|
|
from erpnext.controllers.stock_controller import StockController
|
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
|
get_voucher_wise_serial_batch_from_bundle,
|
|
)
|
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation, get_serial_nos_from_bundle
|
|
from erpnext.stock.utils import get_incoming_rate
|
|
|
|
|
|
class SubcontractingController(StockController):
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
if self.get("is_old_subcontracting_flow"):
|
|
self.subcontract_data = frappe._dict(
|
|
{
|
|
"order_doctype": "Purchase Order",
|
|
"order_field": "purchase_order",
|
|
"rm_detail_field": "po_detail",
|
|
"receipt_supplied_items_field": "Purchase Receipt Item Supplied",
|
|
"order_supplied_items_field": "Purchase Order Item Supplied",
|
|
}
|
|
)
|
|
else:
|
|
self.subcontract_data = frappe._dict(
|
|
{
|
|
"order_doctype": "Subcontracting Order",
|
|
"order_field": "subcontracting_order",
|
|
"rm_detail_field": "sco_rm_detail",
|
|
"receipt_supplied_items_field": "Subcontracting Receipt Supplied Item",
|
|
"order_supplied_items_field": "Subcontracting Order Supplied Item",
|
|
}
|
|
)
|
|
|
|
def before_validate(self):
|
|
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
|
self.remove_empty_rows()
|
|
self.set_items_conversion_factor()
|
|
|
|
def validate(self):
|
|
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
|
self.validate_items()
|
|
self.create_raw_materials_supplied()
|
|
else:
|
|
super().validate()
|
|
|
|
def validate_rejected_warehouse(self):
|
|
for item in self.get("items"):
|
|
if flt(item.rejected_qty) and not item.rejected_warehouse:
|
|
if self.rejected_warehouse:
|
|
item.rejected_warehouse = self.rejected_warehouse
|
|
else:
|
|
frappe.throw(
|
|
_("Row #{0}: Rejected Warehouse is mandatory for the rejected Item {1}").format(
|
|
item.idx, item.item_code
|
|
)
|
|
)
|
|
|
|
if item.get("rejected_warehouse") and (item.get("rejected_warehouse") == item.get("warehouse")):
|
|
frappe.throw(
|
|
_("Row #{0}: Accepted Warehouse and Rejected Warehouse cannot be same").format(item.idx)
|
|
)
|
|
|
|
def remove_empty_rows(self):
|
|
for key in ["service_items", "items", "supplied_items"]:
|
|
if self.get(key):
|
|
idx = 1
|
|
for item in self.get(key)[:]:
|
|
if not (item.get("item_code") or item.get("main_item_code")):
|
|
self.get(key).remove(item)
|
|
else:
|
|
item.idx = idx
|
|
idx += 1
|
|
|
|
def set_items_conversion_factor(self):
|
|
for item in self.get("items"):
|
|
if not item.conversion_factor:
|
|
item.conversion_factor = 1
|
|
|
|
def validate_items(self):
|
|
for item in self.items:
|
|
is_stock_item, is_sub_contracted_item = frappe.get_value(
|
|
"Item", item.item_code, ["is_stock_item", "is_sub_contracted_item"]
|
|
)
|
|
|
|
if not is_stock_item:
|
|
frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name))
|
|
|
|
if not item.get("is_scrap_item"):
|
|
if not is_sub_contracted_item:
|
|
frappe.throw(
|
|
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
|
|
)
|
|
|
|
if item.bom:
|
|
is_active, bom_item = frappe.get_value("BOM", item.bom, ["is_active", "item"])
|
|
|
|
if not is_active:
|
|
frappe.throw(
|
|
_("Row {0}: Please select an active BOM for Item {1}.").format(
|
|
item.idx, item.item_name
|
|
)
|
|
)
|
|
if bom_item != item.item_code:
|
|
frappe.throw(
|
|
_("Row {0}: Please select an valid BOM for Item {1}.").format(
|
|
item.idx, item.item_name
|
|
)
|
|
)
|
|
else:
|
|
frappe.throw(
|
|
_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name)
|
|
)
|
|
else:
|
|
item.bom = None
|
|
|
|
def __get_data_before_save(self):
|
|
item_dict = {}
|
|
if (
|
|
self.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]
|
|
and self._doc_before_save
|
|
):
|
|
for row in self._doc_before_save.get("items"):
|
|
item_dict[row.name] = (row.item_code, row.qty)
|
|
|
|
return item_dict
|
|
|
|
def __identify_change_in_item_table(self):
|
|
self.__changed_name = []
|
|
self.__reference_name = []
|
|
|
|
if self.doctype in ["Purchase Order", "Subcontracting Order"] or self.is_new():
|
|
self.set(self.raw_material_table, [])
|
|
return
|
|
|
|
item_dict = self.__get_data_before_save()
|
|
if not item_dict:
|
|
return True
|
|
|
|
for row in self.items:
|
|
self.__reference_name.append(row.name)
|
|
if (row.name not in item_dict) or (row.item_code, row.qty) != item_dict[row.name]:
|
|
self.__changed_name.append(row.name)
|
|
|
|
if item_dict.get(row.name):
|
|
del item_dict[row.name]
|
|
|
|
self.__changed_name.extend(item_dict.keys())
|
|
|
|
def __get_backflush_based_on(self):
|
|
self.backflush_based_on = frappe.db.get_single_value(
|
|
"Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
|
|
)
|
|
|
|
def initialized_fields(self):
|
|
self.available_materials = frappe._dict()
|
|
self.__transferred_items = frappe._dict()
|
|
self.alternative_item_details = frappe._dict()
|
|
self.__get_backflush_based_on()
|
|
|
|
def __get_subcontract_orders(self):
|
|
self.subcontract_orders = []
|
|
|
|
if self.doctype in ["Purchase Order", "Subcontracting Order"]:
|
|
return
|
|
|
|
self.subcontract_orders = [
|
|
item.get(self.subcontract_data.order_field)
|
|
for item in self.items
|
|
if item.get(self.subcontract_data.order_field)
|
|
]
|
|
|
|
def __get_pending_qty_to_receive(self):
|
|
"""Get qty to be received against the subcontract order."""
|
|
|
|
self.qty_to_be_received = defaultdict(float)
|
|
|
|
if (
|
|
self.doctype != self.subcontract_data.order_doctype
|
|
and self.backflush_based_on != "BOM"
|
|
and self.subcontract_orders
|
|
):
|
|
for row in frappe.get_all(
|
|
f"{self.subcontract_data.order_doctype} Item",
|
|
fields=["item_code", "(qty - received_qty) as qty", "parent", "name"],
|
|
filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)},
|
|
):
|
|
self.qty_to_be_received[(row.item_code, row.parent)] += row.qty
|
|
|
|
def __get_transferred_items(self):
|
|
se = frappe.qb.DocType("Stock Entry")
|
|
se_detail = frappe.qb.DocType("Stock Entry Detail")
|
|
|
|
query = (
|
|
frappe.qb.from_(se)
|
|
.inner_join(se_detail)
|
|
.on(se.name == se_detail.parent)
|
|
.select(
|
|
se[self.subcontract_data.order_field],
|
|
se.name.as_("voucher_no"),
|
|
se_detail.item_code.as_("rm_item_code"),
|
|
se_detail.item_name,
|
|
se_detail.description,
|
|
(
|
|
frappe.qb.terms.Case()
|
|
.when(((se.purpose == "Material Transfer") & (se.is_return == 1)), -1 * se_detail.qty)
|
|
.else_(se_detail.qty)
|
|
).as_("qty"),
|
|
se_detail.basic_rate.as_("rate"),
|
|
se_detail.amount,
|
|
se_detail.serial_no,
|
|
se_detail.serial_and_batch_bundle,
|
|
se_detail.uom,
|
|
se_detail.subcontracted_item.as_("main_item_code"),
|
|
se_detail.stock_uom,
|
|
se_detail.batch_no,
|
|
se_detail.conversion_factor,
|
|
se_detail.s_warehouse,
|
|
se_detail.t_warehouse,
|
|
se_detail.item_group,
|
|
se_detail[self.subcontract_data.rm_detail_field],
|
|
)
|
|
.where(
|
|
(se.docstatus == 1)
|
|
& (se[self.subcontract_data.order_field].isin(self.subcontract_orders))
|
|
& (
|
|
(se.purpose == "Send to Subcontractor")
|
|
| ((se.purpose == "Material Transfer") & (se.is_return == 1))
|
|
)
|
|
)
|
|
)
|
|
|
|
if self.backflush_based_on == "BOM":
|
|
query = query.select(se_detail.original_item)
|
|
|
|
return query.run(as_dict=True)
|
|
|
|
def __set_alternative_item_details(self, row):
|
|
if row.get("original_item"):
|
|
self.alternative_item_details[row.get("original_item")] = row
|
|
|
|
def __get_received_items(self, doctype):
|
|
fields = []
|
|
for field in ["name", self.subcontract_data.order_field, "parent"]:
|
|
fields.append(f"`tab{doctype} Item`.`{field}`")
|
|
|
|
filters = [
|
|
[doctype, "docstatus", "=", 1],
|
|
[f"{doctype} Item", self.subcontract_data.order_field, "in", self.subcontract_orders],
|
|
]
|
|
if doctype == "Purchase Invoice":
|
|
filters.append(["Purchase Invoice", "update_stock", "=", 1])
|
|
|
|
return frappe.get_all(f"{doctype}", fields=fields, filters=filters)
|
|
|
|
def __get_consumed_items(self, doctype, receipt_items):
|
|
fields = [
|
|
"serial_no",
|
|
"rm_item_code",
|
|
"reference_name",
|
|
"batch_no",
|
|
"consumed_qty",
|
|
"main_item_code",
|
|
"parent as voucher_no",
|
|
]
|
|
|
|
if self.subcontract_data.receipt_supplied_items_field != "Purchase Receipt Item Supplied":
|
|
fields.append("serial_and_batch_bundle")
|
|
|
|
return frappe.get_all(
|
|
self.subcontract_data.receipt_supplied_items_field,
|
|
fields=fields,
|
|
filters={"docstatus": 1, "reference_name": ("in", list(receipt_items)), "parenttype": doctype},
|
|
)
|
|
|
|
def __update_consumed_materials(self, doctype, return_consumed_items=False):
|
|
"""Deduct the consumed materials from the available materials."""
|
|
|
|
receipt_items = self.__get_received_items(doctype)
|
|
if not receipt_items:
|
|
return ([], {}) if return_consumed_items else None
|
|
|
|
receipt_items = {item.name: item.get(self.subcontract_data.order_field) for item in receipt_items}
|
|
consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys())
|
|
|
|
if return_consumed_items:
|
|
return (consumed_materials, receipt_items)
|
|
|
|
if not consumed_materials:
|
|
return
|
|
|
|
voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no]
|
|
voucher_bundle_data = (
|
|
get_voucher_wise_serial_batch_from_bundle(
|
|
voucher_no=voucher_nos,
|
|
is_outward=1,
|
|
get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"),
|
|
)
|
|
if voucher_nos
|
|
else {}
|
|
)
|
|
|
|
for row in consumed_materials:
|
|
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
|
if not self.available_materials.get(key):
|
|
continue
|
|
|
|
self.available_materials[key]["qty"] -= row.consumed_qty
|
|
|
|
bundle_key = (row.rm_item_code, row.main_item_code, self.supplier_warehouse, row.voucher_no)
|
|
consumed_bundles = voucher_bundle_data.get(bundle_key, frappe._dict())
|
|
|
|
if consumed_bundles.serial_nos:
|
|
self.available_materials[key]["serial_no"] = list(
|
|
set(self.available_materials[key]["serial_no"]) - set(consumed_bundles.serial_nos)
|
|
)
|
|
|
|
if consumed_bundles.batch_nos:
|
|
for batch_no, qty in consumed_bundles.batch_nos.items():
|
|
if qty:
|
|
# Conumed qty is negative therefore added it instead of subtracting
|
|
self.available_materials[key]["batch_no"][batch_no] += qty
|
|
consumed_bundles.batch_nos[batch_no] += abs(qty)
|
|
|
|
# Will be deprecated in v16
|
|
if row.serial_no and not consumed_bundles.serial_nos:
|
|
<<<<<<< HEAD
|
|
=======
|
|
from erpnext.deprecation_dumpster import deprecation_warning
|
|
|
|
deprecation_warning("unknown", "v16", "No instructions.")
|
|
>>>>>>> 329d14957b (fix: validate negative qty)
|
|
self.available_materials[key]["serial_no"] = list(
|
|
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
|
|
)
|
|
|
|
# Will be deprecated in v16
|
|
if row.batch_no and not consumed_bundles.batch_nos:
|
|
<<<<<<< HEAD
|
|
=======
|
|
from erpnext.deprecation_dumpster import deprecation_warning
|
|
|
|
deprecation_warning("unknown", "v16", "No instructions.")
|
|
>>>>>>> 329d14957b (fix: validate negative qty)
|
|
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
|
|
|
|
def get_available_materials(self):
|
|
"""Get the available raw materials which has been transferred to the supplier.
|
|
available_materials = {
|
|
(item_code, subcontracted_item, subcontract_order): {
|
|
'qty': 1, 'serial_no': [ABC], 'batch_no': {'batch1': 1}, 'data': item_details
|
|
}
|
|
}
|
|
"""
|
|
if not self.subcontract_orders:
|
|
return
|
|
|
|
transferred_items = self.__get_transferred_items()
|
|
|
|
voucher_nos = [row.voucher_no for row in transferred_items]
|
|
voucher_bundle_data = (
|
|
get_voucher_wise_serial_batch_from_bundle(
|
|
voucher_no=voucher_nos,
|
|
is_outward=0,
|
|
get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"),
|
|
)
|
|
if voucher_nos
|
|
else {}
|
|
)
|
|
|
|
for row in transferred_items:
|
|
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
|
|
|
if key not in self.available_materials:
|
|
self.available_materials.setdefault(
|
|
key,
|
|
frappe._dict(
|
|
{
|
|
"qty": 0,
|
|
"serial_no": [],
|
|
"batch_no": defaultdict(float),
|
|
"item_details": row,
|
|
f"{self.subcontract_data.rm_detail_field}s": [],
|
|
}
|
|
),
|
|
)
|
|
|
|
details = self.available_materials[key]
|
|
details.qty += row.qty
|
|
details[f"{self.subcontract_data.rm_detail_field}s"].append(
|
|
row.get(self.subcontract_data.rm_detail_field)
|
|
)
|
|
|
|
if row.serial_no:
|
|
details.serial_no.extend(get_serial_nos(row.serial_no))
|
|
|
|
elif row.batch_no:
|
|
details.batch_no[row.batch_no] += row.qty
|
|
|
|
elif voucher_bundle_data:
|
|
bundle_key = (row.rm_item_code, row.main_item_code, row.t_warehouse, row.voucher_no)
|
|
|
|
bundle_data = voucher_bundle_data.get(bundle_key, frappe._dict())
|
|
if bundle_data.serial_nos:
|
|
details.serial_no.extend(bundle_data.serial_nos)
|
|
bundle_data.serial_nos = []
|
|
|
|
if bundle_data.batch_nos:
|
|
for batch_no, qty in bundle_data.batch_nos.items():
|
|
if qty < 0:
|
|
qty = abs(qty)
|
|
|
|
if qty > 0:
|
|
details.batch_no[batch_no] += qty
|
|
bundle_data.batch_nos[batch_no] -= qty
|
|
|
|
self.__set_alternative_item_details(row)
|
|
|
|
self.__transferred_items = copy.deepcopy(self.available_materials)
|
|
if self.get("is_old_subcontracting_flow"):
|
|
for doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
|
self.__update_consumed_materials(doctype)
|
|
else:
|
|
self.__update_consumed_materials("Subcontracting Receipt")
|
|
|
|
def __remove_changed_rows(self):
|
|
if not self.__changed_name:
|
|
return
|
|
|
|
i = 1
|
|
self.set(self.raw_material_table, [])
|
|
for item in self._doc_before_save.supplied_items:
|
|
if item.reference_name in self.__changed_name:
|
|
self.__remove_serial_and_batch_bundle(item)
|
|
continue
|
|
|
|
if item.reference_name not in self.__reference_name:
|
|
continue
|
|
|
|
item.idx = i
|
|
self.append("supplied_items", item)
|
|
|
|
i += 1
|
|
|
|
def __remove_serial_and_batch_bundle(self, item):
|
|
if item.serial_and_batch_bundle:
|
|
frappe.delete_doc("Serial and Batch Bundle", item.serial_and_batch_bundle, force=True)
|
|
|
|
def __get_materials_from_bom(self, item_code, bom_no, exploded_item=0):
|
|
doctype = "BOM Item" if not exploded_item else "BOM Explosion Item"
|
|
fields = [f"`tab{doctype}`.`stock_qty` / `tabBOM`.`quantity` as qty_consumed_per_unit"]
|
|
|
|
alias_dict = {
|
|
"item_code": "rm_item_code",
|
|
"name": "bom_detail_no",
|
|
"source_warehouse": "reserve_warehouse",
|
|
}
|
|
for field in [
|
|
"item_code",
|
|
"name",
|
|
"rate",
|
|
"stock_uom",
|
|
"source_warehouse",
|
|
"description",
|
|
"item_name",
|
|
"stock_uom",
|
|
]:
|
|
fields.append(f"`tab{doctype}`.`{field}` As {alias_dict.get(field, field)}")
|
|
|
|
filters = [
|
|
[doctype, "parent", "=", bom_no],
|
|
[doctype, "docstatus", "=", 1],
|
|
["BOM", "item", "=", item_code],
|
|
[doctype, "sourced_by_supplier", "=", 0],
|
|
]
|
|
|
|
return frappe.get_all("BOM", fields=fields, filters=filters, order_by=f"`tab{doctype}`.`idx`") or []
|
|
|
|
def __update_reserve_warehouse(self, row, item):
|
|
if self.doctype == self.subcontract_data.order_doctype:
|
|
row.reserve_warehouse = self.set_reserve_warehouse or item.warehouse
|
|
|
|
def __set_alternative_item(self, bom_item):
|
|
if self.alternative_item_details.get(bom_item.rm_item_code):
|
|
bom_item.update(self.alternative_item_details[bom_item.rm_item_code])
|
|
|
|
def __set_serial_and_batch_bundle(self, item_row, rm_obj, qty):
|
|
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
|
if not self.available_materials.get(key):
|
|
return
|
|
|
|
if not self.available_materials[key]["serial_no"] and not self.available_materials[key]["batch_no"]:
|
|
return
|
|
|
|
serial_nos = []
|
|
batches = frappe._dict({})
|
|
|
|
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
|
serial_nos = self.__get_serial_nos_for_bundle(qty, key)
|
|
|
|
elif self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
|
batches = self.__get_batch_nos_for_bundle(qty, key)
|
|
|
|
bundle = SerialBatchCreation(
|
|
frappe._dict(
|
|
{
|
|
"company": self.company,
|
|
"item_code": rm_obj.rm_item_code,
|
|
"warehouse": self.supplier_warehouse,
|
|
"qty": qty,
|
|
"serial_nos": serial_nos,
|
|
"batches": batches,
|
|
"posting_date": self.posting_date,
|
|
"posting_time": self.posting_time,
|
|
"voucher_type": "Subcontracting Receipt",
|
|
"do_not_submit": True,
|
|
"type_of_transaction": "Outward" if qty > 0 else "Inward",
|
|
}
|
|
)
|
|
).make_serial_and_batch_bundle()
|
|
|
|
return bundle.name
|
|
|
|
def __get_batch_nos_for_bundle(self, qty, key):
|
|
available_batches = defaultdict(float)
|
|
|
|
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
|
qty_to_consumed = 0
|
|
if qty > 0:
|
|
if batch_qty >= qty:
|
|
qty_to_consumed = qty
|
|
else:
|
|
qty_to_consumed = batch_qty
|
|
|
|
qty -= qty_to_consumed
|
|
if qty_to_consumed > 0:
|
|
available_batches[batch_no] += qty_to_consumed
|
|
self.available_materials[key]["batch_no"][batch_no] -= qty_to_consumed
|
|
|
|
return available_batches
|
|
|
|
def __get_serial_nos_for_bundle(self, qty, key):
|
|
available_sns = sorted(self.available_materials[key]["serial_no"])[0 : cint(qty)]
|
|
serial_nos = []
|
|
|
|
for serial_no in available_sns:
|
|
serial_nos.append(serial_no)
|
|
|
|
self.available_materials[key]["serial_no"].remove(serial_no)
|
|
|
|
return serial_nos
|
|
|
|
def __add_supplied_item(self, item_row, bom_item, qty):
|
|
bom_item.conversion_factor = item_row.conversion_factor
|
|
rm_obj = self.append(self.raw_material_table, bom_item)
|
|
if rm_obj.get("qty"):
|
|
# Qty field not exists
|
|
rm_obj.qty = 0.0
|
|
|
|
rm_obj.reference_name = item_row.name
|
|
|
|
use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
|
|
|
|
if self.doctype == self.subcontract_data.order_doctype:
|
|
rm_obj.required_qty = flt(qty, rm_obj.precision("required_qty"))
|
|
rm_obj.amount = flt(rm_obj.required_qty * rm_obj.rate, rm_obj.precision("amount"))
|
|
else:
|
|
rm_obj.consumed_qty = flt(qty, rm_obj.precision("consumed_qty"))
|
|
rm_obj.required_qty = flt(bom_item.required_qty or qty, rm_obj.precision("required_qty"))
|
|
rm_obj.serial_and_batch_bundle = None
|
|
setattr(
|
|
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
|
|
)
|
|
|
|
if use_serial_batch_fields:
|
|
rm_obj.use_serial_batch_fields = 1
|
|
self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
|
|
|
|
if self.doctype == "Subcontracting Receipt" and not use_serial_batch_fields:
|
|
rm_obj.serial_and_batch_bundle = self.__set_serial_and_batch_bundle(
|
|
item_row, rm_obj, rm_obj.consumed_qty
|
|
)
|
|
|
|
self.set_rate_for_supplied_items(rm_obj, item_row)
|
|
|
|
def update_rate_for_supplied_items(self):
|
|
if self.doctype != "Subcontracting Receipt":
|
|
return
|
|
|
|
for row in self.supplied_items:
|
|
item_row = None
|
|
if row.reference_name:
|
|
item_row = self.get_item_row(row.reference_name)
|
|
|
|
if not item_row:
|
|
continue
|
|
|
|
self.set_rate_for_supplied_items(row, item_row)
|
|
|
|
def get_item_row(self, reference_name):
|
|
for item in self.items:
|
|
if item.name == reference_name:
|
|
return item
|
|
|
|
def set_rate_for_supplied_items(self, rm_obj, item_row):
|
|
args = frappe._dict(
|
|
{
|
|
"item_code": rm_obj.rm_item_code,
|
|
"warehouse": self.supplier_warehouse,
|
|
"posting_date": self.posting_date,
|
|
"posting_time": self.posting_time,
|
|
"qty": -1 * flt(rm_obj.consumed_qty),
|
|
"actual_qty": -1 * flt(rm_obj.consumed_qty),
|
|
"voucher_type": self.doctype,
|
|
"voucher_no": self.name,
|
|
"voucher_detail_no": item_row.name,
|
|
"company": self.company,
|
|
"allow_zero_valuation": 1,
|
|
}
|
|
)
|
|
|
|
if rm_obj.serial_and_batch_bundle:
|
|
args["serial_and_batch_bundle"] = rm_obj.serial_and_batch_bundle
|
|
|
|
if rm_obj.use_serial_batch_fields:
|
|
args["batch_no"] = rm_obj.batch_no
|
|
args["serial_no"] = rm_obj.serial_no
|
|
|
|
rm_obj.rate = get_incoming_rate(args)
|
|
|
|
def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
|
|
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
|
|
|
if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
|
new_rm_obj = None
|
|
for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
|
|
if batch_qty >= qty or (
|
|
rm_obj.consumed_qty == 0
|
|
and self.backflush_based_on == "BOM"
|
|
and len(self.available_materials[key]["batch_no"]) == 1
|
|
):
|
|
if rm_obj.consumed_qty == 0:
|
|
self.__set_consumed_qty(rm_obj, qty)
|
|
|
|
self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
|
|
self.available_materials[key]["batch_no"][batch_no] -= qty
|
|
return
|
|
|
|
elif qty > 0 and batch_qty > 0:
|
|
qty -= batch_qty
|
|
new_rm_obj = self.append(self.raw_material_table, bom_item)
|
|
new_rm_obj.serial_and_batch_bundle = None
|
|
new_rm_obj.use_serial_batch_fields = 1
|
|
new_rm_obj.reference_name = item_row.name
|
|
self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
|
|
self.available_materials[key]["batch_no"][batch_no] = 0
|
|
|
|
if new_rm_obj:
|
|
self.remove(rm_obj)
|
|
elif abs(qty) > 0:
|
|
self.__set_consumed_qty(rm_obj, qty)
|
|
|
|
else:
|
|
self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
|
|
self.__set_serial_nos(item_row, rm_obj)
|
|
|
|
def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
|
|
rm_obj.required_qty = flt(required_qty, rm_obj.precision("required_qty"))
|
|
rm_obj.consumed_qty = flt(consumed_qty, rm_obj.precision("consumed_qty"))
|
|
|
|
def __set_serial_nos(self, item_row, rm_obj):
|
|
key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
|
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
|
used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
|
|
rm_obj.serial_no = "\n".join(used_serial_nos)
|
|
|
|
# Removed the used serial nos from the list
|
|
for sn in used_serial_nos:
|
|
self.available_materials[key]["serial_no"].remove(sn)
|
|
|
|
def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
|
|
rm_obj.update(
|
|
{
|
|
"consumed_qty": qty,
|
|
"batch_no": batch_no,
|
|
"required_qty": qty,
|
|
self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field),
|
|
}
|
|
)
|
|
|
|
self.__set_serial_nos(item_row, rm_obj)
|
|
|
|
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
|
|
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
|
|
|
|
if self.qty_to_be_received == item_row.qty:
|
|
return transfer_item.qty
|
|
|
|
if self.qty_to_be_received:
|
|
qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
|
|
transfer_item.item_details.required_qty = transfer_item.qty
|
|
|
|
if transfer_item.serial_no or frappe.get_cached_value(
|
|
"UOM", transfer_item.item_details.stock_uom, "must_be_whole_number"
|
|
):
|
|
return frappe.utils.ceil(qty)
|
|
|
|
return qty
|
|
|
|
def __set_supplied_items(self):
|
|
self.bom_items = {}
|
|
|
|
has_supplied_items = True if self.get(self.raw_material_table) else False
|
|
for row in self.items:
|
|
if self.doctype != self.subcontract_data.order_doctype and (
|
|
(self.__changed_name and row.name not in self.__changed_name)
|
|
or (has_supplied_items and not self.__changed_name)
|
|
):
|
|
continue
|
|
|
|
if self.doctype == self.subcontract_data.order_doctype or self.backflush_based_on == "BOM":
|
|
for bom_item in self.__get_materials_from_bom(
|
|
row.item_code, row.bom, row.get("include_exploded_items")
|
|
):
|
|
qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor
|
|
bom_item.main_item_code = row.item_code
|
|
self.__update_reserve_warehouse(bom_item, row)
|
|
self.__set_alternative_item(bom_item)
|
|
self.__add_supplied_item(row, bom_item, qty)
|
|
|
|
elif self.backflush_based_on != "BOM":
|
|
for key, transfer_item in self.available_materials.items():
|
|
if (key[1], key[2]) == (
|
|
row.item_code,
|
|
row.get(self.subcontract_data.order_field),
|
|
) and transfer_item.qty > 0:
|
|
qty = flt(self.__get_qty_based_on_material_transfer(row, transfer_item))
|
|
transfer_item.qty -= qty
|
|
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
|
|
|
|
if self.qty_to_be_received:
|
|
self.qty_to_be_received[
|
|
(row.item_code, row.get(self.subcontract_data.order_field))
|
|
] -= row.qty
|
|
|
|
def __set_rate_for_serial_and_batch_bundle(self):
|
|
if self.doctype != "Subcontracting Receipt":
|
|
return
|
|
|
|
for row in self.get(self.raw_material_table):
|
|
if not row.get("serial_and_batch_bundle"):
|
|
continue
|
|
|
|
row.rate = frappe.get_cached_value(
|
|
"Serial and Batch Bundle", row.serial_and_batch_bundle, "avg_rate"
|
|
)
|
|
|
|
def __modify_serial_and_batch_bundle(self):
|
|
if self.is_new():
|
|
return
|
|
|
|
if self.doctype != "Subcontracting Receipt":
|
|
return
|
|
|
|
for item_row in self.items:
|
|
if self.__changed_name and item_row.name in self.__changed_name:
|
|
continue
|
|
|
|
modified_data = self.__get_bundle_to_modify(item_row.name)
|
|
if modified_data:
|
|
serial_nos = []
|
|
batches = frappe._dict({})
|
|
key = (
|
|
modified_data.rm_item_code,
|
|
item_row.item_code,
|
|
item_row.get(self.subcontract_data.order_field),
|
|
)
|
|
|
|
if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
|
|
serial_nos = self.__get_serial_nos_for_bundle(modified_data.consumed_qty, key)
|
|
|
|
elif self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
|
|
batches = self.__get_batch_nos_for_bundle(modified_data.consumed_qty, key)
|
|
|
|
SerialBatchCreation(
|
|
{
|
|
"item_code": modified_data.rm_item_code,
|
|
"warehouse": self.supplier_warehouse,
|
|
"serial_and_batch_bundle": modified_data.serial_and_batch_bundle,
|
|
"type_of_transaction": "Outward",
|
|
"serial_nos": serial_nos,
|
|
"batches": batches,
|
|
"qty": modified_data.consumed_qty * -1,
|
|
}
|
|
).update_serial_and_batch_entries()
|
|
|
|
def __get_bundle_to_modify(self, name):
|
|
for row in self.get("supplied_items"):
|
|
if row.reference_name == name and row.serial_and_batch_bundle:
|
|
if row.consumed_qty != abs(
|
|
frappe.get_cached_value(
|
|
"Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty"
|
|
)
|
|
):
|
|
return row
|
|
|
|
def __prepare_supplied_items(self):
|
|
self.initialized_fields()
|
|
self.__get_subcontract_orders()
|
|
self.__get_pending_qty_to_receive()
|
|
self.get_available_materials()
|
|
self.__remove_changed_rows()
|
|
self.__set_supplied_items()
|
|
self.__modify_serial_and_batch_bundle()
|
|
self.__set_rate_for_serial_and_batch_bundle()
|
|
|
|
def __validate_batch_no(self, row, key):
|
|
if row.get("batch_no") and row.get("batch_no") not in self.__transferred_items.get(key).get(
|
|
"batch_no"
|
|
):
|
|
link = get_link_to_form(
|
|
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
|
)
|
|
msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the {self.subcontract_data.order_doctype} {link}'
|
|
frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
|
|
|
|
def __validate_serial_no(self, row, key):
|
|
if row.get("serial_and_batch_bundle") and self.__transferred_items.get(key).get("serial_no"):
|
|
serial_nos = get_serial_nos_from_bundle(row.get("serial_and_batch_bundle"))
|
|
incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get("serial_no"))
|
|
|
|
if incorrect_sn:
|
|
incorrect_sn = "\n".join(incorrect_sn)
|
|
link = get_link_to_form(
|
|
self.subcontract_data.order_doctype, row.get(self.subcontract_data.order_field)
|
|
)
|
|
msg = f"The Serial Nos {incorrect_sn} has not supplied against the {self.subcontract_data.order_doctype} {link}"
|
|
frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
|
|
|
|
def __validate_supplied_items(self):
|
|
if self.doctype not in ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]:
|
|
return
|
|
|
|
for row in self.get(self.raw_material_table):
|
|
key = (row.rm_item_code, row.main_item_code, row.get(self.subcontract_data.order_field))
|
|
if not self.__transferred_items or not self.__transferred_items.get(key):
|
|
return
|
|
|
|
self.__validate_batch_no(row, key)
|
|
self.__validate_serial_no(row, key)
|
|
|
|
def set_materials_for_subcontracted_items(self, raw_material_table):
|
|
if self.doctype == "Purchase Invoice" and not self.update_stock:
|
|
return
|
|
|
|
self.raw_material_table = raw_material_table
|
|
self.__identify_change_in_item_table()
|
|
self.__prepare_supplied_items()
|
|
self.__validate_supplied_items()
|
|
|
|
def create_raw_materials_supplied(self, raw_material_table="supplied_items"):
|
|
self.set_materials_for_subcontracted_items(raw_material_table)
|
|
|
|
if self.doctype in ["Subcontracting Receipt", "Purchase Receipt", "Purchase Invoice"]:
|
|
for item in self.get("items"):
|
|
item.rm_supp_cost = 0.0
|
|
|
|
def __update_consumed_qty_in_subcontract_order(self, itemwise_consumed_qty):
|
|
fields = ["main_item_code", "rm_item_code", "parent", "supplied_qty", "name"]
|
|
filters = {"docstatus": 1, "parent": ("in", self.subcontract_orders)}
|
|
|
|
for row in frappe.get_all(
|
|
self.subcontract_data.order_supplied_items_field, fields=fields, filters=filters, order_by="idx"
|
|
):
|
|
key = (row.rm_item_code, row.main_item_code, row.parent)
|
|
consumed_qty = itemwise_consumed_qty.get(key, 0)
|
|
|
|
if row.supplied_qty < consumed_qty:
|
|
consumed_qty = row.supplied_qty
|
|
|
|
itemwise_consumed_qty[key] -= consumed_qty
|
|
frappe.db.set_value(
|
|
self.subcontract_data.order_supplied_items_field, row.name, "consumed_qty", consumed_qty
|
|
)
|
|
|
|
def set_consumed_qty_in_subcontract_order(self):
|
|
# Update consumed qty back in the subcontract order
|
|
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"] or self.get(
|
|
"is_old_subcontracting_flow"
|
|
):
|
|
self.__get_subcontract_orders()
|
|
itemwise_consumed_qty = defaultdict(float)
|
|
if self.get("is_old_subcontracting_flow"):
|
|
doctypes = ["Purchase Receipt", "Purchase Invoice"]
|
|
else:
|
|
doctypes = ["Subcontracting Receipt"]
|
|
|
|
for doctype in doctypes:
|
|
consumed_items, receipt_items = self.__update_consumed_materials(
|
|
doctype, return_consumed_items=True
|
|
)
|
|
|
|
for row in consumed_items:
|
|
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
|
|
itemwise_consumed_qty[key] += row.consumed_qty
|
|
|
|
self.__update_consumed_qty_in_subcontract_order(itemwise_consumed_qty)
|
|
|
|
def update_ordered_and_reserved_qty(self):
|
|
sco_map = {}
|
|
for item in self.get("items"):
|
|
if self.doctype == "Subcontracting Receipt" and item.subcontracting_order:
|
|
sco_map.setdefault(item.subcontracting_order, []).append(item.subcontracting_order_item)
|
|
|
|
for sco, sco_item_rows in sco_map.items():
|
|
if sco and sco_item_rows:
|
|
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
|
|
|
if sco_doc.status in ["Closed", "Cancelled"]:
|
|
frappe.throw(
|
|
_("{0} {1} is cancelled or closed").format(_("Subcontracting Order"), sco),
|
|
frappe.InvalidStatusError,
|
|
)
|
|
|
|
sco_doc.update_ordered_qty_for_subcontracting(sco_item_rows)
|
|
sco_doc.update_reserved_qty_for_subcontracting()
|
|
|
|
def make_sl_entries_for_supplier_warehouse(self, sl_entries):
|
|
if hasattr(self, "supplied_items"):
|
|
for item in self.get("supplied_items"):
|
|
# negative quantity is passed, as raw material qty has to be decreased
|
|
# when SCR is submitted and it has to be increased when SCR is cancelled
|
|
sl_entries.append(
|
|
self.get_sl_entries(
|
|
item,
|
|
{
|
|
"item_code": item.rm_item_code,
|
|
"incoming_rate": item.rate if self.is_return else 0,
|
|
"warehouse": self.supplier_warehouse,
|
|
"actual_qty": -1 * flt(item.consumed_qty, item.precision("consumed_qty")),
|
|
"dependant_sle_voucher_detail_no": item.reference_name,
|
|
},
|
|
)
|
|
)
|
|
|
|
def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
|
|
self.update_ordered_and_reserved_qty()
|
|
|
|
sl_entries = []
|
|
stock_items = self.get_stock_items()
|
|
|
|
for item in self.get("items"):
|
|
if item.item_code in stock_items and item.warehouse:
|
|
scr_qty = flt(item.qty) * flt(item.conversion_factor)
|
|
|
|
if scr_qty:
|
|
sle = self.get_sl_entries(item, {"actual_qty": flt(scr_qty)})
|
|
rate_db_precision = 6 if cint(self.precision("rate", item)) <= 6 else 9
|
|
incoming_rate = flt(item.rate, rate_db_precision)
|
|
sle.update(
|
|
{
|
|
"incoming_rate": incoming_rate,
|
|
"recalculate_rate": 1,
|
|
}
|
|
)
|
|
sl_entries.append(sle)
|
|
|
|
if flt(item.rejected_qty) != 0:
|
|
sl_entries.append(
|
|
self.get_sl_entries(
|
|
item,
|
|
{
|
|
"warehouse": item.rejected_warehouse,
|
|
"serial_and_batch_bundle": item.get("rejected_serial_and_batch_bundle"),
|
|
"actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor),
|
|
"incoming_rate": 0.0,
|
|
},
|
|
)
|
|
)
|
|
|
|
self.make_sl_entries_for_supplier_warehouse(sl_entries)
|
|
self.make_sl_entries(
|
|
sl_entries,
|
|
allow_negative_stock=allow_negative_stock,
|
|
via_landed_cost_voucher=via_landed_cost_voucher,
|
|
)
|
|
|
|
def get_supplied_items_cost(self, item_row_id, reset_outgoing_rate=True):
|
|
supplied_items_cost = 0.0
|
|
for item in self.get("supplied_items"):
|
|
if item.reference_name == item_row_id:
|
|
if (
|
|
self.get("is_old_subcontracting_flow")
|
|
and reset_outgoing_rate
|
|
and frappe.get_cached_value("Item", item.rm_item_code, "is_stock_item")
|
|
):
|
|
rate = get_incoming_rate(
|
|
{
|
|
"item_code": item.rm_item_code,
|
|
"warehouse": self.supplier_warehouse,
|
|
"posting_date": self.posting_date,
|
|
"posting_time": self.posting_time,
|
|
"qty": -1 * item.consumed_qty,
|
|
"voucher_detail_no": item.name,
|
|
"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
|
|
"serial_no": item.get("serial_no"),
|
|
"batch_no": item.get("batch_no"),
|
|
}
|
|
)
|
|
|
|
if rate > 0:
|
|
item.rate = rate
|
|
|
|
item.amount = flt(flt(item.consumed_qty) * flt(item.rate), item.precision("amount"))
|
|
supplied_items_cost += item.amount
|
|
|
|
return supplied_items_cost
|
|
|
|
def set_subcontracting_order_status(self):
|
|
if self.doctype == "Subcontracting Order":
|
|
self.update_status()
|
|
elif self.doctype == "Subcontracting Receipt":
|
|
self.__get_subcontract_orders
|
|
|
|
if self.subcontract_orders:
|
|
for sco in set(self.subcontract_orders):
|
|
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
|
sco_doc.update_status()
|
|
|
|
def calculate_additional_costs(self):
|
|
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
|
|
|
|
if self.total_additional_costs:
|
|
if self.distribute_additional_costs_based_on == "Amount":
|
|
total_amt = sum(
|
|
flt(item.amount) for item in self.get("items") if not item.get("is_scrap_item")
|
|
)
|
|
for item in self.items:
|
|
if not item.get("is_scrap_item"):
|
|
item.additional_cost_per_qty = (
|
|
(item.amount * self.total_additional_costs) / total_amt
|
|
) / item.qty
|
|
else:
|
|
total_qty = sum(flt(item.qty) for item in self.get("items") if not item.get("is_scrap_item"))
|
|
additional_cost_per_qty = self.total_additional_costs / total_qty
|
|
for item in self.items:
|
|
if not item.get("is_scrap_item"):
|
|
item.additional_cost_per_qty = additional_cost_per_qty
|
|
else:
|
|
for item in self.items:
|
|
if not item.get("is_scrap_item"):
|
|
item.additional_cost_per_qty = 0
|
|
|
|
@frappe.whitelist()
|
|
def get_current_stock(self):
|
|
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
|
for item in self.get("supplied_items"):
|
|
if self.supplier_warehouse:
|
|
actual_qty = frappe.db.get_value(
|
|
"Bin",
|
|
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
|
|
"actual_qty",
|
|
)
|
|
item.current_stock = flt(actual_qty)
|
|
|
|
@property
|
|
def sub_contracted_items(self):
|
|
if not hasattr(self, "_sub_contracted_items"):
|
|
self._sub_contracted_items = []
|
|
item_codes = list(set(item.item_code for item in self.get("items")))
|
|
if item_codes:
|
|
items = frappe.get_all(
|
|
"Item", filters={"name": ["in", item_codes], "is_sub_contracted_item": 1}
|
|
)
|
|
self._sub_contracted_items = [item.name for item in items]
|
|
|
|
return self._sub_contracted_items
|
|
|
|
def update_requested_qty(self):
|
|
material_request_map = {}
|
|
for d in self.get("items"):
|
|
if d.material_request_item:
|
|
material_request_map.setdefault(d.material_request, []).append(d.material_request_item)
|
|
|
|
for mr, mr_item_rows in material_request_map.items():
|
|
if mr and mr_item_rows:
|
|
mr_obj = frappe.get_doc("Material Request", mr)
|
|
|
|
if mr_obj.status in ["Stopped", "Cancelled"]:
|
|
frappe.throw(
|
|
_("Material Request {0} is cancelled or stopped").format(mr),
|
|
frappe.InvalidStatusError,
|
|
)
|
|
|
|
mr_obj.update_requested_qty(mr_item_rows)
|
|
|
|
|
|
def get_item_details(items):
|
|
item = frappe.qb.DocType("Item")
|
|
item_list = (
|
|
frappe.qb.from_(item)
|
|
.select(item.item_code, item.item_name, item.description, item.allow_alternative_item)
|
|
.where(item.name.isin(items))
|
|
.run(as_dict=True)
|
|
)
|
|
|
|
item_details = {}
|
|
for item in item_list:
|
|
item_details[item.item_code] = item
|
|
|
|
return item_details
|
|
|
|
|
|
@frappe.whitelist()
|
|
def make_rm_stock_entry(
|
|
subcontract_order, rm_items=None, order_doctype="Subcontracting Order", target_doc=None
|
|
):
|
|
if subcontract_order:
|
|
subcontract_order = frappe.get_doc(order_doctype, subcontract_order)
|
|
|
|
if not rm_items:
|
|
if not subcontract_order.supplied_items:
|
|
frappe.throw(_("No item available for transfer."))
|
|
|
|
rm_items = subcontract_order.supplied_items
|
|
|
|
fg_item_code_list = list(
|
|
set(item.get("main_item_code") or item.get("item_code") for item in rm_items)
|
|
)
|
|
|
|
if fg_item_code_list:
|
|
rm_item_code_list = tuple(set(item.get("rm_item_code") for item in rm_items))
|
|
item_wh = get_item_details(rm_item_code_list)
|
|
|
|
field_no_map, rm_detail_field = "purchase_order", "sco_rm_detail"
|
|
if order_doctype == "Purchase Order":
|
|
field_no_map, rm_detail_field = "subcontracting_order", "po_detail"
|
|
|
|
if target_doc and target_doc.get("items"):
|
|
target_doc.items = []
|
|
|
|
stock_entry = get_mapped_doc(
|
|
order_doctype,
|
|
subcontract_order.name,
|
|
{
|
|
order_doctype: {
|
|
"doctype": "Stock Entry",
|
|
"field_map": {
|
|
"supplier": "supplier",
|
|
"supplier_name": "supplier_name",
|
|
"supplier_address": "supplier_address",
|
|
"to_warehouse": "supplier_warehouse",
|
|
},
|
|
"field_no_map": [field_no_map],
|
|
"validation": {
|
|
"docstatus": ["=", 1],
|
|
},
|
|
},
|
|
},
|
|
target_doc,
|
|
ignore_child_tables=True,
|
|
)
|
|
|
|
stock_entry.purpose = "Send to Subcontractor"
|
|
|
|
if order_doctype == "Purchase Order":
|
|
stock_entry.purchase_order = subcontract_order.name
|
|
else:
|
|
stock_entry.subcontracting_order = subcontract_order.name
|
|
|
|
stock_entry.set_stock_entry_type()
|
|
|
|
for fg_item_code in fg_item_code_list:
|
|
for rm_item in rm_items:
|
|
if (
|
|
rm_item.get("main_item_code") == fg_item_code
|
|
or rm_item.get("item_code") == fg_item_code
|
|
):
|
|
rm_item_code = rm_item.get("rm_item_code")
|
|
items_dict = {
|
|
rm_item_code: {
|
|
rm_detail_field: rm_item.get("name"),
|
|
"item_name": rm_item.get("item_name")
|
|
or item_wh.get(rm_item_code, {}).get("item_name", ""),
|
|
"description": item_wh.get(rm_item_code, {}).get("description", ""),
|
|
"qty": rm_item.get("qty")
|
|
or max(rm_item.get("required_qty") - rm_item.get("total_supplied_qty"), 0),
|
|
"from_warehouse": rm_item.get("warehouse")
|
|
or rm_item.get("reserve_warehouse"),
|
|
"to_warehouse": subcontract_order.supplier_warehouse,
|
|
"stock_uom": rm_item.get("stock_uom"),
|
|
"serial_and_batch_bundle": rm_item.get("serial_and_batch_bundle"),
|
|
"main_item_code": fg_item_code,
|
|
"allow_alternative_item": item_wh.get(rm_item_code, {}).get(
|
|
"allow_alternative_item"
|
|
),
|
|
"use_serial_batch_fields": rm_item.get("use_serial_batch_fields"),
|
|
"serial_no": rm_item.get("serial_no")
|
|
if rm_item.get("use_serial_batch_fields")
|
|
else None,
|
|
"batch_no": rm_item.get("batch_no")
|
|
if rm_item.get("use_serial_batch_fields")
|
|
else None,
|
|
}
|
|
}
|
|
|
|
stock_entry.add_to_stock_entry_detail(items_dict)
|
|
|
|
if target_doc:
|
|
return stock_entry
|
|
else:
|
|
return stock_entry.as_dict()
|
|
else:
|
|
frappe.throw(_("No Items selected for transfer."))
|
|
|
|
|
|
def add_items_in_ste(ste_doc, row, qty, rm_details, rm_detail_field="sco_rm_detail", batch_no=None):
|
|
item = ste_doc.append("items", row.item_details)
|
|
|
|
rm_detail = list(set(row.get(f"{rm_detail_field}s")).intersection(rm_details))
|
|
item.update(
|
|
{
|
|
"qty": qty,
|
|
"batch_no": batch_no,
|
|
"basic_rate": row.item_details["rate"],
|
|
rm_detail_field: rm_detail[0] if rm_detail else "",
|
|
"s_warehouse": row.item_details["t_warehouse"],
|
|
"t_warehouse": row.item_details["s_warehouse"],
|
|
"item_code": row.item_details["rm_item_code"],
|
|
"subcontracted_item": row.item_details["main_item_code"],
|
|
"serial_no": "\n".join(row.serial_no) if row.serial_no else "",
|
|
}
|
|
)
|
|
|
|
|
|
def make_return_stock_entry_for_subcontract(
|
|
available_materials, order_doc, rm_details, order_doctype="Subcontracting Order"
|
|
):
|
|
def post_process(source_doc, target_doc):
|
|
target_doc.purpose = "Material Transfer"
|
|
|
|
if source_doc.doctype == "Purchase Order":
|
|
target_doc.purchase_order = source_doc.name
|
|
else:
|
|
target_doc.subcontracting_order = source_doc.name
|
|
|
|
target_doc.company = source_doc.company
|
|
target_doc.is_return = 1
|
|
|
|
ste_doc = get_mapped_doc(
|
|
order_doctype,
|
|
order_doc.name,
|
|
{
|
|
order_doctype: {
|
|
"doctype": "Stock Entry",
|
|
"field_no_map": ["purchase_order", "subcontracting_order"],
|
|
},
|
|
},
|
|
ignore_child_tables=True,
|
|
postprocess=post_process,
|
|
)
|
|
|
|
if order_doctype == "Purchase Order":
|
|
rm_detail_field = "po_detail"
|
|
else:
|
|
rm_detail_field = "sco_rm_detail"
|
|
|
|
for _key, value in available_materials.items():
|
|
if not value.qty:
|
|
continue
|
|
|
|
if value.batch_no:
|
|
for batch_no, qty in value.batch_no.items():
|
|
if qty > 0:
|
|
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field, batch_no)
|
|
else:
|
|
add_items_in_ste(ste_doc, value, value.qty, rm_details, rm_detail_field)
|
|
|
|
ste_doc.set_stock_entry_type()
|
|
|
|
return ste_doc
|
|
|
|
|
|
@frappe.whitelist()
|
|
def get_materials_from_supplier(subcontract_order, rm_details, order_doctype="Subcontracting Order"):
|
|
if isinstance(rm_details, str):
|
|
rm_details = json.loads(rm_details)
|
|
|
|
doc = frappe.get_cached_doc(order_doctype, subcontract_order)
|
|
doc.initialized_fields()
|
|
doc.subcontract_orders = [doc.name]
|
|
doc.get_available_materials()
|
|
|
|
if not doc.available_materials:
|
|
frappe.throw(
|
|
_("Materials are already received against the {0} {1}").format(order_doctype, subcontract_order)
|
|
)
|
|
|
|
return make_return_stock_entry_for_subcontract(doc.available_materials, doc, rm_details, order_doctype)
|