mirror of
https://github.com/frappe/erpnext.git
synced 2026-09-22 21:07:16 +00:00
Compare commits
36 Commits
develop
...
fix/intern
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a60fa59a78 | ||
|
|
3b8740e455 | ||
|
|
6d1f10ab22 | ||
|
|
89f8e379e7 | ||
|
|
babfa3c13f | ||
|
|
00c251c2f1 | ||
|
|
d4d704f336 | ||
|
|
6f2981cfe5 | ||
|
|
ca1f4de38d | ||
|
|
fb47d04929 | ||
|
|
a8051b0518 | ||
|
|
d7140099ca | ||
|
|
2c55621bae | ||
|
|
a1dc88e698 | ||
|
|
f6de755c83 | ||
|
|
7a7e186fb4 | ||
|
|
67f63e62c8 | ||
|
|
8290cd6294 | ||
|
|
00ff0684af | ||
|
|
3213810721 | ||
|
|
88860763c1 | ||
|
|
794e3050b7 | ||
|
|
bfcd9a33aa | ||
|
|
c13542f8d0 | ||
|
|
d790e47f90 | ||
|
|
032f36b361 | ||
|
|
4eb9796c04 | ||
|
|
6f5353b280 | ||
|
|
423383fb89 | ||
|
|
4d09a94768 | ||
|
|
87c42b0ec0 | ||
|
|
8b277f4b47 | ||
|
|
ab0d052fa2 | ||
|
|
fb905f9d6f | ||
|
|
8921f160d1 | ||
|
|
3eef93e792 |
@@ -22,6 +22,6 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: alyf-de/po-review-action@57fff275f4a0518a2ca55869ec6776fa3813b3d5 # v1.2.0
|
||||
- uses: alyf-de/po-review-action@5928f84d6bc9094f9ad6e2c5780f01c0044b800e # v1.1.1
|
||||
with:
|
||||
hidden-po-files: eo.po
|
||||
|
||||
@@ -47,13 +47,10 @@ class ERPNextAddress(Address):
|
||||
super().on_update()
|
||||
|
||||
address_display = get_address_display(self.as_dict())
|
||||
customers = frappe.db.get_all(
|
||||
"Customer", filters={"customer_primary_address": self.name}, pluck="name"
|
||||
)
|
||||
for customer in customers:
|
||||
frappe.db.set_value(
|
||||
"Customer", customer, "primary_address", address_display, update_modified=False
|
||||
)
|
||||
filters = {"customer_primary_address": self.name}
|
||||
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
|
||||
for customer_name in customers:
|
||||
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"description": "Heads (or groups) against which Accounting Entries are made and balances are maintained.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/chart-of-accounts",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"properties",
|
||||
@@ -201,7 +200,7 @@
|
||||
"options": "Account Category"
|
||||
}
|
||||
],
|
||||
"icon": "vault",
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "folder-tree",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
"precision": "9"
|
||||
}
|
||||
],
|
||||
"icon": "scale",
|
||||
"icon": "fa fa-list",
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-22 19:13:50.400404",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "field:label",
|
||||
"creation": "2019-05-04 18:13:37.002352",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounting-dimensions",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"document_type",
|
||||
@@ -50,7 +49,6 @@
|
||||
"options": "Accounting Dimension Detail"
|
||||
}
|
||||
],
|
||||
"icon": "layers",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:05:56.890002",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "format:{accounting_dimension}-{#####}",
|
||||
"creation": "2020-11-08 18:28:11.906146",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounting-dimension-filter",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -100,7 +99,6 @@
|
||||
"label": "Fieldname"
|
||||
}
|
||||
],
|
||||
"icon": "funnel",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-08 14:13:22.203011",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "field:period_name",
|
||||
"creation": "2018-04-13 18:50:14.672323",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounting-period",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -78,7 +77,6 @@
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "calendar-range",
|
||||
"links": [],
|
||||
"modified": "2026-03-09 17:15:33.577217",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"creation": "2013-06-24 15:49:57",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/accounts-settings",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -802,7 +801,7 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "settings",
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"creation": "2024-10-16 16:57:12.085072",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"icon": "book-open",
|
||||
"is_submittable": 1,
|
||||
"field_order": [
|
||||
"company",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"creation": "2018-04-07 16:59:59.496668",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -101,7 +100,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "landmark",
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:39.423431",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"creation": "2017-05-29 21:35:13.136357",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank-account",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account_name",
|
||||
@@ -228,7 +227,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "credit-card",
|
||||
"links": [
|
||||
{
|
||||
"group": "Transactions",
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "wallet",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-06-16 22:17:48.007982",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"icon": "credit-card",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:37.221876",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"icon": "credit-card",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:37.347035",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "badge-check",
|
||||
"icon": "fa fa-check",
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"creation": "2016-12-17 10:43:35.731631",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank-guarantee",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -217,7 +216,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "shield-check",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-05-25 18:12:10.768835",
|
||||
|
||||
@@ -116,7 +116,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "arrow-left-right",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -792,7 +792,6 @@ def create_bulk_payment_entry_and_reconcile(
|
||||
"deposit",
|
||||
"withdrawal",
|
||||
"bank_account",
|
||||
"company",
|
||||
"currency",
|
||||
"unallocated_amount",
|
||||
"date",
|
||||
@@ -827,7 +826,11 @@ def create_bulk_payment_entry_and_reconcile(
|
||||
"paid_from": paid_from,
|
||||
"paid_to": paid_to,
|
||||
"paid_amount": bank_transaction.unallocated_amount,
|
||||
"base_paid_amount": bank_transaction.unallocated_amount,
|
||||
"received_amount": bank_transaction.unallocated_amount,
|
||||
"base_received_amount": bank_transaction.unallocated_amount,
|
||||
"target_exchange_rate": 1,
|
||||
"source_exchange_rate": 1,
|
||||
"reference_date": bank_transaction.date,
|
||||
"posting_date": bank_transaction.date,
|
||||
"reference_no": (bank_transaction.reference_number or bank_transaction.description or "")[
|
||||
@@ -836,8 +839,6 @@ def create_bulk_payment_entry_and_reconcile(
|
||||
}
|
||||
)
|
||||
|
||||
set_multi_currency_amounts(payment_entry_doc)
|
||||
|
||||
payment_entry_doc.insert()
|
||||
payment_entry_doc.submit()
|
||||
|
||||
@@ -876,7 +877,6 @@ def create_payment_entry_and_reconcile(bank_transaction_name: str | int, payment
|
||||
"doctype": "Payment Entry",
|
||||
}
|
||||
)
|
||||
set_multi_currency_amounts(payment_entry)
|
||||
payment_entry.insert()
|
||||
payment_entry.submit()
|
||||
transaction = reconcile_vouchers(
|
||||
@@ -899,33 +899,6 @@ def create_payment_entry_and_reconcile(bank_transaction_name: str | int, payment
|
||||
}
|
||||
|
||||
|
||||
def set_multi_currency_amounts(pe):
|
||||
"""Set real exchange rates when the bank and party accounts differ in currency."""
|
||||
company_currency = frappe.get_cached_value("Company", pe.company, "default_currency")
|
||||
pe.paid_from_account_currency = frappe.get_cached_value("Account", pe.paid_from, "account_currency")
|
||||
pe.paid_to_account_currency = frappe.get_cached_value("Account", pe.paid_to, "account_currency")
|
||||
|
||||
pe.source_exchange_rate = (
|
||||
1.0
|
||||
if pe.paid_from_account_currency == company_currency
|
||||
else get_exchange_rate(pe.paid_from_account_currency, company_currency, pe.posting_date)
|
||||
)
|
||||
pe.target_exchange_rate = (
|
||||
1.0
|
||||
if pe.paid_to_account_currency == company_currency
|
||||
else get_exchange_rate(pe.paid_to_account_currency, company_currency, pe.posting_date)
|
||||
)
|
||||
|
||||
# derive the party-side amount from the authoritative bank-side amount; Payment Entry books any
|
||||
# rounding residual to Exchange Gain/Loss during validation (set_exchange_gain_loss)
|
||||
if pe.payment_type == "Receive" and pe.source_exchange_rate:
|
||||
base_amount = flt(pe.received_amount) * pe.target_exchange_rate
|
||||
pe.paid_amount = flt(base_amount / pe.source_exchange_rate, pe.precision("paid_amount"))
|
||||
elif pe.payment_type == "Pay" and pe.target_exchange_rate:
|
||||
base_amount = flt(pe.paid_amount) * pe.source_exchange_rate
|
||||
pe.received_amount = flt(base_amount / pe.target_exchange_rate, pe.precision("received_amount"))
|
||||
|
||||
|
||||
@frappe.whitelist(methods=["GET"])
|
||||
def search_for_transfer_transaction(transaction_id: str | int):
|
||||
"""
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
# See license.txt
|
||||
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.utils import add_days, today
|
||||
|
||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||
auto_reconcile_vouchers,
|
||||
create_bulk_payment_entry_and_reconcile,
|
||||
create_payment_entry_and_reconcile,
|
||||
get_auto_reconcile_message,
|
||||
get_bank_transactions,
|
||||
get_linked_payments,
|
||||
@@ -20,8 +16,6 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
RATE_METHOD = "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_exchange_rate"
|
||||
|
||||
|
||||
class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
|
||||
def setUp(self):
|
||||
@@ -236,117 +230,3 @@ class TestBankReconciliationTool(ERPNextTestSuite, AccountsTestMixin):
|
||||
self.assertIn("1 Transaction Partially Reconciled", singular)
|
||||
plural, _ = get_auto_reconcile_message(["p1", "p2"], [])
|
||||
self.assertIn("2 Transactions Partially Reconciled", plural)
|
||||
|
||||
def test_multi_currency_pay_converts_and_balances(self):
|
||||
# withdrawal from an INR bank paying a USD supplier; rate 3.0 makes 100/3 non-exact
|
||||
self.enable_multi_currency_setup()
|
||||
pe = self.reconcile_new_payment(
|
||||
self.make_multi_currency_txn(withdrawal=100),
|
||||
payment_type="Pay",
|
||||
party_type="Supplier",
|
||||
party=self.supplier,
|
||||
party_account=self.creditors_usd,
|
||||
paid_from=self.bank,
|
||||
paid_to=self.creditors_usd,
|
||||
rate=3.0,
|
||||
)
|
||||
self.assertEqual(pe.docstatus, 1) # submits despite the rounding residual
|
||||
self.assertEqual((pe.source_exchange_rate, pe.target_exchange_rate), (1.0, 3.0))
|
||||
self.assertEqual((pe.paid_amount, pe.received_amount), (100, 33.33)) # bank side kept, 100/3
|
||||
self.assertEqual(pe.difference_amount, 0)
|
||||
# Payment Entry auto-books the rounding residual to Exchange Gain/Loss
|
||||
self.assertTrue(pe.deductions[0].is_exchange_gain_loss)
|
||||
self.assertEqual(pe.deductions[0].amount, 0.01) # 100 - 33.33 * 3
|
||||
|
||||
def test_multi_currency_receive_converts_and_balances(self):
|
||||
# deposit into an INR bank from a USD customer; the party side must convert
|
||||
self.enable_multi_currency_setup()
|
||||
pe = self.reconcile_new_payment(
|
||||
self.make_multi_currency_txn(deposit=100),
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=self.customer,
|
||||
party_account=self.debtors_usd,
|
||||
paid_from=self.debtors_usd,
|
||||
paid_to=self.bank,
|
||||
rate=3.0,
|
||||
)
|
||||
self.assertEqual(pe.docstatus, 1)
|
||||
self.assertEqual((pe.source_exchange_rate, pe.target_exchange_rate), (3.0, 1.0))
|
||||
self.assertEqual((pe.received_amount, pe.paid_amount), (100, 33.33)) # bank side kept, 100/3
|
||||
self.assertEqual(pe.difference_amount, 0)
|
||||
|
||||
def test_multi_currency_bulk_pay_converts_and_balances(self):
|
||||
# the bulk path builds the Payment Entry itself, so it must convert too
|
||||
self.enable_multi_currency_setup()
|
||||
txn = self.make_multi_currency_txn(withdrawal=100)
|
||||
with patch(RATE_METHOD, return_value=3.0):
|
||||
result = create_bulk_payment_entry_and_reconcile(
|
||||
[txn.name], "Supplier", self.supplier, self.creditors_usd
|
||||
)
|
||||
|
||||
pe = frappe.get_doc("Payment Entry", result[0]["payment_entry"].name)
|
||||
self.assertEqual(pe.docstatus, 1)
|
||||
self.assertEqual(pe.target_exchange_rate, 3.0)
|
||||
self.assertEqual((pe.paid_amount, pe.received_amount), (100, 33.33))
|
||||
self.assertEqual(pe.difference_amount, 0)
|
||||
|
||||
def enable_multi_currency_setup(self):
|
||||
# USD party/accounts + a company gain/loss account to absorb rounding residuals
|
||||
self.company_abbr = "_TC"
|
||||
self.create_supplier(supplier_name="_Test Supplier USD", currency="USD")
|
||||
self.create_customer(customer_name="_Test Customer USD", currency="USD")
|
||||
self.create_usd_payable_account()
|
||||
self.create_usd_receivable_account()
|
||||
self.set_party_account("Supplier", self.supplier, self.creditors_usd)
|
||||
if not frappe.db.get_value("Company", self.company, "exchange_gain_loss_account"):
|
||||
frappe.db.set_value(
|
||||
"Company", self.company, "exchange_gain_loss_account", "Exchange Gain/Loss - _TC"
|
||||
)
|
||||
|
||||
def set_party_account(self, party_type, party, account):
|
||||
doc = frappe.get_doc(party_type, party)
|
||||
if not any(row.company == self.company for row in doc.accounts):
|
||||
doc.append("accounts", {"company": self.company, "account": account})
|
||||
doc.save()
|
||||
|
||||
def make_multi_currency_txn(self, withdrawal=0, deposit=0):
|
||||
return (
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Bank Transaction",
|
||||
"date": today(),
|
||||
"withdrawal": withdrawal,
|
||||
"deposit": deposit,
|
||||
"bank_account": self.bank_account,
|
||||
"currency": "INR",
|
||||
"reference_number": "TEST-FX-REF",
|
||||
}
|
||||
)
|
||||
.save()
|
||||
.submit()
|
||||
)
|
||||
|
||||
def reconcile_new_payment(
|
||||
self, txn, *, payment_type, party_type, party, party_account, paid_from, paid_to, rate
|
||||
):
|
||||
# mimics the /banking frontend, which sends a hardcoded 1:1 rate
|
||||
payment_entry_doc = {
|
||||
"payment_type": payment_type,
|
||||
"company": self.company,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"party_account": party_account,
|
||||
"paid_from": paid_from,
|
||||
"paid_to": paid_to,
|
||||
"paid_amount": txn.unallocated_amount,
|
||||
"received_amount": txn.unallocated_amount,
|
||||
"source_exchange_rate": 1,
|
||||
"target_exchange_rate": 1,
|
||||
"posting_date": today(),
|
||||
"reference_no": f"TEST-FX-{payment_type}",
|
||||
"reference_date": today(),
|
||||
}
|
||||
with patch(RATE_METHOD, return_value=rate):
|
||||
result = create_payment_entry_and_reconcile(txn.name, payment_entry_doc)
|
||||
return frappe.get_doc("Payment Entry", result["payment_entry"].name)
|
||||
|
||||
@@ -225,7 +225,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "file-down",
|
||||
"links": [],
|
||||
"modified": "2026-06-19 14:18:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -188,7 +188,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "file-clock",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-07-09 17:55:25.615942",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2018-10-22 18:19:02.784533",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/bank-transaction",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -278,7 +277,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "arrow-left-right",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-11 20:41:15.124085",
|
||||
|
||||
@@ -140,7 +140,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "zap",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-11 21:36:26.754667",
|
||||
|
||||
@@ -167,7 +167,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "split",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
"label": "Generated"
|
||||
}
|
||||
],
|
||||
"icon": "git-branch",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:39.766063",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2016-05-16 11:42:29.632528",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/budgeting",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -331,7 +330,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "piggy-bank",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -122,7 +122,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "lock",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:44.260440",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"description": "Import Chart of Accounts from a csv file",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/chart-of-accounts-importer",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -48,7 +47,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "file-input",
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "field:bank_name",
|
||||
"creation": "2016-05-04 14:35:00.402544",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/cheque-print-template",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"settings",
|
||||
@@ -294,7 +293,6 @@
|
||||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"icon": "printer",
|
||||
"links": [],
|
||||
"max_attachments": 1,
|
||||
"modified": "2026-06-08 12:10:35.829531",
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"description": "Track separate Income and Expense for product verticals or divisions.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/cost-center",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"sb0",
|
||||
@@ -123,7 +122,7 @@
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"icon": "chart-pie",
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "CC-ALLOC-.#####",
|
||||
"creation": "2022-01-13 20:07:29.871109",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/cost_center_allocation",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -66,7 +65,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "chart-pie",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -5,10 +5,7 @@ frappe.ui.form.on("Coupon Code", {
|
||||
setup: function (frm) {
|
||||
frm.set_query("pricing_rule", function () {
|
||||
return {
|
||||
filters: {
|
||||
coupon_code_based: 1,
|
||||
disable: 0,
|
||||
},
|
||||
filters: [["Pricing Rule", "coupon_code_based", "=", "1"]],
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"creation": "2018-01-22 14:34:39.701832",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/coupon-code",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -125,7 +124,6 @@
|
||||
"label": "From External Ecomm Platform"
|
||||
}
|
||||
],
|
||||
"icon": "ticket-percent",
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:41.010871",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -42,23 +42,7 @@ class CouponCode(Document):
|
||||
self.coupon_code = frappe.generate_hash()[:10].upper()
|
||||
|
||||
def validate(self):
|
||||
self.validate_from_to_dates("valid_from", "valid_upto")
|
||||
self.validate_pricing_rule()
|
||||
|
||||
if self.coupon_type == "Gift Card":
|
||||
self.maximum_use = 1
|
||||
if not self.customer:
|
||||
frappe.throw(_("Please select the customer."))
|
||||
|
||||
def validate_pricing_rule(self):
|
||||
if not self.pricing_rule or self.from_external_ecomm_platform:
|
||||
return
|
||||
|
||||
# Allow existing coupons to be updated after their pricing rule is disabled.
|
||||
if not (
|
||||
self.has_value_changed("pricing_rule") or self.has_value_changed("from_external_ecomm_platform")
|
||||
):
|
||||
return
|
||||
|
||||
if frappe.db.get_value("Pricing Rule", self.pricing_rule, "disable"):
|
||||
frappe.throw(_("Pricing Rule {0} is disabled").format(frappe.bold(self.pricing_rule)))
|
||||
|
||||
@@ -112,43 +112,6 @@ class TestCouponCode(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
test_create_test_data()
|
||||
|
||||
def test_disabled_pricing_rule_validation(self):
|
||||
coupon = frappe.get_doc("Coupon Code", "SAVE30")
|
||||
rule = frappe.get_doc("Pricing Rule", coupon.pricing_rule)
|
||||
rule.disable = 1
|
||||
rule.save()
|
||||
|
||||
with self.subTest("new coupon cannot select a disabled rule"):
|
||||
new_coupon = frappe.copy_doc(coupon)
|
||||
new_coupon.coupon_name = "Festival Savings"
|
||||
new_coupon.coupon_code = "FESTSAVE"
|
||||
with self.assertRaisesRegex(frappe.ValidationError, "is disabled"):
|
||||
new_coupon.insert()
|
||||
|
||||
with self.subTest("existing coupon can retain a disabled rule"):
|
||||
coupon.description = "Offer paused"
|
||||
coupon.save()
|
||||
coupon.reload()
|
||||
self.assertEqual(coupon.description, "Offer paused")
|
||||
self.assertEqual(coupon.pricing_rule, rule.name)
|
||||
|
||||
with self.subTest("existing coupon cannot switch to a disabled rule"):
|
||||
disabled_rule = frappe.copy_doc(rule)
|
||||
disabled_rule.insert()
|
||||
coupon.reload()
|
||||
coupon.pricing_rule = disabled_rule.name
|
||||
with self.assertRaisesRegex(frappe.ValidationError, "is disabled"):
|
||||
coupon.save()
|
||||
coupon.reload()
|
||||
self.assertEqual(coupon.pricing_rule, rule.name)
|
||||
|
||||
def test_cannot_save_coupon_with_reversed_validity_dates(self):
|
||||
coupon = frappe.get_doc("Coupon Code", "SAVE30")
|
||||
coupon.valid_from = "2026-09-17"
|
||||
coupon.valid_upto = "2026-09-02"
|
||||
with self.assertRaises(frappe.exceptions.InvalidDates):
|
||||
coupon.save()
|
||||
|
||||
def test_sales_order_with_coupon_code(self):
|
||||
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"actions": [],
|
||||
"creation": "2022-01-10 13:03:26.237081",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/currency-exchange-settings",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -102,7 +101,6 @@
|
||||
"label": "Use HTTP Protocol"
|
||||
}
|
||||
],
|
||||
"icon": "refresh-cw",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2019-07-05 16:34:31.013238",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/dunning",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
@@ -399,7 +398,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "bell-ring",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-05-30 23:18:04.712528",
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "bell",
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Dunning",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "ACC-ERR-.YYYY.-.#####",
|
||||
"creation": "2018-04-13 18:25:55.943587",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/exchange-rate-revaluation",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -108,7 +107,6 @@
|
||||
"precision": "9"
|
||||
}
|
||||
],
|
||||
"icon": "arrow-right-left",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:09:42.951164",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"creation": "2018-04-13 17:42:43.252224",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/finance-book",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -19,7 +18,7 @@
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"icon": "book",
|
||||
"icon": "fa fa-book",
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:42.386104",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "field:template_name",
|
||||
"creation": "2025-08-02 04:44:15.184541",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/financial-report-template",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"template_name",
|
||||
@@ -65,7 +64,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "file-spreadsheet",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-23 01:04:05.797161",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"description": "Represents a Financial Year. All accounting entries and other major transactions are tracked against the Fiscal Year.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/fiscal-year",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"year",
|
||||
@@ -80,7 +79,7 @@
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"icon": "calendar",
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:42.509102",
|
||||
|
||||
@@ -377,7 +377,7 @@
|
||||
"precision": "9"
|
||||
}
|
||||
],
|
||||
"icon": "book-open",
|
||||
"icon": "fa fa-list",
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "ACC-INV-DISC-.YYYY.-.#####",
|
||||
"creation": "2019-03-07 12:01:56.296952",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/invoice_discounting",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -170,7 +169,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "ticket-percent",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-09-09 17:04:59.512294",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"creation": "2022-01-19 01:09:13.297137",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/item-tax-template",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -57,7 +56,6 @@
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"icon": "circle-percent",
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:43.571355",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"creation": "2022-01-25 10:29:58.717206",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/journal-entry",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"entry_type_and_date",
|
||||
@@ -662,7 +661,7 @@
|
||||
"label": "Custom Remark"
|
||||
}
|
||||
],
|
||||
"icon": "notebook-pen",
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
|
||||
@@ -253,10 +253,6 @@ def make_reverse_journal_entry(source_name: str, target_doc: str | dict | Docume
|
||||
|
||||
def post_process(source, target) -> None:
|
||||
target.reversal_of = source.name
|
||||
target.naming_series = source.naming_series
|
||||
if source.voucher_type == "Bank Entry":
|
||||
target.cheque_no = source.cheque_no
|
||||
target.cheque_date = source.cheque_date
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Journal Entry",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"creation": "2020-04-09 01:32:51.332301",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/journal-entry-template",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -86,7 +85,6 @@
|
||||
"label": "Multi Currency"
|
||||
}
|
||||
],
|
||||
"icon": "notebook-text",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:09:58.814734",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
"label": "General and Payment Ledger mismatch"
|
||||
}
|
||||
],
|
||||
"icon": "heart-pulse",
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "activity",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -92,7 +92,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "merge",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:02.198009",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -97,7 +97,6 @@
|
||||
"label": "Discretionary Reason"
|
||||
}
|
||||
],
|
||||
"icon": "star",
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2024-07-01 08:51:13.927009",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "field:loyalty_program_name",
|
||||
"creation": "2018-01-23 06:23:05.731431",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/loyalty-program",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -154,7 +153,6 @@
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"icon": "gift",
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:44.144864",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"creation": "2012-12-04 17:49:20",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/mode-of-payment",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"mode_of_payment",
|
||||
@@ -45,7 +44,7 @@
|
||||
"label": "Enabled"
|
||||
}
|
||||
],
|
||||
"icon": "wallet",
|
||||
"icon": "fa fa-credit-card",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"options": "Monthly Distribution Percentage"
|
||||
}
|
||||
],
|
||||
"icon": "chart-bar",
|
||||
"icon": "fa fa-bar-chart",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:44.908490",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"allow_copy": 1,
|
||||
"creation": "2017-08-29 02:22:54.947711",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/opening-invoice-creation-tool",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -89,7 +88,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "file-plus",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-05-30 23:18:48.691227",
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
"options": "secondary_role"
|
||||
}
|
||||
],
|
||||
"icon": "link",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:08.607170",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2016-06-01 14:38:51.012597",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-entry",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"type_of_payment",
|
||||
@@ -785,7 +784,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "banknote",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
|
||||
@@ -84,7 +84,6 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "credit-card",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-14 16:49:55.210352",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"icon": "book-open",
|
||||
"is_submittable": 1,
|
||||
"field_order": [
|
||||
"posting_date",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2018-07-20 16:43:08.505978",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-order",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -108,7 +107,6 @@
|
||||
"label": "Account"
|
||||
}
|
||||
],
|
||||
"icon": "send",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:10.186727",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"allow_copy": 1,
|
||||
"creation": "2014-07-09 12:04:51.681583",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-reconciliation",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
@@ -236,7 +235,7 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "arrow-left-right",
|
||||
"icon": "icon-resize-horizontal",
|
||||
"is_virtual": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2015-12-15 22:23:24.745065",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-request",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_request_type",
|
||||
@@ -475,7 +474,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "hand-coins",
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "field:payment_term_name",
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-terms",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -116,7 +115,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "calendar-clock",
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:45.693762",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "field:template_name",
|
||||
"creation": "2017-08-10 15:34:28.058054",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/payment-terms-template",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -35,7 +34,6 @@
|
||||
"label": "Allocate Payment Based On Payment Terms"
|
||||
}
|
||||
],
|
||||
"icon": "calendar-clock",
|
||||
"links": [],
|
||||
"modified": "2024-04-07 11:26:42.021585",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "anchor",
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "ACC-PCV-.YYYY.-.#####",
|
||||
"creation": "2013-01-10 16:34:07",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/period-closing-voucher",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"transaction_date",
|
||||
@@ -111,7 +110,7 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "folder-lock",
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -280,13 +280,12 @@ class PeriodClosingVoucher(AccountsController):
|
||||
|
||||
data = self.get_data_for_mapreduce()
|
||||
mapreduce(
|
||||
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.process_date_range",
|
||||
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.aggregate_partial_result",
|
||||
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.mapper",
|
||||
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.reducer",
|
||||
"erpnext.accounts.doctype.period_closing_voucher.period_closing_voucher.summarize_and_post_ledger",
|
||||
data,
|
||||
self.doctype,
|
||||
self.name,
|
||||
f"Closing FY {self.fiscal_year}",
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
@@ -331,8 +330,8 @@ class PeriodClosingVoucher(AccountsController):
|
||||
|
||||
def make_gl_entries(self):
|
||||
if frappe.db.estimate_count("GL Entry") > 100_000:
|
||||
frappe.enqueue_task(
|
||||
method=process_gl_and_closing_entries,
|
||||
frappe.enqueue(
|
||||
process_gl_and_closing_entries,
|
||||
doc=self,
|
||||
timeout=1800,
|
||||
)
|
||||
@@ -835,7 +834,7 @@ def get_previous_closed_period_in_current_year(fiscal_year, company):
|
||||
return prev_closed_period_end_date
|
||||
|
||||
|
||||
def process_date_range(val):
|
||||
def mapper(val):
|
||||
start_date = val.from_date
|
||||
end_date = val.to_date
|
||||
pcv = val.pcv
|
||||
@@ -882,7 +881,7 @@ def process_date_range(val):
|
||||
return res
|
||||
|
||||
|
||||
def aggregate_partial_result(final, partial_res):
|
||||
def reducer(final, partial_res):
|
||||
if final is None:
|
||||
final = []
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.listview_settings["Period Closing Voucher"] = {
|
||||
add_fields: ["gle_processing_status"],
|
||||
get_indicator: function (doc) {
|
||||
const status_colors = {
|
||||
Draft: "red",
|
||||
Submitted: "blue",
|
||||
Cancelled: "red",
|
||||
};
|
||||
|
||||
const gle_processing_status = {
|
||||
"In Progress": [__("Processing GL Entries"), "blue"],
|
||||
Completed: [__("Period Closed"), "green"],
|
||||
Failed: [__("Period Closing Failed"), "red"],
|
||||
};
|
||||
if (doc.docstatus != 0) {
|
||||
return [
|
||||
gle_processing_status[doc.gle_processing_status][0],
|
||||
gle_processing_status[doc.gle_processing_status][1],
|
||||
"gle_processing_status,=," + doc.gle_processing_status,
|
||||
];
|
||||
}
|
||||
|
||||
return [__(doc.docstatus), status_colors[doc.docstatus], "docstatus,=," + doc.docstatus];
|
||||
},
|
||||
};
|
||||
@@ -254,7 +254,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "book-lock",
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
{
|
||||
|
||||
@@ -1640,7 +1640,7 @@
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "receipt-text",
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:45.029925",
|
||||
|
||||
@@ -126,7 +126,6 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "merge",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -129,7 +129,6 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "folder-open",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:16.034296",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "Prompt",
|
||||
"creation": "2013-05-24 12:15:51",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/pos-profile",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
@@ -558,7 +557,7 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "monitor",
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 0,
|
||||
"icon": "settings",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-16 13:28:19.677217",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2014-02-21 15:02:51",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/pricing-rule",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"applicability_section",
|
||||
@@ -655,7 +654,7 @@
|
||||
"label": "Don't Enforce Free Item Qty"
|
||||
}
|
||||
],
|
||||
"icon": "badge-percent",
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-17 12:24:07.553505",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "ACC-PDA-.#####",
|
||||
"creation": "2019-11-04 18:01:23.454775",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/process-deferred-accounting",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -75,7 +74,6 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"icon": "list-clock",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -154,7 +154,6 @@
|
||||
"reqd": 0
|
||||
}
|
||||
],
|
||||
"icon": "scale",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "file-clock",
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "lock",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "Prompt",
|
||||
"creation": "2020-05-22 16:46:18.712954",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/process-statement-of-accounts",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -425,7 +424,6 @@
|
||||
"label": "Show Opening Entries"
|
||||
}
|
||||
],
|
||||
"icon": "mail",
|
||||
"links": [],
|
||||
"modified": "2026-06-01 15:37:07.660442",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
"options": "Subscription"
|
||||
}
|
||||
],
|
||||
"icon": "repeat",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"autoname": "Prompt",
|
||||
"creation": "2019-02-08 17:10:36.077402",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/promotional-scheme",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -280,7 +279,6 @@
|
||||
"options": "Promotional Scheme Product Discount"
|
||||
}
|
||||
],
|
||||
"icon": "megaphone",
|
||||
"links": [],
|
||||
"modified": "2025-08-20 11:48:23.231081",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/purchase-invoice",
|
||||
"email_append_to": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -1693,7 +1692,7 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "receipt-text",
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -256,10 +256,6 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
)
|
||||
|
||||
else:
|
||||
self.make_rejected_warehouse_gl_entry(
|
||||
gl_entries, item, voucher_wise_stock_value, inventory_account_map
|
||||
)
|
||||
|
||||
if not doc.is_internal_transfer():
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -576,18 +572,12 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
def make_rejected_warehouse_gl_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, inventory_account_map
|
||||
) -> float:
|
||||
"""Book the material the invoice moved into the rejected warehouse.
|
||||
|
||||
An internal transfer carries the value credited out of the in-transit warehouse along with
|
||||
the accepted material, so the entry against it is that warehouse, and the caller credits it
|
||||
for both. On an ordinary invoice the supplier entry already holds the cost.
|
||||
"""
|
||||
"""Book the rejected material of an internal transfer, whose value was credited out of the
|
||||
in-transit warehouse along with the accepted material."""
|
||||
doc = self.doc
|
||||
if not (item.rejected_warehouse and flt(item.rejected_qty)):
|
||||
if not (doc.is_internal_transfer() and flt(item.rejected_qty) and item.rejected_warehouse):
|
||||
return 0.0
|
||||
|
||||
transfers_rejected_material = doc.is_internal_transfer()
|
||||
|
||||
rejected_amount = flt(
|
||||
voucher_wise_stock_value.get((item.name, item.rejected_warehouse)),
|
||||
item.precision("base_net_amount"),
|
||||
@@ -595,26 +585,23 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
if not rejected_amount:
|
||||
return 0.0
|
||||
|
||||
rejected_account = doc.get_inventory_account_dict(item, inventory_account_map, "rejected_warehouse")
|
||||
_inv_dict = doc.get_inventory_account_dict(item, inventory_account_map, "rejected_warehouse")
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": rejected_account["account"],
|
||||
"against": item.expense_account if transfers_rejected_material else doc.supplier,
|
||||
"account": _inv_dict["account"],
|
||||
"against": item.expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or doc.project,
|
||||
"remarks": doc.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": rejected_amount,
|
||||
"debit_in_transaction_currency": flt(
|
||||
rejected_amount / doc.conversion_rate, item.precision("net_amount")
|
||||
),
|
||||
},
|
||||
rejected_account["account_currency"],
|
||||
_inv_dict["account_currency"],
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
return rejected_amount if transfers_rejected_material else 0.0
|
||||
return rejected_amount
|
||||
|
||||
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
|
||||
doc = self.doc
|
||||
@@ -629,24 +616,16 @@ class PurchaseInvoiceGLComposer(BaseGLComposer):
|
||||
if doc.is_return and doc.update_stock and (doc.is_internal_supplier or not doc.return_against):
|
||||
net_rate = item.base_net_amount
|
||||
if item.sales_incoming_rate:
|
||||
# Material of a transfer goes back at the rate it came in with, the rejected
|
||||
# material along with the accepted.
|
||||
net_rate = (flt(item.qty) + flt(item.rejected_qty)) * item.sales_incoming_rate
|
||||
net_rate = item.qty * item.sales_incoming_rate
|
||||
|
||||
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
|
||||
warehouse_debit_amount = flt(
|
||||
voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision
|
||||
)
|
||||
|
||||
# The rejected warehouse carries the rest of what the invoice paid for, and is booked
|
||||
# by its own entry, so it is not a variance.
|
||||
returned_stock_value = warehouse_debit_amount + flt(
|
||||
voucher_wise_stock_value.get((item.name, item.rejected_warehouse)), net_amt_precision
|
||||
)
|
||||
|
||||
if flt(stock_amount, net_amt_precision) != flt(returned_stock_value, net_amt_precision):
|
||||
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
|
||||
cost_of_goods_sold_account = self.get_stock_variance_account(item)
|
||||
stock_adjustment_amt = stock_amount - returned_stock_value
|
||||
stock_adjustment_amt = stock_amount - warehouse_debit_amount
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
|
||||
@@ -844,9 +844,7 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
||||
)
|
||||
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0].base_net_amount or 0
|
||||
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name, do_not_save=True)
|
||||
pi.credit_to = "_Test Payable USD - _TC"
|
||||
pi.submit()
|
||||
pi = make_purchase_invoice(currency="USD", conversion_rate=60, project=project.name)
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000,
|
||||
@@ -858,14 +856,12 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
||||
existing_purchase_cost + 15500,
|
||||
)
|
||||
|
||||
pi1.reload()
|
||||
pi1.cancel()
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Project", project.name, "total_purchase_cost"),
|
||||
existing_purchase_cost + 15000,
|
||||
)
|
||||
|
||||
pi.reload()
|
||||
pi.cancel()
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Project", project.name, "total_purchase_cost"), existing_purchase_cost
|
||||
@@ -2232,6 +2228,81 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
||||
|
||||
def test_internal_transfer_invoice_with_rejected_qty(self):
|
||||
"""An invoice that updates stock moves rejected material out of the in-transit warehouse and
|
||||
books it, like a receipt does."""
|
||||
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
get_gl_entries,
|
||||
make_purchase_receipt,
|
||||
prepare_data_for_internal_transfer,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Invoice Transfer From", company=company)
|
||||
transit_warehouse = create_warehouse("_Test Invoice Transfer Transit", company=company)
|
||||
to_warehouse = create_warehouse("_Test Invoice Transfer To", company=company)
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Transfer Rejected", company=company)
|
||||
|
||||
item_doc = create_item("Test Invoice Internal Transfer Item")
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=company,
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code=item_doc.name,
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
update_stock=1,
|
||||
cost_center="Main - TCP1",
|
||||
debit_to="Debtors - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
do_not_save=1,
|
||||
)
|
||||
si.items[0].target_warehouse = transit_warehouse
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pi = make_inter_company_purchase_invoice(si.name)
|
||||
pi.update_stock = 1
|
||||
pi.items[0].warehouse = to_warehouse
|
||||
pi.items[0].qty = 7
|
||||
pi.items[0].rejected_qty = 3
|
||||
pi.items[0].received_qty = 10
|
||||
pi.items[0].rejected_warehouse = rejected_warehouse
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
|
||||
pi.submit()
|
||||
|
||||
sl_entries = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "actual_qty", "stock_value_difference"],
|
||||
)
|
||||
|
||||
stock_qty = {d.warehouse: d.actual_qty for d in sl_entries}
|
||||
self.assertEqual(stock_qty[transit_warehouse], -10)
|
||||
self.assertEqual(stock_qty[to_warehouse], 7)
|
||||
self.assertEqual(stock_qty[rejected_warehouse], 3)
|
||||
|
||||
booked_value = {}
|
||||
for entry in get_gl_entries("Purchase Invoice", pi.name, skip_cancelled=True):
|
||||
booked_value.setdefault(entry.account, 0)
|
||||
booked_value[entry.account] += flt(entry.debit) - flt(entry.credit)
|
||||
|
||||
self.assertEqual(flt(sum(booked_value.values()), 2), 0)
|
||||
self.assertEqual(booked_value[get_inventory_account(company, transit_warehouse)], -1000)
|
||||
self.assertEqual(booked_value[get_inventory_account(company, to_warehouse)], 700)
|
||||
self.assertEqual(booked_value[get_inventory_account(company, rejected_warehouse)], 300)
|
||||
|
||||
def test_adjust_incoming_rate_for_rejected_item(self):
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
|
||||
|
||||
@@ -2628,582 +2699,6 @@ class TestPurchaseInvoice(ERPNextTestSuite, StockTestMixin):
|
||||
return_pi.submit()
|
||||
self.assertEqual(return_pi.docstatus, 1)
|
||||
|
||||
def test_internal_transfer_invoice_with_rejected_qty(self):
|
||||
"""An invoice that updates stock moves rejected material out of the in-transit warehouse and
|
||||
books it, like a receipt does."""
|
||||
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
get_gl_entries,
|
||||
make_purchase_receipt,
|
||||
prepare_data_for_internal_transfer,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Invoice Transfer From", company=company)
|
||||
transit_warehouse = create_warehouse("_Test Invoice Transfer Transit", company=company)
|
||||
to_warehouse = create_warehouse("_Test Invoice Transfer To", company=company)
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Transfer Rejected", company=company)
|
||||
|
||||
item_doc = create_item("Test Invoice Internal Transfer Item")
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=company,
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code=item_doc.name,
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
update_stock=1,
|
||||
cost_center="Main - TCP1",
|
||||
debit_to="Debtors - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
do_not_save=1,
|
||||
)
|
||||
si.items[0].target_warehouse = transit_warehouse
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pi = make_inter_company_purchase_invoice(si.name)
|
||||
pi.update_stock = 1
|
||||
pi.items[0].warehouse = to_warehouse
|
||||
pi.items[0].qty = 7
|
||||
pi.items[0].rejected_qty = 3
|
||||
pi.items[0].received_qty = 10
|
||||
pi.items[0].rejected_warehouse = rejected_warehouse
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
|
||||
pi.submit()
|
||||
|
||||
sl_entries = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "actual_qty", "stock_value_difference"],
|
||||
)
|
||||
|
||||
stock_qty = {d.warehouse: d.actual_qty for d in sl_entries}
|
||||
self.assertEqual(stock_qty[transit_warehouse], -10)
|
||||
self.assertEqual(stock_qty[to_warehouse], 7)
|
||||
self.assertEqual(stock_qty[rejected_warehouse], 3)
|
||||
|
||||
booked_value = {}
|
||||
for entry in get_gl_entries("Purchase Invoice", pi.name, skip_cancelled=True):
|
||||
booked_value.setdefault(entry.account, 0)
|
||||
booked_value[entry.account] += flt(entry.debit) - flt(entry.credit)
|
||||
|
||||
self.assertEqual(flt(sum(booked_value.values()), 2), 0)
|
||||
self.assertEqual(booked_value[get_inventory_account(company, transit_warehouse)], -1000)
|
||||
self.assertEqual(booked_value[get_inventory_account(company, to_warehouse)], 700)
|
||||
self.assertEqual(booked_value[get_inventory_account(company, rejected_warehouse)], 300)
|
||||
|
||||
def test_internal_transfer_invoice_with_rejected_batch_qty(self):
|
||||
"""Batch material rejected on a stock updating internal transfer invoice gets its own package."""
|
||||
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
make_purchase_receipt,
|
||||
prepare_data_for_internal_transfer,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Batch Invoice Transfer From", company=company)
|
||||
transit_warehouse = create_warehouse("_Test Batch Invoice Transfer Transit", company=company)
|
||||
to_warehouse = create_warehouse("_Test Batch Invoice Transfer To", company=company)
|
||||
rejected_warehouse = create_warehouse("_Test Batch Invoice Transfer Rejected", company=company)
|
||||
|
||||
item = make_item(
|
||||
"Test Invoice Internal Transfer Batch Item",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TIITB-.####",
|
||||
},
|
||||
)
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item.name, company=company, warehouse=from_warehouse, qty=10, rate=100
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=company,
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code=item.name,
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
update_stock=1,
|
||||
cost_center="Main - TCP1",
|
||||
debit_to="Debtors - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
do_not_save=1,
|
||||
)
|
||||
si.items[0].target_warehouse = transit_warehouse
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pi = make_inter_company_purchase_invoice(si.name)
|
||||
pi.update_stock = 1
|
||||
pi.items[0].warehouse = to_warehouse
|
||||
pi.items[0].qty = 7
|
||||
pi.items[0].rejected_qty = 3
|
||||
pi.items[0].received_qty = 10
|
||||
pi.items[0].rejected_warehouse = rejected_warehouse
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
|
||||
pi.submit()
|
||||
|
||||
row = pi.items[0]
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "total_qty"), 7
|
||||
)
|
||||
self.assertEqual(
|
||||
frappe.db.get_value("Serial and Batch Bundle", row.rejected_serial_and_batch_bundle, "total_qty"),
|
||||
3,
|
||||
)
|
||||
|
||||
sl_entries = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "actual_qty", "stock_value_difference"],
|
||||
)
|
||||
moved_qty = {d.warehouse: d.actual_qty for d in sl_entries}
|
||||
moved_value = {d.warehouse: d.stock_value_difference for d in sl_entries}
|
||||
|
||||
self.assertEqual(moved_qty[transit_warehouse], -10)
|
||||
self.assertEqual(moved_qty[to_warehouse], 7)
|
||||
self.assertEqual(moved_qty[rejected_warehouse], 3)
|
||||
self.assertEqual(flt(moved_value[transit_warehouse]), -1000)
|
||||
self.assertEqual(flt(moved_value[to_warehouse]), 700)
|
||||
self.assertEqual(flt(moved_value[rejected_warehouse]), 300)
|
||||
|
||||
def test_internal_transfer_invoice_with_every_unit_rejected(self):
|
||||
"""An invoice may reject a whole row, and the in-transit warehouse is emptied all the same."""
|
||||
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
make_purchase_receipt,
|
||||
prepare_data_for_internal_transfer,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Rejected Invoice From", company=company)
|
||||
transit_warehouse = create_warehouse("_Test Rejected Invoice Transit", company=company)
|
||||
to_warehouse = create_warehouse("_Test Rejected Invoice To", company=company)
|
||||
rejected_warehouse = create_warehouse("_Test Rejected Invoice Rejected", company=company)
|
||||
|
||||
item_doc = create_item("Test Fully Rejected Transfer Item")
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=company,
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code=item_doc.name,
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
update_stock=1,
|
||||
cost_center="Main - TCP1",
|
||||
debit_to="Debtors - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
do_not_save=1,
|
||||
)
|
||||
si.items[0].target_warehouse = transit_warehouse
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pi = make_inter_company_purchase_invoice(si.name)
|
||||
pi.update_stock = 1
|
||||
pi.items[0].warehouse = to_warehouse
|
||||
pi.items[0].qty = 0
|
||||
pi.items[0].rejected_qty = 10
|
||||
pi.items[0].received_qty = 10
|
||||
pi.items[0].rejected_warehouse = rejected_warehouse
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
|
||||
pi.submit()
|
||||
|
||||
sl_entries = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "actual_qty", "stock_value_difference"],
|
||||
)
|
||||
moved_qty = {d.warehouse: d.actual_qty for d in sl_entries}
|
||||
moved_value = {d.warehouse: d.stock_value_difference for d in sl_entries}
|
||||
|
||||
self.assertNotIn(to_warehouse, moved_qty)
|
||||
self.assertEqual(moved_qty[transit_warehouse], -10)
|
||||
self.assertEqual(moved_qty[rejected_warehouse], 10)
|
||||
self.assertEqual(flt(moved_value[transit_warehouse]), -1000)
|
||||
self.assertEqual(flt(moved_value[rejected_warehouse]), 1000)
|
||||
|
||||
def test_return_of_an_internal_transfer_invoice_that_rejected_everything(self):
|
||||
"""Material rejected in full goes back to the in-transit warehouse at the rate it came in
|
||||
with, and the entries say the same as the stock."""
|
||||
from erpnext.accounts.doctype.sales_invoice.mapper import make_inter_company_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
|
||||
make_purchase_receipt,
|
||||
prepare_data_for_internal_transfer,
|
||||
)
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
from_warehouse = create_warehouse("_Test Returned Transfer From", company=company)
|
||||
transit_warehouse = create_warehouse("_Test Returned Transfer Transit", company=company)
|
||||
to_warehouse = create_warehouse("_Test Returned Transfer To", company=company)
|
||||
rejected_warehouse = create_warehouse("_Test Returned Transfer Rejected", company=company)
|
||||
|
||||
item_doc = create_item("Test Returned Fully Rejected Transfer Item")
|
||||
|
||||
make_purchase_receipt(
|
||||
item_code=item_doc.name, company=company, warehouse=from_warehouse, qty=10, rate=100
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=company,
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code=item_doc.name,
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
update_stock=1,
|
||||
cost_center="Main - TCP1",
|
||||
debit_to="Debtors - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
do_not_save=1,
|
||||
)
|
||||
si.items[0].target_warehouse = transit_warehouse
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
pi = make_inter_company_purchase_invoice(si.name)
|
||||
pi.update_stock = 1
|
||||
pi.items[0].warehouse = to_warehouse
|
||||
pi.items[0].qty = 0
|
||||
pi.items[0].rejected_qty = 10
|
||||
pi.items[0].received_qty = 10
|
||||
pi.items[0].rejected_warehouse = rejected_warehouse
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - TCP1"
|
||||
pi.submit()
|
||||
|
||||
returned = make_return_doc("Purchase Invoice", pi.name)
|
||||
returned.update_stock = 1
|
||||
returned.submit()
|
||||
|
||||
moved = {
|
||||
d.warehouse: flt(d.stock_value_difference)
|
||||
for d in frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": returned.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "stock_value_difference"],
|
||||
)
|
||||
}
|
||||
self.assertEqual(moved[transit_warehouse], 1000)
|
||||
self.assertEqual(moved[rejected_warehouse], -1000)
|
||||
|
||||
booked = {}
|
||||
for entry in frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": returned.name, "is_cancelled": 0},
|
||||
fields=["account", "debit", "credit"],
|
||||
):
|
||||
booked.setdefault(entry.account, 0)
|
||||
booked[entry.account] += flt(entry.debit) - flt(entry.credit)
|
||||
|
||||
self.assertEqual(flt(sum(booked.values()), 2), 0)
|
||||
self.assertEqual(booked[get_inventory_account(company, transit_warehouse)], 1000)
|
||||
self.assertEqual(booked[get_inventory_account(company, rejected_warehouse)], -1000)
|
||||
|
||||
def test_stock_updating_invoice_rejects_every_unit_of_a_row(self):
|
||||
"""A row of a stock updating invoice may be rejected in full."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
item = make_item(properties={"is_stock_item": 1}).name
|
||||
rejected_warehouse = create_warehouse("_Test Fully Rejected Invoice Warehouse", company=company)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
company=company,
|
||||
item_code=item,
|
||||
warehouse="Stores - TCP1",
|
||||
qty=0,
|
||||
rejected_qty=10,
|
||||
received_qty=10,
|
||||
rate=100,
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
update_stock=1,
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
do_not_save=True,
|
||||
)
|
||||
pi.submit()
|
||||
|
||||
moved_qty = {
|
||||
d.warehouse: d.actual_qty
|
||||
for d in frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "actual_qty"],
|
||||
)
|
||||
}
|
||||
self.assertEqual(moved_qty, {rejected_warehouse: 10})
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Buying Settings",
|
||||
{
|
||||
"bill_for_rejected_quantity_in_purchase_invoice": 1,
|
||||
"set_valuation_rate_for_rejected_materials": 1,
|
||||
},
|
||||
)
|
||||
def test_stock_updating_invoice_bills_the_rejected_quantity(self):
|
||||
"""With the rejected quantity billed and valued, the invoice pays for every unit received and
|
||||
the stock it moves matches the entries it books."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Billed Rejected Warehouse", company=company)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item_code=item,
|
||||
company=company,
|
||||
warehouse="Stores - TCP1",
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
cost_center="Main - TCP1",
|
||||
supplier_warehouse="Work In Progress - TCP1",
|
||||
expense_account="_Test Account Cost for Goods Sold - TCP1",
|
||||
update_stock=1,
|
||||
received_qty=10,
|
||||
qty=6,
|
||||
rejected_qty=4,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
self.assertEqual(pi.items[0].amount, 1000)
|
||||
self.assertEqual(pi.items[0].valuation_rate, 100)
|
||||
|
||||
stock_value = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "stock_value_difference"],
|
||||
)
|
||||
by_warehouse = {d.warehouse: d.stock_value_difference for d in stock_value}
|
||||
|
||||
self.assertEqual(by_warehouse["Stores - TCP1"], 600)
|
||||
self.assertEqual(by_warehouse[rejected_warehouse], 400)
|
||||
|
||||
booked = frappe.get_all(
|
||||
"GL Entry", filters={"voucher_no": pi.name, "is_cancelled": 0}, fields=["debit"]
|
||||
)
|
||||
self.assertEqual(sum(flt(d.debit) for d in booked), 1000)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Buying Settings",
|
||||
{
|
||||
"bill_for_rejected_quantity_in_purchase_invoice": 1,
|
||||
"set_valuation_rate_for_rejected_materials": 1,
|
||||
},
|
||||
)
|
||||
def test_rejected_material_is_reposted_after_the_setting_changes(self):
|
||||
"""The entries an invoice books follow the stock it moved, so they can be built again once
|
||||
the settings have moved on."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Repost Rejected", company=company)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
company=company,
|
||||
item_code=item,
|
||||
warehouse="Stores - TCP1",
|
||||
qty=6,
|
||||
rejected_qty=4,
|
||||
received_qty=10,
|
||||
rate=100,
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
update_stock=1,
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 0)
|
||||
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)
|
||||
|
||||
rebuilt = pi.get_gl_entries()
|
||||
rejected_account = get_inventory_account(company, rejected_warehouse)
|
||||
|
||||
self.assertEqual(
|
||||
flt(sum(flt(entry.get("debit")) - flt(entry.get("credit")) for entry in rebuilt), 2), 0
|
||||
)
|
||||
self.assertEqual(
|
||||
flt(
|
||||
sum(flt(entry.get("debit")) for entry in rebuilt if entry.get("account") == rejected_account)
|
||||
),
|
||||
400,
|
||||
)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Buying Settings",
|
||||
{
|
||||
"bill_for_rejected_quantity_in_purchase_invoice": 1,
|
||||
"set_valuation_rate_for_rejected_materials": 1,
|
||||
},
|
||||
)
|
||||
def test_return_without_a_reference_books_both_warehouses(self):
|
||||
"""A return that stands on its own gives back the rejected material too, and books it once."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
|
||||
accepted_warehouse = create_warehouse("_Test Invoice Return Accepted", company=company)
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Return Rejected", company=company)
|
||||
|
||||
def make_invoice(sign):
|
||||
return make_purchase_invoice(
|
||||
company=company,
|
||||
item_code=item,
|
||||
warehouse=accepted_warehouse,
|
||||
qty=6 * sign,
|
||||
rejected_qty=4 * sign,
|
||||
received_qty=10 * sign,
|
||||
rate=100,
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
update_stock=1,
|
||||
is_return=1 if sign < 0 else 0,
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
)
|
||||
|
||||
make_invoice(1)
|
||||
returned = make_invoice(-1)
|
||||
|
||||
booked = {}
|
||||
for entry in frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": returned.name, "is_cancelled": 0},
|
||||
fields=["account", "debit", "credit"],
|
||||
):
|
||||
booked.setdefault(entry.account, 0)
|
||||
booked[entry.account] += flt(entry.debit) - flt(entry.credit)
|
||||
|
||||
self.assertEqual(flt(sum(booked.values()), 2), 0)
|
||||
self.assertEqual(booked[get_inventory_account(company, accepted_warehouse)], -600)
|
||||
self.assertEqual(booked[get_inventory_account(company, rejected_warehouse)], -400)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Buying Settings",
|
||||
{
|
||||
"bill_for_rejected_quantity_in_purchase_invoice": 1,
|
||||
"set_valuation_rate_for_rejected_materials": 1,
|
||||
},
|
||||
)
|
||||
def test_discount_on_an_invoice_that_bills_the_rejected_quantity(self):
|
||||
"""A discount is spread over every unit the invoice pays for, not the accepted ones alone."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Discount Rejected", company=company)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
company=company,
|
||||
item_code=item,
|
||||
warehouse="Stores - TCP1",
|
||||
qty=6,
|
||||
rejected_qty=4,
|
||||
received_qty=10,
|
||||
rate=100,
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
update_stock=1,
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
do_not_save=True,
|
||||
)
|
||||
pi.apply_discount_on = "Net Total"
|
||||
pi.additional_discount_percentage = 10
|
||||
pi.submit()
|
||||
|
||||
self.assertEqual(pi.items[0].amount, 1000)
|
||||
self.assertEqual(pi.items[0].net_rate, 90)
|
||||
self.assertEqual(pi.grand_total, 900)
|
||||
self.assertEqual(frappe.db.get_value("Item", item, "last_purchase_rate"), 90)
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Buying Settings",
|
||||
{
|
||||
"set_valuation_rate_for_rejected_materials": 1,
|
||||
"bill_for_rejected_quantity_in_purchase_invoice": 0,
|
||||
},
|
||||
)
|
||||
def test_rejected_material_is_not_valued_on_a_stock_updating_invoice(self):
|
||||
"""An invoice that does not bill the rejected quantity has nothing to pay for that material,
|
||||
so it carries no cost and the stock the invoice moves matches the entries it books."""
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
company = "_Test Company with perpetual inventory"
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_method": "FIFO"}).name
|
||||
rejected_warehouse = create_warehouse("_Test Invoice Rejected Warehouse", company=company)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item_code=item,
|
||||
company=company,
|
||||
warehouse="Stores - TCP1",
|
||||
rejected_warehouse=rejected_warehouse,
|
||||
cost_center="Main - TCP1",
|
||||
supplier_warehouse="Work In Progress - TCP1",
|
||||
expense_account="_Test Account Cost for Goods Sold - TCP1",
|
||||
update_stock=1,
|
||||
received_qty=10,
|
||||
qty=6,
|
||||
rejected_qty=4,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
self.assertEqual(pi.items[0].amount, 600)
|
||||
|
||||
stock_value = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
filters={"voucher_no": pi.name, "is_cancelled": 0},
|
||||
fields=["warehouse", "stock_value_difference"],
|
||||
)
|
||||
by_warehouse = {d.warehouse: d.stock_value_difference for d in stock_value}
|
||||
|
||||
self.assertEqual(by_warehouse["Stores - TCP1"], 600)
|
||||
self.assertEqual(by_warehouse[rejected_warehouse], 0)
|
||||
|
||||
booked = frappe.get_all(
|
||||
"GL Entry", filters={"voucher_no": pi.name, "is_cancelled": 0}, fields=["debit"]
|
||||
)
|
||||
self.assertEqual(sum(flt(d.debit) for d in booked), sum(by_warehouse.values()))
|
||||
|
||||
def test_purchase_invoice_with_use_serial_batch_field_for_rejected_qty(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain a list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\", etc.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/purchase-taxes-and-charges-template",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
@@ -75,7 +74,7 @@
|
||||
"options": "Tax Category"
|
||||
}
|
||||
],
|
||||
"icon": "badge-percent",
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:47.506282",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"creation": "2023-07-04 13:07:32.923675",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/repost-accounting-ledger",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -102,7 +101,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "list-restart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
"label": "Repost Error Log"
|
||||
}
|
||||
],
|
||||
"icon": "list-restart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2022-01-25 10:29:57.771398",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/sales-invoice",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"customer_section",
|
||||
@@ -2396,7 +2395,7 @@
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "receipt-text",
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain a list of tax heads and also other expense/income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/sales-taxes-and-charges-template",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
@@ -77,7 +76,7 @@
|
||||
"options": "Tax Category"
|
||||
}
|
||||
],
|
||||
"icon": "badge-percent",
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:48.797423",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "ACC-SHT-.YYYY.-.#####",
|
||||
"creation": "2017-12-25 17:18:03.143726",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/share-transfer",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -191,7 +190,6 @@
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "arrow-right-left",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:40.003178",
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"label": "Description"
|
||||
}
|
||||
],
|
||||
"icon": "tag",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:40.190914",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2017-12-25 16:50:53.878430",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/shareholder",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -113,7 +112,6 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "user-star",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:40.356171",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"creation": "2013-06-25 11:48:03",
|
||||
"description": "Specify conditions to calculate shipping amount",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/shipping-rule",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"label",
|
||||
@@ -147,7 +146,7 @@
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"icon": "truck",
|
||||
"icon": "fa fa-truck",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:49.532098",
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"autoname": "ACC-SUB-.YYYY.-.#####",
|
||||
"creation": "2017-07-18 17:50:43.967266",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/subscription",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -300,7 +299,6 @@
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"icon": "calendar-sync",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "field:plan_name",
|
||||
"creation": "2018-02-24 11:31:23.066506",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/subscription-plan",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -149,7 +148,6 @@
|
||||
"label": "Product Price ID"
|
||||
}
|
||||
],
|
||||
"icon": "calendar-sync",
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:47.998597",
|
||||
"modified_by": "Administrator",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"actions": [],
|
||||
"creation": "2018-02-26 06:13:37.910139",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/subscription-settings",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -33,7 +32,6 @@
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "settings",
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-03-16 13:28:20.485964",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"autoname": "field:title",
|
||||
"creation": "2022-01-19 01:09:28.920486",
|
||||
"doctype": "DocType",
|
||||
"documentation": "https://docs.frappe.io/erpnext/user/manual/en/tax-category",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
@@ -28,7 +27,6 @@
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"icon": "badge-percent",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2026-08-21 23:11:50.740962",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user