Merge remote-tracking branch 'upstream/develop' into payments-based-dunning
diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py
index 7b2575d..42c0e51 100644
--- a/erpnext/accounts/doctype/finance_book/test_finance_book.py
+++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py
@@ -13,7 +13,7 @@
finance_book = create_finance_book()
# create jv entry
- jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank - _TC", "Debtors - _TC", 100, save=False)
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer"})
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 73b1911..e7aca79 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -43,7 +43,7 @@
frappe.db.sql(
"""select name from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s""",
- ("_Test Receivable - _TC", test_voucher.name),
+ ("Debtors - _TC", test_voucher.name),
)
)
@@ -273,7 +273,7 @@
jv.submit()
# create jv in USD, but account currency in INR
- jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable - _TC", 100, save=False)
+ jv = make_journal_entry("_Test Bank - _TC", "Debtors - _TC", 100, save=False)
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD"})
diff --git a/erpnext/accounts/doctype/journal_entry/test_records.json b/erpnext/accounts/doctype/journal_entry/test_records.json
index 5077305..dafcf56 100644
--- a/erpnext/accounts/doctype/journal_entry/test_records.json
+++ b/erpnext/accounts/doctype/journal_entry/test_records.json
@@ -6,7 +6,7 @@
"doctype": "Journal Entry",
"accounts": [
{
- "account": "_Test Receivable - _TC",
+ "account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 400.0,
@@ -70,7 +70,7 @@
"doctype": "Journal Entry",
"accounts": [
{
- "account": "_Test Receivable - _TC",
+ "account": "Debtors - _TC",
"party_type": "Customer",
"party": "_Test Customer",
"credit_in_account_currency": 0.0,
diff --git a/erpnext/accounts/doctype/party_account/party_account.json b/erpnext/accounts/doctype/party_account/party_account.json
index 6933057..7e345d8 100644
--- a/erpnext/accounts/doctype/party_account/party_account.json
+++ b/erpnext/accounts/doctype/party_account/party_account.json
@@ -6,7 +6,8 @@
"engine": "InnoDB",
"field_order": [
"company",
- "account"
+ "account",
+ "advance_account"
],
"fields": [
{
@@ -22,14 +23,20 @@
"fieldname": "account",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "Account",
+ "label": "Default Account",
+ "options": "Account"
+ },
+ {
+ "fieldname": "advance_account",
+ "fieldtype": "Link",
+ "label": "Advance Account",
"options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-04 12:31:02.994197",
+ "modified": "2023-06-06 14:15:42.053150",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Party Account",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index bac84db..0701435 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -319,6 +319,10 @@
}
},
+ company: function(frm){
+ frm.trigger('party');
+ },
+
party: function(frm) {
if (frm.doc.contact_email || frm.doc.contact_person) {
frm.set_value("contact_email", "");
@@ -733,7 +737,6 @@
if(r.message) {
var total_positive_outstanding = 0;
var total_negative_outstanding = 0;
-
$.each(r.message, function(i, d) {
var c = frm.add_child("references");
c.reference_doctype = d.voucher_type;
@@ -744,6 +747,7 @@
c.bill_no = d.bill_no;
c.payment_term = d.payment_term;
c.allocated_amount = d.allocated_amount;
+ c.account = d.account;
if(!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) {
if(flt(d.outstanding_amount) > 0)
@@ -1459,4 +1463,4 @@
});
}
},
-})
+})
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 6224d40..d7b6a19 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -19,6 +19,7 @@
"party_type",
"party",
"party_name",
+ "book_advance_payments_in_separate_party_account",
"column_break_11",
"bank_account",
"party_bank_account",
@@ -735,12 +736,21 @@
"fieldname": "get_outstanding_orders",
"fieldtype": "Button",
"label": "Get Outstanding Orders"
+ },
+ {
+ "default": "0",
+ "fetch_from": "company.book_advance_payments_in_separate_party_account",
+ "fieldname": "book_advance_payments_in_separate_party_account",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Book Advance Payments in Separate Party Account",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-06-19 11:38:04.387219",
+ "modified": "2023-06-23 18:07:38.023010",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index c7f9759..c85c1ae 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -21,7 +21,11 @@
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details,
)
-from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map
+from erpnext.accounts.general_ledger import (
+ make_gl_entries,
+ make_reverse_gl_entries,
+ process_gl_map,
+)
from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
from erpnext.controllers.accounts_controller import (
@@ -60,6 +64,7 @@
def validate(self):
self.setup_party_account_field()
self.set_missing_values()
+ self.set_liability_account()
self.set_missing_ref_details()
self.validate_payment_type()
self.validate_party_details()
@@ -87,11 +92,45 @@
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
+ self.make_advance_gl_entries()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_payment_schedule()
self.set_status()
+ def set_liability_account(self):
+ if not self.book_advance_payments_in_separate_party_account:
+ return
+
+ account_type = frappe.get_value(
+ "Account", {"name": self.party_account, "company": self.company}, "account_type"
+ )
+
+ if (account_type == "Payable" and self.party_type == "Customer") or (
+ account_type == "Receivable" and self.party_type == "Supplier"
+ ):
+ return
+
+ if self.unallocated_amount == 0:
+ for d in self.references:
+ if d.reference_doctype in ["Sales Order", "Purchase Order"]:
+ break
+ else:
+ return
+
+ liability_account = get_party_account(
+ self.party_type, self.party, self.company, include_advance=True
+ )[1]
+
+ self.set(self.party_account_field, liability_account)
+
+ msg = "Book Advance Payments as Liability option is chosen. Paid From account changed from {0} to {1}.".format(
+ frappe.bold(self.party_account),
+ frappe.bold(liability_account),
+ )
+
+ frappe.msgprint(_(msg), alert=True)
+
def on_cancel(self):
self.ignore_linked_doctypes = (
"GL Entry",
@@ -101,6 +140,7 @@
"Repost Payment Ledger Items",
)
self.make_gl_entries(cancel=1)
+ self.make_advance_gl_entries(cancel=1)
self.update_outstanding_amounts()
self.update_advance_paid()
self.delink_advance_entry_references()
@@ -174,7 +214,8 @@
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
- }
+ },
+ validate=True,
)
# Group latest_references by (voucher_type, voucher_no)
@@ -379,7 +420,10 @@
elif self.party_type == "Employee":
ref_party_account = ref_doc.payable_account
- if ref_party_account != self.party_account:
+ if (
+ ref_party_account != self.party_account
+ and not self.book_advance_payments_in_separate_party_account
+ ):
frappe.throw(
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
d.reference_doctype, d.reference_name, ref_party_account, self.party_account
@@ -941,24 +985,27 @@
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
+
gle = party_gl_dict.copy()
- gle.update(
- {
- "against_voucher_type": d.reference_doctype,
- "against_voucher": d.reference_name,
- "cost_center": cost_center,
- }
- )
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
+ if self.book_advance_payments_in_separate_party_account:
+ against_voucher_type = "Payment Entry"
+ against_voucher = self.name
+ else:
+ against_voucher_type = d.reference_doctype
+ against_voucher = d.reference_name
+
gle.update(
{
- dr_or_cr + "_in_account_currency": d.allocated_amount,
dr_or_cr: allocated_amount_in_company_currency,
+ dr_or_cr + "_in_account_currency": d.allocated_amount,
+ "against_voucher_type": against_voucher_type,
+ "against_voucher": against_voucher,
+ "cost_center": cost_center,
}
)
-
gl_entries.append(gle)
if self.unallocated_amount:
@@ -966,7 +1013,6 @@
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
-
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
@@ -976,6 +1022,80 @@
gl_entries.append(gle)
+ def make_advance_gl_entries(self, against_voucher_type=None, against_voucher=None, cancel=0):
+ if self.book_advance_payments_in_separate_party_account:
+ gl_entries = []
+ for d in self.get("references"):
+ if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
+ if not (against_voucher_type and against_voucher) or (
+ d.reference_doctype == against_voucher_type and d.reference_name == against_voucher
+ ):
+ self.make_invoice_liability_entry(gl_entries, d)
+
+ if cancel:
+ for entry in gl_entries:
+ frappe.db.set_value(
+ "GL Entry",
+ {
+ "voucher_no": self.name,
+ "voucher_type": self.doctype,
+ "voucher_detail_no": entry.voucher_detail_no,
+ "against_voucher_type": entry.against_voucher_type,
+ "against_voucher": entry.against_voucher,
+ },
+ "is_cancelled",
+ 1,
+ )
+
+ make_reverse_gl_entries(gl_entries=gl_entries, partial_cancel=True)
+ else:
+ make_gl_entries(gl_entries)
+
+ def make_invoice_liability_entry(self, gl_entries, invoice):
+ args_dict = {
+ "party_type": self.party_type,
+ "party": self.party,
+ "account_currency": self.party_account_currency,
+ "cost_center": self.cost_center,
+ "voucher_type": "Payment Entry",
+ "voucher_no": self.name,
+ "voucher_detail_no": invoice.name,
+ }
+
+ dr_or_cr = "credit" if invoice.reference_doctype == "Sales Invoice" else "debit"
+ args_dict["account"] = invoice.account
+ args_dict[dr_or_cr] = invoice.allocated_amount
+ args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
+ args_dict.update(
+ {
+ "against_voucher_type": invoice.reference_doctype,
+ "against_voucher": invoice.reference_name,
+ }
+ )
+ gle = self.get_gl_dict(
+ args_dict,
+ item=self,
+ )
+ gl_entries.append(gle)
+
+ args_dict[dr_or_cr] = 0
+ args_dict[dr_or_cr + "_in_account_currency"] = 0
+ dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
+ args_dict["account"] = self.party_account
+ args_dict[dr_or_cr] = invoice.allocated_amount
+ args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
+ args_dict.update(
+ {
+ "against_voucher_type": "Payment Entry",
+ "against_voucher": self.name,
+ }
+ )
+ gle = self.get_gl_dict(
+ args_dict,
+ item=self,
+ )
+ gl_entries.append(gle)
+
def add_bank_gl_entries(self, gl_entries):
if self.payment_type in ("Pay", "Internal Transfer"):
gl_entries.append(
@@ -1301,7 +1421,7 @@
@frappe.whitelist()
-def get_outstanding_reference_documents(args):
+def get_outstanding_reference_documents(args, validate=False):
if isinstance(args, str):
args = json.loads(args)
@@ -1365,7 +1485,7 @@
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
- args.get("party_account"),
+ get_party_account(args.get("party_type"), args.get("party"), args.get("company")),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
@@ -1421,13 +1541,14 @@
elif args.get("get_orders_to_be_billed"):
ref_document_type = "orders"
- frappe.msgprint(
- _(
- "No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
- ).format(
- ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
+ if not validate:
+ frappe.msgprint(
+ _(
+ "No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
+ ).format(
+ ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
+ )
)
- )
return data
@@ -1463,6 +1584,7 @@
"outstanding_amount": flt(d.outstanding_amount),
"payment_amount": payment_term.payment_amount,
"payment_term": payment_term.payment_term,
+ "account": d.account,
}
)
)
@@ -1587,6 +1709,7 @@
condition=None,
):
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
+ account = "debit_to" if voucher_type == "Sales Invoice" else "credit_to"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
supplier_condition = "and (release_date is null or release_date <= CURRENT_DATE)"
@@ -1600,7 +1723,7 @@
return frappe.db.sql(
"""
select
- "{voucher_type}" as voucher_type, name as voucher_no,
+ "{voucher_type}" as voucher_type, name as voucher_no, {account} as account,
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
outstanding_amount, posting_date,
due_date, conversion_rate as exchange_rate
@@ -1623,6 +1746,7 @@
"party_type": scrub(party_type),
"party_account": "debit_to" if party_type == "Customer" else "credit_to",
"cost_center": cost_center,
+ "account": account,
}
),
(party, party_account),
@@ -1637,7 +1761,6 @@
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
party_account = get_party_account(party_type, party, company)
-
account_currency = get_account_currency(party_account)
account_balance = get_balance_on(party_account, date, cost_center=cost_center)
_party_name = "title" if party_type == "Shareholder" else party_type.lower() + "_name"
@@ -1710,7 +1833,7 @@
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
- total_amount = outstanding_amount = exchange_rate = None
+ total_amount = outstanding_amount = exchange_rate = account = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
@@ -1748,6 +1871,9 @@
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
+ account = (
+ ref_doc.get("debit_to") if reference_doctype == "Sales Invoice" else ref_doc.get("credit_to")
+ )
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
@@ -1755,7 +1881,7 @@
# Get the exchange rate based on the posting date of the ref doc.
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
- return frappe._dict(
+ res = frappe._dict(
{
"due_date": ref_doc.get("due_date"),
"total_amount": flt(total_amount),
@@ -1764,6 +1890,9 @@
"bill_no": ref_doc.get("bill_no"),
}
)
+ if account:
+ res.update({"account": account})
+ return res
@frappe.whitelist()
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index ae2625b..70cc4b3 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -932,7 +932,7 @@
self.assertEqual(pe.cost_center, si.cost_center)
self.assertEqual(flt(expected_account_balance), account_balance)
self.assertEqual(flt(expected_party_balance), party_balance)
- self.assertEqual(flt(expected_party_account_balance), party_account_balance)
+ self.assertEqual(flt(expected_party_account_balance, 2), flt(party_account_balance, 2))
def test_multi_currency_payment_entry_with_taxes(self):
payment_entry = create_payment_entry(
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 3003c68..12aa0b5 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -15,7 +15,8 @@
"outstanding_amount",
"allocated_amount",
"exchange_rate",
- "exchange_gain_loss"
+ "exchange_gain_loss",
+ "account"
],
"fields": [
{
@@ -101,12 +102,18 @@
"label": "Exchange Gain/Loss",
"options": "Company:company:default_currency",
"read_only": 1
+ },
+ {
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "label": "Account",
+ "options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-12-12 12:31:44.919895",
+ "modified": "2023-06-08 07:40:38.487874",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
index 22842ce..9cf2ac6 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json
@@ -13,6 +13,7 @@
"party_type",
"party",
"due_date",
+ "voucher_detail_no",
"cost_center",
"finance_book",
"voucher_type",
@@ -142,12 +143,17 @@
"fieldname": "remarks",
"fieldtype": "Text",
"label": "Remarks"
+ },
+ {
+ "fieldname": "voucher_detail_no",
+ "fieldtype": "Data",
+ "label": "Voucher Detail No"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-08-22 15:32:56.629430",
+ "modified": "2023-06-29 12:24:20.500632",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 89fa151..2adc123 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -29,6 +29,17 @@
};
});
+ this.frm.set_query('default_advance_account', () => {
+ return {
+ filters: {
+ "company": this.frm.doc.company,
+ "is_group": 0,
+ "account_type": this.frm.doc.party_type == 'Customer' ? "Receivable": "Payable",
+ "root_type": this.frm.doc.party_type == 'Customer' ? "Liability": "Asset"
+ }
+ };
+ });
+
this.frm.set_query('bank_cash_account', () => {
return {
filters:[
@@ -128,19 +139,20 @@
this.frm.trigger("clear_child_tables");
if (!this.frm.doc.receivable_payable_account && this.frm.doc.party_type && this.frm.doc.party) {
- return frappe.call({
+ frappe.call({
method: "erpnext.accounts.party.get_party_account",
args: {
company: this.frm.doc.company,
party_type: this.frm.doc.party_type,
- party: this.frm.doc.party
+ party: this.frm.doc.party,
+ include_advance: 1
},
callback: (r) => {
if (!r.exc && r.message) {
- this.frm.set_value("receivable_payable_account", r.message);
+ this.frm.set_value("receivable_payable_account", r.message[0]);
+ this.frm.set_value("default_advance_account", r.message[1]);
}
this.frm.refresh();
-
}
});
}
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index 18d3485..5f6c703 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -10,6 +10,7 @@
"column_break_4",
"party",
"receivable_payable_account",
+ "default_advance_account",
"col_break1",
"from_invoice_date",
"from_payment_date",
@@ -185,13 +186,21 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
+ },
+ {
+ "depends_on": "eval:doc.party",
+ "fieldname": "default_advance_account",
+ "fieldtype": "Link",
+ "label": "Default Advance Account",
+ "mandatory_depends_on": "doc.party_type",
+ "options": "Account"
}
],
"hide_toolbar": 1,
"icon": "icon-resize-horizontal",
"issingle": 1,
"links": [],
- "modified": "2022-04-29 15:37:10.246831",
+ "modified": "2023-06-09 13:02:48.718362",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 216d4ec..25d94c5 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -55,12 +55,28 @@
self.add_payment_entries(non_reconciled_payments)
def get_payment_entries(self):
+ if self.default_advance_account:
+ party_account = [self.receivable_payable_account, self.default_advance_account]
+ else:
+ party_account = [self.receivable_payable_account]
+
order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
- condition = self.get_conditions(get_payments=True)
+ condition = frappe._dict(
+ {
+ "company": self.get("company"),
+ "get_payments": True,
+ "cost_center": self.get("cost_center"),
+ "from_payment_date": self.get("from_payment_date"),
+ "to_payment_date": self.get("to_payment_date"),
+ "maximum_payment_amount": self.get("maximum_payment_amount"),
+ "minimum_payment_amount": self.get("minimum_payment_amount"),
+ }
+ )
+
payment_entries = get_advance_payment_entries(
self.party_type,
self.party,
- self.receivable_payable_account,
+ party_account,
order_doctype,
against_all_orders=True,
limit=self.payment_limit,
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index cced375..32e267f 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -20,7 +20,7 @@
onload(doc) {
super.onload();
- this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry'];
+ this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice Merge Log', 'POS Closing Entry', 'Serial and Batch Bundle'];
if(doc.__islocal && doc.is_pos && frappe.get_route_str() !== 'point-of-sale') {
this.frm.script_manager.trigger("is_pos");
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index bf393c0..4b2fcec 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -93,7 +93,7 @@
)
def on_cancel(self):
- self.ignore_linked_doctypes = "Payment Ledger Entry"
+ self.ignore_linked_doctypes = ["Payment Ledger Entry", "Serial and Batch Bundle"]
# run on cancel method of selling controller
super(SalesInvoice, self).on_cancel()
if not self.is_return and self.loyalty_program:
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index f842a16..0fce61f 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -767,6 +767,39 @@
)
self.assertEqual(rounded_total, 400)
+ def test_pos_batch_reservation(self):
+ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+ get_auto_batch_nos,
+ )
+ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+ create_batch_item_with_batch,
+ )
+
+ create_batch_item_with_batch("_BATCH ITEM Test For Reserve", "TestBatch-RS 02")
+ make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="_BATCH ITEM Test For Reserve",
+ qty=20,
+ basic_rate=100,
+ batch_no="TestBatch-RS 02",
+ )
+
+ pos_inv1 = create_pos_invoice(
+ item="_BATCH ITEM Test For Reserve", rate=300, qty=15, batch_no="TestBatch-RS 02"
+ )
+ pos_inv1.save()
+ pos_inv1.submit()
+
+ batches = get_auto_batch_nos(
+ frappe._dict(
+ {"item_code": "_BATCH ITEM Test For Reserve", "warehouse": "_Test Warehouse - _TC"}
+ )
+ )
+
+ for batch in batches:
+ if batch.batch_no == "TestBatch-RS 02" and batch.warehouse == "_Test Warehouse - _TC":
+ self.assertEqual(batch.qty, 5)
+
def test_pos_batch_item_qty_validation(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
BatchNegativeStockError,
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
index 5a0aeb7..83646c9 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
@@ -38,7 +38,7 @@
si.save()
si.submit()
- process_deferred_accounting = doc = frappe.get_doc(
+ process_deferred_accounting = frappe.get_doc(
dict(
doctype="Process Deferred Accounting",
posting_date="2019-01-01",
@@ -56,7 +56,7 @@
["Sales - _TC", 0.0, 33.85, "2019-01-31"],
]
- check_gl_entries(self, si.name, expected_gle, "2019-01-10")
+ check_gl_entries(self, si.name, expected_gle, "2019-01-31")
def test_pda_submission_and_cancellation(self):
pda = frappe.get_doc(
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 0c18f5e..e247e80 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -1088,6 +1088,7 @@
"fieldtype": "Button",
"label": "Get Advances Paid",
"oldfieldtype": "Button",
+ "options": "set_advances",
"print_hide": 1
},
{
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 45bddfc..8c96480 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1664,6 +1664,63 @@
self.assertTrue(return_pi.docstatus == 1)
+ def test_advance_entries_as_asset(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
+
+ account = create_account(
+ parent_account="Current Assets - _TC",
+ account_name="Advances Paid",
+ company="_Test Company",
+ account_type="Receivable",
+ )
+
+ set_advance_flag(company="_Test Company", flag=1, default_account=account)
+
+ pe = create_payment_entry(
+ company="_Test Company",
+ payment_type="Pay",
+ party_type="Supplier",
+ party="_Test Supplier",
+ paid_from="Cash - _TC",
+ paid_to="Creditors - _TC",
+ paid_amount=500,
+ )
+ pe.submit()
+
+ pi = make_purchase_invoice(
+ company="_Test Company",
+ customer="_Test Supplier",
+ do_not_save=True,
+ do_not_submit=True,
+ rate=1000,
+ price_list_rate=1000,
+ qty=1,
+ )
+ pi.base_grand_total = 1000
+ pi.grand_total = 1000
+ pi.set_advances()
+ for advance in pi.advances:
+ advance.allocated_amount = 500 if advance.reference_name == pe.name else 0
+ pi.save()
+ pi.submit()
+
+ self.assertEqual(pi.advances[0].allocated_amount, 500)
+
+ # Check GL Entry against payment doctype
+ expected_gle = [
+ ["Advances Paid - _TC", 0.0, 500, nowdate()],
+ ["Cash - _TC", 0.0, 500, nowdate()],
+ ["Creditors - _TC", 500, 0.0, nowdate()],
+ ["Creditors - _TC", 500, 0.0, nowdate()],
+ ]
+
+ check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
+
+ pi.load_from_db()
+ self.assertEqual(pi.outstanding_amount, 500)
+
+ set_advance_flag(company="_Test Company", flag=0, default_account="")
+
def test_gl_entries_for_standalone_debit_note(self):
make_purchase_invoice(qty=5, rate=500, update_stock=True)
@@ -1680,16 +1737,32 @@
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
-def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
- gl_entries = frappe.db.sql(
- """select account, debit, credit, posting_date
- from `tabGL Entry`
- where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
- order by posting_date asc, account asc""",
- (voucher_no, posting_date),
- as_dict=1,
+def set_advance_flag(company, flag, default_account):
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": flag,
+ "default_advance_paid_account": default_account,
+ },
)
+
+def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Purchase Invoice"):
+ gl = frappe.qb.DocType("GL Entry")
+ q = (
+ frappe.qb.from_(gl)
+ .select(gl.account, gl.debit, gl.credit, gl.posting_date)
+ .where(
+ (gl.voucher_type == voucher_type)
+ & (gl.voucher_no == voucher_no)
+ & (gl.posting_date >= posting_date)
+ & (gl.is_cancelled == 0)
+ )
+ .orderby(gl.posting_date, gl.account, gl.creation)
+ )
+ gl_entries = q.run(as_dict=True)
+
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
doc.assertEqual(expected_gle[i][1], gle.debit)
diff --git a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
index 9fcbf5c..4db531e 100644
--- a/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
+++ b/erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json
@@ -117,7 +117,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-09-26 15:47:28.167371",
+ "modified": "2023-06-23 21:13:18.013816",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Advance",
@@ -125,5 +125,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index deb202d..c5187a2 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -178,6 +178,7 @@
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Qty",
+ "no_copy": 1,
"read_only": 1
},
{
@@ -903,7 +904,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-04-01 20:08:54.545160",
+ "modified": "2023-07-02 18:39:41.495723",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index d21a50c..4ec103c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -680,19 +680,6 @@
}
}
- // expense account
- frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) {
- if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
- return {
- filters: {
- 'report_type': 'Profit and Loss',
- 'company': doc.company,
- "is_group": 0
- }
- }
- }
- }
-
// discount account
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index 3781f8c..61e5219 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -6,7 +6,7 @@
"cost_center": "_Test Cost Center - _TC",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"items": [
{
@@ -78,7 +78,7 @@
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
@@ -137,7 +137,7 @@
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
@@ -265,7 +265,7 @@
"currency": "INR",
"customer": "_Test Customer",
"customer_name": "_Test Customer",
- "debit_to": "_Test Receivable - _TC",
+ "debit_to": "Debtors - _TC",
"doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 784bdf6..0280c35 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -6,7 +6,6 @@
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
-from frappe.model.naming import make_autoname
from frappe.tests.utils import change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
@@ -35,7 +34,6 @@
get_serial_nos_from_bundle,
make_serial_batch_bundle,
)
-from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
get_qty_after_transaction,
make_stock_entry,
@@ -1726,7 +1724,7 @@
# Party Account currency must be in USD, as there is existing GLE with USD
si4 = create_sales_invoice(
customer="_Test Customer USD",
- debit_to="_Test Receivable - _TC",
+ debit_to="Debtors - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
@@ -1739,7 +1737,7 @@
si3.cancel()
si5 = create_sales_invoice(
customer="_Test Customer USD",
- debit_to="_Test Receivable - _TC",
+ debit_to="Debtors - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
@@ -1818,7 +1816,7 @@
"reference_date": nowdate(),
"received_amount": 300,
"paid_amount": 300,
- "paid_from": "_Test Receivable - _TC",
+ "paid_from": "Debtors - _TC",
"paid_to": "_Test Cash - _TC",
}
)
@@ -3252,9 +3250,10 @@
si.submit()
expected_gle = [
- ["_Test Receivable USD - _TC", 7500.0, 500],
- ["Exchange Gain/Loss - _TC", 500.0, 0.0],
- ["Sales - _TC", 0.0, 7500.0],
+ ["_Test Exchange Gain/Loss - _TC", 500.0, 0.0, nowdate()],
+ ["_Test Receivable USD - _TC", 7500.0, 0.0, nowdate()],
+ ["_Test Receivable USD - _TC", 0.0, 500.0, nowdate()],
+ ["Sales - _TC", 0.0, 7500.0, nowdate()],
]
check_gl_entries(self, si.name, expected_gle, nowdate())
@@ -3310,6 +3309,73 @@
)
self.assertRaises(frappe.ValidationError, si.submit)
+ def test_advance_entries_as_liability(self):
+ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
+
+ account = create_account(
+ parent_account="Current Liabilities - _TC",
+ account_name="Advances Received",
+ company="_Test Company",
+ account_type="Receivable",
+ )
+
+ set_advance_flag(company="_Test Company", flag=1, default_account=account)
+
+ pe = create_payment_entry(
+ company="_Test Company",
+ payment_type="Receive",
+ party_type="Customer",
+ party="_Test Customer",
+ paid_from="Debtors - _TC",
+ paid_to="Cash - _TC",
+ paid_amount=1000,
+ )
+ pe.submit()
+
+ si = create_sales_invoice(
+ company="_Test Company",
+ customer="_Test Customer",
+ do_not_save=True,
+ do_not_submit=True,
+ rate=500,
+ price_list_rate=500,
+ )
+ si.base_grand_total = 500
+ si.grand_total = 500
+ si.set_advances()
+ for advance in si.advances:
+ advance.allocated_amount = 500 if advance.reference_name == pe.name else 0
+ si.save()
+ si.submit()
+
+ self.assertEqual(si.advances[0].allocated_amount, 500)
+
+ # Check GL Entry against payment doctype
+ expected_gle = [
+ ["Advances Received - _TC", 500, 0.0, nowdate()],
+ ["Cash - _TC", 1000, 0.0, nowdate()],
+ ["Debtors - _TC", 0.0, 1000, nowdate()],
+ ["Debtors - _TC", 0.0, 500, nowdate()],
+ ]
+
+ check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
+
+ si.load_from_db()
+ self.assertEqual(si.outstanding_amount, 0)
+
+ set_advance_flag(company="_Test Company", flag=0, default_account="")
+
+
+def set_advance_flag(company, flag, default_account):
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": flag,
+ "default_advance_received_account": default_account,
+ },
+ )
+
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
@@ -3346,16 +3412,20 @@
return si
-def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
- gl_entries = frappe.db.sql(
- """select account, debit, credit, posting_date
- from `tabGL Entry`
- where voucher_type='Sales Invoice' and voucher_no=%s and posting_date > %s
- and is_cancelled = 0
- order by posting_date asc, account asc""",
- (voucher_no, posting_date),
- as_dict=1,
+def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Sales Invoice"):
+ gl = frappe.qb.DocType("GL Entry")
+ q = (
+ frappe.qb.from_(gl)
+ .select(gl.account, gl.debit, gl.credit, gl.posting_date)
+ .where(
+ (gl.voucher_type == voucher_type)
+ & (gl.voucher_no == voucher_no)
+ & (gl.posting_date >= posting_date)
+ & (gl.is_cancelled == 0)
+ )
+ .orderby(gl.posting_date, gl.account, gl.creation)
)
+ gl_entries = q.run(as_dict=True)
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
diff --git a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
index f92b57a..0ae85d9 100644
--- a/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
+++ b/erpnext/accounts/doctype/sales_invoice_advance/sales_invoice_advance.json
@@ -118,7 +118,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-09-26 15:47:46.911595",
+ "modified": "2023-06-23 21:12:57.557731",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Advance",
@@ -126,5 +126,6 @@
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index a929ff1..f1dad87 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -223,6 +223,7 @@
"party_type",
"project",
"finance_book",
+ "voucher_no",
]
if dimensions:
@@ -500,7 +501,12 @@
def make_reverse_gl_entries(
- gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes"
+ gl_entries=None,
+ voucher_type=None,
+ voucher_no=None,
+ adv_adj=False,
+ update_outstanding="Yes",
+ partial_cancel=False,
):
"""
Get original gl entries of the voucher
@@ -520,14 +526,19 @@
if gl_entries:
create_payment_ledger_entry(
- gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
+ gl_entries,
+ cancel=1,
+ adv_adj=adv_adj,
+ update_outstanding=update_outstanding,
+ partial_cancel=partial_cancel,
)
validate_accounting_period(gl_entries)
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
- set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
+ if not partial_cancel:
+ set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries:
new_gle = copy.deepcopy(entry)
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 07b865e..03cf82a 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -367,7 +367,7 @@
@frappe.whitelist()
-def get_party_account(party_type, party=None, company=None):
+def get_party_account(party_type, party=None, company=None, include_advance=False):
"""Returns the account for the given `party`.
Will first search in party (Customer / Supplier) record, if not found,
will search in group (Customer Group / Supplier Group),
@@ -408,6 +408,40 @@
if (account and account_currency != existing_gle_currency) or not account:
account = get_party_gle_account(party_type, party, company)
+ if include_advance and party_type in ["Customer", "Supplier"]:
+ advance_account = get_party_advance_account(party_type, party, company)
+ if advance_account:
+ return [account, advance_account]
+ else:
+ return [account]
+
+ return account
+
+
+def get_party_advance_account(party_type, party, company):
+ account = frappe.db.get_value(
+ "Party Account",
+ {"parenttype": party_type, "parent": party, "company": company},
+ "advance_account",
+ )
+
+ if not account:
+ party_group_doctype = "Customer Group" if party_type == "Customer" else "Supplier Group"
+ group = frappe.get_cached_value(party_type, party, scrub(party_group_doctype))
+ account = frappe.db.get_value(
+ "Party Account",
+ {"parenttype": party_group_doctype, "parent": group, "company": company},
+ "advance_account",
+ )
+
+ if not account:
+ account_name = (
+ "default_advance_received_account"
+ if party_type == "Customer"
+ else "default_advance_paid_account"
+ )
+ account = frappe.get_cached_value("Company", company, account_name)
+
return account
@@ -517,7 +551,10 @@
)
# validate if account is mapped for same company
- validate_account_head(account.idx, account.account, account.company)
+ if account.account:
+ validate_account_head(account.idx, account.account, account.company)
+ if account.advance_account:
+ validate_account_head(account.idx, account.advance_account, account.company)
@frappe.whitelist()
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index 1c461ef..298d838 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -14,8 +14,10 @@
"label": __("Project"),
"fieldtype": "MultiSelectList",
get_data: function(txt) {
- return frappe.db.get_link_options('Project', txt);
- }
+ return frappe.db.get_link_options('Project', txt, {
+ company: frappe.query_report.get_filter_value("company")
+ });
+ },
},
{
"fieldname": "include_default_book_entries",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index a5cb324..9000b0d 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -470,6 +470,9 @@
gl_map = doc.build_gl_map()
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
+ if voucher_type == "Payment Entry":
+ doc.make_advance_gl_entries()
+
# Only update outstanding for newly linked vouchers
for entry in entries:
update_voucher_outstanding(
@@ -490,50 +493,53 @@
ret = None
if args.voucher_type == "Journal Entry":
- ret = frappe.db.sql(
- """
- select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2
- where t1.name = t2.parent and t2.account = %(account)s
- and t2.party_type = %(party_type)s and t2.party = %(party)s
- and (t2.reference_type is null or t2.reference_type in ('', 'Sales Order', 'Purchase Order'))
- and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
- and t1.docstatus=1 """.format(
- dr_or_cr=args.get("dr_or_cr")
- ),
- args,
+ journal_entry = frappe.qb.DocType("Journal Entry")
+ journal_acc = frappe.qb.DocType("Journal Entry Account")
+
+ q = (
+ frappe.qb.from_(journal_entry)
+ .inner_join(journal_acc)
+ .on(journal_entry.name == journal_acc.parent)
+ .select(journal_acc[args.get("dr_or_cr")])
+ .where(
+ (journal_acc.account == args.get("account"))
+ & ((journal_acc.party_type == args.get("party_type")))
+ & ((journal_acc.party == args.get("party")))
+ & (
+ (journal_acc.reference_type.isnull())
+ | (journal_acc.reference_type.isin(["", "Sales Order", "Purchase Order"]))
+ )
+ & ((journal_entry.name == args.get("voucher_no")))
+ & ((journal_acc.name == args.get("voucher_detail_no")))
+ & ((journal_entry.docstatus == 1))
+ )
)
+
else:
- party_account_field = (
- "paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to"
+ payment_entry = frappe.qb.DocType("Payment Entry")
+ payment_ref = frappe.qb.DocType("Payment Entry Reference")
+
+ q = (
+ frappe.qb.from_(payment_entry)
+ .select(payment_entry.name)
+ .where(payment_entry.name == args.get("voucher_no"))
+ .where(payment_entry.docstatus == 1)
+ .where(payment_entry.party_type == args.get("party_type"))
+ .where(payment_entry.party == args.get("party"))
)
if args.voucher_detail_no:
- ret = frappe.db.sql(
- """select t1.name
- from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
- where
- t1.name = t2.parent and t1.docstatus = 1
- and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
- and t1.party_type = %(party_type)s and t1.party = %(party)s and t1.{0} = %(account)s
- and t2.reference_doctype in ('', 'Sales Order', 'Purchase Order')
- and t2.allocated_amount = %(unreconciled_amount)s
- """.format(
- party_account_field
- ),
- args,
+ q = (
+ q.inner_join(payment_ref)
+ .on(payment_entry.name == payment_ref.parent)
+ .where(payment_ref.name == args.get("voucher_detail_no"))
+ .where(payment_ref.reference_doctype.isin(("", "Sales Order", "Purchase Order")))
+ .where(payment_ref.allocated_amount == args.get("unreconciled_amount"))
)
else:
- ret = frappe.db.sql(
- """select name from `tabPayment Entry`
- where
- name = %(voucher_no)s and docstatus = 1
- and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
- and unallocated_amount = %(unreconciled_amount)s
- """.format(
- party_account_field
- ),
- args,
- )
+ q = q.where(payment_entry.unallocated_amount == args.get("unreconciled_amount"))
+
+ ret = q.run(as_dict=True)
if not ret:
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
@@ -612,6 +618,7 @@
if not d.exchange_gain_loss
else payment_entry.get_exchange_rate(),
"exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
+ "account": d.account,
}
if d.voucher_detail_no:
@@ -724,6 +731,7 @@
try:
pe_doc = frappe.get_doc("Payment Entry", pe)
pe_doc.set_amounts()
+ pe_doc.make_advance_gl_entries(against_voucher_type=ref_type, against_voucher=ref_no, cancel=1)
pe_doc.clear_unallocated_reference_document_rows()
pe_doc.validate_payment_type_with_outstanding()
except Exception as e:
@@ -915,6 +923,7 @@
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
"currency": d.currency,
+ "account": d.account,
}
)
)
@@ -1453,6 +1462,7 @@
due_date=gle.due_date,
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,
@@ -1474,7 +1484,7 @@
def create_payment_ledger_entry(
- gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0
+ gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
):
if gl_entries:
ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel)
@@ -1484,7 +1494,7 @@
ple = frappe.get_doc(entry)
if cancel:
- delink_original_entry(ple)
+ delink_original_entry(ple, partial_cancel=partial_cancel)
ple.flags.ignore_permissions = 1
ple.flags.adv_adj = adv_adj
@@ -1531,7 +1541,7 @@
ref_doc.set_status(update=True)
-def delink_original_entry(pl_entry):
+def delink_original_entry(pl_entry, partial_cancel=False):
if pl_entry:
ple = qb.DocType("Payment Ledger Entry")
query = (
@@ -1551,6 +1561,10 @@
& (ple.against_voucher_no == pl_entry.against_voucher_no)
)
)
+
+ if partial_cancel:
+ query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
+
query.run()
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index a536578..5b95d0f 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -8,7 +8,7 @@
frm.set_value("represents_company", "");
}
frm.set_query('account', 'accounts', function (doc, cdt, cdn) {
- var d = locals[cdt][cdn];
+ let d = locals[cdt][cdn];
return {
filters: {
'account_type': 'Payable',
@@ -17,6 +17,19 @@
}
}
});
+
+ frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) {
+ let d = locals[cdt][cdn];
+ return {
+ filters: {
+ "account_type": "Payable",
+ "root_type": "Asset",
+ "company": d.company,
+ "is_group": 0
+ }
+ }
+ });
+
frm.set_query("default_bank_account", function() {
return {
filters: {
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index b3b6185..a07af71 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -53,6 +53,7 @@
"primary_address",
"accounting_tab",
"payment_terms",
+ "default_accounts_section",
"accounts",
"settings_tab",
"allow_purchase_invoice_creation_without_purchase_order",
@@ -450,6 +451,11 @@
"fieldtype": "Column Break"
},
{
+ "fieldname": "default_accounts_section",
+ "fieldtype": "Section Break",
+ "label": "Default Accounts"
+ },
+ {
"fieldname": "portal_users_tab",
"fieldtype": "Tab Break",
"label": "Portal Users"
diff --git a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
index 486bf23..58da851 100644
--- a/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/supplier_scorecard.py
@@ -329,6 +329,11 @@
"variable_label": "Total Shipments",
"path": "get_total_shipments",
},
+ {
+ "param_name": "total_ordered",
+ "variable_label": "Total Ordered",
+ "path": "get_ordered_qty",
+ },
]
install_standing_docs = [
{
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
index fb8819e..4080d1f 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/supplier_scorecard_variable.py
@@ -7,6 +7,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.query_builder.functions import Sum
from frappe.utils import getdate
@@ -422,6 +423,23 @@
return data
+def get_ordered_qty(scorecard):
+ """Returns the total number of ordered quantity (based on Purchase Orders)"""
+
+ po = frappe.qb.DocType("Purchase Order")
+
+ return (
+ frappe.qb.from_(po)
+ .select(Sum(po.total_qty))
+ .where(
+ (po.supplier == scorecard.supplier)
+ & (po.docstatus == 1)
+ & (po.transaction_date >= scorecard.get("start_date"))
+ & (po.transaction_date <= scorecard.get("end_date"))
+ )
+ ).run(as_list=True)[0][0] or 0
+
+
def get_rfq_total_number(scorecard):
"""Gets the total number of RFQs sent to supplier"""
supplier = frappe.get_doc("Supplier", scorecard.supplier)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c83e28d..4193b53 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -7,6 +7,7 @@
import frappe
from frappe import _, bold, throw
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
+from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import (
add_days,
@@ -755,6 +756,7 @@
"party": None,
"project": self.get("project"),
"post_net_value": args.get("post_net_value"),
+ "voucher_detail_no": args.get("voucher_detail_no"),
}
)
@@ -858,7 +860,6 @@
amount = self.get("base_rounded_total") or self.base_grand_total
else:
amount = self.get("rounded_total") or self.grand_total
-
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
@@ -872,25 +873,31 @@
"allocated_amount": allocated_amount,
"ref_exchange_rate": flt(d.exchange_rate), # exchange_rate of advance entry
}
+ if d.get("paid_from"):
+ advance_row["account"] = d.paid_from
+ if d.get("paid_to"):
+ advance_row["account"] = d.paid_to
self.append("advances", advance_row)
def get_advance_entries(self, include_unallocated=True):
if self.doctype == "Sales Invoice":
- party_account = self.debit_to
party_type = "Customer"
party = self.customer
amount_field = "credit_in_account_currency"
order_field = "sales_order"
order_doctype = "Sales Order"
else:
- party_account = self.credit_to
party_type = "Supplier"
party = self.supplier
amount_field = "debit_in_account_currency"
order_field = "purchase_order"
order_doctype = "Purchase Order"
+ party_account = get_party_account(
+ party_type, party=party, company=self.company, include_advance=True
+ )
+
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
journal_entries = get_advance_journal_entries(
@@ -2140,45 +2147,46 @@
order_list,
include_unallocated=True,
):
- dr_or_cr = (
- "credit_in_account_currency" if party_type == "Customer" else "debit_in_account_currency"
+ journal_entry = frappe.qb.DocType("Journal Entry")
+ journal_acc = frappe.qb.DocType("Journal Entry Account")
+ q = (
+ frappe.qb.from_(journal_entry)
+ .inner_join(journal_acc)
+ .on(journal_entry.name == journal_acc.parent)
+ .select(
+ ConstantColumn("Journal Entry").as_("reference_type"),
+ (journal_entry.name).as_("reference_name"),
+ (journal_entry.remark).as_("remarks"),
+ (journal_acc[amount_field]).as_("amount"),
+ (journal_acc.name).as_("reference_row"),
+ (journal_acc.reference_name).as_("against_order"),
+ (journal_acc.exchange_rate),
+ )
+ .where(
+ journal_acc.account.isin(party_account)
+ & (journal_acc.party_type == party_type)
+ & (journal_acc.party == party)
+ & (journal_acc.is_advance == "Yes")
+ & (journal_entry.docstatus == 1)
+ )
)
+ if party_type == "Customer":
+ q = q.where(journal_acc.credit_in_account_currency > 0)
- conditions = []
+ else:
+ q = q.where(journal_acc.debit_in_account_currency > 0)
+
if include_unallocated:
- conditions.append("ifnull(t2.reference_name, '')=''")
+ q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == ""))
if order_list:
- order_condition = ", ".join(["%s"] * len(order_list))
- conditions.append(
- " (t2.reference_type = '{0}' and ifnull(t2.reference_name, '') in ({1}))".format(
- order_doctype, order_condition
- )
+ q = q.where(
+ (journal_acc.reference_type == order_doctype) & ((journal_acc.reference_type).isin(order_list))
)
- reference_condition = " and (" + " or ".join(conditions) + ")" if conditions else ""
+ q = q.orderby(journal_entry.posting_date)
- # nosemgrep
- journal_entries = frappe.db.sql(
- """
- select
- 'Journal Entry' as reference_type, t1.name as reference_name,
- t1.remark as remarks, t2.{0} as amount, t2.name as reference_row,
- t2.reference_name as against_order, t2.exchange_rate
- from
- `tabJournal Entry` t1, `tabJournal Entry Account` t2
- where
- t1.name = t2.parent and t2.account = %s
- and t2.party_type = %s and t2.party = %s
- and t2.is_advance = 'Yes' and t1.docstatus = 1
- and {1} > 0 {2}
- order by t1.posting_date""".format(
- amount_field, dr_or_cr, reference_condition
- ),
- [party_account, party_type, party] + order_list,
- as_dict=1,
- )
-
+ journal_entries = q.run(as_dict=True)
return list(journal_entries)
@@ -2193,65 +2201,131 @@
limit=None,
condition=None,
):
- party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
- currency_field = (
- "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
- )
- payment_type = "Receive" if party_type == "Customer" else "Pay"
- exchange_rate_field = (
- "source_exchange_rate" if payment_type == "Receive" else "target_exchange_rate"
- )
- payment_entries_against_order, unallocated_payment_entries = [], []
- limit_cond = "limit %s" % limit if limit else ""
+ payment_entries = []
+ payment_entry = frappe.qb.DocType("Payment Entry")
if order_list or against_all_orders:
+ q = get_common_query(
+ party_type,
+ party,
+ party_account,
+ limit,
+ condition,
+ )
+ payment_ref = frappe.qb.DocType("Payment Entry Reference")
+
+ q = q.inner_join(payment_ref).on(payment_entry.name == payment_ref.parent)
+ q = q.select(
+ (payment_ref.allocated_amount).as_("amount"),
+ (payment_ref.name).as_("reference_row"),
+ (payment_ref.reference_name).as_("against_order"),
+ )
+
+ q = q.where(payment_ref.reference_doctype == order_doctype)
if order_list:
- reference_condition = " and t2.reference_name in ({0})".format(
- ", ".join(["%s"] * len(order_list))
+ q = q.where(payment_ref.reference_name.isin(order_list))
+
+ allocated = list(q.run(as_dict=True))
+ payment_entries += allocated
+ if include_unallocated:
+ q = get_common_query(
+ party_type,
+ party,
+ party_account,
+ limit,
+ condition,
+ )
+ q = q.select((payment_entry.unallocated_amount).as_("amount"))
+ q = q.where(payment_entry.unallocated_amount > 0)
+
+ unallocated = list(q.run(as_dict=True))
+ payment_entries += unallocated
+ return payment_entries
+
+
+def get_common_query(
+ party_type,
+ party,
+ party_account,
+ limit,
+ condition,
+):
+ payment_type = "Receive" if party_type == "Customer" else "Pay"
+ payment_entry = frappe.qb.DocType("Payment Entry")
+
+ q = (
+ frappe.qb.from_(payment_entry)
+ .select(
+ ConstantColumn("Payment Entry").as_("reference_type"),
+ (payment_entry.name).as_("reference_name"),
+ payment_entry.posting_date,
+ (payment_entry.remarks).as_("remarks"),
+ )
+ .where(payment_entry.payment_type == payment_type)
+ .where(payment_entry.party_type == party_type)
+ .where(payment_entry.party == party)
+ .where(payment_entry.docstatus == 1)
+ )
+
+ if party_type == "Customer":
+ q = q.select((payment_entry.paid_from_account_currency).as_("currency"))
+ q = q.select(payment_entry.paid_from)
+ q = q.where(payment_entry.paid_from.isin(party_account))
+ else:
+ q = q.select((payment_entry.paid_to_account_currency).as_("currency"))
+ q = q.select(payment_entry.paid_to)
+ q = q.where(payment_entry.paid_to.isin(party_account))
+
+ if payment_type == "Receive":
+ q = q.select((payment_entry.source_exchange_rate).as_("exchange_rate"))
+ else:
+ q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
+
+ if condition:
+ q = q.where(payment_entry.company == condition["company"])
+ q = (
+ q.where(payment_entry.posting_date >= condition["from_payment_date"])
+ if condition.get("from_payment_date")
+ else q
+ )
+ q = (
+ q.where(payment_entry.posting_date <= condition["to_payment_date"])
+ if condition.get("to_payment_date")
+ else q
+ )
+ if condition.get("get_payments") == True:
+ q = (
+ q.where(payment_entry.cost_center == condition["cost_center"])
+ if condition.get("cost_center")
+ else q
+ )
+ q = (
+ q.where(payment_entry.unallocated_amount >= condition["minimum_payment_amount"])
+ if condition.get("minimum_payment_amount")
+ else q
+ )
+ q = (
+ q.where(payment_entry.unallocated_amount <= condition["maximum_payment_amount"])
+ if condition.get("maximum_payment_amount")
+ else q
)
else:
- reference_condition = ""
- order_list = []
+ q = (
+ q.where(payment_entry.total_debit >= condition["minimum_payment_amount"])
+ if condition.get("minimum_payment_amount")
+ else q
+ )
+ q = (
+ q.where(payment_entry.total_debit <= condition["maximum_payment_amount"])
+ if condition.get("maximum_payment_amount")
+ else q
+ )
- payment_entries_against_order = frappe.db.sql(
- """
- select
- 'Payment Entry' as reference_type, t1.name as reference_name,
- t1.remarks, t2.allocated_amount as amount, t2.name as reference_row,
- t2.reference_name as against_order, t1.posting_date,
- t1.{0} as currency, t1.{4} as exchange_rate
- from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
- where
- t1.name = t2.parent and t1.{1} = %s and t1.payment_type = %s
- and t1.party_type = %s and t1.party = %s and t1.docstatus = 1
- and t2.reference_doctype = %s {2}
- order by t1.posting_date {3}
- """.format(
- currency_field, party_account_field, reference_condition, limit_cond, exchange_rate_field
- ),
- [party_account, payment_type, party_type, party, order_doctype] + order_list,
- as_dict=1,
- )
+ q = q.orderby(payment_entry.posting_date)
+ q = q.limit(limit) if limit else q
- if include_unallocated:
- unallocated_payment_entries = frappe.db.sql(
- """
- select 'Payment Entry' as reference_type, name as reference_name, posting_date,
- remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency
- from `tabPayment Entry`
- where
- {0} = %s and party_type = %s and party = %s and payment_type = %s
- and docstatus = 1 and unallocated_amount > 0 {condition}
- order by posting_date {1}
- """.format(
- party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""
- ),
- (party_account, party_type, party, payment_type),
- as_dict=1,
- )
-
- return list(payment_entries_against_order) + list(unallocated_payment_entries)
+ return q
def update_invoice_status():
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2de3644..4536abf 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -331,7 +331,7 @@
execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Template', ignore_missing=True)
execute:frappe.delete_doc('DocType', 'Cash Flow Mapping Accounts', ignore_missing=True)
erpnext.patches.v14_0.cleanup_workspaces
-erpnext.patches.v15_0.remove_loan_management_module
+erpnext.patches.v15_0.remove_loan_management_module #2023-07-03
erpnext.patches.v14_0.set_report_in_process_SOA
erpnext.buying.doctype.supplier.patches.migrate_supplier_portal_users
erpnext.patches.v14_0.single_to_multi_dunning
diff --git a/erpnext/patches/v15_0/remove_loan_management_module.py b/erpnext/patches/v15_0/remove_loan_management_module.py
index 6f08c36..8242f9c 100644
--- a/erpnext/patches/v15_0/remove_loan_management_module.py
+++ b/erpnext/patches/v15_0/remove_loan_management_module.py
@@ -7,7 +7,7 @@
frappe.delete_doc("Module Def", "Loan Management", ignore_missing=True, force=True)
- frappe.delete_doc("Workspace", "Loan Management", ignore_missing=True, force=True)
+ frappe.delete_doc("Workspace", "Loans", ignore_missing=True, force=True)
print_formats = frappe.get_all(
"Print Format", {"module": "Loan Management", "standard": "Yes"}, pluck="name"
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index f007430..502ee57 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -289,7 +289,8 @@
"fieldtype": "Link",
"label": "Company",
"options": "Company",
- "remember_last_selected_value": 1
+ "remember_last_selected_value": 1,
+ "reqd": 1
},
{
"fieldname": "column_break_28",
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 0d92683..543d0e9 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -193,7 +193,9 @@
this.frm.set_query("expense_account", "items", function(doc) {
return {
filters: {
- "company": doc.company
+ "company": doc.company,
+ "report_type": "Profit and Loss",
+ "is_group": 0
}
};
});
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 3a446e1..540e767 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -20,8 +20,8 @@
frm.set_query('customer_group', {'is_group': 0});
frm.set_query('default_price_list', { 'selling': 1});
frm.set_query('account', 'accounts', function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- var filters = {
+ let d = locals[cdt][cdn];
+ let filters = {
'account_type': 'Receivable',
'company': d.company,
"is_group": 0
@@ -35,6 +35,19 @@
}
});
+ frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) {
+ let d = locals[cdt][cdn];
+ return {
+ filters: {
+ "account_type": 'Receivable',
+ "root_type": "Liability",
+ "company": d.company,
+ "is_group": 0
+ }
+ }
+ });
+
+
if (frm.doc.__islocal == 1) {
frm.set_value("represents_company", "");
}
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index edfe005..be8f62f 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -336,15 +336,15 @@
{
"fieldname": "default_receivable_accounts",
"fieldtype": "Section Break",
- "label": "Default Receivable Accounts"
+ "label": "Default Accounts"
},
{
- "description": "Mention if a non-standard receivable account",
- "fieldname": "accounts",
- "fieldtype": "Table",
- "label": "Receivable Accounts",
- "options": "Party Account"
- },
+ "description": "Mention if non-standard Receivable account",
+ "fieldname": "accounts",
+ "fieldtype": "Table",
+ "label": "Accounts",
+ "options": "Party Account"
+ },
{
"fieldname": "credit_limit_section",
"fieldtype": "Section Break",
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index e50ce44..3335387 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -226,7 +226,9 @@
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
["unrealized_profit_loss_account", {"root_type": ["in", ["Liability", "Asset"]]}],
- ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}]
+ ["default_provisional_account", {"root_type": ["in", ["Liability", "Asset"]]}],
+ ["default_advance_received_account", {"root_type": "Liability", "account_type": "Receivable"}],
+ ["default_advance_paid_account", {"root_type": "Asset", "account_type": "Payable"}],
], function(i, v) {
erpnext.company.set_custom_query(frm, v);
});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index f087d99..6292ad7 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -70,6 +70,11 @@
"payment_terms",
"cost_center",
"default_finance_book",
+ "advance_payments_section",
+ "book_advance_payments_in_separate_party_account",
+ "column_break_fwcf",
+ "default_advance_received_account",
+ "default_advance_paid_account",
"auto_accounting_for_stock_settings",
"enable_perpetual_inventory",
"enable_provisional_accounting_for_non_stock_items",
@@ -694,6 +699,38 @@
"label": "Default Provisional Account",
"no_copy": 1,
"options": "Account"
+ },
+ {
+ "fieldname": "advance_payments_section",
+ "fieldtype": "Section Break",
+ "label": "Advance Payments"
+ },
+ {
+ "depends_on": "eval:doc.book_advance_payments_in_separate_party_account",
+ "fieldname": "default_advance_received_account",
+ "fieldtype": "Link",
+ "label": "Default Advance Received Account",
+ "mandatory_depends_on": "book_advance_payments_as_liability",
+ "options": "Account"
+ },
+ {
+ "depends_on": "eval:doc.book_advance_payments_in_separate_party_account",
+ "fieldname": "default_advance_paid_account",
+ "fieldtype": "Link",
+ "label": "Default Advance Paid Account",
+ "mandatory_depends_on": "book_advance_payments_as_liability",
+ "options": "Account"
+ },
+ {
+ "fieldname": "column_break_fwcf",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "0",
+ "description": "Enabling this option will allow you to record - <br><br> 1. Advances Received in a <b>Liability Account</b> instead of the <b>Asset Account</b><br><br>2. Advances Paid in an <b>Asset Account</b> instead of the <b> Liability Account</b>",
+ "fieldname": "book_advance_payments_in_separate_party_account",
+ "fieldtype": "Check",
+ "label": "Book Advance Payments in Separate Party Account"
}
],
"icon": "fa fa-building",
@@ -701,7 +738,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
- "modified": "2022-08-16 16:09:02.327724",
+ "modified": "2023-06-23 18:22:27.219706",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/customer_group/customer_group.js b/erpnext/setup/doctype/customer_group/customer_group.js
index 44a5019..49a90f9 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.js
+++ b/erpnext/setup/doctype/customer_group/customer_group.js
@@ -16,23 +16,36 @@
}
}
-//get query select Customer Group
-cur_frm.fields_dict['parent_customer_group'].get_query = function(doc,cdt,cdn) {
- return {
- filters: {
- 'is_group': 1,
- 'name': ['!=', cur_frm.doc.customer_group_name]
- }
- }
-}
+frappe.ui.form.on("Customer Group", {
+ setup: function(frm){
+ frm.set_query('parent_customer_group', function (doc) {
+ return {
+ filters: {
+ 'is_group': 1,
+ 'name': ['!=', cur_frm.doc.customer_group_name]
+ }
+ }
+ });
-cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- return {
- filters: {
- 'account_type': 'Receivable',
- 'company': d.company,
- "is_group": 0
- }
+ frm.set_query('account', 'accounts', function (doc, cdt, cdn) {
+ return {
+ filters: {
+ "account_type": 'Receivable',
+ "company": locals[cdt][cdn].company,
+ "is_group": 0
+ }
+ }
+ });
+
+ frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) {
+ return {
+ filters: {
+ "root_type": 'Liability',
+ "account_type": "Receivable",
+ "company": locals[cdt][cdn].company,
+ "is_group": 0
+ }
+ }
+ });
}
-}
+});
diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json
index d6a431e..4c36bc7 100644
--- a/erpnext/setup/doctype/customer_group/customer_group.json
+++ b/erpnext/setup/doctype/customer_group/customer_group.json
@@ -113,7 +113,7 @@
{
"fieldname": "default_receivable_account",
"fieldtype": "Section Break",
- "label": "Default Receivable Account"
+ "label": "Default Accounts"
},
{
"depends_on": "eval:!doc.__islocal",
@@ -139,7 +139,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2022-12-24 11:15:17.142746",
+ "modified": "2023-06-02 13:40:34.435822",
"modified_by": "Administrator",
"module": "Setup",
"name": "Customer Group",
@@ -171,7 +171,6 @@
"read": 1,
"report": 1,
"role": "Sales Master Manager",
- "set_user_permissions": 1,
"share": 1,
"write": 1
},
diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js
index e75030d..b2acfd7 100644
--- a/erpnext/setup/doctype/supplier_group/supplier_group.js
+++ b/erpnext/setup/doctype/supplier_group/supplier_group.js
@@ -16,23 +16,36 @@
}
};
-// get query select Customer Group
-cur_frm.fields_dict['parent_supplier_group'].get_query = function() {
- return {
- filters: {
- 'is_group': 1,
- 'name': ['!=', cur_frm.doc.supplier_group_name]
- }
- };
-};
+frappe.ui.form.on("Supplier Group", {
+ setup: function(frm){
+ frm.set_query('parent_supplier_group', function (doc) {
+ return {
+ filters: {
+ 'is_group': 1,
+ 'name': ['!=', cur_frm.doc.supplier_group_name]
+ }
+ }
+ });
-cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- return {
- filters: {
- 'account_type': 'Payable',
- 'company': d.company,
- "is_group": 0
- }
- };
-};
+ frm.set_query('account', 'accounts', function (doc, cdt, cdn) {
+ return {
+ filters: {
+ 'account_type': 'Payable',
+ 'company': locals[cdt][cdn].company,
+ "is_group": 0
+ }
+ }
+ });
+
+ frm.set_query('advance_account', 'accounts', function (doc, cdt, cdn) {
+ return {
+ filters: {
+ "root_type": 'Asset',
+ "account_type": "Payable",
+ "company": locals[cdt][cdn].company,
+ "is_group": 0
+ }
+ }
+ });
+ }
+});
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index c6c84ca..07d6e86 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1861,6 +1861,121 @@
self.assertEqual(pr_return.items[0].rejected_qty, 0.0)
self.assertEqual(pr_return.items[0].rejected_warehouse, "")
+ def test_purchase_receipt_with_backdated_landed_cost_voucher(self):
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+ from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
+ create_landed_cost_voucher,
+ )
+ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+ item_code = "_Test Purchase Item With Landed Cost"
+ create_item(item_code)
+
+ warehouse = create_warehouse("_Test Purchase Warehouse With Landed Cost")
+ warehouse1 = create_warehouse("_Test Purchase Warehouse With Landed Cost 1")
+ warehouse2 = create_warehouse("_Test Purchase Warehouse With Landed Cost 2")
+ warehouse3 = create_warehouse("_Test Purchase Warehouse With Landed Cost 3")
+
+ pr = make_purchase_receipt(
+ item_code=item_code,
+ warehouse=warehouse,
+ posting_date=add_days(today(), -10),
+ posting_time="10:59:59",
+ qty=100,
+ rate=275.00,
+ )
+
+ pr_return = make_return_doc("Purchase Receipt", pr.name)
+ pr_return.posting_date = add_days(today(), -9)
+ pr_return.items[0].qty = 2 * -1
+ pr_return.items[0].received_qty = 2 * -1
+ pr_return.submit()
+
+ ste1 = make_stock_entry(
+ purpose="Material Transfer",
+ posting_date=add_days(today(), -8),
+ source=warehouse,
+ target=warehouse1,
+ item_code=item_code,
+ qty=20,
+ company=pr.company,
+ )
+
+ ste1.reload()
+ self.assertEqual(ste1.items[0].valuation_rate, 275.00)
+
+ ste2 = make_stock_entry(
+ purpose="Material Transfer",
+ posting_date=add_days(today(), -7),
+ source=warehouse,
+ target=warehouse2,
+ item_code=item_code,
+ qty=20,
+ company=pr.company,
+ )
+
+ ste2.reload()
+ self.assertEqual(ste2.items[0].valuation_rate, 275.00)
+
+ ste3 = make_stock_entry(
+ purpose="Material Transfer",
+ posting_date=add_days(today(), -6),
+ source=warehouse,
+ target=warehouse3,
+ item_code=item_code,
+ qty=20,
+ company=pr.company,
+ )
+
+ ste3.reload()
+ self.assertEqual(ste3.items[0].valuation_rate, 275.00)
+
+ ste4 = make_stock_entry(
+ purpose="Material Transfer",
+ posting_date=add_days(today(), -5),
+ source=warehouse1,
+ target=warehouse,
+ item_code=item_code,
+ qty=20,
+ company=pr.company,
+ )
+
+ ste4.reload()
+ self.assertEqual(ste4.items[0].valuation_rate, 275.00)
+
+ ste5 = make_stock_entry(
+ purpose="Material Transfer",
+ posting_date=add_days(today(), -4),
+ source=warehouse,
+ target=warehouse1,
+ item_code=item_code,
+ qty=20,
+ company=pr.company,
+ )
+
+ ste5.reload()
+ self.assertEqual(ste5.items[0].valuation_rate, 275.00)
+
+ create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)
+
+ pr.reload()
+ valuation_rate = pr.items[0].valuation_rate
+
+ ste1.reload()
+ self.assertEqual(ste1.items[0].valuation_rate, valuation_rate)
+
+ ste2.reload()
+ self.assertEqual(ste2.items[0].valuation_rate, valuation_rate)
+
+ ste3.reload()
+ self.assertEqual(ste3.items[0].valuation_rate, valuation_rate)
+
+ ste4.reload()
+ self.assertEqual(ste4.items[0].valuation_rate, valuation_rate)
+
+ ste5.reload()
+ self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index e576ab7..3929616 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -213,6 +213,7 @@
"fieldname": "received_qty",
"fieldtype": "Float",
"label": "Received Quantity",
+ "no_copy": 1,
"oldfieldname": "received_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
@@ -1057,7 +1058,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-03-12 13:37:47.778021",
+ "modified": "2023-07-02 18:40:48.152637",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 57bb71e..75b6ec7 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -1241,59 +1241,125 @@
return list(set(ignore_serial_nos) - set(returned_serial_nos))
+def get_reserved_batches_for_pos(kwargs):
+ pos_batches = frappe._dict()
+ pos_invoices = frappe.get_all(
+ "POS Invoice",
+ fields=[
+ "`tabPOS Invoice Item`.batch_no",
+ "`tabPOS Invoice`.is_return",
+ "`tabPOS Invoice Item`.warehouse",
+ "`tabPOS Invoice Item`.name as child_docname",
+ "`tabPOS Invoice`.name as parent_docname",
+ "`tabPOS Invoice Item`.serial_and_batch_bundle",
+ ],
+ filters=[
+ ["POS Invoice", "consolidated_invoice", "is", "not set"],
+ ["POS Invoice", "docstatus", "=", 1],
+ ["POS Invoice Item", "item_code", "=", kwargs.item_code],
+ ["POS Invoice", "name", "!=", kwargs.ignore_voucher_no],
+ ],
+ )
+
+ ids = [
+ pos_invoice.serial_and_batch_bundle
+ for pos_invoice in pos_invoices
+ if pos_invoice.serial_and_batch_bundle
+ ]
+
+ if not ids:
+ return []
+
+ if ids:
+ for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
+ if d.batch_no not in pos_batches:
+ pos_batches[d.batch_no] = frappe._dict(
+ {
+ "qty": d.qty,
+ "warehouse": d.warehouse,
+ }
+ )
+ else:
+ pos_batches[d.batch_no].qty += d.qty
+
+ for row in pos_invoices:
+ if not row.batch_no:
+ continue
+
+ if row.batch_no in pos_batches:
+ pos_batches[row.batch_no] -= row.qty * -1 if row.is_return else row.qty
+ else:
+ pos_batches[row.batch_no] = frappe._dict(
+ {
+ "qty": (row.qty * -1 if row.is_return else row.qty),
+ "warehouse": row.warehouse,
+ }
+ )
+
+ return pos_batches
+
+
def get_auto_batch_nos(kwargs):
available_batches = get_available_batches(kwargs)
qty = flt(kwargs.qty)
+ pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
- if stock_ledgers_batches:
- update_available_batches(available_batches, stock_ledgers_batches)
+ if stock_ledgers_batches or pos_invoice_batches:
+ update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches)
available_batches = list(filter(lambda x: x.qty > 0, available_batches))
-
if not qty:
return available_batches
+ return get_qty_based_available_batches(available_batches, qty)
+
+
+def get_qty_based_available_batches(available_batches, qty):
batches = []
for batch in available_batches:
- if qty > 0:
- batch_qty = flt(batch.qty)
- if qty > batch_qty:
- batches.append(
- frappe._dict(
- {
- "batch_no": batch.batch_no,
- "qty": batch_qty,
- "warehouse": batch.warehouse,
- }
- )
+ if qty <= 0:
+ break
+
+ batch_qty = flt(batch.qty)
+ if qty > batch_qty:
+ batches.append(
+ frappe._dict(
+ {
+ "batch_no": batch.batch_no,
+ "qty": batch_qty,
+ "warehouse": batch.warehouse,
+ }
)
- qty -= batch_qty
- else:
- batches.append(
- frappe._dict(
- {
- "batch_no": batch.batch_no,
- "qty": qty,
- "warehouse": batch.warehouse,
- }
- )
+ )
+ qty -= batch_qty
+ else:
+ batches.append(
+ frappe._dict(
+ {
+ "batch_no": batch.batch_no,
+ "qty": qty,
+ "warehouse": batch.warehouse,
+ }
)
- qty = 0
+ )
+ qty = 0
return batches
-def update_available_batches(available_batches, reserved_batches):
- for batch_no, data in reserved_batches.items():
- batch_not_exists = True
- for batch in available_batches:
- if batch.batch_no == batch_no:
- batch.qty += data.qty
- batch_not_exists = False
+def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None):
+ for batches in [reserved_batches, pos_invoice_batches]:
+ if batches:
+ for batch_no, data in batches.items():
+ batch_not_exists = True
+ for batch in available_batches:
+ if batch.batch_no == batch_no and batch.warehouse == data.warehouse:
+ batch.qty += data.qty
+ batch_not_exists = False
- if batch_not_exists:
- available_batches.append(data)
+ if batch_not_exists:
+ available_batches.append(data)
def get_available_batches(kwargs):
@@ -1312,7 +1378,10 @@
batch_ledger.warehouse,
Sum(batch_ledger.qty).as_("qty"),
)
- .where(((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull())))
+ .where(
+ (batch_table.disabled == 0)
+ & ((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
+ )
.where(stock_ledger_entry.is_cancelled == 0)
.groupby(batch_ledger.batch_no, batch_ledger.warehouse)
)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index b3ed220..7b1eae5 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -646,6 +646,7 @@
def update_distinct_item_warehouses(self, dependant_sle):
key = (dependant_sle.item_code, dependant_sle.warehouse)
val = frappe._dict({"sle": dependant_sle})
+
if key not in self.distinct_item_warehouses:
self.distinct_item_warehouses[key] = val
self.new_items_found = True
@@ -657,6 +658,9 @@
val.sle_changed = True
self.distinct_item_warehouses[key] = val
self.new_items_found = True
+ elif self.distinct_item_warehouses[key].get("reposting_status"):
+ self.distinct_item_warehouses[key] = val
+ self.new_items_found = True
def process_sle(self, sle):
# previous sle data for this warehouse
@@ -1362,6 +1366,8 @@
[
"item_code",
"warehouse",
+ "actual_qty",
+ "qty_after_transaction",
"posting_date",
"posting_time",
"timestamp(posting_date, posting_time) as timestamp",