(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
1996 lines
66 KiB
Python
1996 lines
66 KiB
Python
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
|
# For license information, please see license.txt
|
|
|
|
|
|
import frappe
|
|
from frappe import qb
|
|
from frappe.query_builder.functions import Sum
|
|
<<<<<<< HEAD
|
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
|
=======
|
|
from frappe.tests import IntegrationTestCase
|
|
>>>>>>> 329d14957b (fix: validate negative qty)
|
|
from frappe.utils import add_days, getdate, nowdate
|
|
|
|
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
|
from erpnext.accounts.party import get_party_account
|
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import prepare_data_for_internal_transfer
|
|
from erpnext.stock.doctype.item.test_item import create_item
|
|
|
|
|
|
def make_customer(customer_name, currency=None):
|
|
if not frappe.db.exists("Customer", customer_name):
|
|
customer = frappe.new_doc("Customer")
|
|
customer.customer_name = customer_name
|
|
customer.customer_type = "Individual"
|
|
|
|
if currency:
|
|
customer.default_currency = currency
|
|
customer.save()
|
|
return customer.name
|
|
else:
|
|
return customer_name
|
|
|
|
|
|
def make_supplier(supplier_name, currency=None):
|
|
if not frappe.db.exists("Supplier", supplier_name):
|
|
supplier = frappe.new_doc("Supplier")
|
|
supplier.supplier_name = supplier_name
|
|
supplier.supplier_type = "Individual"
|
|
supplier.supplier_group = "All Supplier Groups"
|
|
|
|
if currency:
|
|
supplier.default_currency = currency
|
|
supplier.save()
|
|
return supplier.name
|
|
else:
|
|
return supplier_name
|
|
|
|
|
|
<<<<<<< HEAD
|
|
class TestAccountsController(FrappeTestCase):
|
|
=======
|
|
class TestAccountsController(IntegrationTestCase):
|
|
>>>>>>> 329d14957b (fix: validate negative qty)
|
|
"""
|
|
Test Exchange Gain/Loss booking on various scenarios.
|
|
Test Cases are numbered for better organization
|
|
|
|
10 series - Sales Invoice against Payment Entries
|
|
20 series - Sales Invoice against Journals
|
|
30 series - Sales Invoice against Credit Notes
|
|
40 series - Company default Cost center is unset
|
|
50 series - Journals against Journals
|
|
60 series - Journals against Payment Entries
|
|
70 series - Advances in Separate party account. Both Party and Advance account are in Foreign currency.
|
|
90 series - Dimension inheritence
|
|
"""
|
|
|
|
def setUp(self):
|
|
self.create_company()
|
|
self.create_account()
|
|
self.create_item()
|
|
self.create_parties()
|
|
self.clear_old_entries()
|
|
|
|
def tearDown(self):
|
|
frappe.db.rollback()
|
|
|
|
def create_company(self):
|
|
company_name = "_Test Company"
|
|
self.company_abbr = abbr = "_TC"
|
|
if frappe.db.exists("Company", company_name):
|
|
company = frappe.get_doc("Company", company_name)
|
|
else:
|
|
company = frappe.get_doc(
|
|
{
|
|
"doctype": "Company",
|
|
"company_name": company_name,
|
|
"country": "India",
|
|
"default_currency": "INR",
|
|
"create_chart_of_accounts_based_on": "Standard Template",
|
|
"chart_of_accounts": "Standard",
|
|
}
|
|
)
|
|
company = company.save()
|
|
|
|
self.company = company.name
|
|
self.cost_center = company.cost_center
|
|
self.warehouse = "Stores - " + abbr
|
|
self.finished_warehouse = "Finished Goods - " + abbr
|
|
self.income_account = "Sales - " + abbr
|
|
self.expense_account = "Cost of Goods Sold - " + abbr
|
|
self.debit_to = "Debtors - " + abbr
|
|
self.debit_usd = "Debtors USD - " + abbr
|
|
self.cash = "Cash - " + abbr
|
|
self.creditors = "Creditors - " + abbr
|
|
|
|
def create_item(self):
|
|
item = create_item(
|
|
item_code="_Test Notebook", is_stock_item=0, company=self.company, warehouse=self.warehouse
|
|
)
|
|
self.item = item if isinstance(item, str) else item.item_code
|
|
|
|
def create_parties(self):
|
|
self.create_customer()
|
|
self.create_supplier()
|
|
|
|
def create_customer(self):
|
|
self.customer = make_customer("_Test MC Customer USD", "USD")
|
|
|
|
def create_supplier(self):
|
|
self.supplier = make_supplier("_Test MC Supplier USD", "USD")
|
|
|
|
def create_account(self):
|
|
accounts = [
|
|
frappe._dict(
|
|
{
|
|
"attribute_name": "debtors_usd",
|
|
"name": "Debtors USD",
|
|
"account_type": "Receivable",
|
|
"account_currency": "USD",
|
|
"parent_account": "Accounts Receivable - " + self.company_abbr,
|
|
}
|
|
),
|
|
frappe._dict(
|
|
{
|
|
"attribute_name": "creditors_usd",
|
|
"name": "Creditors USD",
|
|
"account_type": "Payable",
|
|
"account_currency": "USD",
|
|
"parent_account": "Accounts Payable - " + self.company_abbr,
|
|
}
|
|
),
|
|
# Advance accounts under Asset and Liability header
|
|
frappe._dict(
|
|
{
|
|
"attribute_name": "advance_received_usd",
|
|
"name": "Advance Received USD",
|
|
"account_type": "Receivable",
|
|
"account_currency": "USD",
|
|
"parent_account": "Current Liabilities - " + self.company_abbr,
|
|
}
|
|
),
|
|
frappe._dict(
|
|
{
|
|
"attribute_name": "advance_paid_usd",
|
|
"name": "Advance Paid USD",
|
|
"account_type": "Payable",
|
|
"account_currency": "USD",
|
|
"parent_account": "Current Assets - " + self.company_abbr,
|
|
}
|
|
),
|
|
]
|
|
|
|
for x in accounts:
|
|
if not frappe.db.get_value("Account", filters={"account_name": x.name, "company": self.company}):
|
|
acc = frappe.new_doc("Account")
|
|
acc.account_name = x.name
|
|
acc.parent_account = x.parent_account
|
|
acc.company = self.company
|
|
acc.account_currency = x.account_currency
|
|
acc.account_type = x.account_type
|
|
acc.insert()
|
|
else:
|
|
name = frappe.db.get_value(
|
|
"Account",
|
|
filters={"account_name": x.name, "company": self.company},
|
|
fieldname="name",
|
|
pluck=True,
|
|
)
|
|
acc = frappe.get_doc("Account", name)
|
|
setattr(self, x.attribute_name, acc.name)
|
|
|
|
def setup_advance_accounts_in_party_master(self):
|
|
company = frappe.get_doc("Company", self.company)
|
|
company.book_advance_payments_in_separate_party_account = 1
|
|
company.save()
|
|
|
|
customer = frappe.get_doc("Customer", self.customer)
|
|
customer.append(
|
|
"accounts",
|
|
{
|
|
"company": self.company,
|
|
"account": self.debtors_usd,
|
|
"advance_account": self.advance_received_usd,
|
|
},
|
|
)
|
|
customer.save()
|
|
|
|
supplier = frappe.get_doc("Supplier", self.supplier)
|
|
supplier.append(
|
|
"accounts",
|
|
{
|
|
"company": self.company,
|
|
"account": self.creditors_usd,
|
|
"advance_account": self.advance_paid_usd,
|
|
},
|
|
)
|
|
supplier.save()
|
|
|
|
def remove_advance_accounts_from_party_master(self):
|
|
company = frappe.get_doc("Company", self.company)
|
|
company.book_advance_payments_in_separate_party_account = 0
|
|
company.save()
|
|
customer = frappe.get_doc("Customer", self.customer)
|
|
customer.accounts = []
|
|
customer.save()
|
|
supplier = frappe.get_doc("Supplier", self.supplier)
|
|
supplier.accounts = []
|
|
supplier.save()
|
|
|
|
def create_sales_invoice(
|
|
self,
|
|
qty=1,
|
|
rate=1,
|
|
conversion_rate=80,
|
|
posting_date=None,
|
|
do_not_save=False,
|
|
do_not_submit=False,
|
|
):
|
|
"""
|
|
Helper function to populate default values in sales invoice
|
|
"""
|
|
if posting_date is None:
|
|
posting_date = nowdate()
|
|
|
|
sinv = create_sales_invoice(
|
|
qty=qty,
|
|
rate=rate,
|
|
company=self.company,
|
|
customer=self.customer,
|
|
item_code=self.item,
|
|
item_name=self.item,
|
|
cost_center=self.cost_center,
|
|
warehouse=self.warehouse,
|
|
debit_to=self.debit_usd,
|
|
parent_cost_center=self.cost_center,
|
|
update_stock=0,
|
|
currency="USD",
|
|
conversion_rate=conversion_rate,
|
|
is_pos=0,
|
|
is_return=0,
|
|
return_against=None,
|
|
income_account=self.income_account,
|
|
expense_account=self.expense_account,
|
|
do_not_save=do_not_save,
|
|
do_not_submit=do_not_submit,
|
|
)
|
|
return sinv
|
|
|
|
def create_payment_entry(
|
|
self, amount=1, source_exc_rate=75, posting_date=None, customer=None, submit=True
|
|
):
|
|
"""
|
|
Helper function to populate default values in payment entry
|
|
"""
|
|
if posting_date is None:
|
|
posting_date = nowdate()
|
|
|
|
payment = create_payment_entry(
|
|
company=self.company,
|
|
payment_type="Receive",
|
|
party_type="Customer",
|
|
party=customer or self.customer,
|
|
paid_from=self.debit_usd,
|
|
paid_to=self.cash,
|
|
paid_amount=amount,
|
|
)
|
|
payment.source_exchange_rate = source_exc_rate
|
|
payment.received_amount = source_exc_rate * amount
|
|
payment.posting_date = posting_date
|
|
return payment
|
|
|
|
def create_purchase_invoice(
|
|
self,
|
|
qty=1,
|
|
rate=1,
|
|
conversion_rate=80,
|
|
posting_date=None,
|
|
do_not_save=False,
|
|
do_not_submit=False,
|
|
):
|
|
"""
|
|
Helper function to populate default values in purchase invoice
|
|
"""
|
|
if posting_date is None:
|
|
posting_date = nowdate()
|
|
|
|
pinv = make_purchase_invoice(
|
|
posting_date=posting_date,
|
|
qty=qty,
|
|
rate=rate,
|
|
company=self.company,
|
|
supplier=self.supplier,
|
|
item_code=self.item,
|
|
item_name=self.item,
|
|
cost_center=self.cost_center,
|
|
warehouse=self.warehouse,
|
|
parent_cost_center=self.cost_center,
|
|
update_stock=0,
|
|
currency="USD",
|
|
conversion_rate=conversion_rate,
|
|
is_pos=0,
|
|
is_return=0,
|
|
income_account=self.income_account,
|
|
expense_account=self.expense_account,
|
|
do_not_save=True,
|
|
)
|
|
pinv.credit_to = self.creditors_usd
|
|
if not do_not_save:
|
|
pinv.save()
|
|
if not do_not_submit:
|
|
pinv.submit()
|
|
return pinv
|
|
|
|
def clear_old_entries(self):
|
|
doctype_list = [
|
|
"GL Entry",
|
|
"Payment Ledger Entry",
|
|
"Sales Invoice",
|
|
"Purchase Invoice",
|
|
"Payment Entry",
|
|
"Journal Entry",
|
|
]
|
|
for doctype in doctype_list:
|
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
|
|
|
def create_payment_reconciliation(self):
|
|
pr = frappe.new_doc("Payment Reconciliation")
|
|
pr.company = self.company
|
|
pr.party_type = "Customer"
|
|
pr.party = self.customer
|
|
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
|
return pr
|
|
|
|
def create_journal_entry(
|
|
self,
|
|
acc1=None,
|
|
acc1_exc_rate=None,
|
|
acc2_exc_rate=None,
|
|
acc2=None,
|
|
acc1_amount=0,
|
|
acc2_amount=0,
|
|
posting_date=None,
|
|
cost_center=None,
|
|
):
|
|
je = frappe.new_doc("Journal Entry")
|
|
je.posting_date = posting_date or nowdate()
|
|
je.company = self.company
|
|
je.user_remark = "test"
|
|
je.multi_currency = True
|
|
if not cost_center:
|
|
cost_center = self.cost_center
|
|
je.set(
|
|
"accounts",
|
|
[
|
|
{
|
|
"account": acc1,
|
|
"exchange_rate": acc1_exc_rate or 1,
|
|
"cost_center": cost_center,
|
|
"debit_in_account_currency": acc1_amount if acc1_amount > 0 else 0,
|
|
"credit_in_account_currency": abs(acc1_amount) if acc1_amount < 0 else 0,
|
|
"debit": acc1_amount * acc1_exc_rate if acc1_amount > 0 else 0,
|
|
"credit": abs(acc1_amount * acc1_exc_rate) if acc1_amount < 0 else 0,
|
|
},
|
|
{
|
|
"account": acc2,
|
|
"exchange_rate": acc2_exc_rate or 1,
|
|
"cost_center": cost_center,
|
|
"credit_in_account_currency": acc2_amount if acc2_amount > 0 else 0,
|
|
"debit_in_account_currency": abs(acc2_amount) if acc2_amount < 0 else 0,
|
|
"credit": acc2_amount * acc2_exc_rate if acc2_amount > 0 else 0,
|
|
"debit": abs(acc2_amount * acc2_exc_rate) if acc2_amount < 0 else 0,
|
|
},
|
|
],
|
|
)
|
|
return je
|
|
|
|
def get_journals_for(self, voucher_type: str, voucher_no: str) -> list:
|
|
journals = []
|
|
if voucher_type and voucher_no:
|
|
journals = frappe.db.get_all(
|
|
"Journal Entry Account",
|
|
filters={"reference_type": voucher_type, "reference_name": voucher_no, "docstatus": 1},
|
|
fields=["parent"],
|
|
)
|
|
return journals
|
|
|
|
def assert_ledger_outstanding(
|
|
self,
|
|
voucher_type: str,
|
|
voucher_no: str,
|
|
outstanding: float,
|
|
outstanding_in_account_currency: float,
|
|
) -> None:
|
|
"""
|
|
Assert outstanding amount based on ledger on both company/base currency and account currency
|
|
"""
|
|
|
|
ple = qb.DocType("Payment Ledger Entry")
|
|
current_outstanding = (
|
|
qb.from_(ple)
|
|
.select(
|
|
Sum(ple.amount).as_("outstanding"),
|
|
Sum(ple.amount_in_account_currency).as_("outstanding_in_account_currency"),
|
|
)
|
|
.where(
|
|
(ple.against_voucher_type == voucher_type)
|
|
& (ple.against_voucher_no == voucher_no)
|
|
& (ple.delinked == 0)
|
|
)
|
|
.run(as_dict=True)[0]
|
|
)
|
|
self.assertEqual(outstanding, current_outstanding.outstanding)
|
|
self.assertEqual(outstanding_in_account_currency, current_outstanding.outstanding_in_account_currency)
|
|
|
|
def test_10_payment_against_sales_invoice(self):
|
|
# Sales Invoice in Foreign Currency
|
|
rate = 80
|
|
rate_in_account_currency = 1
|
|
|
|
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency)
|
|
|
|
# Test payments with different exchange rates
|
|
for exc_rate in [75.9, 83.1, 80.01]:
|
|
with self.subTest(exc_rate=exc_rate):
|
|
pe = self.create_payment_entry(amount=1, source_exc_rate=exc_rate).save()
|
|
pe.append(
|
|
"references",
|
|
{"reference_doctype": si.doctype, "reference_name": si.name, "allocated_amount": 1},
|
|
)
|
|
pe = pe.save().submit()
|
|
|
|
# Outstanding in both currencies should be '0'
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
|
|
|
# Cancel Payment
|
|
pe.cancel()
|
|
|
|
# outstanding should be same as grand total
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, rate_in_account_currency)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, rate, rate_in_account_currency)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_pe, [])
|
|
|
|
def test_11_advance_against_sales_invoice(self):
|
|
# Advance Payment
|
|
adv = self.create_payment_entry(amount=1, source_exc_rate=85).save().submit()
|
|
adv.reload()
|
|
|
|
# Sales Invoices in different exchange rates
|
|
for exc_rate in [75.9, 83.1, 80.01]:
|
|
with self.subTest(exc_rate=exc_rate):
|
|
si = self.create_sales_invoice(qty=1, conversion_rate=exc_rate, rate=1, do_not_submit=True)
|
|
advances = si.get_advance_entries()
|
|
self.assertEqual(len(advances), 1)
|
|
self.assertEqual(advances[0].reference_name, adv.name)
|
|
si.append(
|
|
"advances",
|
|
{
|
|
"doctype": "Sales Invoice Advance",
|
|
"reference_type": advances[0].reference_type,
|
|
"reference_name": advances[0].reference_name,
|
|
"reference_row": advances[0].reference_row,
|
|
"advance_amount": 1,
|
|
"allocated_amount": 1,
|
|
"ref_exchange_rate": advances[0].exchange_rate,
|
|
"remarks": advances[0].remarks,
|
|
},
|
|
)
|
|
|
|
si = si.save()
|
|
si = si.submit()
|
|
|
|
# Outstanding in both currencies should be '0'
|
|
adv.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_adv)
|
|
|
|
# Cancel Invoice
|
|
si.cancel()
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_adv, [])
|
|
|
|
def test_12_partial_advance_and_payment_for_sales_invoice(self):
|
|
"""
|
|
Sales invoice with partial advance payment, and a normal payment reconciled
|
|
"""
|
|
# Partial Advance
|
|
adv = self.create_payment_entry(amount=1, source_exc_rate=85).save().submit()
|
|
adv.reload()
|
|
|
|
# sales invoice with advance(partial amount)
|
|
rate_in_account_currency = 1
|
|
si = self.create_sales_invoice(
|
|
qty=2, conversion_rate=80, rate=rate_in_account_currency, do_not_submit=True
|
|
)
|
|
advances = si.get_advance_entries()
|
|
self.assertEqual(len(advances), 1)
|
|
self.assertEqual(advances[0].reference_name, adv.name)
|
|
si.append(
|
|
"advances",
|
|
{
|
|
"doctype": "Sales Invoice Advance",
|
|
"reference_type": advances[0].reference_type,
|
|
"reference_name": advances[0].reference_name,
|
|
"advance_amount": 1,
|
|
"allocated_amount": 1,
|
|
"ref_exchange_rate": advances[0].exchange_rate,
|
|
"remarks": advances[0].remarks,
|
|
},
|
|
)
|
|
si = si.save()
|
|
si = si.submit()
|
|
|
|
# Outstanding should be there in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1) # account currency
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created for the partial advance
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_adv)
|
|
|
|
# Payment for remaining amount
|
|
pe = self.create_payment_entry(amount=1, source_exc_rate=75).save()
|
|
pe.append(
|
|
"references",
|
|
{"reference_doctype": si.doctype, "reference_name": si.name, "allocated_amount": 1},
|
|
)
|
|
pe = pe.save().submit()
|
|
|
|
# Outstanding in both currencies should be '0'
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created for the payment
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
# There should be 2 JE's now. One for the advance and one for the payment
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_pe + exc_je_for_adv)
|
|
|
|
# Cancel Invoice
|
|
si.reload()
|
|
si.cancel()
|
|
|
|
# Exchange Gain/Loss Journal should been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_pe, [])
|
|
self.assertEqual(exc_je_for_adv, [])
|
|
|
|
def test_13_partial_advance_and_payment_for_invoice_with_cancellation(self):
|
|
"""
|
|
Invoice with partial advance payment, and a normal payment. Then cancel advance and payment.
|
|
"""
|
|
# Partial Advance
|
|
adv = self.create_payment_entry(amount=1, source_exc_rate=85).save().submit()
|
|
adv.reload()
|
|
|
|
# invoice with advance(partial amount)
|
|
si = self.create_sales_invoice(qty=2, conversion_rate=80, rate=1, do_not_submit=True)
|
|
advances = si.get_advance_entries()
|
|
self.assertEqual(len(advances), 1)
|
|
self.assertEqual(advances[0].reference_name, adv.name)
|
|
si.append(
|
|
"advances",
|
|
{
|
|
"doctype": "Sales Invoice Advance",
|
|
"reference_type": advances[0].reference_type,
|
|
"reference_name": advances[0].reference_name,
|
|
"advance_amount": 1,
|
|
"allocated_amount": 1,
|
|
"ref_exchange_rate": advances[0].exchange_rate,
|
|
"remarks": advances[0].remarks,
|
|
},
|
|
)
|
|
si = si.save()
|
|
si = si.submit()
|
|
|
|
# Outstanding should be there in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1) # account currency
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created for the partial advance
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_adv)
|
|
|
|
# Payment(remaining amount)
|
|
pe = self.create_payment_entry(amount=1, source_exc_rate=75).save()
|
|
pe.append(
|
|
"references",
|
|
{"reference_doctype": si.doctype, "reference_name": si.name, "allocated_amount": 1},
|
|
)
|
|
pe = pe.save().submit()
|
|
|
|
# Outstanding should be '0' in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created for the payment
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
# There should be 2 JE's now. One for the advance and one for the payment
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_pe + exc_je_for_adv)
|
|
|
|
adv.reload()
|
|
adv.cancel()
|
|
|
|
# Outstanding should be there in both currencies, since advance is cancelled.
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1) # account currency
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
# Exchange Gain/Loss Journal for advance should been cancelled
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_adv, [])
|
|
|
|
def test_14_same_payment_split_against_invoice(self):
|
|
# Invoice in Foreign Currency
|
|
si = self.create_sales_invoice(qty=2, conversion_rate=80, rate=1)
|
|
# Payment
|
|
pe = self.create_payment_entry(amount=2, source_exc_rate=75).save()
|
|
pe.append(
|
|
"references",
|
|
{"reference_doctype": si.doctype, "reference_name": si.name, "allocated_amount": 1},
|
|
)
|
|
pe = pe.save().submit()
|
|
|
|
# There should be outstanding in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
|
|
|
# Reconcile the remaining amount
|
|
pr = frappe.get_doc("Payment Reconciliation")
|
|
pr.company = self.company
|
|
pr.party_type = "Customer"
|
|
pr.party = self.customer
|
|
pr.receivable_payable_account = self.debit_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# Exc gain/loss journal should have been creaetd for the reconciled amount
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_pe), 2)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_pe)
|
|
|
|
# There should be no outstanding
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Cancel Payment
|
|
pe.reload()
|
|
pe.cancel()
|
|
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 2)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_pe, [])
|
|
|
|
def test_15_gain_loss_on_different_posting_date(self):
|
|
# Invoice in Foreign Currency
|
|
si = self.create_sales_invoice(
|
|
posting_date=add_days(nowdate(), -2), qty=2, conversion_rate=80, rate=1
|
|
)
|
|
# Payment
|
|
pe = (
|
|
self.create_payment_entry(posting_date=add_days(nowdate(), -1), amount=2, source_exc_rate=75)
|
|
.save()
|
|
.submit()
|
|
)
|
|
|
|
# There should be outstanding in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 2)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
|
|
|
# Reconcile the remaining amount
|
|
pr = frappe.get_doc("Payment Reconciliation")
|
|
pr.company = self.company
|
|
pr.party_type = "Customer"
|
|
pr.party = self.customer
|
|
pr.receivable_payable_account = self.debit_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.allocation[0].gain_loss_posting_date = add_days(nowdate(), 1)
|
|
pr.reconcile()
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
|
|
|
self.assertEqual(
|
|
frappe.db.get_value("Journal Entry", exc_je_for_si[0].parent, "posting_date"),
|
|
getdate(add_days(nowdate(), 1)),
|
|
)
|
|
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# There should be no outstanding
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Cancel Payment
|
|
pe.reload()
|
|
pe.cancel()
|
|
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 2)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_pe, [])
|
|
|
|
<<<<<<< HEAD
|
|
@change_settings("Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1})
|
|
=======
|
|
@IntegrationTestCase.change_settings(
|
|
"Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1}
|
|
)
|
|
>>>>>>> 329d14957b (fix: validate negative qty)
|
|
def test_16_internal_transfer_at_arms_length_price(self):
|
|
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_purchase_invoice
|
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
|
|
|
prepare_data_for_internal_transfer()
|
|
company = "_Test Company with perpetual inventory"
|
|
target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company)
|
|
warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company)
|
|
arms_length_price = 40
|
|
|
|
si = create_sales_invoice(
|
|
company=company,
|
|
customer="_Test Internal Customer 2",
|
|
debit_to="Debtors - TCP1",
|
|
target_warehouse=target_warehouse,
|
|
warehouse=warehouse,
|
|
income_account="Sales - TCP1",
|
|
expense_account="Cost of Goods Sold - TCP1",
|
|
cost_center="Main - TCP1",
|
|
update_stock=True,
|
|
do_not_save=True,
|
|
do_not_submit=True,
|
|
)
|
|
|
|
si.items[0].rate = arms_length_price
|
|
si.save()
|
|
# rate should not reset to incoming rate
|
|
self.assertEqual(si.items[0].rate, arms_length_price)
|
|
|
|
frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0)
|
|
si.items[0].rate = arms_length_price
|
|
si.save()
|
|
# rate should reset to incoming rate
|
|
self.assertEqual(si.items[0].rate, 100)
|
|
|
|
si.update_stock = 0
|
|
si.save()
|
|
si.submit()
|
|
|
|
pi = make_inter_company_purchase_invoice(si.name)
|
|
pi.update_stock = 1
|
|
pi.items[0].rate = arms_length_price
|
|
pi.items[0].warehouse = target_warehouse
|
|
pi.items[0].from_warehouse = warehouse
|
|
pi.save()
|
|
|
|
self.assertEqual(pi.items[0].rate, 100)
|
|
self.assertEqual(pi.items[0].valuation_rate, 100)
|
|
|
|
frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 1)
|
|
pi = make_inter_company_purchase_invoice(si.name)
|
|
pi.update_stock = 1
|
|
pi.items[0].rate = arms_length_price
|
|
pi.items[0].warehouse = target_warehouse
|
|
pi.items[0].from_warehouse = warehouse
|
|
pi.save()
|
|
|
|
self.assertEqual(pi.items[0].rate, arms_length_price)
|
|
self.assertEqual(pi.items[0].valuation_rate, 100)
|
|
|
|
def test_20_journal_against_sales_invoice(self):
|
|
# Invoice in Foreign Currency
|
|
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
|
# Payment
|
|
je = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=75,
|
|
acc2=self.cash,
|
|
acc1_amount=-1,
|
|
acc2_amount=-75,
|
|
acc2_exc_rate=1,
|
|
)
|
|
je.accounts[0].party_type = "Customer"
|
|
je.accounts[0].party = self.customer
|
|
je = je.save().submit()
|
|
|
|
# Reconcile the remaining amount
|
|
pr = self.create_payment_reconciliation()
|
|
# pr.receivable_payable_account = self.debit_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# There should be no outstanding in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_je = self.get_journals_for(je.doctype, je.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(
|
|
len(exc_je_for_si), 2
|
|
) # payment also has reference. so, there are 2 journals referencing invoice
|
|
self.assertEqual(len(exc_je_for_je), 1)
|
|
self.assertIn(exc_je_for_je[0], exc_je_for_si)
|
|
|
|
# Cancel Payment
|
|
je.reload()
|
|
je.cancel()
|
|
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_je = self.get_journals_for(je.doctype, je.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_je, [])
|
|
|
|
def test_21_advance_journal_against_sales_invoice(self):
|
|
# Advance Payment
|
|
adv_exc_rate = 80
|
|
adv = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=adv_exc_rate,
|
|
acc2=self.cash,
|
|
acc1_amount=-1,
|
|
acc2_amount=adv_exc_rate * -1,
|
|
acc2_exc_rate=1,
|
|
)
|
|
adv.accounts[0].party_type = "Customer"
|
|
adv.accounts[0].party = self.customer
|
|
adv.accounts[0].is_advance = "Yes"
|
|
adv = adv.save().submit()
|
|
adv.reload()
|
|
|
|
# Sales Invoices in different exchange rates
|
|
for exc_rate in [75.9, 83.1]:
|
|
with self.subTest(exc_rate=exc_rate):
|
|
si = self.create_sales_invoice(qty=1, conversion_rate=exc_rate, rate=1, do_not_submit=True)
|
|
advances = si.get_advance_entries()
|
|
self.assertEqual(len(advances), 1)
|
|
self.assertEqual(advances[0].reference_name, adv.name)
|
|
si.append(
|
|
"advances",
|
|
{
|
|
"doctype": "Sales Invoice Advance",
|
|
"reference_type": advances[0].reference_type,
|
|
"reference_name": advances[0].reference_name,
|
|
"reference_row": advances[0].reference_row,
|
|
"advance_amount": 1,
|
|
"allocated_amount": 1,
|
|
"ref_exchange_rate": advances[0].exchange_rate,
|
|
"remarks": advances[0].remarks,
|
|
},
|
|
)
|
|
|
|
si = si.save()
|
|
si = si.submit()
|
|
|
|
# Outstanding in both currencies should be '0'
|
|
adv.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = [
|
|
x for x in self.get_journals_for(si.doctype, si.name) if x.parent != adv.name
|
|
]
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_adv)
|
|
|
|
# Cancel Invoice
|
|
si.cancel()
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_adv, [])
|
|
|
|
def test_22_partial_advance_and_payment_for_invoice_with_cancellation(self):
|
|
"""
|
|
Invoice with partial advance payment as Journal, and a normal payment. Then cancel advance and payment.
|
|
"""
|
|
# Partial Advance
|
|
adv_exc_rate = 75
|
|
adv = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=adv_exc_rate,
|
|
acc2=self.cash,
|
|
acc1_amount=-1,
|
|
acc2_amount=adv_exc_rate * -1,
|
|
acc2_exc_rate=1,
|
|
)
|
|
adv.accounts[0].party_type = "Customer"
|
|
adv.accounts[0].party = self.customer
|
|
adv.accounts[0].is_advance = "Yes"
|
|
adv = adv.save().submit()
|
|
adv.reload()
|
|
|
|
# invoice with advance(partial amount)
|
|
si = self.create_sales_invoice(qty=3, conversion_rate=80, rate=1, do_not_submit=True)
|
|
advances = si.get_advance_entries()
|
|
self.assertEqual(len(advances), 1)
|
|
self.assertEqual(advances[0].reference_name, adv.name)
|
|
si.append(
|
|
"advances",
|
|
{
|
|
"doctype": "Sales Invoice Advance",
|
|
"reference_type": advances[0].reference_type,
|
|
"reference_name": advances[0].reference_name,
|
|
"reference_row": advances[0].reference_row,
|
|
"advance_amount": 1,
|
|
"allocated_amount": 1,
|
|
"ref_exchange_rate": advances[0].exchange_rate,
|
|
"remarks": advances[0].remarks,
|
|
},
|
|
)
|
|
|
|
si = si.save()
|
|
si = si.submit()
|
|
|
|
# Outstanding should be there in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 2) # account currency
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created for the partial advance
|
|
exc_je_for_si = [x for x in self.get_journals_for(si.doctype, si.name) if x.parent != adv.name]
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_adv)
|
|
|
|
# Payment
|
|
adv2_exc_rate = 83
|
|
pay = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=adv2_exc_rate,
|
|
acc2=self.cash,
|
|
acc1_amount=-2,
|
|
acc2_amount=adv2_exc_rate * -2,
|
|
acc2_exc_rate=1,
|
|
)
|
|
pay.accounts[0].party_type = "Customer"
|
|
pay.accounts[0].party = self.customer
|
|
pay.accounts[0].is_advance = "Yes"
|
|
pay = pay.save().submit()
|
|
pay.reload()
|
|
|
|
# Reconcile the remaining amount
|
|
pr = self.create_payment_reconciliation()
|
|
# pr.receivable_payable_account = self.debit_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# Outstanding should be '0' in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created for the payment
|
|
exc_je_for_si = [
|
|
x
|
|
for x in self.get_journals_for(si.doctype, si.name)
|
|
if x.parent != adv.name and x.parent != pay.name
|
|
]
|
|
exc_je_for_pe = self.get_journals_for(pay.doctype, pay.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
# There should be 2 JE's now. One for the advance and one for the payment
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_pe + exc_je_for_adv)
|
|
|
|
adv.reload()
|
|
adv.cancel()
|
|
|
|
# Outstanding should be there in both currencies, since advance is cancelled.
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1) # account currency
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
exc_je_for_si = [
|
|
x
|
|
for x in self.get_journals_for(si.doctype, si.name)
|
|
if x.parent != adv.name and x.parent != pay.name
|
|
]
|
|
exc_je_for_pe = self.get_journals_for(pay.doctype, pay.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
# Exchange Gain/Loss Journal for advance should been cancelled
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_adv, [])
|
|
|
|
def test_23_same_journal_split_against_single_invoice(self):
|
|
# Invoice in Foreign Currency
|
|
si = self.create_sales_invoice(qty=2, conversion_rate=80, rate=1)
|
|
# Payment
|
|
je = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=75,
|
|
acc2=self.cash,
|
|
acc1_amount=-2,
|
|
acc2_amount=-150,
|
|
acc2_exc_rate=1,
|
|
)
|
|
je.accounts[0].party_type = "Customer"
|
|
je.accounts[0].party = self.customer
|
|
je = je.save().submit()
|
|
|
|
# Reconcile the first half
|
|
pr = self.create_payment_reconciliation()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
difference_amount = pr.calculate_difference_on_allocation_change(
|
|
[x.as_dict() for x in pr.payments], [x.as_dict() for x in pr.invoices], 1
|
|
)
|
|
pr.allocation[0].allocated_amount = 1
|
|
pr.allocation[0].difference_amount = difference_amount
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
|
|
# There should be outstanding in both currencies
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = [x for x in self.get_journals_for(si.doctype, si.name) if x.parent != je.name]
|
|
exc_je_for_je = self.get_journals_for(je.doctype, je.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_je), 1)
|
|
self.assertIn(exc_je_for_je[0], exc_je_for_si)
|
|
|
|
# reconcile remaining half
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.allocation[0].allocated_amount = 1
|
|
pr.allocation[0].difference_amount = difference_amount
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = [x for x in self.get_journals_for(si.doctype, si.name) if x.parent != je.name]
|
|
exc_je_for_je = self.get_journals_for(je.doctype, je.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_je), 2)
|
|
self.assertIn(exc_je_for_je[0], exc_je_for_si)
|
|
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Cancel Payment
|
|
je.reload()
|
|
je.cancel()
|
|
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 2)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_je = self.get_journals_for(je.doctype, je.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_je, [])
|
|
|
|
def test_24_journal_against_multiple_invoices(self):
|
|
si1 = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
|
si2 = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
|
|
|
# Payment
|
|
je = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=75,
|
|
acc2=self.cash,
|
|
acc1_amount=-2,
|
|
acc2_amount=-150,
|
|
acc2_exc_rate=1,
|
|
)
|
|
je.accounts[0].party_type = "Customer"
|
|
je.accounts[0].party = self.customer
|
|
je = je.save().submit()
|
|
|
|
pr = self.create_payment_reconciliation()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 2)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
si1.reload()
|
|
si2.reload()
|
|
|
|
self.assertEqual(si1.outstanding_amount, 0)
|
|
self.assertEqual(si2.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si1.doctype, si1.name, 0.0, 0.0)
|
|
self.assert_ledger_outstanding(si2.doctype, si2.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created
|
|
# remove payment JE from list
|
|
exc_je_for_si1 = [x for x in self.get_journals_for(si1.doctype, si1.name) if x.parent != je.name]
|
|
exc_je_for_si2 = [x for x in self.get_journals_for(si2.doctype, si2.name) if x.parent != je.name]
|
|
exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
|
|
self.assertEqual(len(exc_je_for_si1), 1)
|
|
self.assertEqual(len(exc_je_for_si2), 1)
|
|
self.assertEqual(len(exc_je_for_je), 2)
|
|
|
|
si1.cancel()
|
|
# Gain/Loss JE of si1 should've been cancelled
|
|
exc_je_for_si1 = [x for x in self.get_journals_for(si1.doctype, si1.name) if x.parent != je.name]
|
|
exc_je_for_si2 = [x for x in self.get_journals_for(si2.doctype, si2.name) if x.parent != je.name]
|
|
exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
|
|
self.assertEqual(len(exc_je_for_si1), 0)
|
|
self.assertEqual(len(exc_je_for_si2), 1)
|
|
self.assertEqual(len(exc_je_for_je), 1)
|
|
|
|
def test_30_cr_note_against_sales_invoice(self):
|
|
"""
|
|
Reconciling Cr Note against Sales Invoice, both having different exchange rates
|
|
"""
|
|
# Invoice in Foreign currency
|
|
si = self.create_sales_invoice(qty=2, conversion_rate=80, rate=1)
|
|
|
|
# Cr Note in Foreign currency of different exchange rate
|
|
cr_note = self.create_sales_invoice(qty=-2, conversion_rate=75, rate=1, do_not_save=True)
|
|
cr_note.is_return = 1
|
|
cr_note.save().submit()
|
|
|
|
# Reconcile the first half
|
|
pr = self.create_payment_reconciliation()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
difference_amount = pr.calculate_difference_on_allocation_change(
|
|
[x.as_dict() for x in pr.payments], [x.as_dict() for x in pr.invoices], 1
|
|
)
|
|
pr.allocation[0].allocated_amount = 1
|
|
pr.allocation[0].difference_amount = difference_amount
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_cr = self.get_journals_for(cr_note.doctype, cr_note.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_cr), 2)
|
|
self.assertEqual(exc_je_for_cr, exc_je_for_si)
|
|
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 1)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
cr_note.reload()
|
|
cr_note.cancel()
|
|
|
|
# with the introduction of 'cancel_system_generated_credit_debit_notes' in accounts controller
|
|
# JE(Credit Note) will be cancelled once the parent is cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_cr = self.get_journals_for(cr_note.doctype, cr_note.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 0)
|
|
self.assertEqual(len(exc_je_for_cr), 0)
|
|
|
|
# No references, full outstanding
|
|
si.reload()
|
|
self.assertEqual(si.outstanding_amount, 2)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 160.0, 2.0)
|
|
|
|
def test_40_cost_center_from_payment_entry(self):
|
|
"""
|
|
Gain/Loss JE should inherit cost center from payment if company default is unset
|
|
"""
|
|
# remove default cost center
|
|
cc = frappe.db.get_value("Company", self.company, "cost_center")
|
|
frappe.db.set_value("Company", self.company, "cost_center", None)
|
|
|
|
rate_in_account_currency = 1
|
|
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
|
si.cost_center = None
|
|
si.save().submit()
|
|
|
|
pe = get_payment_entry(si.doctype, si.name)
|
|
pe.source_exchange_rate = 75
|
|
pe.received_amount = 75
|
|
pe.cost_center = self.cost_center
|
|
pe = pe.save().submit()
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_pe), 1)
|
|
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
|
|
|
self.assertEqual(
|
|
[self.cost_center, self.cost_center],
|
|
frappe.db.get_all(
|
|
"Journal Entry Account", filters={"parent": exc_je_for_si[0].parent}, pluck="cost_center"
|
|
),
|
|
)
|
|
frappe.db.set_value("Company", self.company, "cost_center", cc)
|
|
|
|
def test_41_cost_center_from_journal_entry(self):
|
|
"""
|
|
Gain/Loss JE should inherit cost center from payment if company default is unset
|
|
"""
|
|
# remove default cost center
|
|
cc = frappe.db.get_value("Company", self.company, "cost_center")
|
|
frappe.db.set_value("Company", self.company, "cost_center", None)
|
|
|
|
rate_in_account_currency = 1
|
|
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
|
si.cost_center = None
|
|
si.save().submit()
|
|
|
|
je = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=75,
|
|
acc2=self.cash,
|
|
acc1_amount=-1,
|
|
acc2_amount=-75,
|
|
acc2_exc_rate=1,
|
|
)
|
|
je.accounts[0].party_type = "Customer"
|
|
je.accounts[0].party = self.customer
|
|
je.accounts[0].cost_center = self.cost_center
|
|
je = je.save().submit()
|
|
|
|
# Reconcile
|
|
pr = self.create_payment_reconciliation()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = [x for x in self.get_journals_for(si.doctype, si.name) if x.parent != je.name]
|
|
exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_je), 1)
|
|
self.assertEqual(exc_je_for_si[0], exc_je_for_je[0])
|
|
|
|
self.assertEqual(
|
|
[self.cost_center, self.cost_center],
|
|
frappe.db.get_all(
|
|
"Journal Entry Account", filters={"parent": exc_je_for_si[0].parent}, pluck="cost_center"
|
|
),
|
|
)
|
|
frappe.db.set_value("Company", self.company, "cost_center", cc)
|
|
|
|
def test_42_cost_center_from_cr_note(self):
|
|
"""
|
|
Gain/Loss JE should inherit cost center from payment if company default is unset
|
|
"""
|
|
# remove default cost center
|
|
cc = frappe.db.get_value("Company", self.company, "cost_center")
|
|
frappe.db.set_value("Company", self.company, "cost_center", None)
|
|
|
|
rate_in_account_currency = 1
|
|
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
|
si.cost_center = None
|
|
si.save().submit()
|
|
|
|
cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
|
|
cr_note.cost_center = self.cost_center
|
|
cr_note.is_return = 1
|
|
cr_note.save().submit()
|
|
|
|
# Reconcile
|
|
pr = self.create_payment_reconciliation()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_cr_note = self.get_journals_for(cr_note.doctype, cr_note.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_cr_note), 2)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_cr_note)
|
|
|
|
for x in exc_je_for_si + exc_je_for_cr_note:
|
|
with self.subTest(x=x):
|
|
self.assertEqual(
|
|
[self.cost_center, self.cost_center],
|
|
frappe.db.get_all(
|
|
"Journal Entry Account", filters={"parent": x.parent}, pluck="cost_center"
|
|
),
|
|
)
|
|
|
|
frappe.db.set_value("Company", self.company, "cost_center", cc)
|
|
|
|
def setup_dimensions(self):
|
|
# create dimension
|
|
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
|
|
create_dimension,
|
|
)
|
|
|
|
create_dimension()
|
|
# make it non-mandatory
|
|
loc = frappe.get_doc("Accounting Dimension", "Location")
|
|
for x in loc.dimension_defaults:
|
|
x.mandatory_for_bs = False
|
|
x.mandatory_for_pl = False
|
|
loc.save()
|
|
|
|
def test_90_dimensions_filter(self):
|
|
"""
|
|
Test workings of dimension filters
|
|
"""
|
|
self.setup_dimensions()
|
|
rate_in_account_currency = 1
|
|
|
|
# Invoices
|
|
si1 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
|
si1.department = "Management"
|
|
si1.save().submit()
|
|
|
|
si2 = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
|
si2.department = "Operations"
|
|
si2.save().submit()
|
|
|
|
# Payments
|
|
cr_note1 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
|
|
cr_note1.department = "Management"
|
|
cr_note1.is_return = 1
|
|
cr_note1.save().submit()
|
|
|
|
cr_note2 = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
|
|
cr_note2.department = "Legal"
|
|
cr_note2.is_return = 1
|
|
cr_note2.save().submit()
|
|
|
|
pe1 = get_payment_entry(si1.doctype, si1.name)
|
|
pe1.references = []
|
|
pe1.department = "Research & Development"
|
|
pe1.save().submit()
|
|
|
|
pe2 = get_payment_entry(si1.doctype, si1.name)
|
|
pe2.references = []
|
|
pe2.department = "Management"
|
|
pe2.save().submit()
|
|
|
|
je1 = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=75,
|
|
acc2=self.cash,
|
|
acc1_amount=-1,
|
|
acc2_amount=-75,
|
|
acc2_exc_rate=1,
|
|
)
|
|
je1.accounts[0].party_type = "Customer"
|
|
je1.accounts[0].party = self.customer
|
|
je1.accounts[0].department = "Management"
|
|
je1.save().submit()
|
|
|
|
# assert dimension filter's result
|
|
pr = self.create_payment_reconciliation()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 2)
|
|
self.assertEqual(len(pr.payments), 5)
|
|
|
|
pr.department = "Legal"
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
|
|
pr.department = "Management"
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 3)
|
|
|
|
pr.department = "Research & Development"
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
|
|
def test_91_cr_note_should_inherit_dimension(self):
|
|
self.setup_dimensions()
|
|
rate_in_account_currency = 1
|
|
|
|
# Invoice
|
|
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
|
si.department = "Management"
|
|
si.save().submit()
|
|
|
|
# Payment
|
|
cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
|
|
cr_note.department = "Management"
|
|
cr_note.is_return = 1
|
|
cr_note.save().submit()
|
|
|
|
pr = self.create_payment_reconciliation()
|
|
pr.department = "Management"
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# There should be 2 journals, JE(Cr Note) and JE(Exchange Gain/Loss)
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_cr_note = self.get_journals_for(cr_note.doctype, cr_note.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(len(exc_je_for_si), 2)
|
|
self.assertEqual(len(exc_je_for_cr_note), 2)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_cr_note)
|
|
|
|
for x in exc_je_for_si + exc_je_for_cr_note:
|
|
with self.subTest(x=x):
|
|
self.assertEqual(
|
|
[cr_note.department, cr_note.department],
|
|
frappe.db.get_all(
|
|
"Journal Entry Account", filters={"parent": x.parent}, pluck="department"
|
|
),
|
|
)
|
|
|
|
def test_92_dimension_inhertiance_exc_gain_loss(self):
|
|
# Sales Invoice in Foreign Currency
|
|
self.setup_dimensions()
|
|
rate_in_account_currency = 1
|
|
dpt = "Research & Development"
|
|
|
|
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_save=True)
|
|
si.department = dpt
|
|
si.save().submit()
|
|
|
|
pe = self.create_payment_entry(amount=1, source_exc_rate=82).save()
|
|
pe.department = dpt
|
|
pe = pe.save().submit()
|
|
|
|
pr = self.create_payment_reconciliation()
|
|
pr.department = dpt
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# Exc Gain/Loss journals should inherit dimension from parent
|
|
journals = self.get_journals_for(si.doctype, si.name)
|
|
self.assertEqual(
|
|
[dpt, dpt],
|
|
frappe.db.get_all(
|
|
"Journal Entry Account",
|
|
filters={"parent": ("in", [x.parent for x in journals])},
|
|
pluck="department",
|
|
),
|
|
)
|
|
|
|
def test_93_dimension_inheritance_on_advance(self):
|
|
self.setup_dimensions()
|
|
dpt = "Research & Development"
|
|
|
|
adv = self.create_payment_entry(amount=1, source_exc_rate=85)
|
|
adv.department = dpt
|
|
adv.save().submit()
|
|
adv.reload()
|
|
|
|
# Sales Invoices in different exchange rates
|
|
si = self.create_sales_invoice(qty=1, conversion_rate=82, rate=1, do_not_submit=True)
|
|
si.department = dpt
|
|
advances = si.get_advance_entries()
|
|
self.assertEqual(len(advances), 1)
|
|
self.assertEqual(advances[0].reference_name, adv.name)
|
|
si.append(
|
|
"advances",
|
|
{
|
|
"doctype": "Sales Invoice Advance",
|
|
"reference_type": advances[0].reference_type,
|
|
"reference_name": advances[0].reference_name,
|
|
"reference_row": advances[0].reference_row,
|
|
"advance_amount": 1,
|
|
"allocated_amount": 1,
|
|
"ref_exchange_rate": advances[0].exchange_rate,
|
|
"remarks": advances[0].remarks,
|
|
},
|
|
)
|
|
si = si.save().submit()
|
|
|
|
# Outstanding in both currencies should be '0'
|
|
adv.reload()
|
|
self.assertEqual(si.outstanding_amount, 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exc Gain/Loss journals should inherit dimension from parent
|
|
journals = self.get_journals_for(si.doctype, si.name)
|
|
self.assertEqual(
|
|
[dpt, dpt],
|
|
frappe.db.get_all(
|
|
"Journal Entry Account",
|
|
filters={"parent": ("in", [x.parent for x in journals])},
|
|
pluck="department",
|
|
),
|
|
)
|
|
|
|
def test_50_journal_against_journal(self):
|
|
# Invoice in Foreign Currency
|
|
journal_as_invoice = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=83,
|
|
acc2=self.cash,
|
|
acc1_amount=1,
|
|
acc2_amount=83,
|
|
acc2_exc_rate=1,
|
|
)
|
|
journal_as_invoice.accounts[0].party_type = "Customer"
|
|
journal_as_invoice.accounts[0].party = self.customer
|
|
journal_as_invoice = journal_as_invoice.save().submit()
|
|
|
|
# Payment
|
|
journal_as_payment = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=75,
|
|
acc2=self.cash,
|
|
acc1_amount=-1,
|
|
acc2_amount=-75,
|
|
acc2_exc_rate=1,
|
|
)
|
|
journal_as_payment.accounts[0].party_type = "Customer"
|
|
journal_as_payment.accounts[0].party = self.customer
|
|
journal_as_payment = journal_as_payment.save().submit()
|
|
|
|
# Reconcile the remaining amount
|
|
pr = self.create_payment_reconciliation()
|
|
# pr.receivable_payable_account = self.debit_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# There should be no outstanding in both currencies
|
|
journal_as_invoice.reload()
|
|
self.assert_ledger_outstanding(journal_as_invoice.doctype, journal_as_invoice.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created.
|
|
exc_je_for_si = self.get_journals_for(journal_as_invoice.doctype, journal_as_invoice.name)
|
|
exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name)
|
|
self.assertNotEqual(exc_je_for_si, [])
|
|
self.assertEqual(
|
|
len(exc_je_for_si), 2
|
|
) # payment also has reference. so, there are 2 journals referencing invoice
|
|
self.assertEqual(len(exc_je_for_je), 1)
|
|
self.assertIn(exc_je_for_je[0], exc_je_for_si)
|
|
|
|
# Cancel Payment
|
|
journal_as_payment.reload()
|
|
journal_as_payment.cancel()
|
|
|
|
journal_as_invoice.reload()
|
|
self.assert_ledger_outstanding(journal_as_invoice.doctype, journal_as_invoice.name, 83.0, 1.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(journal_as_invoice.doctype, journal_as_invoice.name)
|
|
exc_je_for_je = self.get_journals_for(journal_as_payment.doctype, journal_as_payment.name)
|
|
self.assertEqual(exc_je_for_si, [])
|
|
self.assertEqual(exc_je_for_je, [])
|
|
|
|
def test_60_payment_entry_against_journal(self):
|
|
# Invoices
|
|
exc_rate1 = 75
|
|
exc_rate2 = 77
|
|
amount = 1
|
|
je1 = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=exc_rate1,
|
|
acc2=self.cash,
|
|
acc1_amount=amount,
|
|
acc2_amount=(amount * 75),
|
|
acc2_exc_rate=1,
|
|
)
|
|
je1.accounts[0].party_type = "Customer"
|
|
je1.accounts[0].party = self.customer
|
|
je1 = je1.save().submit()
|
|
|
|
je2 = self.create_journal_entry(
|
|
acc1=self.debit_usd,
|
|
acc1_exc_rate=exc_rate2,
|
|
acc2=self.cash,
|
|
acc1_amount=amount,
|
|
acc2_amount=(amount * exc_rate2),
|
|
acc2_exc_rate=1,
|
|
)
|
|
je2.accounts[0].party_type = "Customer"
|
|
je2.accounts[0].party = self.customer
|
|
je2 = je2.save().submit()
|
|
|
|
# Payment
|
|
pe = self.create_payment_entry(amount=2, source_exc_rate=exc_rate1).save().submit()
|
|
|
|
pr = self.create_payment_reconciliation()
|
|
pr.receivable_payable_account = self.debit_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 2)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# There should be no outstanding in both currencies
|
|
self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0)
|
|
self.assert_ledger_outstanding(je2.doctype, je2.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created only for JE2
|
|
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
|
|
exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
|
|
self.assertEqual(exc_je_for_je1, [])
|
|
self.assertEqual(len(exc_je_for_je2), 1)
|
|
|
|
# Cancel Payment
|
|
pe.reload()
|
|
pe.cancel()
|
|
|
|
self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount)
|
|
self.assert_ledger_outstanding(je2.doctype, je2.name, (amount * exc_rate2), amount)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
|
|
exc_je_for_je2 = self.get_journals_for(je2.doctype, je2.name)
|
|
self.assertEqual(exc_je_for_je1, [])
|
|
self.assertEqual(exc_je_for_je2, [])
|
|
|
|
def test_61_payment_entry_against_journal_for_payable_accounts(self):
|
|
# Invoices
|
|
exc_rate1 = 75
|
|
exc_rate2 = 77
|
|
amount = 1
|
|
je1 = self.create_journal_entry(
|
|
acc1=self.creditors_usd,
|
|
acc1_exc_rate=exc_rate1,
|
|
acc2=self.cash,
|
|
acc1_amount=-amount,
|
|
acc2_amount=(-amount * 75),
|
|
acc2_exc_rate=1,
|
|
)
|
|
je1.accounts[0].party_type = "Supplier"
|
|
je1.accounts[0].party = self.supplier
|
|
je1 = je1.save().submit()
|
|
|
|
# Payment
|
|
pe = create_payment_entry(
|
|
company=self.company,
|
|
payment_type="Pay",
|
|
party_type="Supplier",
|
|
party=self.supplier,
|
|
paid_from=self.cash,
|
|
paid_to=self.creditors_usd,
|
|
paid_amount=amount,
|
|
)
|
|
pe.target_exchange_rate = exc_rate2
|
|
pe.received_amount = amount
|
|
pe.paid_amount = amount * exc_rate2
|
|
pe.save().submit()
|
|
|
|
pr = frappe.get_doc(
|
|
{
|
|
"doctype": "Payment Reconciliation",
|
|
"company": self.company,
|
|
"party_type": "Supplier",
|
|
"party": self.supplier,
|
|
"receivable_payable_account": get_party_account("Supplier", self.supplier, self.company),
|
|
}
|
|
)
|
|
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(len(pr.invoices), 1)
|
|
self.assertEqual(len(pr.payments), 1)
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
|
|
# There should be no outstanding in both currencies
|
|
self.assert_ledger_outstanding(je1.doctype, je1.name, 0.0, 0.0)
|
|
|
|
# Exchange Gain/Loss Journal should've been created
|
|
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
|
|
self.assertEqual(len(exc_je_for_je1), 1)
|
|
|
|
# Cancel Payment
|
|
pe.reload()
|
|
pe.cancel()
|
|
|
|
self.assert_ledger_outstanding(je1.doctype, je1.name, (amount * exc_rate1), amount)
|
|
|
|
# Exchange Gain/Loss Journal should've been cancelled
|
|
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
|
|
self.assertEqual(exc_je_for_je1, [])
|
|
|
|
def test_70_advance_payment_against_sales_invoice_in_foreign_currency(self):
|
|
"""
|
|
Customer advance booked under Liability
|
|
"""
|
|
self.setup_advance_accounts_in_party_master()
|
|
|
|
adv = self.create_payment_entry(amount=1, source_exc_rate=83)
|
|
adv.save() # explicit 'save' is needed to trigger set_liability_account()
|
|
self.assertEqual(adv.paid_from, self.advance_received_usd)
|
|
adv.submit()
|
|
|
|
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
|
|
si.debit_to = self.debtors_usd
|
|
si.save().submit()
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
|
|
pr = self.create_payment_reconciliation()
|
|
pr.receivable_payable_account = self.debtors_usd
|
|
pr.default_advance_account = self.advance_received_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(pr.invoices[0].invoice_number, si.name)
|
|
self.assertEqual(pr.payments[0].reference_name, adv.name)
|
|
|
|
# Allocate and Reconcile
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
|
|
|
|
# Exc Gain/Loss journal should've been creatad
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(len(exc_je_for_si), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_si, exc_je_for_adv)
|
|
|
|
adv.reload()
|
|
adv.cancel()
|
|
si.reload()
|
|
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
|
# Exc Gain/Loss journal should've been cancelled
|
|
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(len(exc_je_for_si), 0)
|
|
self.assertEqual(len(exc_je_for_adv), 0)
|
|
|
|
self.remove_advance_accounts_from_party_master()
|
|
|
|
def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self):
|
|
"""
|
|
Supplier advance booked under Asset
|
|
"""
|
|
self.setup_advance_accounts_in_party_master()
|
|
|
|
usd_amount = 1
|
|
inr_amount = 85
|
|
exc_rate = 85
|
|
adv = create_payment_entry(
|
|
company=self.company,
|
|
payment_type="Pay",
|
|
party_type="Supplier",
|
|
party=self.supplier,
|
|
paid_from=self.cash,
|
|
paid_to=self.advance_paid_usd,
|
|
paid_amount=inr_amount,
|
|
)
|
|
adv.source_exchange_rate = 1
|
|
adv.target_exchange_rate = exc_rate
|
|
adv.received_amount = usd_amount
|
|
adv.paid_amount = exc_rate * usd_amount
|
|
adv.posting_date = nowdate()
|
|
adv.save()
|
|
# Make sure that advance account is still set
|
|
self.assertEqual(adv.paid_to, self.advance_paid_usd)
|
|
adv.submit()
|
|
|
|
pi = self.create_purchase_invoice(qty=1, conversion_rate=83, rate=1)
|
|
self.assertEqual(pi.credit_to, self.creditors_usd)
|
|
self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
|
|
|
|
pr = self.create_payment_reconciliation()
|
|
pr.party_type = "Supplier"
|
|
pr.party = self.supplier
|
|
pr.receivable_payable_account = self.creditors_usd
|
|
pr.default_advance_account = self.advance_paid_usd
|
|
pr.get_unreconciled_entries()
|
|
self.assertEqual(pr.invoices[0].invoice_number, pi.name)
|
|
self.assertEqual(pr.payments[0].reference_name, adv.name)
|
|
|
|
# Allocate and Reconcile
|
|
invoices = [x.as_dict() for x in pr.invoices]
|
|
payments = [x.as_dict() for x in pr.payments]
|
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
|
pr.reconcile()
|
|
self.assertEqual(len(pr.invoices), 0)
|
|
self.assertEqual(len(pr.payments), 0)
|
|
self.assert_ledger_outstanding(pi.doctype, pi.name, 0.0, 0.0)
|
|
|
|
# Exc Gain/Loss journal should've been creatad
|
|
exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(len(exc_je_for_pi), 1)
|
|
self.assertEqual(len(exc_je_for_adv), 1)
|
|
self.assertEqual(exc_je_for_pi, exc_je_for_adv)
|
|
|
|
adv.reload()
|
|
adv.cancel()
|
|
pi.reload()
|
|
self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
|
|
# Exc Gain/Loss journal should've been cancelled
|
|
exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
|
|
exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
|
|
self.assertEqual(len(exc_je_for_pi), 0)
|
|
self.assertEqual(len(exc_je_for_adv), 0)
|
|
|
|
self.remove_advance_accounts_from_party_master()
|