Merge branch 'frappe:develop' into fix/pricing-rule-help-content

This commit is contained in:
Flavia de Castro
2025-08-04 15:43:44 -03:00
committed by GitHub
97 changed files with 29621 additions and 25801 deletions

View File

@@ -8,7 +8,6 @@ erpnext/assets/ @khushi8112
erpnext/regional @ruthra-kumar
erpnext/selling @ruthra-kumar
erpnext/support/ @ruthra-kumar
pos* @diptanilsaha
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
erpnext/maintenance/ @rohitwaghchaure

View File

@@ -302,7 +302,9 @@ class Account(NestedSet):
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
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 frappe.db.get_value("GL Entry", {"account": self.name}):

View File

@@ -18,6 +18,7 @@ def create_charts(
accounts = []
def _import_accounts(children, parent, root_type, root_account=False):
nonlocal custom_chart
for account_name, child in children.items():
if root_account:
root_type = child.get("root_type")
@@ -55,7 +56,8 @@ def create_charts(
"account_number": account_number,
"account_type": child.get("account_type"),
"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"),
}
)

View File

@@ -111,17 +111,15 @@ class AccountingDimension(Document):
def make_dimension_in_accounting_doctypes(doc, doclist=None):
if not doclist:
doclist = get_doctypes_with_dimensions()
doc_count = len(get_accounting_dimensions())
count = 0
repostable_doctypes = get_allowed_types_from_settings()
repostable_doctypes = get_allowed_types_from_settings(child_doc=True)
for doctype in doclist:
if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break"
else:
insert_after_field = "accounting_dimensions_section"
df = {
"fieldname": doc.fieldname,
"label": doc.label,

View File

@@ -12,7 +12,8 @@
"against_voucher_no",
"amount",
"currency",
"event"
"event",
"delinked"
],
"fields": [
{
@@ -68,12 +69,20 @@
"label": "Company",
"options": "Company",
"read_only": 1
},
{
"default": "0",
"fieldname": "delinked",
"fieldtype": "Check",
"label": "DeLinked",
"read_only": 1
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-11-05 10:31:28.736671",
"modified": "2025-07-29 11:37:42.678556",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Payment Ledger Entry",
@@ -107,7 +116,8 @@
"share": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -1,9 +1,11 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe.model.document import Document
from erpnext.accounts.utils import update_voucher_outstanding
class AdvancePaymentLedgerEntry(Document):
# begin: auto-generated types
@@ -19,9 +21,16 @@ class AdvancePaymentLedgerEntry(Document):
amount: DF.Currency
company: DF.Link | None
currency: DF.Link | None
delinked: DF.Check
event: DF.Data | None
voucher_no: DF.DynamicLink | None
voucher_type: DF.Link | None
# 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)

View File

@@ -109,6 +109,7 @@ class BankAccount(Document):
"party_type": self.party_type,
"party": self.party,
"is_company_account": self.is_company_account,
"company": self.company,
"is_default": 1,
"disabled": 0,
},

View File

@@ -195,8 +195,6 @@ class JournalEntry(AccountsController):
self.validate_cheque_info()
self.check_credit_limit()
self.make_gl_entries()
self.make_advance_payment_ledger_entries()
self.update_advance_paid()
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -298,8 +296,6 @@ class JournalEntry(AccountsController):
"Advance Payment Ledger Entry",
)
self.make_gl_entries(1)
self.make_advance_payment_ledger_entries()
self.update_advance_paid()
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
self.unlink_inter_company_jv()
@@ -309,18 +305,6 @@ class JournalEntry(AccountsController):
def get_title(self):
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):
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
doc = frappe.db.get_value(
@@ -1195,49 +1179,65 @@ class JournalEntry(AccountsController):
self.transaction_exchange_rate = row.exchange_rate
break
advance_doctypes = get_advance_payment_doctypes()
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark]
r = [x for x in r if x]
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(
self.get_gl_dict(
{
"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,
},
row,
item=d,
)
)

View File

@@ -32,6 +32,8 @@
"reference_name",
"reference_due_date",
"reference_detail_no",
"advance_voucher_type",
"advance_voucher_no",
"col_break3",
"is_advance",
"user_remark",
@@ -262,20 +264,37 @@
"hidden": 1,
"label": "Reference Detail No",
"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,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:58.647732",
"modified": "2025-07-25 04:45:28.117715",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -17,8 +17,9 @@ class JournalEntryAccount(Document):
account: DF.Link
account_currency: DF.Link | None
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
against_account: DF.Text | None
balance: DF.Currency
bank_account: DF.Link | None
cost_center: DF.Link | None
credit: DF.Currency
@@ -31,7 +32,6 @@ class JournalEntryAccount(Document):
parentfield: DF.Data
parenttype: DF.Data
party: DF.DynamicLink | None
party_balance: DF.Currency
party_type: DF.Link | None
project: DF.Link | None
reference_detail_no: DF.Data | None

View File

@@ -199,12 +199,10 @@ class PaymentEntry(AccountsController):
def on_submit(self):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.update_payment_requests()
self.make_gl_entries()
self.update_outstanding_amounts()
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()
def validate_for_repost(self):
@@ -304,13 +302,11 @@ class PaymentEntry(AccountsController):
"Advance Payment Ledger Entry",
)
super().on_cancel()
self.update_payment_requests(cancel=True)
self.make_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.delink_advance_entry_references()
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()
def update_payment_requests(self, cancel=False):
@@ -1439,23 +1435,27 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_transaction_currency": d.allocated_amount
if self.transaction_currency == self.party_account_currency
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,
)
)
if self.book_advance_payments_in_separate_party_account:
if d.reference_doctype in advance_payment_doctypes:
# Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine
gle.update(
{
"against_voucher_type": d.reference_doctype,
"against_voucher": 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})
if d.reference_doctype in advance_payment_doctypes:
# advance reference
gle.update(
{
"against_voucher_type": self.doctype,
"against_voucher": self.name,
"advance_voucher_type": d.reference_doctype,
"advance_voucher_no": d.reference_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:
gle.update(
{
@@ -1585,6 +1585,8 @@ class PaymentEntry(AccountsController):
{
"against_voucher_type": invoice.reference_doctype,
"against_voucher": invoice.reference_name,
"advance_voucher_type": invoice.advance_voucher_type,
"advance_voucher_no": invoice.advance_voucher_no,
"posting_date": posting_date,
}
)
@@ -1609,6 +1611,8 @@ class PaymentEntry(AccountsController):
{
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
"advance_voucher_type": invoice.advance_voucher_type,
"advance_voucher_no": invoice.advance_voucher_no,
}
)
gle = self.get_gl_dict(
@@ -1757,17 +1761,6 @@ class PaymentEntry(AccountsController):
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):
self.reference_no = reference_doc.name
self.reference_date = nowdate()

View File

@@ -52,7 +52,7 @@ class TestPaymentEntry(IntegrationTestCase):
self.assertEqual(pe.paid_to_account_type, "Cash")
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)
@@ -84,7 +84,7 @@ class TestPaymentEntry(IntegrationTestCase):
expected_gle = dict(
(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)

View File

@@ -22,7 +22,9 @@
"exchange_gain_loss",
"account",
"payment_request",
"payment_request_outstanding"
"payment_request_outstanding",
"advance_voucher_type",
"advance_voucher_no"
],
"fields": [
{
@@ -151,20 +153,37 @@
"fieldtype": "Date",
"label": "Reconcile Effect On",
"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,
"istable": 1,
"links": [],
"modified": "2025-01-13 15:56:18.895082",
"modified": "2025-07-25 04:32:11.040025",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -16,6 +16,8 @@ class PaymentEntryReference(Document):
account: DF.Link | None
account_type: DF.Data | None
advance_voucher_no: DF.DynamicLink | None
advance_voucher_type: DF.Link | None
allocated_amount: DF.Float
bill_no: DF.Data | None
due_date: DF.Date | None
@@ -26,7 +28,6 @@ class PaymentEntryReference(Document):
parentfield: DF.Data
parenttype: DF.Data
payment_request: DF.Link | None
payment_request_outstanding: DF.Float
payment_term: DF.Link | None
payment_term_outstanding: DF.Float
payment_type: DF.Data | None

View File

@@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", {
frm.set_df_property("payment_gateway", "read_only", 1);
}
},
setup(frm) {
frm.set_query("payment_account", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
},
});

View File

@@ -7,6 +7,7 @@
"field_order": [
"payment_gateway",
"payment_channel",
"company",
"is_default",
"column_break_4",
"payment_account",
@@ -71,11 +72,21 @@
"fieldtype": "Select",
"label": "Payment Channel",
"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,
"links": [],
"modified": "2024-03-29 18:53:09.836254",
"modified": "2025-07-14 16:49:55.210352",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Gateway Account",
@@ -94,6 +105,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []

View File

@@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document):
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link
currency: DF.ReadOnly | None
is_default: DF.Check
message: DF.SmallText | None
@@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document):
# end: auto-generated types
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):
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):
if self.is_default:
frappe.db.sql(
"""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """
frappe.db.set_value(
"Payment Gateway Account",
{"is_default": 1, "name": ["!=", self.name], "company": self.company},
"is_default",
0,
)
def set_as_default_if_not_set(self):
if not frappe.db.get_value(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
if not frappe.db.exists(
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company}
):
self.is_default = 1

View File

@@ -197,4 +197,4 @@
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -2208,6 +2208,138 @@ class TestPaymentReconciliation(IntegrationTestCase):
self.assertEqual(len(pr.get("payments")), 0)
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):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -9,6 +9,14 @@ frappe.ui.form.on("Payment Request", {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
};
});
frm.set_query("payment_gateway_account", function () {
return {
filters: {
company: frm.doc.company,
},
};
});
},
});

View File

@@ -534,7 +534,8 @@ def make_payment_request(**args):
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)
if not args.get("company"):
args.company = ref_doc.company
gateway_account = get_gateway_details(args) or frappe._dict()
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
"""
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)

View File

@@ -34,12 +34,14 @@ payment_method = [
"payment_gateway": "_Test Gateway",
"payment_account": "_Test Bank - _TC",
"currency": "INR",
"company": "_Test Company",
},
{
"doctype": "Payment Gateway Account",
"payment_gateway": "_Test Gateway",
"payment_account": "_Test Bank USD - _TC",
"currency": "USD",
"company": "_Test Company",
},
{
"doctype": "Payment Gateway Account",
@@ -47,6 +49,7 @@ payment_method = [
"payment_account": "_Test Bank USD - _TC",
"payment_channel": "Other",
"currency": "USD",
"company": "_Test Company",
},
{
"doctype": "Payment Gateway Account",
@@ -54,6 +57,7 @@ payment_method = [
"payment_account": "_Test Bank USD - _TC",
"payment_channel": "Phone",
"currency": "USD",
"company": "_Test Company",
},
]
@@ -67,7 +71,11 @@ class TestPaymentRequest(IntegrationTestCase):
for method in payment_method:
if not frappe.db.get_value(
"Payment Gateway Account",
{"payment_gateway": method["payment_gateway"], "currency": method["currency"]},
{
"payment_gateway": method["payment_gateway"],
"currency": method["currency"],
"company": method["company"],
},
"name",
):
frappe.get_doc(method).insert(ignore_permissions=True)
@@ -103,7 +111,7 @@ class TestPaymentRequest(IntegrationTestCase):
dt="Sales Order",
dn=so_inr.name,
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")
@@ -117,7 +125,7 @@ class TestPaymentRequest(IntegrationTestCase):
dt="Sales Invoice",
dn=si_usd.name,
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")
@@ -130,7 +138,7 @@ class TestPaymentRequest(IntegrationTestCase):
pr = make_payment_request(
dt="Sales Order",
dn=so.name,
payment_gateway_account="_Test Gateway Other - USD",
payment_gateway_account="_Test Gateway Other - USD - _TC",
submit_doc=True,
return_doc=True,
)
@@ -145,7 +153,7 @@ class TestPaymentRequest(IntegrationTestCase):
pr = make_payment_request(
dt="Sales Order",
dn=so.name,
payment_gateway_account="_Test Gateway - USD", # email channel
payment_gateway_account="_Test Gateway - USD - _TC", # email channel
submit_doc=False,
return_doc=True,
)
@@ -163,7 +171,7 @@ class TestPaymentRequest(IntegrationTestCase):
pr = make_payment_request(
dt="Sales Order",
dn=so.name,
payment_gateway_account="_Test Gateway Phone - USD",
payment_gateway_account="_Test Gateway Phone - USD - _TC",
submit_doc=True,
return_doc=True,
)
@@ -180,7 +188,7 @@ class TestPaymentRequest(IntegrationTestCase):
pr = make_payment_request(
dt="Sales Order",
dn=so.name,
payment_gateway_account="_Test Gateway - USD", # email channel
payment_gateway_account="_Test Gateway - USD - _TC", # email channel
submit_doc=True,
return_doc=True,
)
@@ -201,7 +209,7 @@ class TestPaymentRequest(IntegrationTestCase):
pr = make_payment_request(
dt="Sales Order",
dn=so.name,
payment_gateway_account="_Test Gateway - USD", # email channel
payment_gateway_account="_Test Gateway - USD - _TC", # email channel
make_sales_invoice=True,
mute_email=True,
submit_doc=True,
@@ -232,7 +240,7 @@ class TestPaymentRequest(IntegrationTestCase):
party="_Test Supplier USD",
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -257,7 +265,7 @@ class TestPaymentRequest(IntegrationTestCase):
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
return_doc=1,
)
@@ -276,7 +284,7 @@ class TestPaymentRequest(IntegrationTestCase):
dn=purchase_invoice.name,
recipient_id="user@example.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
return_doc=1,
)
@@ -300,7 +308,7 @@ class TestPaymentRequest(IntegrationTestCase):
dn=so_inr.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - INR",
payment_gateway_account="_Test Gateway - INR - _TC",
submit_doc=1,
return_doc=1,
)
@@ -322,7 +330,7 @@ class TestPaymentRequest(IntegrationTestCase):
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_doc=1,
return_doc=1,
)
@@ -366,7 +374,7 @@ class TestPaymentRequest(IntegrationTestCase):
dn=si_usd.name,
recipient_id="saurabh@erpnext.com",
mute_email=1,
payment_gateway_account="_Test Gateway - USD",
payment_gateway_account="_Test Gateway - USD - _TC",
submit_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.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)
so.load_from_db()

View File

@@ -296,6 +296,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1598,7 +1599,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2025-07-18 16:50:30.516162",
"modified": "2025-08-04 22:22:31.471752",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -63,6 +63,7 @@
"column_break_50",
"base_total",
"base_net_total",
"claimed_landed_cost_amount",
"column_break_28",
"total",
"net_total",
@@ -321,6 +322,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1651,6 +1653,15 @@
"label": "Select Dispatch Address ",
"options": "Address",
"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,
@@ -1658,7 +1669,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2025-04-09 16:49:22.175081",
"modified": "2025-08-04 19:19:11.380664",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
@@ -1723,4 +1734,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -104,6 +104,7 @@ class PurchaseInvoice(BuyingController):
billing_address_display: DF.TextEditor | None
buying_price_list: DF.Link | None
cash_bank_account: DF.Link | None
claimed_landed_cost_amount: DF.Currency
clearance_date: DF.Date | None
company: DF.Link | None
contact_display: DF.SmallText | None

View File

@@ -5,6 +5,7 @@ import inspect
import frappe
from frappe import _, qb
from frappe.desk.form.linked_with import get_child_tables_of_doctypes
from frappe.model.document import Document
from frappe.utils.data import comma_and
@@ -169,6 +170,10 @@ def start_repost(account_repost_doc=str) -> None:
frappe.db.delete(
"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 not repost_doc.delete_cancelled_entries:
@@ -204,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None:
doc.make_gl_entries()
def get_allowed_types_from_settings():
return [
def get_allowed_types_from_settings(child_doc: bool = False):
repost_docs = [
x.document_type
for x in frappe.db.get_all(
"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):

View File

@@ -1,9 +1,14 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
import frappe
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):
# begin: auto-generated types
@@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document):
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import 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)

View File

@@ -8,7 +8,7 @@ from frappe import _, qb
from frappe.model.document import Document
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"]
@@ -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):
if voucher_type and voucher_no and gle_map:
_delete_pl_entries(voucher_type, voucher_no)
_delete_adv_pl_entries(voucher_type, voucher_no)
create_payment_ledger_entry(gle_map, cancel=0)

View File

@@ -373,6 +373,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"hide_days": 1,
@@ -2232,7 +2233,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-06-26 14:06:56.773552",
"modified": "2025-08-04 19:20:28.732039",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -465,10 +465,26 @@ class TestUnreconcilePayment(AccountsTestMixin, IntegrationTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
# Assert 'Advance Paid'
so.reload()
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()
def test_unreconcile_advance_from_journal_entry(self):

View File

@@ -12,7 +12,6 @@ from frappe.utils.data import comma_and
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_advance_payment_doctypes,
unlink_ref_doc_from_payment_entries,
update_voucher_outstanding,
)
@@ -45,31 +44,12 @@ class UnreconcilePayment(Document):
@frappe.whitelist()
def get_allocations_from_payment(self):
allocated_references = []
ple = qb.DocType("Payment Ledger Entry")
allocated_references = (
qb.from_(ple)
.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 get_linked_payments_for_doc(
company=self.company,
doctype=self.voucher_type,
docname=self.voucher_no,
)
return allocated_references
def add_references(self):
allocations = self.get_allocations_from_payment()
@@ -82,42 +62,43 @@ class UnreconcilePayment(Document):
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
# update outstanding amounts
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():
self.make_advance_payment_ledger(alloc)
doc.set_total_advance_paid()
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
def make_advance_payment_ledger(self, alloc):
if alloc.allocated_amount > 0:
doc = frappe.new_doc("Advance Payment Ledger Entry")
doc.company = self.company
doc.voucher_type = self.voucher_type
doc.voucher_no = self.voucher_no
doc.against_voucher_type = alloc.reference_doctype
doc.against_voucher_no = alloc.reference_name
doc.amount = -1 * alloc.allocated_amount
doc.event = "Unreconcile"
doc.currency = alloc.account_currency
doc.flags.ignore_permissions = 1
doc.save()
@frappe.whitelist()
def doc_has_references(doctype: str | None = None, docname: str | None = None):
count = 0
if doctype in ["Sales Invoice", "Purchase Invoice"]:
return frappe.db.count(
count = frappe.db.count(
"Payment Ledger Entry",
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
)
else:
return frappe.db.count(
count = frappe.db.count(
"Payment Ledger Entry",
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()
@@ -139,9 +120,12 @@ def get_linked_payments_for_doc(
res = (
qb.from_(ple)
.select(
ple.account,
ple.party_type,
ple.party,
ple.company,
ple.voucher_type,
ple.voucher_no,
ple.voucher_type.as_("reference_doctype"),
ple.voucher_no.as_("reference_name"),
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
ple.account_currency,
)
@@ -163,19 +147,52 @@ def get_linked_payments_for_doc(
qb.from_(ple)
.select(
ple.company,
ple.against_voucher_type.as_("voucher_type"),
ple.against_voucher_no.as_("voucher_no"),
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(Criterion.all(criteria))
.groupby(ple.against_voucher_no)
)
res = query.run(as_dict=True)
res += get_linked_advances(company, _dn)
return res
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()
def create_unreconcile_doc_for_selection(selections=None):
if selections:

View File

@@ -316,6 +316,8 @@ def get_merge_properties(dimensions=None):
"project",
"finance_book",
"voucher_no",
"advance_voucher_type",
"advance_voucher_no",
]
if dimensions:
merge_properties.extend(dimensions)

View File

@@ -46,6 +46,7 @@ def get_ordered_to_be_billed_data(args, filters=None):
child_doctype.item_name,
child_doctype.description,
project_field,
doctype.company,
)
.where(
(doctype.docstatus == 1)

View File

@@ -46,6 +46,7 @@ class PaymentLedger:
against_voucher_no=ple.against_voucher_no,
amount=ple.amount,
currency=ple.account_currency,
company=ple.company,
)
if self.filters.include_account_currency:
@@ -77,6 +78,7 @@ class PaymentLedger:
against_voucher_no="Outstanding:",
amount=total,
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
if self.filters.include_account_currency:
@@ -85,7 +87,12 @@ class PaymentLedger:
voucher_data.append(entry)
# 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)
def build_conditions(self):
@@ -130,7 +137,6 @@ class PaymentLedger:
)
def get_columns(self):
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
options = None
self.columns.append(
dict(
@@ -195,7 +201,7 @@ class PaymentLedger:
label=_("Amount"),
fieldname="amount",
fieldtype="Currency",
options=company_currency,
options="Company:company:default_currency",
width="100",
)
)

View File

@@ -482,42 +482,27 @@ def reconcile_against_document(
reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
for key, entries in reconciled_entries.items():
voucher_type = key[0]
voucher_no = key[1]
voucher_type, voucher_no = key
# cancel advance entry
doc = frappe.get_doc(voucher_type, voucher_no)
frappe.flags.ignore_party_validation = True
# When Advance is allocated from an Order to an Invoice
# 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)
reposting_rows = []
for entry in entries:
check_if_advance_entry_modified(entry)
validate_allocated_amount(entry)
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
# update ref in advance entry
if voucher_type == "Journal Entry":
referenced_row, update_advance_paid = update_reference_in_journal_entry(
entry, doc, do_not_save=False
)
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
# amount and account in args
# 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)
else:
referenced_row, update_advance_paid = update_reference_in_payment_entry(
referenced_row = update_reference_in_payment_entry(
entry,
doc,
do_not_save=True,
@@ -526,20 +511,16 @@ def reconcile_against_document(
)
if referenced_row.get("outstanding_amount"):
referenced_row.outstanding_amount -= flt(entry.allocated_amount)
reposting_rows.append(referenced_row)
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:
# When Advance is allocated from an Order to an Invoice
# whole ledger must be reposted
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()
for row in reposting_rows:
doc.make_advance_gl_entries(entry=row)
else:
_delete_pl_entries(voucher_type, voucher_no)
gl_map = doc.build_gl_map()
# Make sure there is no overallocation
from erpnext.accounts.general_ledger import process_debit_credit_difference
@@ -556,11 +537,6 @@ def reconcile_against_document(
entry.party_type,
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
@@ -646,12 +622,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]
# 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 = (
"debit_in_account_currency"
if d["dr_or_cr"] == "credit_in_account_currency"
@@ -704,6 +674,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.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
journal_entry.flags.ignore_validate_update_after_submit = True
# Ledgers will be reposted by Reconciliation tool
@@ -711,7 +685,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
if not do_not_save:
journal_entry.save(ignore_permissions=True)
return new_row.name, update_advance_paid
return new_row
def update_reference_in_payment_entry(
@@ -730,7 +704,8 @@ def update_reference_in_payment_entry(
"account": d.account,
"dimensions": d.dimensions,
}
update_advance_paid = []
advance_payment_doctypes = get_advance_payment_doctypes()
# Update Reconciliation effect date in reference
if payment_entry.book_advance_payments_in_separate_party_account:
@@ -742,10 +717,6 @@ def update_reference_in_payment_entry(
if d.voucher_detail_no:
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:
existing_row.allocated_amount -= d.allocated_amount
@@ -753,7 +724,13 @@ def update_reference_in_payment_entry(
new_row.docstatus = 1
for field in list(reference_details):
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
else:
new_row = payment_entry.append("references")
new_row.docstatus = 1
@@ -788,7 +765,8 @@ def update_reference_in_payment_entry(
payment_entry.flags.ignore_reposting_on_reconciliation = True
if not do_not_save:
payment_entry.save(ignore_permissions=True)
return row, update_advance_paid
return row
def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date):
@@ -966,6 +944,24 @@ def update_accounting_ledgers_after_reference_removal(
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
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):
# TODO: this might need some testing
@@ -1002,6 +998,8 @@ def remove_ref_doc_link_from_jv(
qb.update(jea)
.set(jea.reference_type, 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_by, frappe.session.user)
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
@@ -1363,6 +1361,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email", company=Non
"payment_account": bank_account.name,
"currency": bank_account.account_currency,
"payment_channel": payment_channel,
"company": company,
}
).insert(ignore_permissions=True, ignore_if_duplicate=True)
@@ -1525,6 +1524,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()
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):
gle = qb.DocType("GL Entry")
qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run()
@@ -1844,6 +1848,11 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
dr_or_cr *= -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(
doctype="Payment Ledger Entry",
posting_date=gle.posting_date,
@@ -1858,14 +1867,12 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
voucher_type=gle.voucher_type,
voucher_no=gle.voucher_no,
voucher_detail_no=gle.voucher_detail_no,
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,
against_voucher_type=against_voucher_type,
against_voucher_no=against_voucher_no,
account_currency=gle.account_currency,
amount=dr_or_cr,
amount_in_account_currency=dr_or_cr_account_currency,
delinked=True if cancel else False,
delinked=cancel,
remarks=gle.remarks,
)
@@ -1874,10 +1881,40 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
for dimension in dimensions_and_defaults[0]:
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)
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(
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
):
@@ -1898,6 +1935,14 @@ def create_payment_ledger_entry(
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
@@ -1937,7 +1982,27 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
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")
query = (
qb.update(ple)

View File

@@ -512,6 +512,7 @@ class PurchaseOrder(BuyingController):
self.ignore_linked_doctypes = (
"GL Entry",
"Payment Ledger Entry",
"Advance Payment Ledger Entry",
"Unreconcile Payment",
"Unreconcile Payment Entries",
)
@@ -743,6 +744,7 @@ def close_or_unclose_purchase_orders(names, status):
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals")
target.run_method("set_use_serial_batch_fields")
@frappe.whitelist()

View File

@@ -394,7 +394,6 @@ class AccountsController(TransactionBase):
def on_trash(self):
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_unreconcile()
self.remove_serial_and_batch_bundle()
@@ -423,6 +422,8 @@ class AccountsController(TransactionBase):
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
).run()
self._remove_advance_payment_ledger_entries()
def remove_serial_and_batch_bundle(self):
bundles = frappe.get_all(
"Serial and Batch Bundle",
@@ -2212,55 +2213,30 @@ class AccountsController(TransactionBase):
def calculate_total_advance_from_ledger(self):
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
advance = (
frappe.qb.from_(adv)
.select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount"))
.where(
(adv.against_voucher_type == self.doctype)
& (adv.against_voucher_no == self.name)
& (adv.company == self.company)
)
return (
qb.from_(adv)
.select(Abs(Sum(adv.amount)).as_("amount"), adv.currency.as_("account_currency"))
.where(adv.company == self.company)
.where(adv.delinked == 0)
.where(adv.against_voucher_type == self.doctype)
.where(adv.against_voucher_no == self.name)
.run(as_dict=True)
)
return advance
def set_total_advance_paid(self):
advance = self.calculate_total_advance_from_ledger()
advance_paid, order_total = None, None
advance_paid = 0
if advance:
advance = advance[0]
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:
frappe.db.set_value(
self.doctype, self.name, "party_account_currency", advance.account_currency
)
if advance.account_currency == self.currency:
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.db_set("advance_paid", advance_paid)
self.set_advance_payment_status()
def set_advance_payment_status(self):
@@ -2656,7 +2632,10 @@ class AccountsController(TransactionBase):
if 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):
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:
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):
for x in gl_entries:
x["transaction_currency"] = self.currency

View File

@@ -663,7 +663,9 @@ class StockController(AccountsController):
).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):
if not frappe.flags.debit_field_precision:

View File

@@ -2,8 +2,9 @@
// License: GNU General Public License v3. See license.txt
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 {
setup() {
this.frm.make_methods = {
@@ -238,5 +239,6 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
crm_activities.refresh();
}
};
extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));
if (this.frm) {
extend_cscript(this.frm.cscript, new erpnext.LeadController({ frm: this.frm }));
}

View File

@@ -153,7 +153,12 @@ class OpportunitySummaryBySalesStage:
}[self.filters.get("based_on")]
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"]
else:
assignments = json.loads(d.get(based_on))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -76,6 +76,7 @@
"fieldname": "hour_rate",
"fieldtype": "Currency",
"label": "Hour Rate",
"non_negative": 1,
"oldfieldname": "hour_rate",
"oldfieldtype": "Currency",
"options": "currency",
@@ -90,6 +91,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Operation Time",
"non_negative": 1,
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"reqd": 1
@@ -285,13 +287,14 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-01-09 15:45:37.695800",
"modified": "2025-07-31 16:17:47.287117",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Operation",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -42,6 +42,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "Qty",
"non_negative": 1,
"reqd": 1
},
{
@@ -49,6 +50,7 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"non_negative": 1,
"options": "currency"
},
{
@@ -92,15 +94,16 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:06:41.395036",
"modified": "2025-07-31 16:21:44.047007",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Scrap Item",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -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"))
erpnext.patches.v14_0.update_total_asset_cost_field
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.update_flag_for_return_invoices #2024-03-22
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
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
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
@@ -425,7 +425,9 @@ erpnext.patches.v15_0.rename_pos_closing_entry_fields #2025-06-13
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_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.repost_gl_entries_with_no_account_subcontracting
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-07-31
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.add_company_payment_gateway_account

View File

@@ -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)

View File

@@ -9,6 +9,6 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
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():
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)

View File

@@ -1,65 +1,28 @@
import frappe
from frappe import qb
from frappe.query_builder.custom import ConstantColumn
from frappe.model.naming import _generate_random_string
from frappe.query_builder import Case
from frappe.utils import now_datetime
from erpnext.accounts.utils import get_advance_payment_doctypes
def get_advance_doctypes() -> list:
return frappe.get_hooks("advance_payment_receivable_doctypes") + frappe.get_hooks(
"advance_payment_payable_doctypes"
)
DOCTYPE = "Advance Payment Ledger Entry"
def get_payments_with_so_po_reference() -> list:
advance_payment_entries = []
advance_doctypes = get_advance_doctypes()
per = qb.DocType("Payment Entry Reference")
payments_with_reference = (
qb.from_(per)
.select(per.parent)
.distinct()
.where(per.reference_doctype.isin(advance_doctypes) & per.docstatus.eq(1))
.run()
)
if payments_with_reference:
pe = qb.DocType("Payment Entry")
advance_payment_entries = (
qb.from_(pe)
.select(ConstantColumn("Payment Entry").as_("doctype"))
.select(pe.name)
.where(pe.name.isin(payments_with_reference) & pe.docstatus.eq(1))
.run(as_dict=True)
)
return advance_payment_entries
def get_journals_with_so_po_reference() -> list:
advance_journal_entries = []
advance_doctypes = get_advance_doctypes()
jea = qb.DocType("Journal Entry Account")
journals_with_reference = (
qb.from_(jea)
.select(jea.parent)
.distinct()
.where(jea.reference_type.isin(advance_doctypes) & jea.docstatus.eq(1))
.run()
)
if journals_with_reference:
je = qb.DocType("Journal Entry")
advance_journal_entries = (
qb.from_(je)
.select(ConstantColumn("Journal Entry").as_("doctype"))
.select(je.name)
.where(je.name.isin(journals_with_reference) & je.docstatus.eq(1))
.run(as_dict=True)
)
return advance_journal_entries
def make_advance_ledger_entries(vouchers: list):
for x in vouchers:
frappe.get_doc(x.doctype, x.name).make_advance_payment_ledger_entries()
FIELDS = [
"name",
"creation",
"modified",
"owner",
"modified_by",
"company",
"voucher_type",
"voucher_no",
"against_voucher_type",
"against_voucher_no",
"amount",
"currency",
"event",
"delinked",
]
def execute():
@@ -67,9 +30,102 @@ def execute():
Description:
Create Advance Payment Ledger Entry for all Payments made against Sales / Purchase Orders
"""
frappe.db.truncate("Advance Payment Ledger Entry")
payment_entries = get_payments_with_so_po_reference()
make_advance_ledger_entries(payment_entries)
frappe.db.truncate(DOCTYPE)
advance_doctpyes = get_advance_payment_doctypes()
journals = get_journals_with_so_po_reference()
make_advance_ledger_entries(journals)
if not advance_doctpyes:
return
make_advance_ledger_entries_for_payment_entries(advance_doctpyes)
make_advance_ledger_entries_for_journal_entries(advance_doctpyes)
def make_advance_ledger_entries_for_payment_entries(advance_doctpyes) -> list:
pe = frappe.qb.DocType("Payment Entry")
per = frappe.qb.DocType("Payment Entry Reference")
entries = (
frappe.qb.from_(per)
.inner_join(pe)
.on(pe.name == per.parent)
.select(
pe.company,
per.parenttype.as_("voucher_type"),
per.parent.as_("voucher_no"),
per.reference_doctype.as_("against_voucher_type"),
per.reference_name.as_("against_voucher_no"),
per.allocated_amount.as_("amount"),
Case()
.when(pe.payment_type == "Receive", pe.paid_from_account_currency)
.else_(pe.paid_to_account_currency)
.as_("currency"),
)
.where(per.reference_doctype.isin(advance_doctpyes) & per.docstatus.eq(1))
.run(as_dict=True)
)
if not entries:
return
bulk_insert_advance_entries(entries)
def make_advance_ledger_entries_for_journal_entries(advance_doctpyes) -> list:
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
entries = (
frappe.qb.from_(jea)
.inner_join(je)
.on(je.name == jea.parent)
.select(
je.company,
jea.parenttype.as_("voucher_type"),
jea.parent.as_("voucher_no"),
jea.reference_type.as_("against_voucher_type"),
jea.reference_name.as_("against_voucher_no"),
Case()
.when(jea.account_type == "Receivable", jea.credit_in_account_currency)
.else_(jea.debit_in_account_currency)
.as_("amount"),
jea.account_currency.as_("currency"),
)
.where(jea.reference_type.isin(advance_doctpyes) & jea.docstatus.eq(1))
.run(as_dict=True)
)
if not entries:
return
bulk_insert_advance_entries(entries)
def bulk_insert_advance_entries(entries):
details = []
user = frappe.session.user
now = now_datetime()
for entry in entries:
if entry.amount < 0:
continue
details.append(get_values(user, now, entry))
frappe.db.bulk_insert(DOCTYPE, fields=FIELDS, values=details)
def get_values(user, now, entry):
return (
_generate_random_string(10),
now,
now,
user,
user,
entry.company,
entry.voucher_type,
entry.voucher_no,
entry.against_voucher_type,
entry.against_voucher_no,
entry.amount * -1,
entry.currency,
"Submit",
0,
)

View File

@@ -19,7 +19,7 @@ def execute():
if not item.cost_center:
item.db_set("cost_center", cost_center)
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
doc.make_gl_entries()
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
doc.make_gl_entries()

View File

@@ -0,0 +1,25 @@
import frappe
from erpnext.accounts.utils import get_advance_payment_doctypes
DOCTYPE = "Payment Ledger Entry"
def execute():
"""
Description:
Set against_voucher as entry for Payment Ledger Entry against advance vouchers.
"""
advance_payment_doctypes = get_advance_payment_doctypes()
if not advance_payment_doctypes:
return
ple = frappe.qb.DocType(DOCTYPE)
(
frappe.qb.update(ple)
.set(ple.against_voucher_type, ple.voucher_type)
.set(ple.against_voucher_no, ple.voucher_no)
.where(ple.against_voucher_type.isin(advance_payment_doctypes))
.run()
)

View File

@@ -42,8 +42,8 @@ erpnext.accounts.unreconcile_payment = {
selection_map = selections.map(function (elem) {
return {
company: elem.company,
voucher_type: elem.voucher_type,
voucher_no: elem.voucher_no,
voucher_type: elem.reference_doctype,
voucher_no: elem.reference_name,
against_voucher_type: frm.doc.doctype,
against_voucher_no: frm.doc.name,
};
@@ -54,8 +54,8 @@ erpnext.accounts.unreconcile_payment = {
company: elem.company,
voucher_type: frm.doc.doctype,
voucher_no: frm.doc.name,
against_voucher_type: elem.voucher_type,
against_voucher_no: elem.voucher_no,
against_voucher_type: elem.reference_doctype,
against_voucher_no: elem.reference_name,
};
});
}
@@ -69,7 +69,7 @@ erpnext.accounts.unreconcile_payment = {
let child_table_fields = [
{
label: __("Voucher Type"),
fieldname: "voucher_type",
fieldname: "reference_doctype",
fieldtype: "Link",
options: "DocType",
in_list_view: 1,
@@ -77,9 +77,9 @@ erpnext.accounts.unreconcile_payment = {
},
{
label: __("Voucher No"),
fieldname: "voucher_no",
fieldname: "reference_name",
fieldtype: "Dynamic Link",
options: "voucher_type",
options: "reference_doctype",
in_list_view: 1,
read_only: 1,
},

View File

@@ -457,6 +457,7 @@ class SalesOrder(SellingController):
"GL Entry",
"Stock Ledger Entry",
"Payment Ledger Entry",
"Advance Payment Ledger Entry",
"Unreconcile Payment",
"Unreconcile Payment Entries",
)

View File

@@ -251,6 +251,7 @@
"width": "100px"
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1415,7 +1416,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2025-01-06 15:02:30.558756",
"modified": "2025-08-04 19:20:47.724218",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",
@@ -1506,6 +1507,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "status,customer,customer_name, territory,base_grand_total",
"show_name_in_global_search": 1,
"sort_field": "creation",
@@ -1515,4 +1517,4 @@
"title_field": "customer_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -96,7 +96,6 @@ class DeliveryNote(SellingController):
per_billed: DF.Percent
per_installed: DF.Percent
per_returned: DF.Percent
pick_list: DF.Link | None
plc_conversion_rate: DF.Float
po_date: DF.Date | None
po_no: DF.SmallText | None

View File

@@ -0,0 +1,44 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-07-30 19:20:35.277688",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"vendor_invoice",
"amount"
],
"fields": [
{
"fieldname": "vendor_invoice",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Vendor Invoice",
"options": "Purchase Invoice",
"search_index": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Amount (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-07-30 23:15:43.737772",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Vendor Invoice",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,24 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class LandedCostVendorInvoice(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
amount: DF.Currency
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
vendor_invoice: DF.Link | None
# end: auto-generated types
pass

View File

@@ -165,6 +165,15 @@ frappe.ui.form.on("Landed Cost Voucher", {
};
}
});
frm.set_query("vendor_invoice", "vendor_invoices", (doc, cdt, cdn) => {
return {
query: "erpnext.stock.doctype.landed_cost_voucher.landed_cost_voucher.get_vendor_invoices",
filters: {
company: doc.company,
},
};
});
},
});
@@ -189,3 +198,24 @@ frappe.ui.form.on("Landed Cost Purchase Receipt", {
}
},
});
frappe.ui.form.on("Landed Cost Vendor Invoice", {
vendor_invoice(frm, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.vendor_invoice) {
frappe.call({
method: "get_vendor_invoice_amount",
doc: frm.doc,
args: {
vendor_invoice: d.vendor_invoice,
},
callback: function (r) {
if (r.message) {
$.extend(d, r.message);
refresh_field("vendor_invoices");
}
},
});
}
},
});

View File

@@ -16,8 +16,10 @@
"get_items_from_purchase_receipts",
"items",
"sec_break1",
"vendor_invoices",
"taxes",
"section_break_9",
"total_vendor_invoices_cost",
"total_taxes_and_charges",
"col_break1",
"distribute_charges_based_on",
@@ -79,7 +81,7 @@
{
"fieldname": "taxes",
"fieldtype": "Table",
"label": "Taxes and Charges",
"label": "Landed Cost",
"options": "Landed Cost Taxes and Charges",
"reqd": 1
},
@@ -90,7 +92,7 @@
{
"fieldname": "total_taxes_and_charges",
"fieldtype": "Currency",
"label": "Total Taxes and Charges (Company Currency)",
"label": "Total Landed Cost (Company Currency)",
"options": "Company:company:default_currency",
"read_only": 1,
"reqd": 1
@@ -139,6 +141,20 @@
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"fieldname": "vendor_invoices",
"fieldtype": "Table",
"label": "Vendor Invoices",
"options": "Landed Cost Vendor Invoice"
},
{
"fieldname": "total_vendor_invoices_cost",
"fieldtype": "Currency",
"label": "Total Vendor Invoices Cost (Company Currency)",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"grid_page_length": 50,
@@ -146,7 +162,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-06-09 10:08:39.574009",
"modified": "2025-07-30 19:25:04.899698",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Voucher",

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _
from frappe import _, bold
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.query_builder.custom import ConstantColumn
@@ -30,6 +30,9 @@ class LandedCostVoucher(Document):
from erpnext.stock.doctype.landed_cost_taxes_and_charges.landed_cost_taxes_and_charges import (
LandedCostTaxesandCharges,
)
from erpnext.stock.doctype.landed_cost_vendor_invoice.landed_cost_vendor_invoice import (
LandedCostVendorInvoice,
)
amended_from: DF.Link | None
company: DF.Link
@@ -40,6 +43,8 @@ class LandedCostVoucher(Document):
purchase_receipts: DF.Table[LandedCostPurchaseReceipt]
taxes: DF.Table[LandedCostTaxesandCharges]
total_taxes_and_charges: DF.Currency
total_vendor_invoices_cost: DF.Currency
vendor_invoices: DF.Table[LandedCostVendorInvoice]
# end: auto-generated types
@frappe.whitelist()
@@ -76,6 +81,12 @@ class LandedCostVoucher(Document):
self.get_items_from_purchase_receipts()
self.set_applicable_charges_on_item()
self.set_total_vendor_invoices_cost()
def set_total_vendor_invoices_cost(self):
self.total_vendor_invoices_cost = 0.0
for row in self.vendor_invoices:
self.total_vendor_invoices_cost += flt(row.amount)
def validate_line_items(self):
for d in self.get("items"):
@@ -234,9 +245,20 @@ class LandedCostVoucher(Document):
def on_submit(self):
self.validate_applicable_charges_for_item()
self.update_landed_cost()
self.update_claimed_landed_cost()
def on_cancel(self):
self.update_landed_cost()
self.update_claimed_landed_cost()
def update_claimed_landed_cost(self):
for row in self.vendor_invoices:
frappe.db.set_value(
"Purchase Invoice",
row.vendor_invoice,
"claimed_landed_cost_amount",
flt(row.amount, row.precision("amount")) if self.docstatus == 1 else 0.0,
)
def update_landed_cost(self):
for d in self.get("purchase_receipts"):
@@ -333,6 +355,24 @@ class LandedCostVoucher(Document):
tuple([item.valuation_rate, *serial_nos]),
)
@frappe.whitelist()
def get_vendor_invoice_amount(self, vendor_invoice):
filters = frappe._dict(
{
"name": vendor_invoice,
"company": self.company,
}
)
query = get_vendor_invoice_query(filters)
result = query.run(as_dict=True)
amount = result[0].unclaimed_amount if result else 0.0
return {
"amount": amount,
}
def get_pr_items(purchase_receipt):
item = frappe.qb.DocType("Item")
@@ -383,3 +423,55 @@ def get_pr_items(purchase_receipt):
)
return query.run(as_dict=True)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_vendor_invoices(doctype, txt, searchfield, start, page_len, filters):
if not frappe.has_permission("Purchase Invoice", "read"):
return []
if txt and txt.lower().startswith(("select", "delete", "update")):
frappe.throw(_("Invalid search query"), title=_("Invalid Query"))
query = get_vendor_invoice_query(filters)
if txt:
query = query.where(doctype.name.like(f"%{txt}%"))
if start:
query = query.limit(page_len).offset(start)
return query.run(as_list=True)
def get_vendor_invoice_query(filters):
doctype = frappe.qb.DocType("Purchase Invoice")
child_doctype = frappe.qb.DocType("Purchase Invoice Item")
item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(doctype)
.inner_join(child_doctype)
.on(child_doctype.parent == doctype.name)
.inner_join(item)
.on(item.name == child_doctype.item_code)
.select(
doctype.name,
(doctype.base_total - doctype.claimed_landed_cost_amount).as_("unclaimed_amount"),
)
.where(
(doctype.docstatus == 1)
& (doctype.is_subcontracted == 0)
& (doctype.is_return == 0)
& (doctype.update_stock == 0)
& (doctype.company == filters.get("company"))
& (item.is_stock_item == 0)
)
.having(frappe.qb.Field("unclaimed_amount") > 0)
)
if filters.get("name"):
query = query.where(doctype.name == filters.get("name"))
return query

View File

@@ -243,6 +243,7 @@
"width": "100px"
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -1290,7 +1291,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2025-04-09 16:52:19.323878",
"modified": "2025-08-04 19:18:47.754957",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",
@@ -1360,4 +1361,4 @@
"timeline_field": "supplier",
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -479,7 +479,7 @@ class PurchaseReceipt(BuyingController):
self.make_tax_gl_entries(gl_entries, via_landed_cost_voucher)
update_regional_gl_entries(gl_entries, self)
return process_gl_map(gl_entries)
return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation)
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (

View File

@@ -146,7 +146,7 @@ class SerialandBatchBundle(Document):
)
elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}):
if self.voucher_type == "Delivery Note" and frappe.db.exists(
if self.voucher_type in ["Delivery Note", "Sales Invoice"] and frappe.db.exists(
"Packed Item", self.voucher_detail_no
):
return

View File

@@ -218,6 +218,7 @@
"search_index": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
@@ -697,7 +698,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-08-13 19:05:42.386955",
"modified": "2025-08-04 19:21:03.338958",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
@@ -763,6 +764,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "posting_date, from_warehouse, to_warehouse, purpose, remarks",
"show_name_in_global_search": 1,
"sort_field": "creation",
@@ -770,4 +772,4 @@
"states": [],
"title_field": "stock_entry_type",
"track_changes": 1
}
}

View File

@@ -1650,7 +1650,7 @@ class StockEntry(StockController):
self.set_gl_entries_for_landed_cost_voucher(gl_entries, warehouse_account)
return process_gl_map(gl_entries)
return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation)
def set_gl_entries_for_landed_cost_voucher(self, gl_entries, warehouse_account):
landed_cost_entries = self.get_item_account_wise_lcv_entries()

View File

@@ -8,20 +8,7 @@ def get_data():
"non_standard_fieldnames": {
"Stock Reservation Entry": "from_voucher_no",
},
"internal_links": {
"Purchase Order": ["items", "purchase_order"],
"Subcontracting Order": ["items", "subcontracting_order"],
"Subcontracting Receipt": ["items", "subcontracting_receipt"],
},
"transactions": [
{
"label": _("Reference"),
"items": [
"Purchase Order",
"Subcontracting Order",
"Subcontracting Receipt",
],
},
{"label": _("Stock Reservation"), "items": ["Stock Reservation Entry"]},
],
}

View File

@@ -72,6 +72,7 @@
"reqd": 1
},
{
"default": "Now",
"fieldname": "posting_time",
"fieldtype": "Time",
"in_list_view": 1,
@@ -183,7 +184,7 @@
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:44.699413",
"modified": "2025-08-04 19:21:20.179658",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
@@ -203,9 +204,10 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "posting_date",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -814,9 +814,7 @@ def get_sre_reserved_qty_for_items_and_warehouses(
Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"),
)
.where(
(sre.docstatus == 1)
& sre.item_code.isin(item_code_list)
& (sre.status.notin(["Delivered", "Cancelled"]))
(sre.docstatus == 1) & sre.item_code.isin(item_code_list) & (sre.delivered_qty < sre.reserved_qty)
)
.groupby(sre.item_code, sre.warehouse)
)

View File

@@ -1343,11 +1343,11 @@ def get_conversion_factor(item_code, uom):
if item.variant_of:
filters["parent"] = ("in", (item_code, item.variant_of))
conversion_factor = frappe.db.get_value("UOM Conversion Detail", filters, "conversion_factor")
conversion_factor = frappe.get_all("UOM Conversion Detail", filters, pluck="conversion_factor")
if not conversion_factor:
conversion_factor = get_uom_conv_factor(uom, item.stock_uom)
conversion_factor = [get_uom_conv_factor(uom, item.stock_uom) or 1]
return {"conversion_factor": conversion_factor or 1.0}
return {"conversion_factor": conversion_factor[-1]}
@frappe.whitelist()

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.query_reports["Landed Cost Report"] = {
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1,
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1,
},
{
fieldname: "to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.get_today(),
reqd: 1,
},
{
fieldname: "raw_material_voucher_type",
label: __("Raw Material Voucher Type"),
fieldtype: "Select",
options: "\nPurchase Receipt\nPurchase Invoice\nStock Entry\nSubcontracting Receipt",
},
{
fieldname: "raw_material_voucher_no",
label: __("Raw Material Voucher No"),
fieldtype: "Dynamic Link",
get_options: function () {
let voucher_type = frappe.query_report.get_filter_value("raw_material_voucher_type");
return voucher_type;
},
get_query: function () {
let company = frappe.query_report.get_filter_value("company");
let voucher_type = frappe.query_report.get_filter_value("raw_material_voucher_type");
let query_filters = {
docstatus: 1,
company: company,
};
if (voucher_type === "Purchase Invoice") {
query_filters["update_stock"] = 1;
}
return {
filters: query_filters,
};
},
},
],
};

View File

@@ -0,0 +1,38 @@
{
"add_total_row": 0,
"add_translate_data": 0,
"columns": [],
"creation": "2025-07-31 14:36:55.047172",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"json": "{}",
"letterhead": null,
"modified": "2025-07-31 14:37:37.141783",
"modified_by": "Administrator",
"module": "Stock",
"name": "Landed Cost Report",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Landed Cost Voucher",
"report_name": "Landed Cost Report",
"report_type": "Script Report",
"roles": [
{
"role": "Stock Manager"
},
{
"role": "Stock User"
},
{
"role": "Purchase Manager"
},
{
"role": "Purchase User"
}
],
"timeout": 0
}

View File

@@ -0,0 +1,152 @@
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
def execute(filters: dict | None = None):
columns = get_columns()
data = get_data(filters)
return columns, data
def get_columns() -> list[dict]:
return [
{
"label": _("Landed Cost Id"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Landed Cost Voucher",
},
{
"label": _("Total Landed Cost"),
"fieldname": "landed_cost",
"fieldtype": "Currency",
},
{
"label": _("Purchase Voucher Type"),
"fieldname": "voucher_type",
"fieldtype": "Data",
"width": 200,
},
{
"label": _("Purchase Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
"width": 220,
},
{
"label": _("Vendor Invoice"),
"fieldname": "vendor_invoice",
"fieldtype": "Link",
"options": "Purchase Invoice",
"width": 200,
},
]
def get_data(filters) -> list[list]:
landed_cost_vouchers = get_landed_cost_vouchers(filters) or {}
landed_vouchers = list(landed_cost_vouchers.keys())
vendor_invoices = {}
if landed_vouchers:
vendor_invoices = get_vendor_invoices(landed_vouchers)
data = []
print(vendor_invoices)
for name, vouchers in landed_cost_vouchers.items():
res = {
"name": name,
}
last_index = 0
vendor_invoice_list = vendor_invoices.get(name, [])
for i, d in enumerate(vouchers):
if i == 0:
res.update(
{
"landed_cost": d.landed_cost,
"voucher_type": d.voucher_type,
"voucher_no": d.voucher_no,
}
)
else:
res = {
"voucher_type": d.voucher_type,
"voucher_no": d.voucher_no,
}
if len(vendor_invoice_list) > i:
res["vendor_invoice"] = vendor_invoice_list[i]
data.append(res)
last_index = i
if vendor_invoice_list and len(vendor_invoice_list) > len(vouchers):
for row in vendor_invoice_list[last_index + 1 :]:
print(row)
data.append({"vendor_invoice": row})
return data
def get_landed_cost_vouchers(filters):
lcv = frappe.qb.DocType("Landed Cost Voucher")
lcv_voucher = frappe.qb.DocType("Landed Cost Purchase Receipt")
query = (
frappe.qb.from_(lcv)
.inner_join(lcv_voucher)
.on(lcv.name == lcv_voucher.parent)
.select(
lcv.name,
lcv.total_taxes_and_charges.as_("landed_cost"),
lcv_voucher.receipt_document_type.as_("voucher_type"),
lcv_voucher.receipt_document.as_("voucher_no"),
)
.where((lcv.docstatus == 1) & (lcv.company == filters.company))
)
if filters.from_date and filters.to_date:
query = query.where(lcv.posting_date.between(filters.from_date, filters.to_date))
if filters.raw_material_voucher_type:
query = query.where(lcv_voucher.receipt_document_type == filters.raw_material_voucher_type)
if filters.raw_material_voucher_no:
query = query.where(lcv_voucher.receipt_document == filters.raw_material_voucher_no)
data = query.run(as_dict=True) or []
result = {}
for row in data:
result.setdefault((row.name), []).append(row)
return result
def get_vendor_invoices(landed_vouchers):
doctype = frappe.qb.DocType("Landed Cost Vendor Invoice")
query = (
frappe.qb.from_(doctype)
.select(
doctype.parent,
doctype.vendor_invoice,
)
.where((doctype.docstatus == 1) & (doctype.parent.isin(landed_vouchers)))
.orderby(
doctype.idx,
)
)
data = query.run(as_dict=True) or []
result = {}
for row in data:
result.setdefault(row.parent, []).append(row.vendor_invoice)
return result

View File

@@ -574,7 +574,7 @@ class SubcontractingReceipt(SubcontractingController):
self.make_item_gl_entries(gl_entries, warehouse_account)
self.make_item_gl_entries_for_lcv(gl_entries, warehouse_account)
return process_gl_map(gl_entries)
return process_gl_map(gl_entries, from_repost=frappe.flags.through_repost_item_valuation)
def make_item_gl_entries(self, gl_entries, warehouse_account=None):
warehouse_with_no_account = []