mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-25 16:04:46 +00:00
Merge pull request #38689 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -423,9 +423,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
|||||||
vouchers = json.loads(vouchers)
|
vouchers = json.loads(vouchers)
|
||||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||||
transaction.add_payment_entries(vouchers)
|
transaction.add_payment_entries(vouchers)
|
||||||
transaction.save()
|
return frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||||
|
|
||||||
return transaction
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"status",
|
"status",
|
||||||
"bank_account",
|
"bank_account",
|
||||||
"company",
|
"company",
|
||||||
"amended_from",
|
|
||||||
"section_break_4",
|
"section_break_4",
|
||||||
"deposit",
|
"deposit",
|
||||||
"withdrawal",
|
"withdrawal",
|
||||||
@@ -26,10 +25,10 @@
|
|||||||
"transaction_id",
|
"transaction_id",
|
||||||
"transaction_type",
|
"transaction_type",
|
||||||
"section_break_14",
|
"section_break_14",
|
||||||
"column_break_oufv",
|
|
||||||
"payment_entries",
|
"payment_entries",
|
||||||
"section_break_18",
|
"section_break_18",
|
||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
|
"amended_from",
|
||||||
"column_break_17",
|
"column_break_17",
|
||||||
"unallocated_amount",
|
"unallocated_amount",
|
||||||
"party_section",
|
"party_section",
|
||||||
@@ -139,12 +138,10 @@
|
|||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
|
||||||
"fieldname": "allocated_amount",
|
"fieldname": "allocated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Allocated Amount",
|
"label": "Allocated Amount",
|
||||||
"options": "currency",
|
"options": "currency"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "amended_from",
|
"fieldname": "amended_from",
|
||||||
@@ -160,12 +157,10 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
|
||||||
"fieldname": "unallocated_amount",
|
"fieldname": "unallocated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Unallocated Amount",
|
"label": "Unallocated Amount",
|
||||||
"options": "currency",
|
"options": "currency"
|
||||||
"read_only": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_section",
|
"fieldname": "party_section",
|
||||||
@@ -230,15 +225,11 @@
|
|||||||
"fieldname": "bank_party_account_number",
|
"fieldname": "bank_party_account_number",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Party Account No. (Bank Statement)"
|
"label": "Party Account No. (Bank Statement)"
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_oufv",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-11-18 18:32:47.203694",
|
"modified": "2023-06-06 13:58:12.821411",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Bank Transaction",
|
"name": "Bank Transaction",
|
||||||
|
|||||||
@@ -2,73 +2,78 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
|
||||||
from frappe.utils import flt
|
from frappe.utils import flt
|
||||||
|
|
||||||
from erpnext.controllers.status_updater import StatusUpdater
|
from erpnext.controllers.status_updater import StatusUpdater
|
||||||
|
|
||||||
|
|
||||||
class BankTransaction(StatusUpdater):
|
class BankTransaction(StatusUpdater):
|
||||||
def before_validate(self):
|
def after_insert(self):
|
||||||
self.update_allocated_amount()
|
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit))
|
||||||
|
|
||||||
def validate(self):
|
def on_submit(self):
|
||||||
self.validate_duplicate_references()
|
self.clear_linked_payment_entries()
|
||||||
|
|
||||||
def validate_duplicate_references(self):
|
|
||||||
"""Make sure the same voucher is not allocated twice within the same Bank Transaction"""
|
|
||||||
if not self.payment_entries:
|
|
||||||
return
|
|
||||||
|
|
||||||
pe = []
|
|
||||||
for row in self.payment_entries:
|
|
||||||
reference = (row.payment_document, row.payment_entry)
|
|
||||||
if reference in pe:
|
|
||||||
frappe.throw(
|
|
||||||
_("{0} {1} is allocated twice in this Bank Transaction").format(
|
|
||||||
row.payment_document, row.payment_entry
|
|
||||||
)
|
|
||||||
)
|
|
||||||
pe.append(reference)
|
|
||||||
|
|
||||||
def update_allocated_amount(self):
|
|
||||||
self.allocated_amount = (
|
|
||||||
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
|
|
||||||
)
|
|
||||||
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
|
|
||||||
|
|
||||||
def before_submit(self):
|
|
||||||
self.allocate_payment_entries()
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
|
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
|
||||||
self.auto_set_party()
|
self.auto_set_party()
|
||||||
|
|
||||||
def before_update_after_submit(self):
|
_saving_flag = False
|
||||||
self.validate_duplicate_references()
|
|
||||||
self.allocate_payment_entries()
|
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
|
||||||
self.update_allocated_amount()
|
def on_update_after_submit(self):
|
||||||
|
"Run on save(). Avoid recursion caused by multiple saves"
|
||||||
|
if not self._saving_flag:
|
||||||
|
self._saving_flag = True
|
||||||
|
self.clear_linked_payment_entries()
|
||||||
|
self.update_allocations()
|
||||||
|
self._saving_flag = False
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
for payment_entry in self.payment_entries:
|
self.clear_linked_payment_entries(for_cancel=True)
|
||||||
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
|
self.set_status(update=True)
|
||||||
|
|
||||||
|
def update_allocations(self):
|
||||||
|
"The doctype does not allow modifications after submission, so write to the db direct"
|
||||||
|
if self.payment_entries:
|
||||||
|
allocated_amount = sum(p.allocated_amount for p in self.payment_entries)
|
||||||
|
else:
|
||||||
|
allocated_amount = 0.0
|
||||||
|
|
||||||
|
amount = abs(flt(self.withdrawal) - flt(self.deposit))
|
||||||
|
self.db_set("allocated_amount", flt(allocated_amount))
|
||||||
|
self.db_set("unallocated_amount", amount - flt(allocated_amount))
|
||||||
|
self.reload()
|
||||||
self.set_status(update=True)
|
self.set_status(update=True)
|
||||||
|
|
||||||
def add_payment_entries(self, vouchers):
|
def add_payment_entries(self, vouchers):
|
||||||
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
|
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
|
||||||
if 0.0 >= self.unallocated_amount:
|
if 0.0 >= self.unallocated_amount:
|
||||||
frappe.throw(_("Bank Transaction {0} is already fully reconciled").format(self.name))
|
frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name))
|
||||||
|
|
||||||
|
added = False
|
||||||
for voucher in vouchers:
|
for voucher in vouchers:
|
||||||
self.append(
|
# Can't add same voucher twice
|
||||||
"payment_entries",
|
found = False
|
||||||
{
|
for pe in self.payment_entries:
|
||||||
|
if (
|
||||||
|
pe.payment_document == voucher["payment_doctype"]
|
||||||
|
and pe.payment_entry == voucher["payment_name"]
|
||||||
|
):
|
||||||
|
found = True
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
pe = {
|
||||||
"payment_document": voucher["payment_doctype"],
|
"payment_document": voucher["payment_doctype"],
|
||||||
"payment_entry": voucher["payment_name"],
|
"payment_entry": voucher["payment_name"],
|
||||||
"allocated_amount": 0.0, # Temporary
|
"allocated_amount": 0.0, # Temporary
|
||||||
},
|
}
|
||||||
)
|
child = self.append("payment_entries", pe)
|
||||||
|
added = True
|
||||||
|
|
||||||
|
# runs on_update_after_submit
|
||||||
|
if added:
|
||||||
|
self.save()
|
||||||
|
|
||||||
def allocate_payment_entries(self):
|
def allocate_payment_entries(self):
|
||||||
"""Refactored from bank reconciliation tool.
|
"""Refactored from bank reconciliation tool.
|
||||||
@@ -85,7 +90,6 @@ class BankTransaction(StatusUpdater):
|
|||||||
- clear means: set the latest transaction date as clearance date
|
- clear means: set the latest transaction date as clearance date
|
||||||
"""
|
"""
|
||||||
remaining_amount = self.unallocated_amount
|
remaining_amount = self.unallocated_amount
|
||||||
to_remove = []
|
|
||||||
for payment_entry in self.payment_entries:
|
for payment_entry in self.payment_entries:
|
||||||
if payment_entry.allocated_amount == 0.0:
|
if payment_entry.allocated_amount == 0.0:
|
||||||
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
|
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
|
||||||
@@ -95,39 +99,49 @@ class BankTransaction(StatusUpdater):
|
|||||||
if 0.0 == unallocated_amount:
|
if 0.0 == unallocated_amount:
|
||||||
if should_clear:
|
if should_clear:
|
||||||
latest_transaction.clear_linked_payment_entry(payment_entry)
|
latest_transaction.clear_linked_payment_entry(payment_entry)
|
||||||
to_remove.append(payment_entry)
|
self.db_delete_payment_entry(payment_entry)
|
||||||
|
|
||||||
elif remaining_amount <= 0.0:
|
elif remaining_amount <= 0.0:
|
||||||
to_remove.append(payment_entry)
|
self.db_delete_payment_entry(payment_entry)
|
||||||
|
|
||||||
elif 0.0 < unallocated_amount <= remaining_amount:
|
elif 0.0 < unallocated_amount and unallocated_amount <= remaining_amount:
|
||||||
payment_entry.allocated_amount = unallocated_amount
|
payment_entry.db_set("allocated_amount", unallocated_amount)
|
||||||
remaining_amount -= unallocated_amount
|
remaining_amount -= unallocated_amount
|
||||||
if should_clear:
|
if should_clear:
|
||||||
latest_transaction.clear_linked_payment_entry(payment_entry)
|
latest_transaction.clear_linked_payment_entry(payment_entry)
|
||||||
|
|
||||||
elif 0.0 < unallocated_amount:
|
elif 0.0 < unallocated_amount and unallocated_amount > remaining_amount:
|
||||||
payment_entry.allocated_amount = remaining_amount
|
payment_entry.db_set("allocated_amount", remaining_amount)
|
||||||
remaining_amount = 0.0
|
remaining_amount = 0.0
|
||||||
|
|
||||||
elif 0.0 > unallocated_amount:
|
elif 0.0 > unallocated_amount:
|
||||||
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
|
self.db_delete_payment_entry(payment_entry)
|
||||||
|
frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
|
||||||
|
|
||||||
for payment_entry in to_remove:
|
self.reload()
|
||||||
self.remove(to_remove)
|
|
||||||
|
def db_delete_payment_entry(self, payment_entry):
|
||||||
|
frappe.db.delete("Bank Transaction Payments", {"name": payment_entry.name})
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def remove_payment_entries(self):
|
def remove_payment_entries(self):
|
||||||
for payment_entry in self.payment_entries:
|
for payment_entry in self.payment_entries:
|
||||||
self.remove_payment_entry(payment_entry)
|
self.remove_payment_entry(payment_entry)
|
||||||
|
# runs on_update_after_submit
|
||||||
self.save() # runs before_update_after_submit
|
self.save()
|
||||||
|
|
||||||
def remove_payment_entry(self, payment_entry):
|
def remove_payment_entry(self, payment_entry):
|
||||||
"Clear payment entry and clearance"
|
"Clear payment entry and clearance"
|
||||||
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
|
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
|
||||||
self.remove(payment_entry)
|
self.remove(payment_entry)
|
||||||
|
|
||||||
|
def clear_linked_payment_entries(self, for_cancel=False):
|
||||||
|
if for_cancel:
|
||||||
|
for payment_entry in self.payment_entries:
|
||||||
|
self.clear_linked_payment_entry(payment_entry, for_cancel)
|
||||||
|
else:
|
||||||
|
self.allocate_payment_entries()
|
||||||
|
|
||||||
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
|
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
|
||||||
clearance_date = None if for_cancel else self.date
|
clearance_date = None if for_cancel else self.date
|
||||||
set_voucher_clearance(
|
set_voucher_clearance(
|
||||||
@@ -148,10 +162,11 @@ class BankTransaction(StatusUpdater):
|
|||||||
deposit=self.deposit,
|
deposit=self.deposit,
|
||||||
).match()
|
).match()
|
||||||
|
|
||||||
if not result:
|
if result:
|
||||||
return
|
party_type, party = result
|
||||||
|
frappe.db.set_value(
|
||||||
self.party_type, self.party = result
|
"Bank Transaction", self.name, field={"party_type": party_type, "party": party}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -183,7 +198,9 @@ def get_clearance_details(transaction, payment_entry):
|
|||||||
if gle["gl_account"] == gl_bank_account:
|
if gle["gl_account"] == gl_bank_account:
|
||||||
if gle["amount"] <= 0.0:
|
if gle["amount"] <= 0.0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"])
|
frappe._("Voucher {0} value is broken: {1}").format(
|
||||||
|
payment_entry.payment_entry, gle["amount"]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
unmatched_gles -= 1
|
unmatched_gles -= 1
|
||||||
@@ -204,7 +221,7 @@ def get_clearance_details(transaction, payment_entry):
|
|||||||
|
|
||||||
def get_related_bank_gl_entries(doctype, docname):
|
def get_related_bank_gl_entries(doctype, docname):
|
||||||
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
|
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
|
||||||
return frappe.db.sql(
|
result = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
|
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
|
||||||
@@ -222,6 +239,7 @@ def get_related_bank_gl_entries(doctype, docname):
|
|||||||
dict(doctype=doctype, docname=docname),
|
dict(doctype=doctype, docname=docname),
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_total_allocated_amount(doctype, docname):
|
def get_total_allocated_amount(doctype, docname):
|
||||||
@@ -354,7 +372,6 @@ def set_voucher_clearance(doctype, docname, clearance_date, self):
|
|||||||
if clearance_date:
|
if clearance_date:
|
||||||
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
|
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
|
||||||
bt.add_payment_entries(vouchers)
|
bt.add_payment_entries(vouchers)
|
||||||
bt.save()
|
|
||||||
else:
|
else:
|
||||||
for pe in bt.payment_entries:
|
for pe in bt.payment_entries:
|
||||||
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
|
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
|
|
||||||
class PaymentReconciliationAllocation(Document):
|
class PaymentReconciliationAllocation(Document):
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
|
|
||||||
class PaymentReconciliationInvoice(Document):
|
class PaymentReconciliationInvoice(Document):
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ from frappe.model.document import Document
|
|||||||
|
|
||||||
|
|
||||||
class PaymentReconciliationPayment(Document):
|
class PaymentReconciliationPayment(Document):
|
||||||
pass
|
@staticmethod
|
||||||
|
def get_list(args):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class RepostAccountingLedger(Document):
|
|||||||
return rendered_page
|
return rendered_page
|
||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if len(self.vouchers) > 1:
|
if len(self.vouchers) > 5:
|
||||||
job_name = "repost_accounting_ledger_" + self.name
|
job_name = "repost_accounting_ledger_" + self.name
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
||||||
@@ -152,8 +152,6 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
doc.make_gl_entries(1)
|
doc.make_gl_entries(1)
|
||||||
doc.make_gl_entries()
|
doc.make_gl_entries()
|
||||||
|
|
||||||
frappe.db.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def get_allowed_types_from_settings():
|
def get_allowed_types_from_settings():
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
self.create_item()
|
self.create_item()
|
||||||
self.update_repost_settings()
|
update_repost_settings()
|
||||||
|
|
||||||
def teadDown(self):
|
def tearDown(self):
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def update_repost_settings(self):
|
|
||||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
|
||||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
|
||||||
for x in allowed_types:
|
|
||||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
|
||||||
repost_settings.save()
|
|
||||||
|
|
||||||
def test_01_basic_functions(self):
|
def test_01_basic_functions(self):
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item=self.item,
|
item=self.item,
|
||||||
@@ -90,9 +83,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
# Submit repost document
|
# Submit repost document
|
||||||
ral.save().submit()
|
ral.save().submit()
|
||||||
|
|
||||||
# background jobs don't run on test cases. Manually triggering repost function.
|
|
||||||
start_repost(ral.name)
|
|
||||||
|
|
||||||
res = (
|
res = (
|
||||||
qb.from_(gl)
|
qb.from_(gl)
|
||||||
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
|
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
|
||||||
@@ -177,26 +167,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
pe = get_payment_entry(si.doctype, si.name)
|
pe = get_payment_entry(si.doctype, si.name)
|
||||||
pe.save().submit()
|
pe.save().submit()
|
||||||
|
|
||||||
# without deletion flag set
|
|
||||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
|
||||||
ral.company = self.company
|
|
||||||
ral.delete_cancelled_entries = False
|
|
||||||
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
|
||||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
|
||||||
ral.save()
|
|
||||||
|
|
||||||
# assert preview data is generated
|
|
||||||
preview = ral.generate_preview()
|
|
||||||
self.assertIsNotNone(preview)
|
|
||||||
|
|
||||||
ral.save().submit()
|
|
||||||
|
|
||||||
# background jobs don't run on test cases. Manually triggering repost function.
|
|
||||||
start_repost(ral.name)
|
|
||||||
|
|
||||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
|
||||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
|
||||||
|
|
||||||
# with deletion flag set
|
# with deletion flag set
|
||||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||||
ral.company = self.company
|
ral.company = self.company
|
||||||
@@ -205,6 +175,38 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
|||||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||||
ral.save().submit()
|
ral.save().submit()
|
||||||
|
|
||||||
start_repost(ral.name)
|
|
||||||
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||||
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||||
|
|
||||||
|
def test_05_without_deletion_flag(self):
|
||||||
|
si = create_sales_invoice(
|
||||||
|
item=self.item,
|
||||||
|
company=self.company,
|
||||||
|
customer=self.customer,
|
||||||
|
debit_to=self.debit_to,
|
||||||
|
parent_cost_center=self.cost_center,
|
||||||
|
cost_center=self.cost_center,
|
||||||
|
rate=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
pe = get_payment_entry(si.doctype, si.name)
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
# without deletion flag set
|
||||||
|
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||||
|
ral.company = self.company
|
||||||
|
ral.delete_cancelled_entries = False
|
||||||
|
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
||||||
|
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||||
|
ral.save().submit()
|
||||||
|
|
||||||
|
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||||
|
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||||
|
|
||||||
|
|
||||||
|
def update_repost_settings():
|
||||||
|
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||||
|
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||||
|
for x in allowed_types:
|
||||||
|
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||||
|
repost_settings.save()
|
||||||
|
|||||||
@@ -2791,6 +2791,12 @@ class TestSalesInvoice(FrappeTestCase):
|
|||||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
|
||||||
|
update_repost_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
update_repost_settings()
|
||||||
|
|
||||||
additional_discount_account = create_account(
|
additional_discount_account = create_account(
|
||||||
account_name="Discount Account",
|
account_name="Discount Account",
|
||||||
parent_account="Indirect Expenses - _TC",
|
parent_account="Indirect Expenses - _TC",
|
||||||
|
|||||||
@@ -8,7 +8,17 @@ import re
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
add_months,
|
||||||
|
cint,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
formatdate,
|
||||||
|
get_first_day,
|
||||||
|
getdate,
|
||||||
|
today,
|
||||||
|
)
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
@@ -43,6 +53,8 @@ def get_period_list(
|
|||||||
year_start_date = getdate(period_start_date)
|
year_start_date = getdate(period_start_date)
|
||||||
year_end_date = getdate(period_end_date)
|
year_end_date = getdate(period_end_date)
|
||||||
|
|
||||||
|
year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
|
||||||
|
|
||||||
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
|
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
|
||||||
|
|
||||||
period_list = []
|
period_list = []
|
||||||
|
|||||||
@@ -427,6 +427,10 @@ class Asset(AccountsController):
|
|||||||
n == 0
|
n == 0
|
||||||
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||||
and not self.opening_accumulated_depreciation
|
and not self.opening_accumulated_depreciation
|
||||||
|
and get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||||
|
self, value_after_depreciation, finance_book, False
|
||||||
|
)
|
||||||
|
== finance_book.rate_of_depreciation
|
||||||
):
|
):
|
||||||
from_date = add_days(
|
from_date = add_days(
|
||||||
self.available_for_use_date, -1
|
self.available_for_use_date, -1
|
||||||
@@ -1387,7 +1391,9 @@ def get_depreciation_amount(
|
|||||||
|
|
||||||
|
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
|
def get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||||
|
asset, depreciable_value, fb_row, show_msg=True
|
||||||
|
):
|
||||||
return fb_row.rate_of_depreciation
|
return fb_row.rate_of_depreciation
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -638,4 +638,5 @@ additional_timeline_content = {
|
|||||||
|
|
||||||
extend_bootinfo = [
|
extend_bootinfo = [
|
||||||
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.add_sla_doctypes",
|
||||||
|
"erpnext.startup.boot.bootinfo",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -599,6 +599,8 @@ def regenerate_repayment_schedule(loan, cancel=0):
|
|||||||
last_repayment_amount = None
|
last_repayment_amount = None
|
||||||
last_balance_amount = None
|
last_balance_amount = None
|
||||||
|
|
||||||
|
original_repayment_schedule_len = len(loan_doc.get("repayment_schedule"))
|
||||||
|
|
||||||
for term in reversed(loan_doc.get("repayment_schedule")):
|
for term in reversed(loan_doc.get("repayment_schedule")):
|
||||||
if not term.is_accrued:
|
if not term.is_accrued:
|
||||||
next_accrual_date = term.payment_date
|
next_accrual_date = term.payment_date
|
||||||
@@ -616,7 +618,7 @@ def regenerate_repayment_schedule(loan, cancel=0):
|
|||||||
|
|
||||||
if loan_doc.repayment_method == "Repay Fixed Amount per Period":
|
if loan_doc.repayment_method == "Repay Fixed Amount per Period":
|
||||||
monthly_repayment_amount = flt(
|
monthly_repayment_amount = flt(
|
||||||
balance_amount / len(loan_doc.get("repayment_schedule")) - accrued_entries
|
balance_amount / (original_repayment_schedule_len - accrued_entries)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
repayment_period = loan_doc.repayment_periods - accrued_entries
|
repayment_period = loan_doc.repayment_periods - accrued_entries
|
||||||
|
|||||||
@@ -441,7 +441,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
item.pricing_rules = ''
|
item.pricing_rules = ''
|
||||||
return this.frm.call({
|
return this.frm.call({
|
||||||
method: "erpnext.stock.get_item_details.get_item_details",
|
method: "erpnext.stock.get_item_details.get_item_details",
|
||||||
child: item,
|
|
||||||
args: {
|
args: {
|
||||||
doc: me.frm.doc,
|
doc: me.frm.doc,
|
||||||
args: {
|
args: {
|
||||||
@@ -490,6 +489,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if(!r.exc) {
|
if(!r.exc) {
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
|
() => {
|
||||||
|
var child = locals[cdt][cdn];
|
||||||
|
var std_field_list = ["doctype"]
|
||||||
|
.concat(frappe.model.std_fields_list)
|
||||||
|
.concat(frappe.model.child_table_field_list);
|
||||||
|
|
||||||
|
for (var key in r.message) {
|
||||||
|
if (std_field_list.indexOf(key) === -1) {
|
||||||
|
if (key === "qty" && child[key]) continue;
|
||||||
|
child[key] = r.message[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
() => {
|
() => {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
me.add_taxes_from_item_tax_template(d.item_tax_rate);
|
me.add_taxes_from_item_tax_template(d.item_tax_rate);
|
||||||
|
|||||||
@@ -2,10 +2,16 @@ frappe.provide("erpnext.financial_statements");
|
|||||||
|
|
||||||
erpnext.financial_statements = {
|
erpnext.financial_statements = {
|
||||||
"filters": get_filters(),
|
"filters": get_filters(),
|
||||||
"formatter": function(value, row, column, data, default_formatter) {
|
"formatter": function(value, row, column, data, default_formatter, filter) {
|
||||||
if (data && column.fieldname=="account") {
|
if (data && column.fieldname=="account") {
|
||||||
value = data.account_name || value;
|
value = data.account_name || value;
|
||||||
|
|
||||||
|
if (filter && filter?.text && filter?.type == "contains") {
|
||||||
|
if (!value.toLowerCase().includes(filter.text)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.account) {
|
if (data.account) {
|
||||||
column.link_onclick =
|
column.link_onclick =
|
||||||
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
"erpnext.financial_statements.open_general_ledger(" + JSON.stringify(data) + ")";
|
||||||
|
|||||||
@@ -73,3 +73,11 @@ def update_page_info(bootinfo):
|
|||||||
"Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"},
|
"Sales Person Tree": {"title": "Sales Person Tree", "route": "Tree/Sales Person"},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def bootinfo(bootinfo):
|
||||||
|
if bootinfo.get("user") and bootinfo["user"].get("name"):
|
||||||
|
bootinfo["user"]["employee"] = ""
|
||||||
|
employee = frappe.db.get_value("Employee", {"user_id": bootinfo["user"]["name"]}, "name")
|
||||||
|
if employee:
|
||||||
|
bootinfo["user"]["employee"] = employee
|
||||||
|
|||||||
Reference in New Issue
Block a user