mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 15:12:51 +00:00
Merge branch 'develop' into vat_201_export
This commit is contained in:
11
CODEOWNERS
11
CODEOWNERS
@@ -8,16 +8,15 @@ erpnext/assets/ @khushi8112
|
|||||||
erpnext/regional @ruthra-kumar
|
erpnext/regional @ruthra-kumar
|
||||||
erpnext/selling @ruthra-kumar
|
erpnext/selling @ruthra-kumar
|
||||||
erpnext/support/ @ruthra-kumar
|
erpnext/support/ @ruthra-kumar
|
||||||
pos*
|
|
||||||
|
|
||||||
erpnext/buying/ @rohitwaghchaure
|
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
|
||||||
erpnext/maintenance/ @rohitwaghchaure
|
erpnext/maintenance/ @rohitwaghchaure
|
||||||
erpnext/manufacturing/ @rohitwaghchaure
|
erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
|
||||||
erpnext/quality_management/ @rohitwaghchaure
|
erpnext/quality_management/ @rohitwaghchaure
|
||||||
erpnext/stock/ @rohitwaghchaure
|
erpnext/stock/ @rohitwaghchaure @mihir-kandoi
|
||||||
erpnext/subcontracting @rohitwaghchaure
|
erpnext/subcontracting @mihir-kandoi
|
||||||
|
|
||||||
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure
|
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
|
||||||
erpnext/patches/ @ruthra-kumar
|
erpnext/patches/ @ruthra-kumar
|
||||||
|
|
||||||
.github/ @ruthra-kumar
|
.github/ @ruthra-kumar
|
||||||
|
|||||||
@@ -302,7 +302,9 @@ class Account(NestedSet):
|
|||||||
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||||
self.currency_explicitly_specified = False
|
self.currency_explicitly_specified = False
|
||||||
|
|
||||||
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
|
gl_currency = frappe.db.get_value(
|
||||||
|
"GL Entry", {"account": self.name, "is_cancelled": 0}, "account_currency"
|
||||||
|
)
|
||||||
|
|
||||||
if gl_currency and self.account_currency != gl_currency:
|
if gl_currency and self.account_currency != gl_currency:
|
||||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ def create_charts(
|
|||||||
accounts = []
|
accounts = []
|
||||||
|
|
||||||
def _import_accounts(children, parent, root_type, root_account=False):
|
def _import_accounts(children, parent, root_type, root_account=False):
|
||||||
|
nonlocal custom_chart
|
||||||
for account_name, child in children.items():
|
for account_name, child in children.items():
|
||||||
if root_account:
|
if root_account:
|
||||||
root_type = child.get("root_type")
|
root_type = child.get("root_type")
|
||||||
@@ -55,7 +56,8 @@ def create_charts(
|
|||||||
"account_number": account_number,
|
"account_number": account_number,
|
||||||
"account_type": child.get("account_type"),
|
"account_type": child.get("account_type"),
|
||||||
"account_currency": child.get("account_currency")
|
"account_currency": child.get("account_currency")
|
||||||
or frappe.get_cached_value("Company", company, "default_currency"),
|
if custom_chart
|
||||||
|
else frappe.get_cached_value("Company", company, "default_currency"),
|
||||||
"tax_rate": child.get("tax_rate"),
|
"tax_rate": child.get("tax_rate"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -111,17 +111,15 @@ class AccountingDimension(Document):
|
|||||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||||
if not doclist:
|
if not doclist:
|
||||||
doclist = get_doctypes_with_dimensions()
|
doclist = get_doctypes_with_dimensions()
|
||||||
|
|
||||||
doc_count = len(get_accounting_dimensions())
|
doc_count = len(get_accounting_dimensions())
|
||||||
count = 0
|
count = 0
|
||||||
repostable_doctypes = get_allowed_types_from_settings()
|
repostable_doctypes = get_allowed_types_from_settings(child_doc=True)
|
||||||
|
|
||||||
for doctype in doclist:
|
for doctype in doclist:
|
||||||
if (doc_count + 1) % 2 == 0:
|
if (doc_count + 1) % 2 == 0:
|
||||||
insert_after_field = "dimension_col_break"
|
insert_after_field = "dimension_col_break"
|
||||||
else:
|
else:
|
||||||
insert_after_field = "accounting_dimensions_section"
|
insert_after_field = "accounting_dimensions_section"
|
||||||
|
|
||||||
df = {
|
df = {
|
||||||
"fieldname": doc.fieldname,
|
"fieldname": doc.fieldname,
|
||||||
"label": doc.label,
|
"label": doc.label,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"show_payment_schedule_in_print",
|
"show_payment_schedule_in_print",
|
||||||
"item_price_settings_section",
|
"item_price_settings_section",
|
||||||
"maintain_same_internal_transaction_rate",
|
"maintain_same_internal_transaction_rate",
|
||||||
|
"fetch_valuation_rate_for_internal_transaction",
|
||||||
"column_break_feyo",
|
"column_break_feyo",
|
||||||
"maintain_same_rate_action",
|
"maintain_same_rate_action",
|
||||||
"role_to_override_stop_action",
|
"role_to_override_stop_action",
|
||||||
@@ -644,6 +645,12 @@
|
|||||||
"fieldname": "drop_ar_procedures",
|
"fieldname": "drop_ar_procedures",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Drop Procedures"
|
"label": "Drop Procedures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "fetch_valuation_rate_for_internal_transaction",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Fetch Valuation Rate for Internal Transaction"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -652,7 +659,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-23 15:55:33.346398",
|
"modified": "2025-07-18 13:56:47.192437",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class AccountsSettings(Document):
|
|||||||
enable_immutable_ledger: DF.Check
|
enable_immutable_ledger: DF.Check
|
||||||
enable_party_matching: DF.Check
|
enable_party_matching: DF.Check
|
||||||
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
||||||
|
fetch_valuation_rate_for_internal_transaction: DF.Check
|
||||||
frozen_accounts_modifier: DF.Link | None
|
frozen_accounts_modifier: DF.Link | None
|
||||||
general_ledger_remarks_length: DF.Int
|
general_ledger_remarks_length: DF.Int
|
||||||
ignore_account_closing_balance: DF.Check
|
ignore_account_closing_balance: DF.Check
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"against_voucher_no",
|
"against_voucher_no",
|
||||||
"amount",
|
"amount",
|
||||||
"currency",
|
"currency",
|
||||||
"event"
|
"event",
|
||||||
|
"delinked"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -68,12 +69,20 @@
|
|||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "delinked",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "DeLinked",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"grid_page_length": 50,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-11-05 10:31:28.736671",
|
"modified": "2025-07-29 11:37:42.678556",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Advance Payment Ledger Entry",
|
"name": "Advance Payment Ledger Entry",
|
||||||
@@ -107,6 +116,7 @@
|
|||||||
"share": 1
|
"share": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
from erpnext.accounts.utils import update_voucher_outstanding
|
||||||
|
|
||||||
|
|
||||||
class AdvancePaymentLedgerEntry(Document):
|
class AdvancePaymentLedgerEntry(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
@@ -19,9 +21,16 @@ class AdvancePaymentLedgerEntry(Document):
|
|||||||
amount: DF.Currency
|
amount: DF.Currency
|
||||||
company: DF.Link | None
|
company: DF.Link | None
|
||||||
currency: DF.Link | None
|
currency: DF.Link | None
|
||||||
|
delinked: DF.Check
|
||||||
event: DF.Data | None
|
event: DF.Data | None
|
||||||
voucher_no: DF.DynamicLink | None
|
voucher_no: DF.DynamicLink | None
|
||||||
voucher_type: DF.Link | None
|
voucher_type: DF.Link | None
|
||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
pass
|
def on_update(self):
|
||||||
|
if (
|
||||||
|
self.against_voucher_type in ["Purchase Order", "Sales Order"]
|
||||||
|
and self.flags.update_outstanding == "Yes"
|
||||||
|
and not frappe.flags.is_reverse_depr_entry
|
||||||
|
):
|
||||||
|
update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None)
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ class BankAccount(Document):
|
|||||||
"party_type": self.party_type,
|
"party_type": self.party_type,
|
||||||
"party": self.party,
|
"party": self.party,
|
||||||
"is_company_account": self.is_company_account,
|
"is_company_account": self.is_company_account,
|
||||||
|
"company": self.company,
|
||||||
"is_default": 1,
|
"is_default": 1,
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
|||||||
|
|
||||||
open_url_post(method, {
|
open_url_post(method, {
|
||||||
doctype: "Bank Transaction",
|
doctype: "Bank Transaction",
|
||||||
export_records: "5_records",
|
export_records: "blank_template",
|
||||||
export_fields: {
|
export_fields: {
|
||||||
"Bank Transaction": [
|
"Bank Transaction": [
|
||||||
"date",
|
"date",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from erpnext.accounts.party import (
|
|||||||
validate_party_frozen_disabled,
|
validate_party_frozen_disabled,
|
||||||
validate_party_gle_currency,
|
validate_party_gle_currency,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
from erpnext.accounts.utils import OUTSTANDING_DOCTYPES, get_account_currency, get_fiscal_year
|
||||||
from erpnext.exceptions import InvalidAccountCurrency
|
from erpnext.exceptions import InvalidAccountCurrency
|
||||||
|
|
||||||
exclude_from_linked_with = True
|
exclude_from_linked_with = True
|
||||||
@@ -224,26 +224,23 @@ class GLEntry(Document):
|
|||||||
def validate_account_details(self, adv_adj):
|
def validate_account_details(self, adv_adj):
|
||||||
"""Account must be ledger, active and not freezed"""
|
"""Account must be ledger, active and not freezed"""
|
||||||
|
|
||||||
ret = frappe.db.sql(
|
account = frappe.get_cached_value(
|
||||||
"""select is_group, docstatus, company
|
"Account", self.account, fieldname=["is_group", "docstatus", "company"], as_dict=True
|
||||||
from tabAccount where name=%s""",
|
)
|
||||||
self.account,
|
|
||||||
as_dict=1,
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
if ret.is_group == 1:
|
if account.is_group == 1:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
|
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
|
||||||
).format(self.voucher_type, self.voucher_no, self.account)
|
).format(self.voucher_type, self.voucher_no, self.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
if ret.docstatus == 2:
|
if account.docstatus == 2:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
|
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
if ret.company != self.company:
|
if account.company != self.company:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1}: Account {2} does not belong to Company {3}").format(
|
_("{0} {1}: Account {2} does not belong to Company {3}").format(
|
||||||
self.voucher_type, self.voucher_no, self.account, self.company
|
self.voucher_type, self.voucher_no, self.account, self.company
|
||||||
@@ -385,7 +382,7 @@ def update_outstanding_amt(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
|
if against_voucher_type in OUTSTANDING_DOCTYPES:
|
||||||
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
||||||
|
|
||||||
# Didn't use db_set for optimization purpose
|
# Didn't use db_set for optimization purpose
|
||||||
@@ -462,4 +459,9 @@ def rename_temporarily_named_docs(doctype):
|
|||||||
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
||||||
(newname, now(), oldname),
|
(newname, now(), oldname),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for hook_type in ("on_gle_rename", "on_sle_rename"):
|
||||||
|
for hook in frappe.get_hooks(hook_type):
|
||||||
|
frappe.call(hook, newname=newname, oldname=oldname)
|
||||||
|
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|||||||
@@ -196,6 +196,7 @@ frappe.ui.form.on("Journal Entry", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
erpnext.utils.set_letter_head(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
voucher_type: function (frm) {
|
voucher_type: function (frm) {
|
||||||
|
|||||||
@@ -195,8 +195,6 @@ class JournalEntry(AccountsController):
|
|||||||
self.validate_cheque_info()
|
self.validate_cheque_info()
|
||||||
self.check_credit_limit()
|
self.check_credit_limit()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.make_advance_payment_ledger_entries()
|
|
||||||
self.update_advance_paid()
|
|
||||||
self.update_asset_value()
|
self.update_asset_value()
|
||||||
self.update_inter_company_jv()
|
self.update_inter_company_jv()
|
||||||
self.update_invoice_discounting()
|
self.update_invoice_discounting()
|
||||||
@@ -298,8 +296,6 @@ class JournalEntry(AccountsController):
|
|||||||
"Advance Payment Ledger Entry",
|
"Advance Payment Ledger Entry",
|
||||||
)
|
)
|
||||||
self.make_gl_entries(1)
|
self.make_gl_entries(1)
|
||||||
self.make_advance_payment_ledger_entries()
|
|
||||||
self.update_advance_paid()
|
|
||||||
self.unlink_advance_entry_reference()
|
self.unlink_advance_entry_reference()
|
||||||
self.unlink_asset_reference()
|
self.unlink_asset_reference()
|
||||||
self.unlink_inter_company_jv()
|
self.unlink_inter_company_jv()
|
||||||
@@ -309,18 +305,6 @@ class JournalEntry(AccountsController):
|
|||||||
def get_title(self):
|
def get_title(self):
|
||||||
return self.pay_to_recd_from or self.accounts[0].account
|
return self.pay_to_recd_from or self.accounts[0].account
|
||||||
|
|
||||||
def update_advance_paid(self):
|
|
||||||
advance_paid = frappe._dict()
|
|
||||||
advance_payment_doctypes = get_advance_payment_doctypes()
|
|
||||||
for d in self.get("accounts"):
|
|
||||||
if d.is_advance:
|
|
||||||
if d.reference_type in advance_payment_doctypes:
|
|
||||||
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
|
|
||||||
|
|
||||||
for voucher_type, order_list in advance_paid.items():
|
|
||||||
for voucher_no in list(set(order_list)):
|
|
||||||
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
|
|
||||||
|
|
||||||
def validate_inter_company_accounts(self):
|
def validate_inter_company_accounts(self):
|
||||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||||
doc = frappe.db.get_value(
|
doc = frappe.db.get_value(
|
||||||
@@ -1145,9 +1129,7 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
def set_print_format_fields(self):
|
def set_print_format_fields(self):
|
||||||
bank_amount = party_amount = total_amount = 0.0
|
bank_amount = party_amount = total_amount = 0.0
|
||||||
currency = (
|
currency = bank_account_currency = party_account_currency = pay_to_recd_from = None
|
||||||
bank_account_currency
|
|
||||||
) = party_account_currency = pay_to_recd_from = self.pay_to_recd_from = None
|
|
||||||
party_type = None
|
party_type = None
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if d.party_type in ["Customer", "Supplier"] and d.party:
|
if d.party_type in ["Customer", "Supplier"] and d.party:
|
||||||
@@ -1197,49 +1179,65 @@ class JournalEntry(AccountsController):
|
|||||||
self.transaction_exchange_rate = row.exchange_rate
|
self.transaction_exchange_rate = row.exchange_rate
|
||||||
break
|
break
|
||||||
|
|
||||||
|
advance_doctypes = get_advance_payment_doctypes()
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
||||||
r = [d.user_remark, self.remark]
|
r = [d.user_remark, self.remark]
|
||||||
r = [x for x in r if x]
|
r = [x for x in r if x]
|
||||||
remarks = "\n".join(r)
|
remarks = "\n".join(r)
|
||||||
|
|
||||||
|
row = {
|
||||||
|
"account": d.account,
|
||||||
|
"party_type": d.party_type,
|
||||||
|
"due_date": self.due_date,
|
||||||
|
"party": d.party,
|
||||||
|
"against": d.against_account,
|
||||||
|
"debit": flt(d.debit, d.precision("debit")),
|
||||||
|
"credit": flt(d.credit, d.precision("credit")),
|
||||||
|
"account_currency": d.account_currency,
|
||||||
|
"debit_in_account_currency": flt(
|
||||||
|
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||||
|
),
|
||||||
|
"credit_in_account_currency": flt(
|
||||||
|
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||||
|
),
|
||||||
|
"transaction_currency": self.transaction_currency,
|
||||||
|
"transaction_exchange_rate": self.transaction_exchange_rate,
|
||||||
|
"debit_in_transaction_currency": flt(
|
||||||
|
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||||
|
)
|
||||||
|
if self.transaction_currency == d.account_currency
|
||||||
|
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
|
||||||
|
"credit_in_transaction_currency": flt(
|
||||||
|
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||||
|
)
|
||||||
|
if self.transaction_currency == d.account_currency
|
||||||
|
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
|
||||||
|
"against_voucher_type": d.reference_type,
|
||||||
|
"against_voucher": d.reference_name,
|
||||||
|
"remarks": remarks,
|
||||||
|
"voucher_detail_no": d.reference_detail_no,
|
||||||
|
"cost_center": d.cost_center,
|
||||||
|
"project": d.project,
|
||||||
|
"finance_book": self.finance_book,
|
||||||
|
"advance_voucher_type": d.advance_voucher_type,
|
||||||
|
"advance_voucher_no": d.advance_voucher_no,
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.reference_type in advance_doctypes:
|
||||||
|
row.update(
|
||||||
|
{
|
||||||
|
"against_voucher_type": self.doctype,
|
||||||
|
"against_voucher": self.name,
|
||||||
|
"advance_voucher_type": d.reference_type,
|
||||||
|
"advance_voucher_no": d.reference_name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
gl_map.append(
|
gl_map.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
{
|
row,
|
||||||
"account": d.account,
|
|
||||||
"party_type": d.party_type,
|
|
||||||
"due_date": self.due_date,
|
|
||||||
"party": d.party,
|
|
||||||
"against": d.against_account,
|
|
||||||
"debit": flt(d.debit, d.precision("debit")),
|
|
||||||
"credit": flt(d.credit, d.precision("credit")),
|
|
||||||
"account_currency": d.account_currency,
|
|
||||||
"debit_in_account_currency": flt(
|
|
||||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
|
||||||
),
|
|
||||||
"credit_in_account_currency": flt(
|
|
||||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
|
||||||
),
|
|
||||||
"transaction_currency": self.transaction_currency,
|
|
||||||
"transaction_exchange_rate": self.transaction_exchange_rate,
|
|
||||||
"debit_in_transaction_currency": flt(
|
|
||||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
|
||||||
)
|
|
||||||
if self.transaction_currency == d.account_currency
|
|
||||||
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
|
|
||||||
"credit_in_transaction_currency": flt(
|
|
||||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
|
||||||
)
|
|
||||||
if self.transaction_currency == d.account_currency
|
|
||||||
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
|
|
||||||
"against_voucher_type": d.reference_type,
|
|
||||||
"against_voucher": d.reference_name,
|
|
||||||
"remarks": remarks,
|
|
||||||
"voucher_detail_no": d.reference_detail_no,
|
|
||||||
"cost_center": d.cost_center,
|
|
||||||
"project": d.project,
|
|
||||||
"finance_book": self.finance_book,
|
|
||||||
},
|
|
||||||
item=d,
|
item=d,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -579,6 +579,18 @@ class TestJournalEntry(IntegrationTestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_pay_to_recd_from(self):
|
||||||
|
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||||
|
jv.pay_to_recd_from = "_Test Receiver"
|
||||||
|
jv.save()
|
||||||
|
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver")
|
||||||
|
|
||||||
|
jv.pay_to_recd_from = "_Test Receiver 2"
|
||||||
|
jv.save()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
|
||||||
|
|
||||||
|
|
||||||
def make_journal_entry(
|
def make_journal_entry(
|
||||||
account1,
|
account1,
|
||||||
|
|||||||
@@ -32,6 +32,8 @@
|
|||||||
"reference_name",
|
"reference_name",
|
||||||
"reference_due_date",
|
"reference_due_date",
|
||||||
"reference_detail_no",
|
"reference_detail_no",
|
||||||
|
"advance_voucher_type",
|
||||||
|
"advance_voucher_no",
|
||||||
"col_break3",
|
"col_break3",
|
||||||
"is_advance",
|
"is_advance",
|
||||||
"user_remark",
|
"user_remark",
|
||||||
@@ -262,18 +264,35 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Reference Detail No",
|
"label": "Reference Detail No",
|
||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "advance_voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Advance Voucher Type",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "advance_voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Advance Voucher No",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "advance_voucher_type",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:09:58.647732",
|
"modified": "2025-07-25 04:45:28.117715",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Journal Entry Account",
|
"name": "Journal Entry Account",
|
||||||
"naming_rule": "Random",
|
"naming_rule": "Random",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ class JournalEntryAccount(Document):
|
|||||||
account: DF.Link
|
account: DF.Link
|
||||||
account_currency: DF.Link | None
|
account_currency: DF.Link | None
|
||||||
account_type: DF.Data | None
|
account_type: DF.Data | None
|
||||||
|
advance_voucher_no: DF.DynamicLink | None
|
||||||
|
advance_voucher_type: DF.Link | None
|
||||||
against_account: DF.Text | None
|
against_account: DF.Text | None
|
||||||
balance: DF.Currency
|
|
||||||
bank_account: DF.Link | None
|
bank_account: DF.Link | None
|
||||||
cost_center: DF.Link | None
|
cost_center: DF.Link | None
|
||||||
credit: DF.Currency
|
credit: DF.Currency
|
||||||
@@ -31,7 +32,6 @@ class JournalEntryAccount(Document):
|
|||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
party: DF.DynamicLink | None
|
party: DF.DynamicLink | None
|
||||||
party_balance: DF.Currency
|
|
||||||
party_type: DF.Link | None
|
party_type: DF.Link | None
|
||||||
project: DF.Link | None
|
project: DF.Link | None
|
||||||
reference_detail_no: DF.Data | None
|
reference_detail_no: DF.Data | None
|
||||||
|
|||||||
@@ -273,6 +273,7 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
frm.events.set_dynamic_labels(frm);
|
frm.events.set_dynamic_labels(frm);
|
||||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
erpnext.utils.set_letter_head(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
contact_person: function (frm) {
|
contact_person: function (frm) {
|
||||||
|
|||||||
@@ -199,12 +199,10 @@ class PaymentEntry(AccountsController):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
if self.difference_amount:
|
if self.difference_amount:
|
||||||
frappe.throw(_("Difference Amount must be zero"))
|
frappe.throw(_("Difference Amount must be zero"))
|
||||||
|
self.update_payment_requests()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.update_payment_schedule()
|
self.update_payment_schedule()
|
||||||
self.update_payment_requests()
|
|
||||||
self.make_advance_payment_ledger_entries()
|
|
||||||
self.update_advance_paid() # advance_paid_status depends on the payment request amount
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def validate_for_repost(self):
|
def validate_for_repost(self):
|
||||||
@@ -304,13 +302,11 @@ class PaymentEntry(AccountsController):
|
|||||||
"Advance Payment Ledger Entry",
|
"Advance Payment Ledger Entry",
|
||||||
)
|
)
|
||||||
super().on_cancel()
|
super().on_cancel()
|
||||||
|
self.update_payment_requests(cancel=True)
|
||||||
self.make_gl_entries(cancel=1)
|
self.make_gl_entries(cancel=1)
|
||||||
self.update_outstanding_amounts()
|
self.update_outstanding_amounts()
|
||||||
self.delink_advance_entry_references()
|
self.delink_advance_entry_references()
|
||||||
self.update_payment_schedule(cancel=1)
|
self.update_payment_schedule(cancel=1)
|
||||||
self.update_payment_requests(cancel=True)
|
|
||||||
self.make_advance_payment_ledger_entries()
|
|
||||||
self.update_advance_paid() # advance_paid_status depends on the payment request amount
|
|
||||||
self.set_status()
|
self.set_status()
|
||||||
|
|
||||||
def update_payment_requests(self, cancel=False):
|
def update_payment_requests(self, cancel=False):
|
||||||
@@ -1439,23 +1435,27 @@ class PaymentEntry(AccountsController):
|
|||||||
dr_or_cr + "_in_transaction_currency": d.allocated_amount
|
dr_or_cr + "_in_transaction_currency": d.allocated_amount
|
||||||
if self.transaction_currency == self.party_account_currency
|
if self.transaction_currency == self.party_account_currency
|
||||||
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
|
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
|
||||||
|
"advance_voucher_type": d.advance_voucher_type,
|
||||||
|
"advance_voucher_no": d.advance_voucher_no,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.book_advance_payments_in_separate_party_account:
|
if d.reference_doctype in advance_payment_doctypes:
|
||||||
if d.reference_doctype in advance_payment_doctypes:
|
# advance reference
|
||||||
# Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine
|
gle.update(
|
||||||
gle.update(
|
{
|
||||||
{
|
"against_voucher_type": self.doctype,
|
||||||
"against_voucher_type": d.reference_doctype,
|
"against_voucher": self.name,
|
||||||
"against_voucher": d.reference_name,
|
"advance_voucher_type": d.reference_doctype,
|
||||||
}
|
"advance_voucher_no": d.reference_name,
|
||||||
)
|
}
|
||||||
else:
|
)
|
||||||
# Do not reference Invoices while Advance is in separate party account
|
|
||||||
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
|
elif self.book_advance_payments_in_separate_party_account:
|
||||||
|
# Do not reference Invoices while Advance is in separate party account
|
||||||
|
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
|
||||||
else:
|
else:
|
||||||
gle.update(
|
gle.update(
|
||||||
{
|
{
|
||||||
@@ -1560,13 +1560,14 @@ class PaymentEntry(AccountsController):
|
|||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": invoice.name,
|
"voucher_detail_no": invoice.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if invoice.reconcile_effect_on:
|
if invoice.reconcile_effect_on:
|
||||||
posting_date = invoice.reconcile_effect_on
|
posting_date = invoice.reconcile_effect_on
|
||||||
else:
|
else:
|
||||||
# For backwards compatibility
|
# For backwards compatibility
|
||||||
# Supporting reposting on payment entries reconciled before select field introduction
|
# Supporting reposting on payment entries reconciled before select field introduction
|
||||||
posting_date = get_reconciliation_effect_date(invoice, self.company, self.posting_date)
|
posting_date = get_reconciliation_effect_date(
|
||||||
|
invoice.reference_doctype, invoice.reference_name, self.company, self.posting_date
|
||||||
|
)
|
||||||
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
|
frappe.db.set_value("Payment Entry Reference", invoice.name, "reconcile_effect_on", posting_date)
|
||||||
|
|
||||||
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
|
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
|
||||||
@@ -1584,6 +1585,8 @@ class PaymentEntry(AccountsController):
|
|||||||
{
|
{
|
||||||
"against_voucher_type": invoice.reference_doctype,
|
"against_voucher_type": invoice.reference_doctype,
|
||||||
"against_voucher": invoice.reference_name,
|
"against_voucher": invoice.reference_name,
|
||||||
|
"advance_voucher_type": invoice.advance_voucher_type,
|
||||||
|
"advance_voucher_no": invoice.advance_voucher_no,
|
||||||
"posting_date": posting_date,
|
"posting_date": posting_date,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1608,6 +1611,8 @@ class PaymentEntry(AccountsController):
|
|||||||
{
|
{
|
||||||
"against_voucher_type": "Payment Entry",
|
"against_voucher_type": "Payment Entry",
|
||||||
"against_voucher": self.name,
|
"against_voucher": self.name,
|
||||||
|
"advance_voucher_type": invoice.advance_voucher_type,
|
||||||
|
"advance_voucher_no": invoice.advance_voucher_no,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
gle = self.get_gl_dict(
|
gle = self.get_gl_dict(
|
||||||
@@ -1756,17 +1761,6 @@ class PaymentEntry(AccountsController):
|
|||||||
|
|
||||||
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
|
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
|
||||||
|
|
||||||
def update_advance_paid(self):
|
|
||||||
if self.payment_type not in ("Receive", "Pay") or not self.party:
|
|
||||||
return
|
|
||||||
|
|
||||||
advance_payment_doctypes = get_advance_payment_doctypes()
|
|
||||||
for d in self.get("references"):
|
|
||||||
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
|
|
||||||
frappe.get_lazy_doc(
|
|
||||||
d.reference_doctype, d.reference_name, for_update=True
|
|
||||||
).set_total_advance_paid()
|
|
||||||
|
|
||||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||||
self.reference_no = reference_doc.name
|
self.reference_no = reference_doc.name
|
||||||
self.reference_date = nowdate()
|
self.reference_date = nowdate()
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class TestPaymentEntry(IntegrationTestCase):
|
|||||||
self.assertEqual(pe.paid_to_account_type, "Cash")
|
self.assertEqual(pe.paid_to_account_type, "Cash")
|
||||||
|
|
||||||
expected_gle = dict(
|
expected_gle = dict(
|
||||||
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
|
(d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.validate_gl_entries(pe.name, expected_gle)
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
@@ -84,7 +84,7 @@ class TestPaymentEntry(IntegrationTestCase):
|
|||||||
|
|
||||||
expected_gle = dict(
|
expected_gle = dict(
|
||||||
(d[0], d)
|
(d[0], d)
|
||||||
for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]]
|
for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.validate_gl_entries(pe.name, expected_gle)
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
|
|||||||
@@ -22,7 +22,9 @@
|
|||||||
"exchange_gain_loss",
|
"exchange_gain_loss",
|
||||||
"account",
|
"account",
|
||||||
"payment_request",
|
"payment_request",
|
||||||
"payment_request_outstanding"
|
"payment_request_outstanding",
|
||||||
|
"advance_voucher_type",
|
||||||
|
"advance_voucher_no"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -151,18 +153,35 @@
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Reconcile Effect On",
|
"label": "Reconcile Effect On",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "advance_voucher_type",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Advance Voucher Type",
|
||||||
|
"options": "DocType",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 2,
|
||||||
|
"fieldname": "advance_voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Advance Voucher No",
|
||||||
|
"options": "advance_voucher_type",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-13 15:56:18.895082",
|
"modified": "2025-07-25 04:32:11.040025",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry Reference",
|
"name": "Payment Entry Reference",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class PaymentEntryReference(Document):
|
|||||||
|
|
||||||
account: DF.Link | None
|
account: DF.Link | None
|
||||||
account_type: DF.Data | None
|
account_type: DF.Data | None
|
||||||
|
advance_voucher_no: DF.DynamicLink | None
|
||||||
|
advance_voucher_type: DF.Link | None
|
||||||
allocated_amount: DF.Float
|
allocated_amount: DF.Float
|
||||||
bill_no: DF.Data | None
|
bill_no: DF.Data | None
|
||||||
due_date: DF.Date | None
|
due_date: DF.Date | None
|
||||||
@@ -26,7 +28,6 @@ class PaymentEntryReference(Document):
|
|||||||
parentfield: DF.Data
|
parentfield: DF.Data
|
||||||
parenttype: DF.Data
|
parenttype: DF.Data
|
||||||
payment_request: DF.Link | None
|
payment_request: DF.Link | None
|
||||||
payment_request_outstanding: DF.Float
|
|
||||||
payment_term: DF.Link | None
|
payment_term: DF.Link | None
|
||||||
payment_term_outstanding: DF.Float
|
payment_term_outstanding: DF.Float
|
||||||
payment_type: DF.Data | None
|
payment_type: DF.Data | None
|
||||||
|
|||||||
@@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", {
|
|||||||
frm.set_df_property("payment_gateway", "read_only", 1);
|
frm.set_df_property("payment_gateway", "read_only", 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setup(frm) {
|
||||||
|
frm.set_query("payment_account", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"field_order": [
|
"field_order": [
|
||||||
"payment_gateway",
|
"payment_gateway",
|
||||||
"payment_channel",
|
"payment_channel",
|
||||||
|
"company",
|
||||||
"is_default",
|
"is_default",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"payment_account",
|
"payment_account",
|
||||||
@@ -71,11 +72,21 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Payment Channel",
|
"label": "Payment Channel",
|
||||||
"options": "\nEmail\nPhone\nOther"
|
"options": "\nEmail\nPhone\nOther"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"print_hide": 1,
|
||||||
|
"remember_last_selected_value": 1,
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-29 18:53:09.836254",
|
"modified": "2025-07-14 16:49:55.210352",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Gateway Account",
|
"name": "Payment Gateway Account",
|
||||||
@@ -94,6 +105,7 @@
|
|||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document):
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from frappe.types import DF
|
from frappe.types import DF
|
||||||
|
|
||||||
|
company: DF.Link
|
||||||
currency: DF.ReadOnly | None
|
currency: DF.ReadOnly | None
|
||||||
is_default: DF.Check
|
is_default: DF.Check
|
||||||
message: DF.SmallText | None
|
message: DF.SmallText | None
|
||||||
@@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document):
|
|||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
self.name = self.payment_gateway + " - " + self.currency
|
abbr = frappe.db.get_value("Company", self.company, "abbr")
|
||||||
|
self.name = self.payment_gateway + " - " + self.currency + " - " + abbr
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency")
|
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency")
|
||||||
@@ -34,13 +36,15 @@ class PaymentGatewayAccount(Document):
|
|||||||
|
|
||||||
def update_default_payment_gateway(self):
|
def update_default_payment_gateway(self):
|
||||||
if self.is_default:
|
if self.is_default:
|
||||||
frappe.db.sql(
|
frappe.db.set_value(
|
||||||
"""update `tabPayment Gateway Account` set is_default = 0
|
"Payment Gateway Account",
|
||||||
where is_default = 1 """
|
{"is_default": 1, "name": ["!=", self.name], "company": self.company},
|
||||||
|
"is_default",
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_as_default_if_not_set(self):
|
def set_as_default_if_not_set(self):
|
||||||
if not frappe.db.get_value(
|
if not frappe.db.exists(
|
||||||
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
|
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company}
|
||||||
):
|
):
|
||||||
self.is_default = 1
|
self.is_default = 1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from erpnext.accounts.doctype.gl_entry.gl_entry import (
|
|||||||
validate_balance_type,
|
validate_balance_type,
|
||||||
validate_frozen_account,
|
validate_frozen_account,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.utils import update_voucher_outstanding
|
from erpnext.accounts.utils import OUTSTANDING_DOCTYPES, update_voucher_outstanding
|
||||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
|
|
||||||
|
|
||||||
@@ -51,38 +51,36 @@ class PaymentLedgerEntry(Document):
|
|||||||
# end: auto-generated types
|
# end: auto-generated types
|
||||||
|
|
||||||
def validate_account(self):
|
def validate_account(self):
|
||||||
valid_account = frappe.db.get_list(
|
account = frappe.get_cached_value(
|
||||||
"Account",
|
"Account", self.account, fieldname=["account_type", "company"], as_dict=True
|
||||||
"name",
|
|
||||||
filters={"name": self.account, "account_type": self.account_type, "company": self.company},
|
|
||||||
ignore_permissions=True,
|
|
||||||
)
|
)
|
||||||
if not valid_account:
|
|
||||||
|
if account.company != self.company:
|
||||||
|
frappe.throw(_("{0} account is not of company {1}").format(self.account, self.company))
|
||||||
|
|
||||||
|
if account.account_type != self.account_type:
|
||||||
frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
|
frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
|
||||||
|
|
||||||
def validate_account_details(self):
|
def validate_account_details(self):
|
||||||
"""Account must be ledger, active and not freezed"""
|
"""Account must be ledger, active and not freezed"""
|
||||||
|
|
||||||
ret = frappe.db.sql(
|
account = frappe.get_cached_value(
|
||||||
"""select is_group, docstatus, company
|
"Account", self.account, fieldname=["is_group", "docstatus", "company"], as_dict=True
|
||||||
from tabAccount where name=%s""",
|
)
|
||||||
self.account,
|
|
||||||
as_dict=1,
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
if ret.is_group == 1:
|
if account.is_group == 1:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
|
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
|
||||||
).format(self.voucher_type, self.voucher_no, self.account)
|
).format(self.voucher_type, self.voucher_no, self.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
if ret.docstatus == 2:
|
if account.docstatus == 2:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
|
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
if ret.company != self.company:
|
if account.company != self.company:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} {1}: Account {2} does not belong to Company {3}").format(
|
_("{0} {1}: Account {2} does not belong to Company {3}").format(
|
||||||
self.voucher_type, self.voucher_no, self.account, self.company
|
self.voucher_type, self.voucher_no, self.account, self.company
|
||||||
@@ -170,7 +168,7 @@ class PaymentLedgerEntry(Document):
|
|||||||
|
|
||||||
# update outstanding amount
|
# update outstanding amount
|
||||||
if (
|
if (
|
||||||
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
|
self.against_voucher_type in OUTSTANDING_DOCTYPES
|
||||||
and self.flags.update_outstanding == "Yes"
|
and self.flags.update_outstanding == "Yes"
|
||||||
and not frappe.flags.is_reverse_depr_entry
|
and not frappe.flags.is_reverse_depr_entry
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1714,6 +1714,67 @@ class TestPaymentReconciliation(IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(len(pl_entries), 3)
|
self.assertEqual(len(pl_entries), 3)
|
||||||
|
|
||||||
|
def test_advance_payment_reconciliation_date_for_older_date(self):
|
||||||
|
old_settings = frappe.db.get_value(
|
||||||
|
"Company",
|
||||||
|
self.company,
|
||||||
|
[
|
||||||
|
"reconciliation_takes_effect_on",
|
||||||
|
"default_advance_paid_account",
|
||||||
|
"book_advance_payments_in_separate_party_account",
|
||||||
|
],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Company",
|
||||||
|
self.company,
|
||||||
|
{
|
||||||
|
"book_advance_payments_in_separate_party_account": 1,
|
||||||
|
"default_advance_paid_account": self.advance_payable_account,
|
||||||
|
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.supplier = "_Test Supplier"
|
||||||
|
|
||||||
|
pi1 = self.create_purchase_invoice(qty=10, rate=100)
|
||||||
|
po = self.create_purchase_order(qty=10, rate=100)
|
||||||
|
|
||||||
|
pay = get_payment_entry(po.doctype, po.name)
|
||||||
|
pay.paid_amount = 1000
|
||||||
|
pay.save().submit()
|
||||||
|
|
||||||
|
pr = frappe.new_doc("Payment Reconciliation")
|
||||||
|
pr.company = self.company
|
||||||
|
pr.party_type = "Supplier"
|
||||||
|
pr.party = self.supplier
|
||||||
|
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
|
||||||
|
pr.default_advance_account = self.advance_payable_account
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices]
|
||||||
|
payments = [x.as_dict() for x in pr.payments]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.allocation[0].allocated_amount = 100
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
pay.reload()
|
||||||
|
self.assertEqual(getdate(pay.references[0].reconcile_effect_on), getdate(pi1.posting_date))
|
||||||
|
|
||||||
|
# test setting of date if not available
|
||||||
|
frappe.db.set_value("Payment Entry Reference", pay.references[1].name, "reconcile_effect_on", None)
|
||||||
|
pay.reload()
|
||||||
|
pay.cancel()
|
||||||
|
|
||||||
|
pay.reload()
|
||||||
|
pi1.reload()
|
||||||
|
po.reload()
|
||||||
|
|
||||||
|
self.assertEqual(getdate(pay.references[0].reconcile_effect_on), getdate(pi1.posting_date))
|
||||||
|
pi1.cancel()
|
||||||
|
po.cancel()
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", self.company, old_settings)
|
||||||
|
|
||||||
def test_advance_payment_reconciliation_against_journal_for_customer(self):
|
def test_advance_payment_reconciliation_against_journal_for_customer(self):
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
"Company",
|
"Company",
|
||||||
@@ -2147,6 +2208,138 @@ class TestPaymentReconciliation(IntegrationTestCase):
|
|||||||
self.assertEqual(len(pr.get("payments")), 0)
|
self.assertEqual(len(pr.get("payments")), 0)
|
||||||
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
|
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
|
||||||
|
|
||||||
|
def test_partial_advance_payment_with_closed_fiscal_year(self):
|
||||||
|
"""
|
||||||
|
Test Advance Payment partial reconciliation before period closing and partial after period closing
|
||||||
|
"""
|
||||||
|
default_settings = frappe.db.get_value(
|
||||||
|
"Company",
|
||||||
|
self.company,
|
||||||
|
[
|
||||||
|
"book_advance_payments_in_separate_party_account",
|
||||||
|
"default_advance_paid_account",
|
||||||
|
"reconciliation_takes_effect_on",
|
||||||
|
],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
first_fy_start_date = frappe.db.get_value(
|
||||||
|
"Fiscal Year", {"disabled": 0}, [{"MIN": "year_start_date"}]
|
||||||
|
)
|
||||||
|
prev_fy_start_date = add_years(first_fy_start_date, -1)
|
||||||
|
prev_fy_end_date = add_days(first_fy_start_date, -1)
|
||||||
|
|
||||||
|
create_fiscal_year(
|
||||||
|
company=self.company, year_start_date=prev_fy_start_date, year_end_date=prev_fy_end_date
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Company",
|
||||||
|
self.company,
|
||||||
|
{
|
||||||
|
"book_advance_payments_in_separate_party_account": 1,
|
||||||
|
"default_advance_paid_account": self.advance_payable_account,
|
||||||
|
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.supplier = "_Test Supplier"
|
||||||
|
|
||||||
|
# Create advance payment of 1000 (previous FY)
|
||||||
|
pe = self.create_payment_entry(amount=1000, posting_date=prev_fy_start_date)
|
||||||
|
pe.party_type = "Supplier"
|
||||||
|
pe.party = self.supplier
|
||||||
|
pe.payment_type = "Pay"
|
||||||
|
pe.paid_from = self.cash
|
||||||
|
pe.paid_to = self.advance_payable_account
|
||||||
|
pe.save().submit()
|
||||||
|
|
||||||
|
# Create purchase invoice of 600 (previous FY)
|
||||||
|
pi1 = self.create_purchase_invoice(qty=1, rate=600, do_not_submit=True)
|
||||||
|
pi1.posting_date = prev_fy_start_date
|
||||||
|
pi1.set_posting_time = 1
|
||||||
|
pi1.supplier = self.supplier
|
||||||
|
pi1.credit_to = self.creditors
|
||||||
|
pi1.save().submit()
|
||||||
|
|
||||||
|
# Reconcile advance payment
|
||||||
|
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||||
|
pr.party = self.supplier
|
||||||
|
pr.receivable_payable_account = self.creditors
|
||||||
|
pr.default_advance_account = self.advance_payable_account
|
||||||
|
pr.from_invoice_date = pr.to_invoice_date = pi1.posting_date
|
||||||
|
pr.from_payment_date = pr.to_payment_date = pe.posting_date
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi1.name]
|
||||||
|
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
# Verify partial reconciliation
|
||||||
|
pe.reload()
|
||||||
|
pi1.reload()
|
||||||
|
|
||||||
|
self.assertEqual(len(pe.references), 1)
|
||||||
|
self.assertEqual(pe.references[0].allocated_amount, 600)
|
||||||
|
self.assertEqual(flt(pe.unallocated_amount), 400)
|
||||||
|
|
||||||
|
self.assertEqual(pi1.outstanding_amount, 0)
|
||||||
|
self.assertEqual(pi1.status, "Paid")
|
||||||
|
|
||||||
|
# Close accounting period for March (previous FY)
|
||||||
|
pcv = make_period_closing_voucher(
|
||||||
|
company=self.company, cost_center=self.cost_center, posting_date=prev_fy_end_date
|
||||||
|
)
|
||||||
|
pcv.reload()
|
||||||
|
self.assertEqual(pcv.gle_processing_status, "Completed")
|
||||||
|
|
||||||
|
# Change reconciliation setting to "Reconciliation Date"
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Company",
|
||||||
|
self.company,
|
||||||
|
"reconciliation_takes_effect_on",
|
||||||
|
"Reconciliation Date",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create new purchase invoice for 400 in new fiscal year
|
||||||
|
pi2 = self.create_purchase_invoice(qty=1, rate=400, do_not_submit=True)
|
||||||
|
pi2.posting_date = today()
|
||||||
|
pi2.set_posting_time = 1
|
||||||
|
pi2.supplier = self.supplier
|
||||||
|
pi2.currency = "INR"
|
||||||
|
pi2.credit_to = self.creditors
|
||||||
|
pi2.save()
|
||||||
|
pi2.submit()
|
||||||
|
|
||||||
|
# Allocate 600 from advance payment to purchase invoice
|
||||||
|
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||||
|
pr.party = self.supplier
|
||||||
|
pr.receivable_payable_account = self.creditors
|
||||||
|
pr.default_advance_account = self.advance_payable_account
|
||||||
|
pr.from_invoice_date = pr.to_invoice_date = pi2.posting_date
|
||||||
|
pr.from_payment_date = pr.to_payment_date = pe.posting_date
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi2.name]
|
||||||
|
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
pe.reload()
|
||||||
|
pi2.reload()
|
||||||
|
|
||||||
|
# Assert advance payment is fully allocated
|
||||||
|
self.assertEqual(len(pe.references), 2)
|
||||||
|
self.assertEqual(flt(pe.unallocated_amount), 0)
|
||||||
|
|
||||||
|
# Assert new invoice is fully paid
|
||||||
|
self.assertEqual(pi2.outstanding_amount, 0)
|
||||||
|
self.assertEqual(pi2.status, "Paid")
|
||||||
|
|
||||||
|
# Verify reconciliation dates are correct based on company setting
|
||||||
|
self.assertEqual(getdate(pe.references[0].reconcile_effect_on), getdate(pi1.posting_date))
|
||||||
|
self.assertEqual(getdate(pe.references[1].reconcile_effect_on), getdate(pi2.posting_date))
|
||||||
|
|
||||||
|
frappe.db.set_value("Company", self.company, default_settings)
|
||||||
|
|
||||||
|
|
||||||
def make_customer(customer_name, currency=None):
|
def make_customer(customer_name, currency=None):
|
||||||
if not frappe.db.exists("Customer", customer_name):
|
if not frappe.db.exists("Customer", customer_name):
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ frappe.ui.form.on("Payment Request", {
|
|||||||
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
|
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("payment_gateway_account", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -534,7 +534,8 @@ def make_payment_request(**args):
|
|||||||
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
|
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
|
||||||
|
|
||||||
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
||||||
|
if not args.get("company"):
|
||||||
|
args.company = ref_doc.company
|
||||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||||
|
|
||||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||||
@@ -781,7 +782,7 @@ def get_gateway_details(args): # nosemgrep
|
|||||||
"""
|
"""
|
||||||
Return gateway and payment account of default payment gateway
|
Return gateway and payment account of default payment gateway
|
||||||
"""
|
"""
|
||||||
gateway_account = args.get("payment_gateway_account", {"is_default": 1})
|
gateway_account = args.get("payment_gateway_account", {"is_default": 1, "company": args.company})
|
||||||
return get_payment_gateway_account(gateway_account)
|
return get_payment_gateway_account(gateway_account)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ payment_method = [
|
|||||||
"payment_gateway": "_Test Gateway",
|
"payment_gateway": "_Test Gateway",
|
||||||
"payment_account": "_Test Bank - _TC",
|
"payment_account": "_Test Bank - _TC",
|
||||||
"currency": "INR",
|
"currency": "INR",
|
||||||
|
"company": "_Test Company",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Payment Gateway Account",
|
"doctype": "Payment Gateway Account",
|
||||||
"payment_gateway": "_Test Gateway",
|
"payment_gateway": "_Test Gateway",
|
||||||
"payment_account": "_Test Bank USD - _TC",
|
"payment_account": "_Test Bank USD - _TC",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"company": "_Test Company",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Payment Gateway Account",
|
"doctype": "Payment Gateway Account",
|
||||||
@@ -47,6 +49,7 @@ payment_method = [
|
|||||||
"payment_account": "_Test Bank USD - _TC",
|
"payment_account": "_Test Bank USD - _TC",
|
||||||
"payment_channel": "Other",
|
"payment_channel": "Other",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"company": "_Test Company",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"doctype": "Payment Gateway Account",
|
"doctype": "Payment Gateway Account",
|
||||||
@@ -54,6 +57,7 @@ payment_method = [
|
|||||||
"payment_account": "_Test Bank USD - _TC",
|
"payment_account": "_Test Bank USD - _TC",
|
||||||
"payment_channel": "Phone",
|
"payment_channel": "Phone",
|
||||||
"currency": "USD",
|
"currency": "USD",
|
||||||
|
"company": "_Test Company",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -67,7 +71,11 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
for method in payment_method:
|
for method in payment_method:
|
||||||
if not frappe.db.get_value(
|
if not frappe.db.get_value(
|
||||||
"Payment Gateway Account",
|
"Payment Gateway Account",
|
||||||
{"payment_gateway": method["payment_gateway"], "currency": method["currency"]},
|
{
|
||||||
|
"payment_gateway": method["payment_gateway"],
|
||||||
|
"currency": method["currency"],
|
||||||
|
"company": method["company"],
|
||||||
|
},
|
||||||
"name",
|
"name",
|
||||||
):
|
):
|
||||||
frappe.get_doc(method).insert(ignore_permissions=True)
|
frappe.get_doc(method).insert(ignore_permissions=True)
|
||||||
@@ -103,7 +111,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so_inr.name,
|
dn=so_inr.name,
|
||||||
recipient_id="saurabh@erpnext.com",
|
recipient_id="saurabh@erpnext.com",
|
||||||
payment_gateway_account="_Test Gateway - INR",
|
payment_gateway_account="_Test Gateway - INR - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pr.reference_doctype, "Sales Order")
|
self.assertEqual(pr.reference_doctype, "Sales Order")
|
||||||
@@ -117,7 +125,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dt="Sales Invoice",
|
dt="Sales Invoice",
|
||||||
dn=si_usd.name,
|
dn=si_usd.name,
|
||||||
recipient_id="saurabh@erpnext.com",
|
recipient_id="saurabh@erpnext.com",
|
||||||
payment_gateway_account="_Test Gateway - USD",
|
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(pr.reference_doctype, "Sales Invoice")
|
self.assertEqual(pr.reference_doctype, "Sales Invoice")
|
||||||
@@ -130,7 +138,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
pr = make_payment_request(
|
pr = make_payment_request(
|
||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so.name,
|
dn=so.name,
|
||||||
payment_gateway_account="_Test Gateway Other - USD",
|
payment_gateway_account="_Test Gateway Other - USD - _TC",
|
||||||
submit_doc=True,
|
submit_doc=True,
|
||||||
return_doc=True,
|
return_doc=True,
|
||||||
)
|
)
|
||||||
@@ -145,7 +153,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
pr = make_payment_request(
|
pr = make_payment_request(
|
||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so.name,
|
dn=so.name,
|
||||||
payment_gateway_account="_Test Gateway - USD", # email channel
|
payment_gateway_account="_Test Gateway - USD - _TC", # email channel
|
||||||
submit_doc=False,
|
submit_doc=False,
|
||||||
return_doc=True,
|
return_doc=True,
|
||||||
)
|
)
|
||||||
@@ -163,7 +171,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
pr = make_payment_request(
|
pr = make_payment_request(
|
||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so.name,
|
dn=so.name,
|
||||||
payment_gateway_account="_Test Gateway Phone - USD",
|
payment_gateway_account="_Test Gateway Phone - USD - _TC",
|
||||||
submit_doc=True,
|
submit_doc=True,
|
||||||
return_doc=True,
|
return_doc=True,
|
||||||
)
|
)
|
||||||
@@ -180,7 +188,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
pr = make_payment_request(
|
pr = make_payment_request(
|
||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so.name,
|
dn=so.name,
|
||||||
payment_gateway_account="_Test Gateway - USD", # email channel
|
payment_gateway_account="_Test Gateway - USD - _TC", # email channel
|
||||||
submit_doc=True,
|
submit_doc=True,
|
||||||
return_doc=True,
|
return_doc=True,
|
||||||
)
|
)
|
||||||
@@ -201,7 +209,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
pr = make_payment_request(
|
pr = make_payment_request(
|
||||||
dt="Sales Order",
|
dt="Sales Order",
|
||||||
dn=so.name,
|
dn=so.name,
|
||||||
payment_gateway_account="_Test Gateway - USD", # email channel
|
payment_gateway_account="_Test Gateway - USD - _TC", # email channel
|
||||||
make_sales_invoice=True,
|
make_sales_invoice=True,
|
||||||
mute_email=True,
|
mute_email=True,
|
||||||
submit_doc=True,
|
submit_doc=True,
|
||||||
@@ -232,7 +240,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
party="_Test Supplier USD",
|
party="_Test Supplier USD",
|
||||||
recipient_id="user@example.com",
|
recipient_id="user@example.com",
|
||||||
mute_email=1,
|
mute_email=1,
|
||||||
payment_gateway_account="_Test Gateway - USD",
|
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||||
submit_doc=1,
|
submit_doc=1,
|
||||||
return_doc=1,
|
return_doc=1,
|
||||||
)
|
)
|
||||||
@@ -257,7 +265,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dn=purchase_invoice.name,
|
dn=purchase_invoice.name,
|
||||||
recipient_id="user@example.com",
|
recipient_id="user@example.com",
|
||||||
mute_email=1,
|
mute_email=1,
|
||||||
payment_gateway_account="_Test Gateway - USD",
|
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||||
return_doc=1,
|
return_doc=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -276,7 +284,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dn=purchase_invoice.name,
|
dn=purchase_invoice.name,
|
||||||
recipient_id="user@example.com",
|
recipient_id="user@example.com",
|
||||||
mute_email=1,
|
mute_email=1,
|
||||||
payment_gateway_account="_Test Gateway - USD",
|
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||||
return_doc=1,
|
return_doc=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -300,7 +308,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dn=so_inr.name,
|
dn=so_inr.name,
|
||||||
recipient_id="saurabh@erpnext.com",
|
recipient_id="saurabh@erpnext.com",
|
||||||
mute_email=1,
|
mute_email=1,
|
||||||
payment_gateway_account="_Test Gateway - INR",
|
payment_gateway_account="_Test Gateway - INR - _TC",
|
||||||
submit_doc=1,
|
submit_doc=1,
|
||||||
return_doc=1,
|
return_doc=1,
|
||||||
)
|
)
|
||||||
@@ -322,7 +330,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dn=si_usd.name,
|
dn=si_usd.name,
|
||||||
recipient_id="saurabh@erpnext.com",
|
recipient_id="saurabh@erpnext.com",
|
||||||
mute_email=1,
|
mute_email=1,
|
||||||
payment_gateway_account="_Test Gateway - USD",
|
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||||
submit_doc=1,
|
submit_doc=1,
|
||||||
return_doc=1,
|
return_doc=1,
|
||||||
)
|
)
|
||||||
@@ -366,7 +374,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
dn=si_usd.name,
|
dn=si_usd.name,
|
||||||
recipient_id="saurabh@erpnext.com",
|
recipient_id="saurabh@erpnext.com",
|
||||||
mute_email=1,
|
mute_email=1,
|
||||||
payment_gateway_account="_Test Gateway - USD",
|
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||||
submit_doc=1,
|
submit_doc=1,
|
||||||
return_doc=1,
|
return_doc=1,
|
||||||
)
|
)
|
||||||
@@ -471,7 +479,7 @@ class TestPaymentRequest(IntegrationTestCase):
|
|||||||
|
|
||||||
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
|
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
|
||||||
self.assertEqual(pe.references[0].allocated_amount, 800)
|
self.assertEqual(pe.references[0].allocated_amount, 800)
|
||||||
self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero
|
self.assertEqual(pe.references[0].outstanding_amount, 0) # Also for orders it will zero
|
||||||
self.assertEqual(pe.references[0].payment_request, pr.name)
|
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||||
|
|
||||||
so.load_from_db()
|
so.load_from_db()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
|||||||
}
|
}
|
||||||
|
|
||||||
company() {
|
company() {
|
||||||
|
erpnext.utils.set_letter_head(this.frm);
|
||||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||||
this.frm.set_value("set_warehouse", "");
|
this.frm.set_value("set_warehouse", "");
|
||||||
this.frm.set_value("taxes_and_charges", "");
|
this.frm.set_value("taxes_and_charges", "");
|
||||||
|
|||||||
@@ -296,6 +296,7 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Now",
|
||||||
"fieldname": "posting_time",
|
"fieldname": "posting_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"label": "Posting Time",
|
"label": "Posting Time",
|
||||||
@@ -1598,7 +1599,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-18 16:50:30.516162",
|
"modified": "2025-08-04 22:22:31.471752",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ frappe.ui.form.on("POS Profile", {
|
|||||||
company: function (frm) {
|
company: function (frm) {
|
||||||
frm.trigger("toggle_display_account_head");
|
frm.trigger("toggle_display_account_head");
|
||||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||||
|
erpnext.utils.set_letter_head(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle_display_account_head: function (frm) {
|
toggle_display_account_head: function (frm) {
|
||||||
|
|||||||
@@ -19,13 +19,14 @@
|
|||||||
"fieldname": "field",
|
"fieldname": "field",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Field"
|
"label": "Field",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:10:16.969895",
|
"modified": "2025-07-29 18:08:40.323579",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Search Fields",
|
"name": "POS Search Fields",
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ frappe.ui.form.on("Process Statement Of Accounts", {
|
|||||||
frm.set_value("account", "");
|
frm.set_value("account", "");
|
||||||
frm.set_value("cost_center", "");
|
frm.set_value("cost_center", "");
|
||||||
frm.set_value("project", "");
|
frm.set_value("project", "");
|
||||||
|
erpnext.utils.set_letter_head(frm);
|
||||||
},
|
},
|
||||||
report: function (frm) {
|
report: function (frm) {
|
||||||
let filters = {
|
let filters = {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
"column_break_50",
|
"column_break_50",
|
||||||
"base_total",
|
"base_total",
|
||||||
"base_net_total",
|
"base_net_total",
|
||||||
|
"claimed_landed_cost_amount",
|
||||||
"column_break_28",
|
"column_break_28",
|
||||||
"total",
|
"total",
|
||||||
"net_total",
|
"net_total",
|
||||||
@@ -321,6 +322,7 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Now",
|
||||||
"fieldname": "posting_time",
|
"fieldname": "posting_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"label": "Posting Time",
|
"label": "Posting Time",
|
||||||
@@ -1651,6 +1653,15 @@
|
|||||||
"label": "Select Dispatch Address ",
|
"label": "Select Dispatch Address ",
|
||||||
"options": "Address",
|
"options": "Address",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "claimed_landed_cost_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Claimed Landed Cost Amount (Company Currency)",
|
||||||
|
"no_copy": 1,
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
|
"print_hide": 1,
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -1658,7 +1669,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-04-09 16:49:22.175081",
|
"modified": "2025-08-04 19:19:11.380664",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
billing_address_display: DF.TextEditor | None
|
billing_address_display: DF.TextEditor | None
|
||||||
buying_price_list: DF.Link | None
|
buying_price_list: DF.Link | None
|
||||||
cash_bank_account: DF.Link | None
|
cash_bank_account: DF.Link | None
|
||||||
|
claimed_landed_cost_amount: DF.Currency
|
||||||
clearance_date: DF.Date | None
|
clearance_date: DF.Date | None
|
||||||
company: DF.Link | None
|
company: DF.Link | None
|
||||||
contact_display: DF.SmallText | None
|
contact_display: DF.SmallText | None
|
||||||
@@ -972,7 +973,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
self.get_provisional_accounts()
|
self.get_provisional_accounts()
|
||||||
|
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount):
|
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
|
||||||
if item.item_code:
|
if item.item_code:
|
||||||
frappe.get_cached_value("Item", item.item_code, "asset_category")
|
frappe.get_cached_value("Item", item.item_code, "asset_category")
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import inspect
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, qb
|
from frappe import _, qb
|
||||||
|
from frappe.desk.form.linked_with import get_child_tables_of_doctypes
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils.data import comma_and
|
from frappe.utils.data import comma_and
|
||||||
|
|
||||||
@@ -169,6 +170,10 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
frappe.db.delete(
|
frappe.db.delete(
|
||||||
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
|
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
|
||||||
)
|
)
|
||||||
|
frappe.db.delete(
|
||||||
|
"Advance Payment Ledger Entry",
|
||||||
|
filters={"voucher_type": doc.doctype, "voucher_no": doc.name},
|
||||||
|
)
|
||||||
|
|
||||||
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
if not repost_doc.delete_cancelled_entries:
|
if not repost_doc.delete_cancelled_entries:
|
||||||
@@ -204,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None:
|
|||||||
doc.make_gl_entries()
|
doc.make_gl_entries()
|
||||||
|
|
||||||
|
|
||||||
def get_allowed_types_from_settings():
|
def get_allowed_types_from_settings(child_doc: bool = False):
|
||||||
return [
|
repost_docs = [
|
||||||
x.document_type
|
x.document_type
|
||||||
for x in frappe.db.get_all(
|
for x in frappe.db.get_all(
|
||||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
result = repost_docs
|
||||||
|
|
||||||
|
if repost_docs and child_doc:
|
||||||
|
result.extend(get_child_docs(repost_docs))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_child_docs(doc: list) -> list:
|
||||||
|
child_doc = []
|
||||||
|
doc = get_child_tables_of_doctypes(doc)
|
||||||
|
for child_list in doc.values():
|
||||||
|
for child in child_list:
|
||||||
|
if child.get("child_table"):
|
||||||
|
child_doc.append(child["child_table"])
|
||||||
|
return child_doc
|
||||||
|
|
||||||
|
|
||||||
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
# import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
|
get_accounting_dimensions,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
|
||||||
|
|
||||||
|
|
||||||
class RepostAccountingLedgerSettings(Document):
|
class RepostAccountingLedgerSettings(Document):
|
||||||
# begin: auto-generated types
|
# begin: auto-generated types
|
||||||
@@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document):
|
|||||||
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
|
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
|
||||||
|
|
||||||
allowed_types: DF.Table[RepostAllowedTypes]
|
allowed_types: DF.Table[RepostAllowedTypes]
|
||||||
# end: auto-generated types
|
|
||||||
|
|
||||||
pass
|
# end: auto-generated types
|
||||||
|
def validate(self):
|
||||||
|
self.update_property_for_accounting_dimension()
|
||||||
|
|
||||||
|
def update_property_for_accounting_dimension(self):
|
||||||
|
doctypes = [entry.document_type for entry in self.allowed_types if entry.allowed]
|
||||||
|
if not doctypes:
|
||||||
|
return
|
||||||
|
doctypes += get_child_docs(doctypes)
|
||||||
|
|
||||||
|
set_allow_on_submit_for_dimension_fields(doctypes)
|
||||||
|
|
||||||
|
|
||||||
|
def set_allow_on_submit_for_dimension_fields(doctypes):
|
||||||
|
for dt in doctypes:
|
||||||
|
meta = frappe.get_meta(dt)
|
||||||
|
for dimension in get_accounting_dimensions():
|
||||||
|
df = meta.get_field(dimension)
|
||||||
|
if df and not df.allow_on_submit:
|
||||||
|
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from frappe import _, qb
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder.custom import ConstantColumn
|
from frappe.query_builder.custom import ConstantColumn
|
||||||
|
|
||||||
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
|
from erpnext.accounts.utils import _delete_adv_pl_entries, _delete_pl_entries, create_payment_ledger_entry
|
||||||
|
|
||||||
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal
|
|||||||
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
|
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
|
||||||
if voucher_type and voucher_no and gle_map:
|
if voucher_type and voucher_no and gle_map:
|
||||||
_delete_pl_entries(voucher_type, voucher_no)
|
_delete_pl_entries(voucher_type, voucher_no)
|
||||||
|
_delete_adv_pl_entries(voucher_type, voucher_no)
|
||||||
create_payment_ledger_entry(gle_map, cancel=0)
|
create_payment_ledger_entry(gle_map, cancel=0)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -373,6 +373,7 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Now",
|
||||||
"fieldname": "posting_time",
|
"fieldname": "posting_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
"hide_days": 1,
|
"hide_days": 1,
|
||||||
@@ -2232,7 +2233,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-06-26 14:06:56.773552",
|
"modified": "2025-08-04 19:20:28.732039",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.party import get_party_account
|
from erpnext.accounts.party import get_party_account
|
||||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.create_company()
|
self.create_company()
|
||||||
self.create_customer()
|
self.create_customer()
|
||||||
|
self.create_supplier()
|
||||||
self.create_usd_receivable_account()
|
self.create_usd_receivable_account()
|
||||||
self.create_item()
|
self.create_item()
|
||||||
self.clear_old_entries()
|
self.clear_old_entries()
|
||||||
@@ -364,13 +366,13 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase):
|
|||||||
# Assert 'Advance Paid'
|
# Assert 'Advance Paid'
|
||||||
so.reload()
|
so.reload()
|
||||||
pe.reload()
|
pe.reload()
|
||||||
self.assertEqual(so.advance_paid, 100)
|
self.assertEqual(so.advance_paid, 0)
|
||||||
self.assertEqual(len(pe.references), 0)
|
self.assertEqual(len(pe.references), 0)
|
||||||
self.assertEqual(pe.unallocated_amount, 100)
|
self.assertEqual(pe.unallocated_amount, 100)
|
||||||
|
|
||||||
pe.cancel()
|
pe.cancel()
|
||||||
so.reload()
|
so.reload()
|
||||||
self.assertEqual(so.advance_paid, 100)
|
self.assertEqual(so.advance_paid, 0)
|
||||||
|
|
||||||
def test_06_unreconcile_advance_from_payment_entry(self):
|
def test_06_unreconcile_advance_from_payment_entry(self):
|
||||||
self.enable_advance_as_liability()
|
self.enable_advance_as_liability()
|
||||||
@@ -417,7 +419,7 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase):
|
|||||||
so2.reload()
|
so2.reload()
|
||||||
pe.reload()
|
pe.reload()
|
||||||
self.assertEqual(so1.advance_paid, 150)
|
self.assertEqual(so1.advance_paid, 150)
|
||||||
self.assertEqual(so2.advance_paid, 110)
|
self.assertEqual(so2.advance_paid, 0)
|
||||||
self.assertEqual(len(pe.references), 1)
|
self.assertEqual(len(pe.references), 1)
|
||||||
self.assertEqual(pe.unallocated_amount, 110)
|
self.assertEqual(pe.unallocated_amount, 110)
|
||||||
|
|
||||||
@@ -463,8 +465,77 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase):
|
|||||||
self.assertEqual(len(pr.get("invoices")), 0)
|
self.assertEqual(len(pr.get("invoices")), 0)
|
||||||
self.assertEqual(len(pr.get("payments")), 0)
|
self.assertEqual(len(pr.get("payments")), 0)
|
||||||
|
|
||||||
# Assert 'Advance Paid'
|
|
||||||
so.reload()
|
so.reload()
|
||||||
self.assertEqual(so.advance_paid, 1000)
|
self.assertEqual(so.advance_paid, 1000)
|
||||||
|
|
||||||
|
unreconcile = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Unreconcile Payment",
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": pe.doctype,
|
||||||
|
"voucher_no": pe.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
unreconcile.add_references()
|
||||||
|
unreconcile.allocations = [x for x in unreconcile.allocations if x.reference_name == si.name]
|
||||||
|
unreconcile.save().submit()
|
||||||
|
|
||||||
|
# after unreconcilaition advance paid will be reduced
|
||||||
|
# Assert 'Advance Paid'
|
||||||
|
so.reload()
|
||||||
|
self.assertEqual(so.advance_paid, 0)
|
||||||
|
|
||||||
self.disable_advance_as_liability()
|
self.disable_advance_as_liability()
|
||||||
|
|
||||||
|
def test_unreconcile_advance_from_journal_entry(self):
|
||||||
|
po = create_purchase_order(
|
||||||
|
company=self.company,
|
||||||
|
supplier=self.supplier,
|
||||||
|
item=self.item,
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
transaction_date=today(),
|
||||||
|
do_not_submit=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
je = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Journal Entry",
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": "Journal Entry",
|
||||||
|
"posting_date": po.transaction_date,
|
||||||
|
"multi_currency": True,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"account": "Creditors - _TC",
|
||||||
|
"party_type": "Supplier",
|
||||||
|
"party": po.supplier,
|
||||||
|
"debit_in_account_currency": 100,
|
||||||
|
"is_advance": "Yes",
|
||||||
|
"reference_type": po.doctype,
|
||||||
|
"reference_name": po.name,
|
||||||
|
},
|
||||||
|
{"account": "Cash - _TC", "credit_in_account_currency": 100},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
je.save().submit()
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 100)
|
||||||
|
|
||||||
|
unreconcile = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Unreconcile Payment",
|
||||||
|
"company": self.company,
|
||||||
|
"voucher_type": je.doctype,
|
||||||
|
"voucher_no": je.name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
unreconcile.add_references()
|
||||||
|
self.assertEqual(len(unreconcile.allocations), 1)
|
||||||
|
allocations = [x.reference_name for x in unreconcile.allocations]
|
||||||
|
self.assertEqual([po.name], allocations)
|
||||||
|
unreconcile.save().submit()
|
||||||
|
|
||||||
|
po.reload()
|
||||||
|
self.assertEqual(po.advance_paid, 0)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from frappe.utils.data import comma_and
|
|||||||
|
|
||||||
from erpnext.accounts.utils import (
|
from erpnext.accounts.utils import (
|
||||||
cancel_exchange_gain_loss_journal,
|
cancel_exchange_gain_loss_journal,
|
||||||
get_advance_payment_doctypes,
|
|
||||||
unlink_ref_doc_from_payment_entries,
|
unlink_ref_doc_from_payment_entries,
|
||||||
update_voucher_outstanding,
|
update_voucher_outstanding,
|
||||||
)
|
)
|
||||||
@@ -45,31 +44,12 @@ class UnreconcilePayment(Document):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_allocations_from_payment(self):
|
def get_allocations_from_payment(self):
|
||||||
allocated_references = []
|
return get_linked_payments_for_doc(
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
company=self.company,
|
||||||
allocated_references = (
|
doctype=self.voucher_type,
|
||||||
qb.from_(ple)
|
docname=self.voucher_no,
|
||||||
.select(
|
|
||||||
ple.account,
|
|
||||||
ple.party_type,
|
|
||||||
ple.party,
|
|
||||||
ple.against_voucher_type.as_("reference_doctype"),
|
|
||||||
ple.against_voucher_no.as_("reference_name"),
|
|
||||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
|
||||||
ple.account_currency,
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
(ple.docstatus == 1)
|
|
||||||
& (ple.voucher_type == self.voucher_type)
|
|
||||||
& (ple.voucher_no == self.voucher_no)
|
|
||||||
& (ple.voucher_no != ple.against_voucher_no)
|
|
||||||
)
|
|
||||||
.groupby(ple.against_voucher_type, ple.against_voucher_no)
|
|
||||||
.run(as_dict=True)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return allocated_references
|
|
||||||
|
|
||||||
def add_references(self):
|
def add_references(self):
|
||||||
allocations = self.get_allocations_from_payment()
|
allocations = self.get_allocations_from_payment()
|
||||||
|
|
||||||
@@ -82,27 +62,43 @@ class UnreconcilePayment(Document):
|
|||||||
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
|
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
|
||||||
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
|
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
|
||||||
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
|
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
|
||||||
|
|
||||||
|
# update outstanding amounts
|
||||||
update_voucher_outstanding(
|
update_voucher_outstanding(
|
||||||
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
|
alloc.reference_doctype,
|
||||||
|
alloc.reference_name,
|
||||||
|
alloc.account,
|
||||||
|
alloc.party_type,
|
||||||
|
alloc.party,
|
||||||
)
|
)
|
||||||
if doc.doctype in get_advance_payment_doctypes():
|
|
||||||
doc.set_total_advance_paid()
|
|
||||||
|
|
||||||
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
|
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def doc_has_references(doctype: str | None = None, docname: str | None = None):
|
def doc_has_references(doctype: str | None = None, docname: str | None = None):
|
||||||
|
count = 0
|
||||||
if doctype in ["Sales Invoice", "Purchase Invoice"]:
|
if doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
return frappe.db.count(
|
count = frappe.db.count(
|
||||||
"Payment Ledger Entry",
|
"Payment Ledger Entry",
|
||||||
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
|
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return frappe.db.count(
|
count = frappe.db.count(
|
||||||
"Payment Ledger Entry",
|
"Payment Ledger Entry",
|
||||||
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
|
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
|
||||||
)
|
)
|
||||||
|
count += frappe.db.count(
|
||||||
|
"Advance Payment Ledger Entry",
|
||||||
|
filters={
|
||||||
|
"delinked": 0,
|
||||||
|
"voucher_no": docname,
|
||||||
|
"voucher_type": doctype,
|
||||||
|
"event": ["=", "Submit"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -124,9 +120,12 @@ def get_linked_payments_for_doc(
|
|||||||
res = (
|
res = (
|
||||||
qb.from_(ple)
|
qb.from_(ple)
|
||||||
.select(
|
.select(
|
||||||
|
ple.account,
|
||||||
|
ple.party_type,
|
||||||
|
ple.party,
|
||||||
ple.company,
|
ple.company,
|
||||||
ple.voucher_type,
|
ple.voucher_type.as_("reference_doctype"),
|
||||||
ple.voucher_no,
|
ple.voucher_no.as_("reference_name"),
|
||||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||||
ple.account_currency,
|
ple.account_currency,
|
||||||
)
|
)
|
||||||
@@ -148,19 +147,52 @@ def get_linked_payments_for_doc(
|
|||||||
qb.from_(ple)
|
qb.from_(ple)
|
||||||
.select(
|
.select(
|
||||||
ple.company,
|
ple.company,
|
||||||
ple.against_voucher_type.as_("voucher_type"),
|
ple.account,
|
||||||
ple.against_voucher_no.as_("voucher_no"),
|
ple.party_type,
|
||||||
|
ple.party,
|
||||||
|
ple.against_voucher_type.as_("reference_doctype"),
|
||||||
|
ple.against_voucher_no.as_("reference_name"),
|
||||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||||
ple.account_currency,
|
ple.account_currency,
|
||||||
)
|
)
|
||||||
.where(Criterion.all(criteria))
|
.where(Criterion.all(criteria))
|
||||||
.groupby(ple.against_voucher_no)
|
.groupby(ple.against_voucher_no)
|
||||||
)
|
)
|
||||||
|
|
||||||
res = query.run(as_dict=True)
|
res = query.run(as_dict=True)
|
||||||
|
|
||||||
|
res += get_linked_advances(company, _dn)
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_linked_advances(company, docname):
|
||||||
|
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||||
|
criteria = [
|
||||||
|
(adv.company == company),
|
||||||
|
(adv.delinked == 0),
|
||||||
|
(adv.voucher_no == docname),
|
||||||
|
(adv.event == "Submit"),
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
qb.from_(adv)
|
||||||
|
.select(
|
||||||
|
adv.company,
|
||||||
|
adv.against_voucher_type.as_("reference_doctype"),
|
||||||
|
adv.against_voucher_no.as_("reference_name"),
|
||||||
|
Abs(Sum(adv.amount)).as_("allocated_amount"),
|
||||||
|
adv.currency,
|
||||||
|
)
|
||||||
|
.where(Criterion.all(criteria))
|
||||||
|
.having(qb.Field("allocated_amount") > 0)
|
||||||
|
.groupby(adv.against_voucher_no)
|
||||||
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def create_unreconcile_doc_for_selection(selections=None):
|
def create_unreconcile_doc_for_selection(selections=None):
|
||||||
if selections:
|
if selections:
|
||||||
|
|||||||
@@ -316,6 +316,8 @@ def get_merge_properties(dimensions=None):
|
|||||||
"project",
|
"project",
|
||||||
"finance_book",
|
"finance_book",
|
||||||
"voucher_no",
|
"voucher_no",
|
||||||
|
"advance_voucher_type",
|
||||||
|
"advance_voucher_no",
|
||||||
]
|
]
|
||||||
if dimensions:
|
if dimensions:
|
||||||
merge_properties.extend(dimensions)
|
merge_properties.extend(dimensions)
|
||||||
|
|||||||
@@ -14,9 +14,16 @@ erpnext.utils.add_dimensions("Cash Flow", 10);
|
|||||||
|
|
||||||
frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
|
frappe.query_reports["Cash Flow"]["filters"].splice(8, 1);
|
||||||
|
|
||||||
frappe.query_reports["Cash Flow"]["filters"].push({
|
frappe.query_reports["Cash Flow"]["filters"].push(
|
||||||
fieldname: "include_default_book_entries",
|
{
|
||||||
label: __("Include Default FB Entries"),
|
fieldname: "include_default_book_entries",
|
||||||
fieldtype: "Check",
|
label: __("Include Default FB Entries"),
|
||||||
default: 1,
|
fieldtype: "Check",
|
||||||
});
|
default: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "show_opening_and_closing_balance",
|
||||||
|
label: __("Show Opening and Closing Balance"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr
|
from frappe.query_builder import DocType
|
||||||
|
from frappe.utils import cstr, flt
|
||||||
|
from pypika import Order
|
||||||
|
|
||||||
from erpnext.accounts.report.financial_statements import (
|
from erpnext.accounts.report.financial_statements import (
|
||||||
get_columns,
|
get_columns,
|
||||||
@@ -12,6 +16,7 @@ from erpnext.accounts.report.financial_statements import (
|
|||||||
get_data,
|
get_data,
|
||||||
get_filtered_list_for_consolidated_report,
|
get_filtered_list_for_consolidated_report,
|
||||||
get_period_list,
|
get_period_list,
|
||||||
|
set_gl_entries_by_account,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
|
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
|
||||||
get_net_profit_loss,
|
get_net_profit_loss,
|
||||||
@@ -119,10 +124,20 @@ def execute(filters=None):
|
|||||||
filters,
|
filters,
|
||||||
)
|
)
|
||||||
|
|
||||||
add_total_row_account(
|
net_change_in_cash = add_total_row_account(
|
||||||
data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters
|
data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters
|
||||||
)
|
)
|
||||||
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company, True)
|
|
||||||
|
if filters.show_opening_and_closing_balance:
|
||||||
|
show_opening_and_closing_balance(data, period_list, company_currency, net_change_in_cash, filters)
|
||||||
|
|
||||||
|
columns = get_columns(
|
||||||
|
filters.periodicity,
|
||||||
|
period_list,
|
||||||
|
filters.accumulated_values,
|
||||||
|
filters.company,
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
chart = get_chart_data(columns, data, company_currency)
|
chart = get_chart_data(columns, data, company_currency)
|
||||||
|
|
||||||
@@ -255,6 +270,137 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data,
|
|||||||
out.append(total_row)
|
out.append(total_row)
|
||||||
out.append({})
|
out.append({})
|
||||||
|
|
||||||
|
return total_row
|
||||||
|
|
||||||
|
|
||||||
|
def show_opening_and_closing_balance(out, period_list, currency, net_change_in_cash, filters):
|
||||||
|
opening_balance = {
|
||||||
|
"section_name": "Opening",
|
||||||
|
"section": "Opening",
|
||||||
|
"currency": currency,
|
||||||
|
}
|
||||||
|
closing_balance = {
|
||||||
|
"section_name": "Closing (Opening + Total)",
|
||||||
|
"section": "Closing (Opening + Total)",
|
||||||
|
"currency": currency,
|
||||||
|
}
|
||||||
|
|
||||||
|
opening_amount = get_opening_balance(filters.company, period_list, filters) or 0.0
|
||||||
|
running_total = opening_amount
|
||||||
|
|
||||||
|
for i, period in enumerate(period_list):
|
||||||
|
key = period["key"]
|
||||||
|
change = net_change_in_cash.get(key, 0.0)
|
||||||
|
|
||||||
|
opening_balance[key] = opening_amount if i == 0 else running_total
|
||||||
|
running_total += change
|
||||||
|
closing_balance[key] = running_total
|
||||||
|
|
||||||
|
opening_balance["total"] = opening_balance[period_list[0]["key"]]
|
||||||
|
closing_balance["total"] = closing_balance[period_list[-1]["key"]]
|
||||||
|
|
||||||
|
out.extend([opening_balance, net_change_in_cash, closing_balance, {}])
|
||||||
|
|
||||||
|
|
||||||
|
def get_opening_balance(company, period_list, filters):
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
cash_value = {}
|
||||||
|
account_types = get_cash_flow_accounts()
|
||||||
|
net_profit_loss = 0.0
|
||||||
|
|
||||||
|
local_filters = deepcopy(filters)
|
||||||
|
local_filters.start_date, local_filters.end_date = get_opening_range_using_fiscal_year(
|
||||||
|
company, period_list
|
||||||
|
)
|
||||||
|
|
||||||
|
for section in account_types:
|
||||||
|
section_name = section.get("section_name")
|
||||||
|
cash_value.setdefault(section_name, 0.0)
|
||||||
|
|
||||||
|
if section_name == "Operations":
|
||||||
|
net_profit_loss += get_net_income(company, period_list, local_filters)
|
||||||
|
|
||||||
|
for account in section.get("account_types", []):
|
||||||
|
account_type = account.get("account_type")
|
||||||
|
local_filters.account_type = account_type
|
||||||
|
|
||||||
|
amount = get_account_type_based_gl_data(company, local_filters) or 0.0
|
||||||
|
|
||||||
|
if account_type == "Depreciation":
|
||||||
|
cash_value[section_name] += amount * -1
|
||||||
|
else:
|
||||||
|
cash_value[section_name] += amount
|
||||||
|
|
||||||
|
return sum(cash_value.values()) + net_profit_loss
|
||||||
|
|
||||||
|
|
||||||
|
def get_net_income(company, period_list, filters):
|
||||||
|
gl_entries_by_account_for_income, gl_entries_by_account_for_expense = {}, {}
|
||||||
|
income, expense = 0.0, 0.0
|
||||||
|
from_date, to_date = get_opening_range_using_fiscal_year(company, period_list)
|
||||||
|
|
||||||
|
for root_type in ["Income", "Expense"]:
|
||||||
|
for root in frappe.db.sql(
|
||||||
|
"""select lft, rgt from tabAccount
|
||||||
|
where root_type=%s and ifnull(parent_account, '') = ''""",
|
||||||
|
root_type,
|
||||||
|
as_dict=1,
|
||||||
|
):
|
||||||
|
set_gl_entries_by_account(
|
||||||
|
company,
|
||||||
|
from_date,
|
||||||
|
to_date,
|
||||||
|
filters,
|
||||||
|
gl_entries_by_account_for_income
|
||||||
|
if root_type == "Income"
|
||||||
|
else gl_entries_by_account_for_expense,
|
||||||
|
root.lft,
|
||||||
|
root.rgt,
|
||||||
|
root_type=root_type,
|
||||||
|
ignore_closing_entries=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for entries in gl_entries_by_account_for_income.values():
|
||||||
|
for entry in entries:
|
||||||
|
if entry.posting_date <= to_date:
|
||||||
|
amount = (entry.debit - entry.credit) * -1
|
||||||
|
income = flt((income + amount), 2)
|
||||||
|
|
||||||
|
for entries in gl_entries_by_account_for_expense.values():
|
||||||
|
for entry in entries:
|
||||||
|
if entry.posting_date <= to_date:
|
||||||
|
amount = entry.debit - entry.credit
|
||||||
|
expense = flt((expense + amount), 2)
|
||||||
|
|
||||||
|
return income - expense
|
||||||
|
|
||||||
|
|
||||||
|
def get_opening_range_using_fiscal_year(company, period_list):
|
||||||
|
first_from_date = period_list[0]["from_date"]
|
||||||
|
previous_day = first_from_date - timedelta(days=1)
|
||||||
|
|
||||||
|
# Get the earliest fiscal year for the company
|
||||||
|
|
||||||
|
FiscalYear = DocType("Fiscal Year")
|
||||||
|
FiscalYearCompany = DocType("Fiscal Year Company")
|
||||||
|
|
||||||
|
earliest_fy = (
|
||||||
|
frappe.qb.from_(FiscalYear)
|
||||||
|
.join(FiscalYearCompany)
|
||||||
|
.on(FiscalYearCompany.parent == FiscalYear.name)
|
||||||
|
.select(FiscalYear.year_start_date)
|
||||||
|
.where(FiscalYearCompany.company == company)
|
||||||
|
.orderby(FiscalYear.year_start_date, order=Order.asc)
|
||||||
|
.limit(1)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
if not earliest_fy:
|
||||||
|
frappe.throw(_("Not able to find the earliest Fiscal Year for the given company."))
|
||||||
|
|
||||||
|
company_start_date = earliest_fy[0]["year_start_date"]
|
||||||
|
return company_start_date, previous_day
|
||||||
|
|
||||||
|
|
||||||
def get_report_summary(summary_data, currency):
|
def get_report_summary(summary_data, currency):
|
||||||
report_summary = []
|
report_summary = []
|
||||||
@@ -275,7 +421,7 @@ def get_chart_data(columns, data, currency):
|
|||||||
for section in data
|
for section in data
|
||||||
if section.get("parent_section") is None and section.get("currency")
|
if section.get("parent_section") is None and section.get("currency")
|
||||||
]
|
]
|
||||||
datasets = datasets[:-1]
|
datasets = datasets[:-2]
|
||||||
|
|
||||||
chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}
|
chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"}
|
||||||
|
|
||||||
|
|||||||
@@ -277,12 +277,25 @@ class PartyLedgerSummaryReport:
|
|||||||
if gle.posting_date < self.filters.from_date or gle.is_opening == "Yes":
|
if gle.posting_date < self.filters.from_date or gle.is_opening == "Yes":
|
||||||
self.party_data[gle.party].opening_balance += amount
|
self.party_data[gle.party].opening_balance += amount
|
||||||
else:
|
else:
|
||||||
if amount > 0:
|
# Cache the party data reference to avoid repeated dictionary lookups
|
||||||
self.party_data[gle.party].invoiced_amount += amount
|
party_data = self.party_data[gle.party]
|
||||||
elif gle.voucher_no in self.return_invoices:
|
|
||||||
self.party_data[gle.party].return_amount -= amount
|
# Check if this is a direct return invoice (most specific condition first)
|
||||||
|
if gle.voucher_no in self.return_invoices:
|
||||||
|
party_data.return_amount -= amount
|
||||||
|
# Check if this entry is against a return invoice
|
||||||
|
elif gle.against_voucher in self.return_invoices:
|
||||||
|
# For entries against return invoices, positive amounts are payments
|
||||||
|
if amount > 0:
|
||||||
|
party_data.paid_amount -= amount
|
||||||
|
else:
|
||||||
|
party_data.invoiced_amount += amount
|
||||||
|
# Normal transaction logic
|
||||||
else:
|
else:
|
||||||
self.party_data[gle.party].paid_amount -= amount
|
if amount > 0:
|
||||||
|
party_data.invoiced_amount += amount
|
||||||
|
else:
|
||||||
|
party_data.paid_amount -= amount
|
||||||
|
|
||||||
out = []
|
out = []
|
||||||
for party, row in self.party_data.items():
|
for party, row in self.party_data.items():
|
||||||
@@ -291,7 +304,7 @@ class PartyLedgerSummaryReport:
|
|||||||
or row.invoiced_amount
|
or row.invoiced_amount
|
||||||
or row.paid_amount
|
or row.paid_amount
|
||||||
or row.return_amount
|
or row.return_amount
|
||||||
or row.closing_amount
|
or row.closing_balance # Fixed typo from closing_amount to closing_balance
|
||||||
):
|
):
|
||||||
total_party_adjustment = sum(
|
total_party_adjustment = sum(
|
||||||
amount for amount in self.party_adjustment_details.get(party, {}).values()
|
amount for amount in self.party_adjustment_details.get(party, {}).values()
|
||||||
@@ -322,6 +335,7 @@ class PartyLedgerSummaryReport:
|
|||||||
gle.party,
|
gle.party,
|
||||||
gle.voucher_type,
|
gle.voucher_type,
|
||||||
gle.voucher_no,
|
gle.voucher_no,
|
||||||
|
gle.against_voucher, # For handling returned invoices (Credit/Debit Notes)
|
||||||
gle.debit,
|
gle.debit,
|
||||||
gle.credit,
|
gle.credit,
|
||||||
gle.is_opening,
|
gle.is_opening,
|
||||||
|
|||||||
@@ -188,8 +188,8 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase):
|
|||||||
"customer_name": "_Test Customer",
|
"customer_name": "_Test Customer",
|
||||||
"party_name": "_Test Customer",
|
"party_name": "_Test Customer",
|
||||||
"opening_balance": 0,
|
"opening_balance": 0,
|
||||||
"invoiced_amount": 200.0,
|
"invoiced_amount": 100.0,
|
||||||
"paid_amount": 100.0,
|
"paid_amount": 0.0,
|
||||||
"return_amount": 100.0,
|
"return_amount": 100.0,
|
||||||
"closing_balance": 0.0,
|
"closing_balance": 0.0,
|
||||||
"currency": "INR",
|
"currency": "INR",
|
||||||
@@ -234,3 +234,157 @@ class TestCustomerLedgerSummary(AccountsTestMixin, IntegrationTestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(len(data), 1)
|
self.assertEqual(len(data), 1)
|
||||||
self.assertEqual(expected, data[0])
|
self.assertEqual(expected, data[0])
|
||||||
|
|
||||||
|
def test_journal_voucher_against_return_invoice(self):
|
||||||
|
filters = {"company": self.company, "from_date": today(), "to_date": today()}
|
||||||
|
|
||||||
|
# Create Sales Invoice of 10 qty at rate 100 (Amount: 1000.0)
|
||||||
|
si1 = self.create_sales_invoice(do_not_submit=True)
|
||||||
|
si1.save().submit()
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"party": "_Test Customer",
|
||||||
|
"party_name": "_Test Customer",
|
||||||
|
"opening_balance": 0,
|
||||||
|
"invoiced_amount": 1000.0,
|
||||||
|
"paid_amount": 0,
|
||||||
|
"return_amount": 0,
|
||||||
|
"closing_balance": 1000.0,
|
||||||
|
"currency": "INR",
|
||||||
|
"customer_name": "_Test Customer",
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)[1]
|
||||||
|
self.assertEqual(len(report), 1)
|
||||||
|
for field in expected:
|
||||||
|
with self.subTest(field=field):
|
||||||
|
actual_value = report[0].get(field)
|
||||||
|
expected_value = expected.get(field)
|
||||||
|
self.assertEqual(
|
||||||
|
actual_value,
|
||||||
|
expected_value,
|
||||||
|
f"Field {field} does not match expected value. "
|
||||||
|
f"Expected: {expected_value}, Got: {actual_value}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Payment Entry (Receive) for the first invoice
|
||||||
|
pe1 = self.create_payment_entry(si1.name, True)
|
||||||
|
pe1.paid_amount = 1000 # Full payment 1000.0
|
||||||
|
pe1.save().submit()
|
||||||
|
|
||||||
|
expected_after_payment = {
|
||||||
|
"party": "_Test Customer",
|
||||||
|
"party_name": "_Test Customer",
|
||||||
|
"opening_balance": 0,
|
||||||
|
"invoiced_amount": 1000.0,
|
||||||
|
"paid_amount": 1000.0,
|
||||||
|
"return_amount": 0,
|
||||||
|
"closing_balance": 0.0,
|
||||||
|
"currency": "INR",
|
||||||
|
"customer_name": "_Test Customer",
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)[1]
|
||||||
|
self.assertEqual(len(report), 1)
|
||||||
|
for field in expected_after_payment:
|
||||||
|
with self.subTest(field=field):
|
||||||
|
actual_value = report[0].get(field)
|
||||||
|
expected_value = expected_after_payment.get(field)
|
||||||
|
self.assertEqual(
|
||||||
|
actual_value,
|
||||||
|
expected_value,
|
||||||
|
f"Field {field} does not match expected value. "
|
||||||
|
f"Expected: {expected_value}, Got: {actual_value}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Credit Note (return invoice) for first invoice (1000.0)
|
||||||
|
cr_note = self.create_credit_note(si1.name, do_not_submit=True)
|
||||||
|
cr_note.items[0].qty = -10 # 1 item of qty 10 at rate 100 (Amount: 1000.0)
|
||||||
|
cr_note.save().submit()
|
||||||
|
|
||||||
|
expected_after_cr_note = {
|
||||||
|
"party": "_Test Customer",
|
||||||
|
"party_name": "_Test Customer",
|
||||||
|
"opening_balance": 0,
|
||||||
|
"invoiced_amount": 1000.0,
|
||||||
|
"paid_amount": 1000.0,
|
||||||
|
"return_amount": 1000.0,
|
||||||
|
"closing_balance": -1000.0,
|
||||||
|
"currency": "INR",
|
||||||
|
"customer_name": "_Test Customer",
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)[1]
|
||||||
|
self.assertEqual(len(report), 1)
|
||||||
|
for field in expected_after_cr_note:
|
||||||
|
with self.subTest(field=field):
|
||||||
|
actual_value = report[0].get(field)
|
||||||
|
expected_value = expected_after_cr_note.get(field)
|
||||||
|
self.assertEqual(
|
||||||
|
actual_value,
|
||||||
|
expected_value,
|
||||||
|
f"Field {field} does not match expected value. "
|
||||||
|
f"Expected: {expected_value}, Got: {actual_value}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Payment Entry for the returned amount (1000.0) - Pay the customer back
|
||||||
|
pe2 = get_payment_entry("Sales Invoice", cr_note.name, bank_account=self.cash)
|
||||||
|
pe2.insert().submit()
|
||||||
|
|
||||||
|
expected_after_cr_and_return_payment = {
|
||||||
|
"party": "_Test Customer",
|
||||||
|
"party_name": "_Test Customer",
|
||||||
|
"opening_balance": 0,
|
||||||
|
"invoiced_amount": 1000.0,
|
||||||
|
"paid_amount": 0,
|
||||||
|
"return_amount": 1000.0,
|
||||||
|
"closing_balance": 0,
|
||||||
|
"currency": "INR",
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)[1]
|
||||||
|
self.assertEqual(len(report), 1)
|
||||||
|
for field in expected_after_cr_and_return_payment:
|
||||||
|
with self.subTest(field=field):
|
||||||
|
actual_value = report[0].get(field)
|
||||||
|
expected_value = expected_after_cr_and_return_payment.get(field)
|
||||||
|
self.assertEqual(
|
||||||
|
actual_value,
|
||||||
|
expected_value,
|
||||||
|
f"Field {field} does not match expected value. "
|
||||||
|
f"Expected: {expected_value}, Got: {actual_value}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create second Sales Invoice of 10 qty at rate 100 (Amount: 1000.0)
|
||||||
|
si2 = self.create_sales_invoice(do_not_submit=True)
|
||||||
|
si2.save().submit()
|
||||||
|
|
||||||
|
# Create Payment Entry (Receive) for the second invoice - payment (500.0)
|
||||||
|
pe3 = self.create_payment_entry(si2.name, True)
|
||||||
|
pe3.paid_amount = 500 # Partial payment 500.0
|
||||||
|
pe3.save().submit()
|
||||||
|
|
||||||
|
expected_after_cr_and_payment = {
|
||||||
|
"party": "_Test Customer",
|
||||||
|
"party_name": "_Test Customer",
|
||||||
|
"opening_balance": 0.0,
|
||||||
|
"invoiced_amount": 2000.0,
|
||||||
|
"paid_amount": 500.0,
|
||||||
|
"return_amount": 1000.0,
|
||||||
|
"closing_balance": 500.0,
|
||||||
|
"currency": "INR",
|
||||||
|
"customer_name": "_Test Customer",
|
||||||
|
}
|
||||||
|
|
||||||
|
report = execute(filters)[1]
|
||||||
|
self.assertEqual(len(report), 1)
|
||||||
|
for field in expected_after_cr_and_payment:
|
||||||
|
with self.subTest(field=field):
|
||||||
|
actual_value = report[0].get(field)
|
||||||
|
expected_value = expected_after_cr_and_payment.get(field)
|
||||||
|
self.assertEqual(
|
||||||
|
actual_value,
|
||||||
|
expected_value,
|
||||||
|
f"Field {field} does not match expected value. "
|
||||||
|
f"Expected: {expected_value}, Got: {actual_value}",
|
||||||
|
)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_ac
|
|||||||
"finance_book": cstr(filters.get("finance_book")),
|
"finance_book": cstr(filters.get("finance_book")),
|
||||||
}
|
}
|
||||||
|
|
||||||
gl_filters["dimensions"] = set(dimension_list)
|
gl_filters["dimensions"] = tuple(set(dimension_list))
|
||||||
|
|
||||||
if filters.get("include_default_book_entries"):
|
if filters.get("include_default_book_entries"):
|
||||||
gl_filters["company_fb"] = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
gl_filters["company_fb"] = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||||
@@ -179,7 +179,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name, dimension_list):
|
|||||||
def get_condition(dimension):
|
def get_condition(dimension):
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
conditions.append(f"{frappe.scrub(dimension)} in (%(dimensions)s)")
|
conditions.append(f"{frappe.scrub(dimension)} in %(dimensions)s")
|
||||||
|
|
||||||
return " and {}".format(" and ".join(conditions)) if conditions else ""
|
return " and {}".format(" and ".join(conditions)) if conditions else ""
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if filters.get("presentation_currency"):
|
if filters.get("presentation_currency"):
|
||||||
return convert_to_presentation_currency(gl_entries, currency_map)
|
return convert_to_presentation_currency(gl_entries, currency_map, filters)
|
||||||
else:
|
else:
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
"fieldname": "invoice",
|
"fieldname": "invoice",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Purchase Invoice",
|
"options": "Purchase Invoice",
|
||||||
"width": 120,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||||
]
|
]
|
||||||
@@ -310,8 +310,8 @@ def apply_conditions(query, pi, pii, filters):
|
|||||||
|
|
||||||
def get_items(filters, additional_table_columns):
|
def get_items(filters, additional_table_columns):
|
||||||
doctype = "Purchase Invoice"
|
doctype = "Purchase Invoice"
|
||||||
pi = frappe.qb.DocType(doctype)
|
pi = frappe.qb.DocType("Purchase Invoice")
|
||||||
pii = frappe.qb.DocType(f"{doctype} Item")
|
pii = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
Item = frappe.qb.DocType("Item")
|
Item = frappe.qb.DocType("Item")
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(pi)
|
frappe.qb.from_(pi)
|
||||||
@@ -331,6 +331,7 @@ def get_items(filters, additional_table_columns):
|
|||||||
pi.unrealized_profit_loss_account,
|
pi.unrealized_profit_loss_account,
|
||||||
pii.item_code,
|
pii.item_code,
|
||||||
pii.description,
|
pii.description,
|
||||||
|
pii.item_name,
|
||||||
pii.item_group,
|
pii.item_group,
|
||||||
pii.item_name.as_("pi_item_name"),
|
pii.item_name.as_("pi_item_name"),
|
||||||
pii.item_group.as_("pi_item_group"),
|
pii.item_group.as_("pi_item_group"),
|
||||||
@@ -374,7 +375,7 @@ def get_items(filters, additional_table_columns):
|
|||||||
if match_conditions:
|
if match_conditions:
|
||||||
query += " and " + match_conditions
|
query += " and " + match_conditions
|
||||||
|
|
||||||
query = apply_order_by_conditions(query, pi, pii, filters)
|
query = apply_order_by_conditions(doctype, query, filters)
|
||||||
|
|
||||||
return frappe.db.sql(query, params, as_dict=True)
|
return frappe.db.sql(query, params, as_dict=True)
|
||||||
|
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
"fieldname": "invoice",
|
"fieldname": "invoice",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
"width": 120,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||||
]
|
]
|
||||||
@@ -395,15 +395,18 @@ def apply_conditions(query, si, sii, sip, filters, additional_conditions=None):
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
def apply_order_by_conditions(query, si, ii, filters):
|
def apply_order_by_conditions(doctype, query, filters):
|
||||||
|
invoice = f"`tab{doctype}`"
|
||||||
|
invoice_item = f"`tab{doctype} Item`"
|
||||||
|
|
||||||
if not filters.get("group_by"):
|
if not filters.get("group_by"):
|
||||||
query += f" order by {si.posting_date} desc, {ii.item_group} desc"
|
query += f" order by {invoice}.posting_date desc, {invoice_item}.item_group desc"
|
||||||
elif filters.get("group_by") == "Invoice":
|
elif filters.get("group_by") == "Invoice":
|
||||||
query += f" order by {ii.parent} desc"
|
query += f" order by {invoice_item}.parent desc"
|
||||||
elif filters.get("group_by") == "Item":
|
elif filters.get("group_by") == "Item":
|
||||||
query += f" order by {ii.item_code}"
|
query += f" order by {invoice_item}.item_code"
|
||||||
elif filters.get("group_by") == "Item Group":
|
elif filters.get("group_by") == "Item Group":
|
||||||
query += f" order by {ii.item_group}"
|
query += f" order by {invoice_item}.item_group"
|
||||||
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
|
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
|
||||||
filter_field = frappe.scrub(filters.get("group_by"))
|
filter_field = frappe.scrub(filters.get("group_by"))
|
||||||
query += f" order by {filter_field} desc"
|
query += f" order by {filter_field} desc"
|
||||||
@@ -413,9 +416,9 @@ def apply_order_by_conditions(query, si, ii, filters):
|
|||||||
|
|
||||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||||
doctype = "Sales Invoice"
|
doctype = "Sales Invoice"
|
||||||
si = frappe.qb.DocType(doctype)
|
si = frappe.qb.DocType("Sales Invoice")
|
||||||
sip = frappe.qb.DocType(f"{doctype} Payment")
|
sii = frappe.qb.DocType("Sales Invoice Item")
|
||||||
sii = frappe.qb.DocType(f"{doctype} Item")
|
sip = frappe.qb.DocType("Sales Invoice Payment")
|
||||||
item = frappe.qb.DocType("Item")
|
item = frappe.qb.DocType("Item")
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
@@ -488,12 +491,12 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
|||||||
from frappe.desk.reportview import build_match_conditions
|
from frappe.desk.reportview import build_match_conditions
|
||||||
|
|
||||||
query, params = query.walk()
|
query, params = query.walk()
|
||||||
match_conditions = build_match_conditions("Sales Invoice")
|
match_conditions = build_match_conditions(doctype)
|
||||||
|
|
||||||
if match_conditions:
|
if match_conditions:
|
||||||
query += " and " + match_conditions
|
query += " and " + match_conditions
|
||||||
|
|
||||||
query = apply_order_by_conditions(query, si, sii, filters)
|
query = apply_order_by_conditions(doctype, query, filters)
|
||||||
|
|
||||||
return frappe.db.sql(query, params, as_dict=True)
|
return frappe.db.sql(query, params, as_dict=True)
|
||||||
|
|
||||||
@@ -763,25 +766,13 @@ def add_total_row(
|
|||||||
def get_display_value(filters, group_by_field, item):
|
def get_display_value(filters, group_by_field, item):
|
||||||
if filters.get("group_by") == "Item":
|
if filters.get("group_by") == "Item":
|
||||||
if item.get("item_code") != item.get("item_name"):
|
if item.get("item_code") != item.get("item_name"):
|
||||||
value = (
|
value = f"{item.get('item_code')}: {item.get('item_name')}"
|
||||||
cstr(item.get("item_code"))
|
|
||||||
+ "<br><br>"
|
|
||||||
+ "<span style='font-weight: normal'>"
|
|
||||||
+ cstr(item.get("item_name"))
|
|
||||||
+ "</span>"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
value = item.get("item_code", "")
|
value = item.get("item_code", "")
|
||||||
elif filters.get("group_by") in ("Customer", "Supplier"):
|
elif filters.get("group_by") in ("Customer", "Supplier"):
|
||||||
party = frappe.scrub(filters.get("group_by"))
|
party = frappe.scrub(filters.get("group_by"))
|
||||||
if item.get(party) != item.get(party + "_name"):
|
if item.get(party) != item.get(party + "_name"):
|
||||||
value = (
|
value = f"{item.get(party)}: {item.get(party + '_name')}"
|
||||||
item.get(party)
|
|
||||||
+ "<br><br>"
|
|
||||||
+ "<span style='font-weight: normal'>"
|
|
||||||
+ item.get(party + "_name")
|
|
||||||
+ "</span>"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
value = item.get(party)
|
value = item.get(party)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ def get_ordered_to_be_billed_data(args, filters=None):
|
|||||||
child_doctype.item_name,
|
child_doctype.item_name,
|
||||||
child_doctype.description,
|
child_doctype.description,
|
||||||
project_field,
|
project_field,
|
||||||
|
doctype.company,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(doctype.docstatus == 1)
|
(doctype.docstatus == 1)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class PaymentLedger:
|
|||||||
against_voucher_no=ple.against_voucher_no,
|
against_voucher_no=ple.against_voucher_no,
|
||||||
amount=ple.amount,
|
amount=ple.amount,
|
||||||
currency=ple.account_currency,
|
currency=ple.account_currency,
|
||||||
|
company=ple.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.filters.include_account_currency:
|
if self.filters.include_account_currency:
|
||||||
@@ -77,6 +78,7 @@ class PaymentLedger:
|
|||||||
against_voucher_no="Outstanding:",
|
against_voucher_no="Outstanding:",
|
||||||
amount=total,
|
amount=total,
|
||||||
currency=voucher_data[0].currency,
|
currency=voucher_data[0].currency,
|
||||||
|
company=voucher_data[0].company,
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.filters.include_account_currency:
|
if self.filters.include_account_currency:
|
||||||
@@ -85,7 +87,12 @@ class PaymentLedger:
|
|||||||
voucher_data.append(entry)
|
voucher_data.append(entry)
|
||||||
|
|
||||||
# empty row
|
# empty row
|
||||||
voucher_data.append(frappe._dict())
|
voucher_data.append(
|
||||||
|
frappe._dict(
|
||||||
|
currency=voucher_data[0].currency,
|
||||||
|
company=voucher_data[0].company,
|
||||||
|
)
|
||||||
|
)
|
||||||
self.data.extend(voucher_data)
|
self.data.extend(voucher_data)
|
||||||
|
|
||||||
def build_conditions(self):
|
def build_conditions(self):
|
||||||
@@ -130,7 +137,6 @@ class PaymentLedger:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_columns(self):
|
def get_columns(self):
|
||||||
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
|
|
||||||
options = None
|
options = None
|
||||||
self.columns.append(
|
self.columns.append(
|
||||||
dict(
|
dict(
|
||||||
@@ -195,7 +201,7 @@ class PaymentLedger:
|
|||||||
label=_("Amount"),
|
label=_("Amount"),
|
||||||
fieldname="amount",
|
fieldname="amount",
|
||||||
fieldtype="Currency",
|
fieldtype="Currency",
|
||||||
options=company_currency,
|
options="Company:company:default_currency",
|
||||||
width="100",
|
width="100",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def get_rate_as_at(date, from_currency, to_currency):
|
|||||||
return rate
|
return rate
|
||||||
|
|
||||||
|
|
||||||
def convert_to_presentation_currency(gl_entries, currency_info):
|
def convert_to_presentation_currency(gl_entries, currency_info, filters=None):
|
||||||
"""
|
"""
|
||||||
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
||||||
in `currency_info`.
|
in `currency_info`.
|
||||||
@@ -99,6 +99,13 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
|||||||
company_currency = currency_info["company_currency"]
|
company_currency = currency_info["company_currency"]
|
||||||
|
|
||||||
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
|
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
|
||||||
|
exchange_gain_or_loss = False
|
||||||
|
|
||||||
|
if filters and isinstance(filters.get("account"), list):
|
||||||
|
account_filter = filters.get("account")
|
||||||
|
gain_loss_account = frappe.db.get_value("Company", filters.company, "exchange_gain_loss_account")
|
||||||
|
|
||||||
|
exchange_gain_or_loss = len(account_filter) == 1 and account_filter[0] == gain_loss_account
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
debit = flt(entry["debit"])
|
debit = flt(entry["debit"])
|
||||||
@@ -107,7 +114,11 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
|||||||
credit_in_account_currency = flt(entry["credit_in_account_currency"])
|
credit_in_account_currency = flt(entry["credit_in_account_currency"])
|
||||||
account_currency = entry["account_currency"]
|
account_currency = entry["account_currency"]
|
||||||
|
|
||||||
if len(account_currencies) == 1 and account_currency == presentation_currency:
|
if (
|
||||||
|
len(account_currencies) == 1
|
||||||
|
and account_currency == presentation_currency
|
||||||
|
and not exchange_gain_or_loss
|
||||||
|
):
|
||||||
entry["debit"] = debit_in_account_currency
|
entry["debit"] = debit_in_account_currency
|
||||||
entry["credit"] = credit_in_account_currency
|
entry["credit"] = credit_in_account_currency
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
from json import loads
|
from json import loads
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ from frappe.utils import (
|
|||||||
nowdate,
|
nowdate,
|
||||||
)
|
)
|
||||||
from pypika import Order
|
from pypika import Order
|
||||||
|
from pypika.functions import Coalesce
|
||||||
from pypika.terms import ExistsCriterion
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -50,6 +52,7 @@ class PaymentEntryUnlinkError(frappe.ValidationError):
|
|||||||
|
|
||||||
|
|
||||||
GL_REPOSTING_CHUNK = 100
|
GL_REPOSTING_CHUNK = 100
|
||||||
|
OUTSTANDING_DOCTYPES = frozenset(["Sales Invoice", "Purchase Invoice", "Fees"])
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -480,63 +483,45 @@ def reconcile_against_document(
|
|||||||
reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
|
reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
|
||||||
|
|
||||||
for key, entries in reconciled_entries.items():
|
for key, entries in reconciled_entries.items():
|
||||||
voucher_type = key[0]
|
voucher_type, voucher_no = key
|
||||||
voucher_no = key[1]
|
|
||||||
|
|
||||||
# cancel advance entry
|
|
||||||
doc = frappe.get_doc(voucher_type, voucher_no)
|
doc = frappe.get_doc(voucher_type, voucher_no)
|
||||||
frappe.flags.ignore_party_validation = True
|
frappe.flags.ignore_party_validation = True
|
||||||
|
|
||||||
# When Advance is allocated from an Order to an Invoice
|
reposting_rows = []
|
||||||
# whole ledger must be reposted
|
|
||||||
repost_whole_ledger = any([x.voucher_detail_no for x in entries])
|
|
||||||
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
|
|
||||||
if repost_whole_ledger:
|
|
||||||
doc.make_gl_entries(cancel=1)
|
|
||||||
else:
|
|
||||||
doc.make_advance_gl_entries(cancel=1)
|
|
||||||
else:
|
|
||||||
_delete_pl_entries(voucher_type, voucher_no)
|
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
check_if_advance_entry_modified(entry)
|
check_if_advance_entry_modified(entry)
|
||||||
validate_allocated_amount(entry)
|
validate_allocated_amount(entry)
|
||||||
|
|
||||||
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
|
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
|
||||||
|
|
||||||
# update ref in advance entry
|
|
||||||
if voucher_type == "Journal Entry":
|
if voucher_type == "Journal Entry":
|
||||||
referenced_row, update_advance_paid = update_reference_in_journal_entry(
|
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
|
||||||
entry, doc, do_not_save=False
|
|
||||||
)
|
|
||||||
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
|
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
|
||||||
# amount and account in args
|
# amount and account in args
|
||||||
# referenced_row is used to deduplicate gain/loss journal
|
# referenced_row is used to deduplicate gain/loss journal
|
||||||
entry.update({"referenced_row": referenced_row})
|
entry.update({"referenced_row": referenced_row.name})
|
||||||
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
|
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
|
||||||
else:
|
else:
|
||||||
referenced_row, update_advance_paid = update_reference_in_payment_entry(
|
referenced_row = update_reference_in_payment_entry(
|
||||||
entry,
|
entry,
|
||||||
doc,
|
doc,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
|
skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
|
||||||
dimensions_dict=dimensions_dict,
|
dimensions_dict=dimensions_dict,
|
||||||
)
|
)
|
||||||
|
if referenced_row.get("outstanding_amount"):
|
||||||
|
referenced_row.outstanding_amount -= flt(entry.allocated_amount)
|
||||||
|
|
||||||
|
reposting_rows.append(referenced_row)
|
||||||
|
|
||||||
doc.save(ignore_permissions=True)
|
doc.save(ignore_permissions=True)
|
||||||
# re-submit advance entry
|
|
||||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
|
||||||
|
|
||||||
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
|
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
|
||||||
# When Advance is allocated from an Order to an Invoice
|
for row in reposting_rows:
|
||||||
# whole ledger must be reposted
|
doc.make_advance_gl_entries(entry=row)
|
||||||
if repost_whole_ledger:
|
|
||||||
doc.make_gl_entries()
|
|
||||||
else:
|
|
||||||
# both ledgers must be posted to for `Advance` in separate account feature
|
|
||||||
# TODO: find a more efficient way post only for the new linked vouchers
|
|
||||||
doc.make_advance_gl_entries()
|
|
||||||
else:
|
else:
|
||||||
|
_delete_pl_entries(voucher_type, voucher_no)
|
||||||
gl_map = doc.build_gl_map()
|
gl_map = doc.build_gl_map()
|
||||||
# Make sure there is no overallocation
|
# Make sure there is no overallocation
|
||||||
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
||||||
@@ -553,11 +538,6 @@ def reconcile_against_document(
|
|||||||
entry.party_type,
|
entry.party_type,
|
||||||
entry.party,
|
entry.party,
|
||||||
)
|
)
|
||||||
# update advance paid in Advance Receivable/Payable doctypes
|
|
||||||
if update_advance_paid:
|
|
||||||
for t, n in update_advance_paid:
|
|
||||||
frappe.get_lazy_doc(t, n).set_total_advance_paid()
|
|
||||||
|
|
||||||
frappe.flags.ignore_party_validation = False
|
frappe.flags.ignore_party_validation = False
|
||||||
|
|
||||||
|
|
||||||
@@ -643,12 +623,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
|||||||
"""
|
"""
|
||||||
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
||||||
|
|
||||||
# Update Advance Paid in SO/PO since they might be getting unlinked
|
|
||||||
update_advance_paid = []
|
|
||||||
|
|
||||||
if jv_detail.get("reference_type") in get_advance_payment_doctypes():
|
|
||||||
update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name))
|
|
||||||
|
|
||||||
rev_dr_or_cr = (
|
rev_dr_or_cr = (
|
||||||
"debit_in_account_currency"
|
"debit_in_account_currency"
|
||||||
if d["dr_or_cr"] == "credit_in_account_currency"
|
if d["dr_or_cr"] == "credit_in_account_currency"
|
||||||
@@ -701,6 +675,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
|||||||
new_row.is_advance = cstr(jv_detail.is_advance)
|
new_row.is_advance = cstr(jv_detail.is_advance)
|
||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
|
|
||||||
|
if jv_detail.get("reference_type") in get_advance_payment_doctypes():
|
||||||
|
new_row.advance_voucher_type = jv_detail.get("reference_type")
|
||||||
|
new_row.advance_voucher_no = jv_detail.get("reference_name")
|
||||||
|
|
||||||
# will work as update after submit
|
# will work as update after submit
|
||||||
journal_entry.flags.ignore_validate_update_after_submit = True
|
journal_entry.flags.ignore_validate_update_after_submit = True
|
||||||
# Ledgers will be reposted by Reconciliation tool
|
# Ledgers will be reposted by Reconciliation tool
|
||||||
@@ -708,7 +686,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
|||||||
if not do_not_save:
|
if not do_not_save:
|
||||||
journal_entry.save(ignore_permissions=True)
|
journal_entry.save(ignore_permissions=True)
|
||||||
|
|
||||||
return new_row.name, update_advance_paid
|
return new_row
|
||||||
|
|
||||||
|
|
||||||
def update_reference_in_payment_entry(
|
def update_reference_in_payment_entry(
|
||||||
@@ -727,20 +705,19 @@ def update_reference_in_payment_entry(
|
|||||||
"account": d.account,
|
"account": d.account,
|
||||||
"dimensions": d.dimensions,
|
"dimensions": d.dimensions,
|
||||||
}
|
}
|
||||||
update_advance_paid = []
|
|
||||||
|
advance_payment_doctypes = get_advance_payment_doctypes()
|
||||||
|
|
||||||
# Update Reconciliation effect date in reference
|
# Update Reconciliation effect date in reference
|
||||||
if payment_entry.book_advance_payments_in_separate_party_account:
|
if payment_entry.book_advance_payments_in_separate_party_account:
|
||||||
reconcile_on = get_reconciliation_effect_date(d, payment_entry.company, payment_entry.posting_date)
|
reconcile_on = get_reconciliation_effect_date(
|
||||||
|
d.against_voucher_type, d.against_voucher, payment_entry.company, payment_entry.posting_date
|
||||||
|
)
|
||||||
reference_details.update({"reconcile_effect_on": reconcile_on})
|
reference_details.update({"reconcile_effect_on": reconcile_on})
|
||||||
|
|
||||||
if d.voucher_detail_no:
|
if d.voucher_detail_no:
|
||||||
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
||||||
|
|
||||||
# Update Advance Paid in SO/PO since they are getting unlinked
|
|
||||||
if existing_row.get("reference_doctype") in get_advance_payment_doctypes():
|
|
||||||
update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name))
|
|
||||||
|
|
||||||
if d.allocated_amount <= existing_row.allocated_amount:
|
if d.allocated_amount <= existing_row.allocated_amount:
|
||||||
existing_row.allocated_amount -= d.allocated_amount
|
existing_row.allocated_amount -= d.allocated_amount
|
||||||
|
|
||||||
@@ -748,7 +725,13 @@ def update_reference_in_payment_entry(
|
|||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
for field in list(reference_details):
|
for field in list(reference_details):
|
||||||
new_row.set(field, reference_details[field])
|
new_row.set(field, reference_details[field])
|
||||||
|
|
||||||
|
if existing_row.reference_doctype in advance_payment_doctypes:
|
||||||
|
new_row.advance_voucher_type = existing_row.reference_doctype
|
||||||
|
new_row.advance_voucher_no = existing_row.reference_name
|
||||||
|
|
||||||
row = new_row
|
row = new_row
|
||||||
|
|
||||||
else:
|
else:
|
||||||
new_row = payment_entry.append("references")
|
new_row = payment_entry.append("references")
|
||||||
new_row.docstatus = 1
|
new_row.docstatus = 1
|
||||||
@@ -783,23 +766,25 @@ def update_reference_in_payment_entry(
|
|||||||
payment_entry.flags.ignore_reposting_on_reconciliation = True
|
payment_entry.flags.ignore_reposting_on_reconciliation = True
|
||||||
if not do_not_save:
|
if not do_not_save:
|
||||||
payment_entry.save(ignore_permissions=True)
|
payment_entry.save(ignore_permissions=True)
|
||||||
return row, update_advance_paid
|
|
||||||
|
return row
|
||||||
|
|
||||||
|
|
||||||
def get_reconciliation_effect_date(reference, company, posting_date):
|
def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date):
|
||||||
reconciliation_takes_effect_on = frappe.get_cached_value(
|
reconciliation_takes_effect_on = frappe.get_cached_value(
|
||||||
"Company", company, "reconciliation_takes_effect_on"
|
"Company", company, "reconciliation_takes_effect_on"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# default
|
||||||
|
reconcile_on = posting_date
|
||||||
|
|
||||||
if reconciliation_takes_effect_on == "Advance Payment Date":
|
if reconciliation_takes_effect_on == "Advance Payment Date":
|
||||||
reconcile_on = posting_date
|
reconcile_on = posting_date
|
||||||
elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
|
elif reconciliation_takes_effect_on == "Oldest Of Invoice Or Advance":
|
||||||
date_field = "posting_date"
|
date_field = "posting_date"
|
||||||
if reference.against_voucher_type in ["Sales Order", "Purchase Order"]:
|
if against_voucher_type in ["Sales Order", "Purchase Order"]:
|
||||||
date_field = "transaction_date"
|
date_field = "transaction_date"
|
||||||
reconcile_on = frappe.db.get_value(
|
reconcile_on = frappe.db.get_value(against_voucher_type, against_voucher, date_field)
|
||||||
reference.against_voucher_type, reference.against_voucher, date_field
|
|
||||||
)
|
|
||||||
if getdate(reconcile_on) < getdate(posting_date):
|
if getdate(reconcile_on) < getdate(posting_date):
|
||||||
reconcile_on = posting_date
|
reconcile_on = posting_date
|
||||||
elif reconciliation_takes_effect_on == "Reconciliation Date":
|
elif reconciliation_takes_effect_on == "Reconciliation Date":
|
||||||
@@ -960,6 +945,24 @@ def update_accounting_ledgers_after_reference_removal(
|
|||||||
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
|
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
|
||||||
ple_update_query.run()
|
ple_update_query.run()
|
||||||
|
|
||||||
|
# Advance Payment
|
||||||
|
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||||
|
adv_ple = (
|
||||||
|
qb.update(adv)
|
||||||
|
.set(adv.delinked, 1)
|
||||||
|
.set(adv.modified, now())
|
||||||
|
.set(adv.modified_by, frappe.session.user)
|
||||||
|
.where(adv.delinked == 0)
|
||||||
|
.where(
|
||||||
|
((adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no))
|
||||||
|
| ((adv.voucher_type == ref_type) & (adv.voucher_no == ref_no))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if payment_name:
|
||||||
|
adv_ple = adv_ple.where(adv.voucher_no == payment_name)
|
||||||
|
|
||||||
|
adv_ple.run()
|
||||||
|
|
||||||
|
|
||||||
def remove_ref_from_advance_section(ref_doc: object = None):
|
def remove_ref_from_advance_section(ref_doc: object = None):
|
||||||
# TODO: this might need some testing
|
# TODO: this might need some testing
|
||||||
@@ -996,6 +999,8 @@ def remove_ref_doc_link_from_jv(
|
|||||||
qb.update(jea)
|
qb.update(jea)
|
||||||
.set(jea.reference_type, None)
|
.set(jea.reference_type, None)
|
||||||
.set(jea.reference_name, None)
|
.set(jea.reference_name, None)
|
||||||
|
.set(jea.advance_voucher_type, None)
|
||||||
|
.set(jea.advance_voucher_no, None)
|
||||||
.set(jea.modified, now())
|
.set(jea.modified, now())
|
||||||
.set(jea.modified_by, frappe.session.user)
|
.set(jea.modified_by, frappe.session.user)
|
||||||
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
|
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
|
||||||
@@ -1357,6 +1362,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email", company=Non
|
|||||||
"payment_account": bank_account.name,
|
"payment_account": bank_account.name,
|
||||||
"currency": bank_account.account_currency,
|
"currency": bank_account.account_currency,
|
||||||
"payment_channel": payment_channel,
|
"payment_channel": payment_channel,
|
||||||
|
"company": company,
|
||||||
}
|
}
|
||||||
).insert(ignore_permissions=True, ignore_if_duplicate=True)
|
).insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||||
|
|
||||||
@@ -1519,6 +1525,11 @@ def _delete_pl_entries(voucher_type, voucher_no):
|
|||||||
qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run()
|
qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run()
|
||||||
|
|
||||||
|
|
||||||
|
def _delete_adv_pl_entries(voucher_type, voucher_no):
|
||||||
|
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||||
|
qb.from_(adv).delete().where((adv.voucher_type == voucher_type) & (adv.voucher_no == voucher_no)).run()
|
||||||
|
|
||||||
|
|
||||||
def _delete_gl_entries(voucher_type, voucher_no):
|
def _delete_gl_entries(voucher_type, voucher_no):
|
||||||
gle = qb.DocType("GL Entry")
|
gle = qb.DocType("GL Entry")
|
||||||
qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run()
|
qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run()
|
||||||
@@ -1838,6 +1849,11 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
|||||||
dr_or_cr *= -1
|
dr_or_cr *= -1
|
||||||
dr_or_cr_account_currency *= -1
|
dr_or_cr_account_currency *= -1
|
||||||
|
|
||||||
|
against_voucher_type = (
|
||||||
|
gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type
|
||||||
|
)
|
||||||
|
against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no
|
||||||
|
|
||||||
ple = frappe._dict(
|
ple = frappe._dict(
|
||||||
doctype="Payment Ledger Entry",
|
doctype="Payment Ledger Entry",
|
||||||
posting_date=gle.posting_date,
|
posting_date=gle.posting_date,
|
||||||
@@ -1852,14 +1868,12 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
|||||||
voucher_type=gle.voucher_type,
|
voucher_type=gle.voucher_type,
|
||||||
voucher_no=gle.voucher_no,
|
voucher_no=gle.voucher_no,
|
||||||
voucher_detail_no=gle.voucher_detail_no,
|
voucher_detail_no=gle.voucher_detail_no,
|
||||||
against_voucher_type=gle.against_voucher_type
|
against_voucher_type=against_voucher_type,
|
||||||
if gle.against_voucher_type
|
against_voucher_no=against_voucher_no,
|
||||||
else gle.voucher_type,
|
|
||||||
against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no,
|
|
||||||
account_currency=gle.account_currency,
|
account_currency=gle.account_currency,
|
||||||
amount=dr_or_cr,
|
amount=dr_or_cr,
|
||||||
amount_in_account_currency=dr_or_cr_account_currency,
|
amount_in_account_currency=dr_or_cr_account_currency,
|
||||||
delinked=True if cancel else False,
|
delinked=cancel,
|
||||||
remarks=gle.remarks,
|
remarks=gle.remarks,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1868,10 +1882,40 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
|||||||
for dimension in dimensions_and_defaults[0]:
|
for dimension in dimensions_and_defaults[0]:
|
||||||
ple[dimension.fieldname] = gle.get(dimension.fieldname)
|
ple[dimension.fieldname] = gle.get(dimension.fieldname)
|
||||||
|
|
||||||
|
if gle.advance_voucher_no:
|
||||||
|
# create advance entry
|
||||||
|
adv = get_advance_ledger_entry(
|
||||||
|
gle, against_voucher_type, against_voucher_no, dr_or_cr_account_currency, cancel
|
||||||
|
)
|
||||||
|
|
||||||
|
ple_map.append(adv)
|
||||||
|
|
||||||
ple_map.append(ple)
|
ple_map.append(ple)
|
||||||
|
|
||||||
return ple_map
|
return ple_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amount, cancel):
|
||||||
|
event = (
|
||||||
|
"Submit"
|
||||||
|
if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no)
|
||||||
|
else "Adjustment"
|
||||||
|
)
|
||||||
|
return frappe._dict(
|
||||||
|
doctype="Advance Payment Ledger Entry",
|
||||||
|
company=gle.company,
|
||||||
|
voucher_type=gle.voucher_type,
|
||||||
|
voucher_no=gle.voucher_no,
|
||||||
|
voucher_detail_no=gle.voucher_detail_no,
|
||||||
|
against_voucher_type=gle.advance_voucher_type,
|
||||||
|
against_voucher_no=gle.advance_voucher_no,
|
||||||
|
amount=amount,
|
||||||
|
currency=gle.account_currency,
|
||||||
|
event=event,
|
||||||
|
delinked=cancel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_payment_ledger_entry(
|
def create_payment_ledger_entry(
|
||||||
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
|
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
|
||||||
):
|
):
|
||||||
@@ -1892,49 +1936,74 @@ def create_payment_ledger_entry(
|
|||||||
|
|
||||||
|
|
||||||
def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
|
def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
|
||||||
|
if not voucher_type or not voucher_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
if voucher_type in ["Purchase Order", "Sales Order"]:
|
||||||
|
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
||||||
|
ref_doc.set_total_advance_paid()
|
||||||
|
return
|
||||||
|
|
||||||
|
if not (voucher_type in OUTSTANDING_DOCTYPES and party_type and party):
|
||||||
|
return
|
||||||
|
|
||||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||||
vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
|
vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
|
||||||
common_filter = []
|
common_filter = []
|
||||||
|
common_filter.append(ple.party_type == party_type)
|
||||||
|
common_filter.append(ple.party == party)
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
common_filter.append(ple.account == account)
|
common_filter.append(ple.account == account)
|
||||||
|
|
||||||
if party_type:
|
|
||||||
common_filter.append(ple.party_type == party_type)
|
|
||||||
|
|
||||||
if party:
|
|
||||||
common_filter.append(ple.party == party)
|
|
||||||
|
|
||||||
ple_query = QueryPaymentLedger()
|
ple_query = QueryPaymentLedger()
|
||||||
|
|
||||||
# on cancellation outstanding can be an empty list
|
# on cancellation outstanding can be an empty list
|
||||||
voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter)
|
voucher_outstanding = ple_query.get_voucher_outstandings(vouchers, common_filter=common_filter)
|
||||||
if (
|
if not voucher_outstanding:
|
||||||
voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]
|
return
|
||||||
and party_type
|
|
||||||
and party
|
|
||||||
and voucher_outstanding
|
|
||||||
):
|
|
||||||
outstanding = voucher_outstanding[0]
|
|
||||||
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
|
||||||
outstanding_amount = flt(
|
|
||||||
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Didn't use db_set for optimisation purpose
|
outstanding = voucher_outstanding[0]
|
||||||
ref_doc.outstanding_amount = outstanding_amount
|
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
||||||
frappe.db.set_value(
|
outstanding_amount = flt(
|
||||||
voucher_type,
|
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
|
||||||
voucher_no,
|
)
|
||||||
"outstanding_amount",
|
|
||||||
outstanding_amount,
|
|
||||||
)
|
|
||||||
|
|
||||||
ref_doc.set_status(update=True)
|
# Didn't use db_set for optimisation purpose
|
||||||
ref_doc.notify_update()
|
ref_doc.outstanding_amount = outstanding_amount
|
||||||
|
frappe.db.set_value(
|
||||||
|
voucher_type,
|
||||||
|
voucher_no,
|
||||||
|
"outstanding_amount",
|
||||||
|
outstanding_amount,
|
||||||
|
)
|
||||||
|
|
||||||
|
ref_doc.set_status(update=True)
|
||||||
|
ref_doc.notify_update()
|
||||||
|
|
||||||
|
|
||||||
def delink_original_entry(pl_entry, partial_cancel=False):
|
def delink_original_entry(pl_entry, partial_cancel=False):
|
||||||
if pl_entry:
|
if not pl_entry:
|
||||||
|
return
|
||||||
|
|
||||||
|
if pl_entry.doctype == "Advance Payment Ledger Entry":
|
||||||
|
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||||
|
|
||||||
|
(
|
||||||
|
qb.update(adv)
|
||||||
|
.set(adv.delinked, 1)
|
||||||
|
.set(adv.event, "Cancel")
|
||||||
|
.set(adv.modified, now())
|
||||||
|
.set(adv.modified_by, frappe.session.user)
|
||||||
|
.where(adv.voucher_type == pl_entry.voucher_type)
|
||||||
|
.where(adv.voucher_no == pl_entry.voucher_no)
|
||||||
|
.where(adv.against_voucher_type == pl_entry.against_voucher_type)
|
||||||
|
.where(adv.against_voucher_no == pl_entry.against_voucher_no)
|
||||||
|
.where(adv.event == pl_entry.event)
|
||||||
|
.run()
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
ple = qb.DocType("Payment Ledger Entry")
|
ple = qb.DocType("Payment Ledger Entry")
|
||||||
query = (
|
query = (
|
||||||
qb.update(ple)
|
qb.update(ple)
|
||||||
@@ -2383,17 +2452,37 @@ def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15):
|
|||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
|
||||||
|
def get_link_fields_grouped_by_option(doctype):
|
||||||
|
meta = frappe.get_meta(doctype)
|
||||||
|
link_fields_map = defaultdict(list)
|
||||||
|
|
||||||
|
for df in meta.fields:
|
||||||
|
if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions:
|
||||||
|
link_fields_map[df.options].append(df.fieldname)
|
||||||
|
|
||||||
|
return link_fields_map
|
||||||
|
|
||||||
|
|
||||||
def build_qb_match_conditions(doctype, user=None) -> list:
|
def build_qb_match_conditions(doctype, user=None) -> list:
|
||||||
match_filters = build_match_conditions(doctype, user, False)
|
match_filters = build_match_conditions(doctype, user, False)
|
||||||
|
link_fields_map = get_link_fields_grouped_by_option(doctype)
|
||||||
criterion = []
|
criterion = []
|
||||||
if match_filters:
|
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
|
||||||
from frappe import qb
|
|
||||||
|
|
||||||
|
if match_filters:
|
||||||
_dt = qb.DocType(doctype)
|
_dt = qb.DocType(doctype)
|
||||||
|
|
||||||
for filter in match_filters:
|
for filter in match_filters:
|
||||||
for d, names in filter.items():
|
for link_option, allowed_values in filter.items():
|
||||||
fieldname = d.lower().replace(" ", "_")
|
fieldnames = link_fields_map.get(link_option, [])
|
||||||
criterion.append(_dt[fieldname].isin(names))
|
|
||||||
|
for fieldname in fieldnames:
|
||||||
|
field = _dt[fieldname]
|
||||||
|
cond = field.isin(allowed_values)
|
||||||
|
|
||||||
|
if not apply_strict_user_permissions:
|
||||||
|
cond = (Coalesce(field, "") == "") | cond
|
||||||
|
|
||||||
|
criterion.append(cond)
|
||||||
|
|
||||||
return criterion
|
return criterion
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ erpnext.buying.setup_buying_controller();
|
|||||||
|
|
||||||
frappe.ui.form.on("Purchase Order", {
|
frappe.ui.form.on("Purchase Order", {
|
||||||
setup: function (frm) {
|
setup: function (frm) {
|
||||||
frm.ignore_doctypes_on_cancel_all = ["Unreconcile Payment", "Unreconcile Payment Entries"];
|
|
||||||
if (frm.doc.is_old_subcontracting_flow) {
|
if (frm.doc.is_old_subcontracting_flow) {
|
||||||
frm.set_query("reserve_warehouse", "supplied_items", function () {
|
frm.set_query("reserve_warehouse", "supplied_items", function () {
|
||||||
return {
|
return {
|
||||||
@@ -154,6 +153,10 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onload: function (frm) {
|
onload: function (frm) {
|
||||||
|
var ignore_list = ["Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||||
|
frm.ignore_doctypes_on_cancel_all = Object.hasOwn(frm, "ignore_doctypes_on_cancel_all")
|
||||||
|
? frm.ignore_doctypes_on_cancel_all.concat(ignore_list)
|
||||||
|
: ignore_list;
|
||||||
set_schedule_date(frm);
|
set_schedule_date(frm);
|
||||||
if (!frm.doc.transaction_date) {
|
if (!frm.doc.transaction_date) {
|
||||||
frm.set_value("transaction_date", frappe.datetime.get_today());
|
frm.set_value("transaction_date", frappe.datetime.get_today());
|
||||||
|
|||||||
@@ -512,6 +512,7 @@ class PurchaseOrder(BuyingController):
|
|||||||
self.ignore_linked_doctypes = (
|
self.ignore_linked_doctypes = (
|
||||||
"GL Entry",
|
"GL Entry",
|
||||||
"Payment Ledger Entry",
|
"Payment Ledger Entry",
|
||||||
|
"Advance Payment Ledger Entry",
|
||||||
"Unreconcile Payment",
|
"Unreconcile Payment",
|
||||||
"Unreconcile Payment Entries",
|
"Unreconcile Payment Entries",
|
||||||
)
|
)
|
||||||
@@ -743,6 +744,7 @@ def close_or_unclose_purchase_orders(names, status):
|
|||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
target.run_method("set_use_serial_batch_fields")
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -394,7 +394,6 @@ class AccountsController(TransactionBase):
|
|||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
from erpnext.accounts.utils import delete_exchange_gain_loss_journal
|
from erpnext.accounts.utils import delete_exchange_gain_loss_journal
|
||||||
|
|
||||||
self._remove_advance_payment_ledger_entries()
|
|
||||||
self._remove_references_in_repost_doctypes()
|
self._remove_references_in_repost_doctypes()
|
||||||
self._remove_references_in_unreconcile()
|
self._remove_references_in_unreconcile()
|
||||||
self.remove_serial_and_batch_bundle()
|
self.remove_serial_and_batch_bundle()
|
||||||
@@ -423,6 +422,8 @@ class AccountsController(TransactionBase):
|
|||||||
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
|
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
|
self._remove_advance_payment_ledger_entries()
|
||||||
|
|
||||||
def remove_serial_and_batch_bundle(self):
|
def remove_serial_and_batch_bundle(self):
|
||||||
bundles = frappe.get_all(
|
bundles = frappe.get_all(
|
||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
@@ -2212,55 +2213,30 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
def calculate_total_advance_from_ledger(self):
|
def calculate_total_advance_from_ledger(self):
|
||||||
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
|
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
|
||||||
advance = (
|
return (
|
||||||
frappe.qb.from_(adv)
|
qb.from_(adv)
|
||||||
.select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount"))
|
.select(Abs(Sum(adv.amount)).as_("amount"), adv.currency.as_("account_currency"))
|
||||||
.where(
|
.where(adv.company == self.company)
|
||||||
(adv.against_voucher_type == self.doctype)
|
.where(adv.delinked == 0)
|
||||||
& (adv.against_voucher_no == self.name)
|
.where(adv.against_voucher_type == self.doctype)
|
||||||
& (adv.company == self.company)
|
.where(adv.against_voucher_no == self.name)
|
||||||
)
|
|
||||||
.run(as_dict=True)
|
.run(as_dict=True)
|
||||||
)
|
)
|
||||||
return advance
|
|
||||||
|
|
||||||
def set_total_advance_paid(self):
|
def set_total_advance_paid(self):
|
||||||
advance = self.calculate_total_advance_from_ledger()
|
advance = self.calculate_total_advance_from_ledger()
|
||||||
advance_paid, order_total = None, None
|
advance_paid = 0
|
||||||
|
|
||||||
if advance:
|
if advance:
|
||||||
advance = advance[0]
|
advance = advance[0]
|
||||||
|
|
||||||
advance_paid = flt(advance.amount, self.precision("advance_paid"))
|
advance_paid = flt(advance.amount, self.precision("advance_paid"))
|
||||||
formatted_advance_paid = fmt_money(
|
|
||||||
advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency
|
|
||||||
)
|
|
||||||
|
|
||||||
if advance.account_currency:
|
if advance.account_currency:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value(
|
||||||
self.doctype, self.name, "party_account_currency", advance.account_currency
|
self.doctype, self.name, "party_account_currency", advance.account_currency
|
||||||
)
|
)
|
||||||
|
|
||||||
if advance.account_currency == self.currency:
|
self.db_set("advance_paid", advance_paid)
|
||||||
order_total = self.get("rounded_total") or self.grand_total
|
|
||||||
precision = "rounded_total" if self.get("rounded_total") else "grand_total"
|
|
||||||
else:
|
|
||||||
order_total = self.get("base_rounded_total") or self.base_grand_total
|
|
||||||
precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
|
|
||||||
|
|
||||||
formatted_order_total = fmt_money(
|
|
||||||
order_total, precision=self.precision(precision), currency=advance.account_currency
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.currency == self.company_currency and advance_paid > order_total:
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})"
|
|
||||||
).format(formatted_advance_paid, self.name, formatted_order_total)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.db_set("advance_paid", advance_paid)
|
|
||||||
|
|
||||||
self.set_advance_payment_status()
|
self.set_advance_payment_status()
|
||||||
|
|
||||||
def set_advance_payment_status(self):
|
def set_advance_payment_status(self):
|
||||||
@@ -2656,7 +2632,10 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
if li:
|
if li:
|
||||||
duplicates = "<br>" + "<br>".join(li)
|
duplicates = "<br>" + "<br>".join(li)
|
||||||
frappe.throw(_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates))
|
frappe.throw(
|
||||||
|
_("Rows with duplicate due dates in other rows were found: {0}").format(duplicates),
|
||||||
|
title=_("Payment Schedule"),
|
||||||
|
)
|
||||||
|
|
||||||
def validate_payment_schedule_amount(self):
|
def validate_payment_schedule_amount(self):
|
||||||
if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
|
if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
|
||||||
@@ -2937,64 +2916,6 @@ class AccountsController(TransactionBase):
|
|||||||
def get_advance_payment_doctypes(self, payment_type=None) -> list:
|
def get_advance_payment_doctypes(self, payment_type=None) -> list:
|
||||||
return _get_advance_payment_doctypes(payment_type=payment_type)
|
return _get_advance_payment_doctypes(payment_type=payment_type)
|
||||||
|
|
||||||
def make_advance_payment_ledger_for_journal(self):
|
|
||||||
advance_payment_doctypes = self.get_advance_payment_doctypes()
|
|
||||||
advance_doctype_references = [
|
|
||||||
x for x in self.accounts if x.reference_type in advance_payment_doctypes
|
|
||||||
]
|
|
||||||
|
|
||||||
for x in advance_doctype_references:
|
|
||||||
# Looking for payments
|
|
||||||
dr_or_cr = (
|
|
||||||
"credit_in_account_currency"
|
|
||||||
if x.account_type == "Receivable"
|
|
||||||
else "debit_in_account_currency"
|
|
||||||
)
|
|
||||||
|
|
||||||
amount = x.get(dr_or_cr)
|
|
||||||
if amount > 0:
|
|
||||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
|
||||||
doc.company = self.company
|
|
||||||
doc.voucher_type = self.doctype
|
|
||||||
doc.voucher_no = self.name
|
|
||||||
doc.against_voucher_type = x.reference_type
|
|
||||||
doc.against_voucher_no = x.reference_name
|
|
||||||
doc.amount = amount if self.docstatus == 1 else -1 * amount
|
|
||||||
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
|
|
||||||
doc.currency = x.account_currency
|
|
||||||
doc.flags.ignore_permissions = 1
|
|
||||||
doc.save()
|
|
||||||
|
|
||||||
def make_advance_payment_ledger_for_payment(self):
|
|
||||||
advance_payment_doctypes = self.get_advance_payment_doctypes()
|
|
||||||
advance_doctype_references = [
|
|
||||||
x for x in self.references if x.reference_doctype in advance_payment_doctypes
|
|
||||||
]
|
|
||||||
currency = (
|
|
||||||
self.paid_from_account_currency
|
|
||||||
if self.payment_type == "Receive"
|
|
||||||
else self.paid_to_account_currency
|
|
||||||
)
|
|
||||||
for x in advance_doctype_references:
|
|
||||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
|
||||||
doc.company = self.company
|
|
||||||
doc.voucher_type = self.doctype
|
|
||||||
doc.voucher_no = self.name
|
|
||||||
doc.against_voucher_type = x.reference_doctype
|
|
||||||
doc.against_voucher_no = x.reference_name
|
|
||||||
doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount
|
|
||||||
doc.currency = currency
|
|
||||||
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
|
|
||||||
doc.flags.ignore_permissions = 1
|
|
||||||
doc.save()
|
|
||||||
|
|
||||||
def make_advance_payment_ledger_entries(self):
|
|
||||||
if self.docstatus != 0:
|
|
||||||
if self.doctype == "Journal Entry":
|
|
||||||
self.make_advance_payment_ledger_for_journal()
|
|
||||||
elif self.doctype == "Payment Entry":
|
|
||||||
self.make_advance_payment_ledger_for_payment()
|
|
||||||
|
|
||||||
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
|
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
|
||||||
for x in gl_entries:
|
for x in gl_entries:
|
||||||
x["transaction_currency"] = self.currency
|
x["transaction_currency"] = self.currency
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ class BuyingController(SubcontractingController):
|
|||||||
tax_accounts, total_valuation_amount, total_actual_tax_amount = self.get_tax_details()
|
tax_accounts, total_valuation_amount, total_actual_tax_amount = self.get_tax_details()
|
||||||
|
|
||||||
for i, item in enumerate(self.get("items")):
|
for i, item in enumerate(self.get("items")):
|
||||||
if item.item_code and item.qty:
|
if item.item_code and (item.qty or item.get("rejected_qty")):
|
||||||
item_tax_amount, actual_tax_amount = 0.0, 0.0
|
item_tax_amount, actual_tax_amount = 0.0, 0.0
|
||||||
if i == (last_item_idx - 1):
|
if i == (last_item_idx - 1):
|
||||||
item_tax_amount = total_valuation_amount
|
item_tax_amount = total_valuation_amount
|
||||||
@@ -387,7 +387,19 @@ class BuyingController(SubcontractingController):
|
|||||||
if item.sales_incoming_rate: # for internal transfer
|
if item.sales_incoming_rate: # for internal transfer
|
||||||
net_rate = item.qty * item.sales_incoming_rate
|
net_rate = item.qty * item.sales_incoming_rate
|
||||||
|
|
||||||
|
if (
|
||||||
|
not net_rate
|
||||||
|
and item.get("rejected_qty")
|
||||||
|
and frappe.get_single_value(
|
||||||
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
|
)
|
||||||
|
):
|
||||||
|
net_rate = item.rejected_qty * item.net_rate
|
||||||
|
|
||||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||||
|
if not qty_in_stock_uom and item.get("rejected_qty"):
|
||||||
|
qty_in_stock_uom = flt(item.rejected_qty * item.conversion_factor)
|
||||||
|
|
||||||
if self.get("is_old_subcontracting_flow"):
|
if self.get("is_old_subcontracting_flow"):
|
||||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||||
item.valuation_rate = (
|
item.valuation_rate = (
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ status_map = {
|
|||||||
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
|
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
|
||||||
[
|
[
|
||||||
"Completed",
|
"Completed",
|
||||||
"eval:(self.per_billed == 100 and self.docstatus == 1) or (self.docstatus == 1 and self.grand_total == 0 and self.per_returned != 100 and self.is_return == 0)",
|
"eval:(self.per_billed >= 100 and self.docstatus == 1) or (self.docstatus == 1 and self.grand_total == 0 and self.per_returned != 100 and self.is_return == 0)",
|
||||||
],
|
],
|
||||||
["Cancelled", "eval:self.docstatus==2"],
|
["Cancelled", "eval:self.docstatus==2"],
|
||||||
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
|
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
|
||||||
|
|||||||
@@ -663,7 +663,9 @@ class StockController(AccountsController):
|
|||||||
).format(wh, self.company)
|
).format(wh, self.company)
|
||||||
)
|
)
|
||||||
|
|
||||||
return process_gl_map(gl_list, precision=precision)
|
return process_gl_map(
|
||||||
|
gl_list, precision=precision, from_repost=frappe.flags.through_repost_item_valuation
|
||||||
|
)
|
||||||
|
|
||||||
def get_debit_field_precision(self):
|
def get_debit_field_precision(self):
|
||||||
if not frappe.flags.debit_field_precision:
|
if not frappe.flags.debit_field_precision:
|
||||||
|
|||||||
@@ -144,4 +144,3 @@ def set_email_campaign_status():
|
|||||||
for entry in email_campaigns:
|
for entry in email_campaigns:
|
||||||
email_campaign = frappe.get_doc("Email Campaign", entry.name)
|
email_campaign = frappe.get_doc("Email Campaign", entry.name)
|
||||||
email_campaign.update_status()
|
email_campaign.update_status()
|
||||||
email_campaign.save()
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
frappe.provide("erpnext");
|
frappe.provide("erpnext");
|
||||||
cur_frm.email_field = "email_id";
|
if (this.frm) {
|
||||||
|
this.frm.email_field = "email_id";
|
||||||
|
}
|
||||||
erpnext.LeadController = class LeadController extends frappe.ui.form.Controller {
|
erpnext.LeadController = class LeadController extends frappe.ui.form.Controller {
|
||||||
setup() {
|
setup() {
|
||||||
this.frm.make_methods = {
|
this.frm.make_methods = {
|
||||||
@@ -238,5 +239,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
|||||||
crm_activities.refresh();
|
crm_activities.refresh();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (this.frm) {
|
||||||
extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));
|
extend_cscript(this.frm.cscript, new erpnext.LeadController({ frm: this.frm }));
|
||||||
|
}
|
||||||
|
|||||||
@@ -153,7 +153,12 @@ class OpportunitySummaryBySalesStage:
|
|||||||
}[self.filters.get("based_on")]
|
}[self.filters.get("based_on")]
|
||||||
|
|
||||||
if self.filters.get("based_on") == "Opportunity Owner":
|
if self.filters.get("based_on") == "Opportunity Owner":
|
||||||
if d.get(based_on) == "[]" or d.get(based_on) is None or d.get(based_on) == "Not Assigned":
|
if (
|
||||||
|
d.get(based_on) == "[]"
|
||||||
|
or d.get(based_on) is None
|
||||||
|
or d.get(based_on) == "Not Assigned"
|
||||||
|
or d.get(based_on) == ""
|
||||||
|
):
|
||||||
assignments = ["Not Assigned"]
|
assignments = ["Not Assigned"]
|
||||||
else:
|
else:
|
||||||
assignments = json.loads(d.get(based_on))
|
assignments = json.loads(d.get(based_on))
|
||||||
|
|||||||
2100
erpnext/locale/ar.po
2100
erpnext/locale/ar.po
File diff suppressed because it is too large
Load Diff
2104
erpnext/locale/bs.po
2104
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/cs.po
2100
erpnext/locale/cs.po
File diff suppressed because it is too large
Load Diff
2228
erpnext/locale/de.po
2228
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
2102
erpnext/locale/eo.po
2102
erpnext/locale/eo.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/es.po
2100
erpnext/locale/es.po
File diff suppressed because it is too large
Load Diff
2522
erpnext/locale/fa.po
2522
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/fr.po
2100
erpnext/locale/fr.po
File diff suppressed because it is too large
Load Diff
2102
erpnext/locale/hr.po
2102
erpnext/locale/hr.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/hu.po
2100
erpnext/locale/hu.po
File diff suppressed because it is too large
Load Diff
96884
erpnext/locale/id.po
96884
erpnext/locale/id.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/it.po
2100
erpnext/locale/it.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/nl.po
2100
erpnext/locale/nl.po
File diff suppressed because it is too large
Load Diff
2102
erpnext/locale/pl.po
2102
erpnext/locale/pl.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/pt.po
2100
erpnext/locale/pt.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/ru.po
2100
erpnext/locale/ru.po
File diff suppressed because it is too large
Load Diff
2124
erpnext/locale/sr.po
2124
erpnext/locale/sr.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2114
erpnext/locale/sv.po
2114
erpnext/locale/sv.po
File diff suppressed because it is too large
Load Diff
2108
erpnext/locale/th.po
2108
erpnext/locale/th.po
File diff suppressed because it is too large
Load Diff
2102
erpnext/locale/tr.po
2102
erpnext/locale/tr.po
File diff suppressed because it is too large
Load Diff
2100
erpnext/locale/vi.po
2100
erpnext/locale/vi.po
File diff suppressed because it is too large
Load Diff
2102
erpnext/locale/zh.po
2102
erpnext/locale/zh.po
File diff suppressed because it is too large
Load Diff
@@ -76,6 +76,7 @@
|
|||||||
"fieldname": "hour_rate",
|
"fieldname": "hour_rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Hour Rate",
|
"label": "Hour Rate",
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "hour_rate",
|
"oldfieldname": "hour_rate",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"options": "currency",
|
"options": "currency",
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Operation Time",
|
"label": "Operation Time",
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "time_in_mins",
|
"oldfieldname": "time_in_mins",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
@@ -285,12 +287,13 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-01-09 15:45:37.695800",
|
"modified": "2025-07-31 16:17:47.287117",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Operation",
|
"name": "BOM Operation",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": []
|
"states": []
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Qty",
|
"label": "Qty",
|
||||||
|
"non_negative": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -49,6 +50,7 @@
|
|||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
|
"non_negative": 1,
|
||||||
"options": "currency"
|
"options": "currency"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -92,13 +94,14 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:06:41.395036",
|
"modified": "2025-07-31 16:21:44.047007",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Scrap Item",
|
"name": "BOM Scrap Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ frappe.ui.form.on("Job Card", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.events.set_company_filters(frm, "source_warehouse");
|
||||||
|
frm.events.set_company_filters(frm, "wip_warehouse");
|
||||||
|
frm.set_query("source_warehouse", "items", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_indicator_formatter("sub_operation", function (doc) {
|
frm.set_indicator_formatter("sub_operation", function (doc) {
|
||||||
if (doc.status == "Pending") {
|
if (doc.status == "Pending") {
|
||||||
return "red";
|
return "red";
|
||||||
@@ -32,6 +42,16 @@ frappe.ui.form.on("Job Card", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
set_company_filters(frm, fieldname) {
|
||||||
|
frm.set_query(fieldname, () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
make_fields_read_only(frm) {
|
make_fields_read_only(frm) {
|
||||||
if (frm.doc.docstatus === 1) {
|
if (frm.doc.docstatus === 1) {
|
||||||
frm.set_df_property("employee", "read_only", 1);
|
frm.set_df_property("employee", "read_only", 1);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Qty",
|
"label": "Qty",
|
||||||
|
"non_negative": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-03-27 13:09:57.323835",
|
"modified": "2025-07-29 13:09:57.323835",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card Scrap Item",
|
"name": "Job Card Scrap Item",
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ frappe.ui.form.on("Production Plan", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("sub_assembly_warehouse", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("material_request", "material_requests", function () {
|
frm.set_query("material_request", "material_requests", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -349,13 +349,18 @@ frappe.ui.form.on("Work Order", {
|
|||||||
return operations_data;
|
return operations_data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function (data) {
|
function () {
|
||||||
|
const selected_rows = dialog.fields_dict["operations"].grid.get_selected_children();
|
||||||
|
if (selected_rows.length == 0) {
|
||||||
|
frappe.msgprint(__("Please select atleast one operation to create Job Card"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
|
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
args: {
|
args: {
|
||||||
work_order: frm.doc.name,
|
work_order: frm.doc.name,
|
||||||
operations: data.operations,
|
operations: selected_rows,
|
||||||
},
|
},
|
||||||
callback: function () {
|
callback: function () {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
@@ -366,7 +371,7 @@ frappe.ui.form.on("Work Order", {
|
|||||||
__("Create")
|
__("Create")
|
||||||
);
|
);
|
||||||
|
|
||||||
dialog.fields_dict["operations"].grid.wrapper.find(".grid-add-row").hide();
|
dialog.fields_dict["operations"].grid.grid_buttons.hide();
|
||||||
|
|
||||||
var pending_qty = 0;
|
var pending_qty = 0;
|
||||||
frm.doc.operations.forEach((data) => {
|
frm.doc.operations.forEach((data) => {
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
"fieldname": "hour_rate_electricity",
|
"fieldname": "hour_rate_electricity",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Electricity Cost",
|
"label": "Electricity Cost",
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "hour_rate_electricity",
|
"oldfieldname": "hour_rate_electricity",
|
||||||
"oldfieldtype": "Currency"
|
"oldfieldtype": "Currency"
|
||||||
},
|
},
|
||||||
@@ -83,6 +84,7 @@
|
|||||||
"fieldname": "hour_rate_consumable",
|
"fieldname": "hour_rate_consumable",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Consumable Cost",
|
"label": "Consumable Cost",
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "hour_rate_consumable",
|
"oldfieldname": "hour_rate_consumable",
|
||||||
"oldfieldtype": "Currency"
|
"oldfieldtype": "Currency"
|
||||||
},
|
},
|
||||||
@@ -96,6 +98,7 @@
|
|||||||
"fieldname": "hour_rate_rent",
|
"fieldname": "hour_rate_rent",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rent Cost",
|
"label": "Rent Cost",
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "hour_rate_rent",
|
"oldfieldname": "hour_rate_rent",
|
||||||
"oldfieldtype": "Currency"
|
"oldfieldtype": "Currency"
|
||||||
},
|
},
|
||||||
@@ -105,6 +108,7 @@
|
|||||||
"fieldname": "hour_rate_labour",
|
"fieldname": "hour_rate_labour",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Wages",
|
"label": "Wages",
|
||||||
|
"non_negative": 1,
|
||||||
"oldfieldname": "hour_rate_labour",
|
"oldfieldname": "hour_rate_labour",
|
||||||
"oldfieldtype": "Currency"
|
"oldfieldtype": "Currency"
|
||||||
},
|
},
|
||||||
@@ -140,6 +144,7 @@
|
|||||||
"fieldname": "production_capacity",
|
"fieldname": "production_capacity",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Job Capacity",
|
"label": "Job Capacity",
|
||||||
|
"non_negative": 1,
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -254,7 +259,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"image_field": "on_status_image",
|
"image_field": "on_status_image",
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-09-26 13:41:12.279344",
|
"modified": "2025-07-13 16:02:13.615001",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Workstation",
|
"name": "Workstation",
|
||||||
@@ -274,6 +279,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
|
"row_format": "Dynamic",
|
||||||
"show_name_in_global_search": 1,
|
"show_name_in_global_search": 1,
|
||||||
"sort_field": "creation",
|
"sort_field": "creation",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ erpnext.patches.v14_0.set_period_start_end_date_in_pcv
|
|||||||
erpnext.patches.v14_0.update_closing_balances #20-12-2024
|
erpnext.patches.v14_0.update_closing_balances #20-12-2024
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||||
erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts
|
erpnext.patches.v14_0.update_reference_type_in_journal_entry_accounts
|
||||||
erpnext.patches.v14_0.update_subscription_details
|
erpnext.patches.v14_0.update_subscription_details # 23-07-2025
|
||||||
execute:frappe.delete_doc("Report", "Tax Detail", force=True)
|
execute:frappe.delete_doc("Report", "Tax Detail", force=True)
|
||||||
erpnext.patches.v15_0.enable_all_leads
|
erpnext.patches.v15_0.enable_all_leads
|
||||||
erpnext.patches.v14_0.update_company_in_ldc
|
erpnext.patches.v14_0.update_company_in_ldc
|
||||||
@@ -357,12 +357,12 @@ execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency"
|
|||||||
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
|
execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
|
||||||
erpnext.patches.v14_0.update_total_asset_cost_field
|
erpnext.patches.v14_0.update_total_asset_cost_field
|
||||||
erpnext.patches.v15_0.create_advance_payment_status
|
erpnext.patches.v15_0.create_advance_payment_status
|
||||||
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
|
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes #2025-06-19
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
||||||
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
|
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
|
||||||
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
|
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
|
||||||
erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16
|
erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16
|
||||||
erpnext.patches.v15_0.create_advance_payment_ledger_records
|
erpnext.patches.v15_0.create_advance_payment_ledger_records #2025-07-04
|
||||||
# below migration patch should always run last
|
# below migration patch should always run last
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
|
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
|
||||||
@@ -425,7 +425,10 @@ erpnext.patches.v15_0.rename_pos_closing_entry_fields #2025-06-13
|
|||||||
erpnext.patches.v15_0.update_pegged_currencies
|
erpnext.patches.v15_0.update_pegged_currencies
|
||||||
erpnext.patches.v15_0.set_status_cancelled_on_cancelled_pos_opening_entry_and_pos_closing_entry
|
erpnext.patches.v15_0.set_status_cancelled_on_cancelled_pos_opening_entry_and_pos_closing_entry
|
||||||
erpnext.patches.v15_0.set_company_on_pos_inv_merge_log
|
erpnext.patches.v15_0.set_company_on_pos_inv_merge_log
|
||||||
|
erpnext.patches.v15_0.update_payment_ledger_entries_against_advance_doctypes
|
||||||
erpnext.patches.v15_0.rename_price_list_to_buying_price_list
|
erpnext.patches.v15_0.rename_price_list_to_buying_price_list
|
||||||
|
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-04
|
||||||
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request
|
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request
|
||||||
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
|
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
|
||||||
erpnext.patches.v15_0.update_uae_zero_rated_fetch
|
erpnext.patches.v15_0.update_uae_zero_rated_fetch
|
||||||
|
erpnext.patches.v15_0.add_company_payment_gateway_account
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ def execute():
|
|||||||
subscription_invoice.invoice,
|
subscription_invoice.invoice,
|
||||||
"subscription",
|
"subscription",
|
||||||
subscription_invoice.parent,
|
subscription_invoice.parent,
|
||||||
|
update_modified=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.delete_doc_if_exists("DocType", "Subscription Invoice")
|
frappe.delete_doc_if_exists("DocType", "Subscription Invoice", force=1)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
for gateway_account in frappe.get_list("Payment Gateway Account", fields=["name", "payment_account"]):
|
||||||
|
company = frappe.db.get_value("Account", gateway_account.payment_account, "company")
|
||||||
|
frappe.db.set_value("Payment Gateway Account", gateway_account.name, "company", company)
|
||||||
@@ -9,6 +9,6 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
for dt in get_allowed_types_from_settings():
|
for dt in get_allowed_types_from_settings(child_doc=True):
|
||||||
for dimension in get_accounting_dimensions():
|
for dimension in get_accounting_dimensions():
|
||||||
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
|
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user