mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-15 11:09:17 +00:00
Merge pull request #42649 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -165,8 +165,25 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
filters: filters,
|
filters: filters,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
|
frm.set_query("sales_taxes_and_charges_template", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
frm.set_query("purchase_taxes_and_charges_template", function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
refresh: function (frm) {
|
refresh: function (frm) {
|
||||||
erpnext.hide_company(frm);
|
erpnext.hide_company(frm);
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ from erpnext.accounts.utils import (
|
|||||||
get_account_currency,
|
get_account_currency,
|
||||||
get_balance_on,
|
get_balance_on,
|
||||||
get_outstanding_invoices,
|
get_outstanding_invoices,
|
||||||
get_party_types_from_account_type,
|
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import (
|
from erpnext.controllers.accounts_controller import (
|
||||||
AccountsController,
|
AccountsController,
|
||||||
@@ -1119,90 +1118,82 @@ class PaymentEntry(AccountsController):
|
|||||||
self.make_advance_gl_entries(cancel=cancel)
|
self.make_advance_gl_entries(cancel=cancel)
|
||||||
|
|
||||||
def add_party_gl_entries(self, gl_entries):
|
def add_party_gl_entries(self, gl_entries):
|
||||||
if self.party_account:
|
if not self.party_account:
|
||||||
if self.payment_type == "Receive":
|
return
|
||||||
against_account = self.paid_to
|
|
||||||
else:
|
|
||||||
against_account = self.paid_from
|
|
||||||
|
|
||||||
party_gl_dict = self.get_gl_dict(
|
if self.payment_type == "Receive":
|
||||||
|
against_account = self.paid_to
|
||||||
|
else:
|
||||||
|
against_account = self.paid_from
|
||||||
|
|
||||||
|
party_account_type = frappe.db.get_value("Party Type", self.party_type, "account_type")
|
||||||
|
|
||||||
|
party_gl_dict = self.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": self.party_account,
|
||||||
|
"party_type": self.party_type,
|
||||||
|
"party": self.party,
|
||||||
|
"against": against_account,
|
||||||
|
"account_currency": self.party_account_currency,
|
||||||
|
"cost_center": self.cost_center,
|
||||||
|
},
|
||||||
|
item=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in self.get("references"):
|
||||||
|
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
|
||||||
|
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||||
|
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()
|
||||||
|
|
||||||
|
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
|
||||||
|
|
||||||
|
if (
|
||||||
|
d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]
|
||||||
|
and d.allocated_amount < 0
|
||||||
|
and (
|
||||||
|
(party_account_type == "Receivable" and self.payment_type == "Pay")
|
||||||
|
or (party_account_type == "Payable" and self.payment_type == "Receive")
|
||||||
|
)
|
||||||
|
):
|
||||||
|
# reversing dr_cr because because it will get reversed in gl processing due to negative amount
|
||||||
|
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||||
|
|
||||||
|
gle.update(
|
||||||
{
|
{
|
||||||
"account": self.party_account,
|
dr_or_cr: allocated_amount_in_company_currency,
|
||||||
"party_type": self.party_type,
|
dr_or_cr + "_in_account_currency": d.allocated_amount,
|
||||||
"party": self.party,
|
"against_voucher_type": d.reference_doctype,
|
||||||
"against": against_account,
|
"against_voucher": d.reference_name,
|
||||||
"account_currency": self.party_account_currency,
|
"cost_center": cost_center,
|
||||||
"cost_center": self.cost_center,
|
}
|
||||||
},
|
)
|
||||||
item=self,
|
gl_entries.append(gle)
|
||||||
|
|
||||||
|
if self.unallocated_amount:
|
||||||
|
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
||||||
|
exchange_rate = self.get_exchange_rate()
|
||||||
|
base_unallocated_amount = self.unallocated_amount * exchange_rate
|
||||||
|
|
||||||
|
gle = party_gl_dict.copy()
|
||||||
|
gle.update(
|
||||||
|
{
|
||||||
|
dr_or_cr + "_in_account_currency": self.unallocated_amount,
|
||||||
|
dr_or_cr: base_unallocated_amount,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
if self.book_advance_payments_in_separate_party_account:
|
||||||
|
|
||||||
for d in self.get("references"):
|
|
||||||
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
|
|
||||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
|
||||||
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()
|
|
||||||
|
|
||||||
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
|
|
||||||
reverse_dr_or_cr = 0
|
|
||||||
|
|
||||||
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
|
|
||||||
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
|
|
||||||
payable_party_types = get_party_types_from_account_type("Payable")
|
|
||||||
receivable_party_types = get_party_types_from_account_type("Receivable")
|
|
||||||
if (
|
|
||||||
is_return
|
|
||||||
and self.party_type in receivable_party_types
|
|
||||||
and (self.payment_type == "Pay")
|
|
||||||
):
|
|
||||||
reverse_dr_or_cr = 1
|
|
||||||
elif (
|
|
||||||
is_return
|
|
||||||
and self.party_type in payable_party_types
|
|
||||||
and (self.payment_type == "Receive")
|
|
||||||
):
|
|
||||||
reverse_dr_or_cr = 1
|
|
||||||
|
|
||||||
if is_return and not reverse_dr_or_cr:
|
|
||||||
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
|
||||||
|
|
||||||
gle.update(
|
gle.update(
|
||||||
{
|
{
|
||||||
dr_or_cr: abs(allocated_amount_in_company_currency),
|
"against_voucher_type": "Payment Entry",
|
||||||
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
|
"against_voucher": self.name,
|
||||||
"against_voucher_type": d.reference_doctype,
|
|
||||||
"against_voucher": d.reference_name,
|
|
||||||
"cost_center": cost_center,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
gl_entries.append(gle)
|
gl_entries.append(gle)
|
||||||
|
|
||||||
if self.unallocated_amount:
|
|
||||||
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
|
|
||||||
exchange_rate = self.get_exchange_rate()
|
|
||||||
base_unallocated_amount = self.unallocated_amount * exchange_rate
|
|
||||||
|
|
||||||
gle = party_gl_dict.copy()
|
|
||||||
gle.update(
|
|
||||||
{
|
|
||||||
dr_or_cr + "_in_account_currency": self.unallocated_amount,
|
|
||||||
dr_or_cr: base_unallocated_amount,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.book_advance_payments_in_separate_party_account:
|
|
||||||
gle.update(
|
|
||||||
{
|
|
||||||
"against_voucher_type": "Payment Entry",
|
|
||||||
"against_voucher": self.name,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
gl_entries.append(gle)
|
|
||||||
|
|
||||||
def make_advance_gl_entries(
|
def make_advance_gl_entries(
|
||||||
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
|
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.get_item_details import get_item_details
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
@@ -1311,6 +1313,69 @@ class TestPricingRule(unittest.TestCase):
|
|||||||
pricing_rule.is_recursive = True
|
pricing_rule.is_recursive = True
|
||||||
self.assertRaises(frappe.ValidationError, pricing_rule.save)
|
self.assertRaises(frappe.ValidationError, pricing_rule.save)
|
||||||
|
|
||||||
|
def test_ignore_pricing_rule_for_credit_note(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
pricing_rule = make_pricing_rule(
|
||||||
|
discount_percentage=20,
|
||||||
|
selling=1,
|
||||||
|
buying=1,
|
||||||
|
priority=1,
|
||||||
|
title="_Test Pricing Rule",
|
||||||
|
)
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
|
||||||
|
item = si.items[0]
|
||||||
|
si.submit()
|
||||||
|
self.assertEqual(item.discount_percentage, 20)
|
||||||
|
self.assertEqual(item.rate, 80)
|
||||||
|
|
||||||
|
# change discount on pricing rule
|
||||||
|
pricing_rule.discount_percentage = 30
|
||||||
|
pricing_rule.save()
|
||||||
|
|
||||||
|
credit_note = make_return_doc(si.doctype, si.name)
|
||||||
|
credit_note.save()
|
||||||
|
self.assertEqual(credit_note.ignore_pricing_rule, 1)
|
||||||
|
self.assertEqual(credit_note.pricing_rules, [])
|
||||||
|
self.assertEqual(credit_note.items[0].discount_percentage, 20)
|
||||||
|
self.assertEqual(credit_note.items[0].rate, 80)
|
||||||
|
self.assertEqual(credit_note.items[0].pricing_rules, None)
|
||||||
|
|
||||||
|
credit_note.delete()
|
||||||
|
si.cancel()
|
||||||
|
|
||||||
|
def test_ignore_pricing_rule_for_debit_note(self):
|
||||||
|
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||||
|
pricing_rule = make_pricing_rule(
|
||||||
|
discount_percentage=20,
|
||||||
|
buying=1,
|
||||||
|
priority=1,
|
||||||
|
title="_Test Pricing Rule",
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(do_not_submit=True, supplier="_Test Supplier 1", qty=1)
|
||||||
|
item = pi.items[0]
|
||||||
|
pi.submit()
|
||||||
|
self.assertEqual(item.discount_percentage, 20)
|
||||||
|
self.assertEqual(item.rate, 40)
|
||||||
|
|
||||||
|
# change discount on pricing rule
|
||||||
|
pricing_rule.discount_percentage = 30
|
||||||
|
pricing_rule.save()
|
||||||
|
|
||||||
|
# create debit note from purchase invoice
|
||||||
|
debit_note = make_return_doc(pi.doctype, pi.name)
|
||||||
|
debit_note.save()
|
||||||
|
|
||||||
|
self.assertEqual(debit_note.ignore_pricing_rule, 1)
|
||||||
|
self.assertEqual(debit_note.pricing_rules, [])
|
||||||
|
self.assertEqual(debit_note.items[0].discount_percentage, 20)
|
||||||
|
self.assertEqual(debit_note.items[0].rate, 40)
|
||||||
|
self.assertEqual(debit_note.items[0].pricing_rules, None)
|
||||||
|
|
||||||
|
debit_note.delete()
|
||||||
|
pi.cancel()
|
||||||
|
|
||||||
|
|
||||||
test_dependencies = ["Campaign"]
|
test_dependencies = ["Campaign"]
|
||||||
|
|
||||||
|
|||||||
@@ -375,12 +375,14 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
|||||||
AND ja.party in %s
|
AND ja.party in %s
|
||||||
AND j.apply_tds = 1
|
AND j.apply_tds = 1
|
||||||
AND j.tax_withholding_category = %s
|
AND j.tax_withholding_category = %s
|
||||||
|
AND j.company = %s
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
tax_details.from_date,
|
tax_details.from_date,
|
||||||
tax_details.to_date,
|
tax_details.to_date,
|
||||||
tuple(parties),
|
tuple(parties),
|
||||||
tax_details.get("tax_withholding_category"),
|
tax_details.get("tax_withholding_category"),
|
||||||
|
company,
|
||||||
),
|
),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
@@ -497,6 +499,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
|
|||||||
"unallocated_amount": (">", 0),
|
"unallocated_amount": (">", 0),
|
||||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||||
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
"tax_withholding_category": tax_details.get("tax_withholding_category"),
|
||||||
|
"company": inv.company,
|
||||||
}
|
}
|
||||||
|
|
||||||
field = "sum(tax_withholding_net_total)"
|
field = "sum(tax_withholding_net_total)"
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
|
|||||||
if total_credit:
|
if total_credit:
|
||||||
data.append(total_credit)
|
data.append(total_credit)
|
||||||
|
|
||||||
report_summary = get_bs_summary(
|
report_summary, primitive_summary = get_bs_summary(
|
||||||
companies,
|
companies,
|
||||||
asset,
|
asset,
|
||||||
liability,
|
liability,
|
||||||
@@ -175,7 +175,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
|
|||||||
|
|
||||||
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
|
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
|
||||||
|
|
||||||
report_summary = get_pl_summary(
|
report_summary, primitive_summary = get_pl_summary(
|
||||||
companies, "", income, expense, net_profit_loss, company_currency, filters, True
|
companies, "", income, expense, net_profit_loss, company_currency, filters, True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,11 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
label: __("Ignore Exchange Rate Revaluation Journals"),
|
label: __("Ignore Exchange Rate Revaluation Journals"),
|
||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "ignore_cr_dr_notes",
|
||||||
|
label: __("Ignore System Generated Credit / Debit Notes"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,20 @@ def get_conditions(filters):
|
|||||||
if err_journals:
|
if err_journals:
|
||||||
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
||||||
|
|
||||||
|
if filters.get("ignore_cr_dr_notes"):
|
||||||
|
system_generated_cr_dr_journals = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"company": filters.get("company"),
|
||||||
|
"docstatus": 1,
|
||||||
|
"voucher_type": ("in", ["Credit Note", "Debit Note"]),
|
||||||
|
"is_system_generated": 1,
|
||||||
|
},
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
if system_generated_cr_dr_journals:
|
||||||
|
filters.update({"voucher_no_not_in": [x[0] for x in system_generated_cr_dr_journals]})
|
||||||
|
|
||||||
if filters.get("voucher_no_not_in"):
|
if filters.get("voucher_no_not_in"):
|
||||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,32 @@
|
|||||||
# MIT License. See license.txt
|
# MIT License. See license.txt
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe import qb
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import flt, today
|
from frappe.utils import flt, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
from erpnext.accounts.report.general_ledger.general_ledger import execute
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
|
||||||
class TestGeneralLedger(FrappeTestCase):
|
class TestGeneralLedger(FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.company = "_Test Company"
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
|
def clear_old_entries(self):
|
||||||
|
doctype_list = [
|
||||||
|
"GL Entry",
|
||||||
|
"Payment Ledger Entry",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Payment Entry",
|
||||||
|
"Journal Entry",
|
||||||
|
]
|
||||||
|
for doctype in doctype_list:
|
||||||
|
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||||
|
|
||||||
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
|
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
|
||||||
"""
|
"""
|
||||||
Checks the correctness of balance after exchange rate revaluation
|
Checks the correctness of balance after exchange rate revaluation
|
||||||
@@ -248,3 +267,68 @@ class TestGeneralLedger(FrappeTestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
|
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
|
||||||
|
|
||||||
|
def test_ignore_cr_dr_notes_filter(self):
|
||||||
|
si = create_sales_invoice()
|
||||||
|
|
||||||
|
cr_note = make_return_doc(si.doctype, si.name)
|
||||||
|
cr_note.submit()
|
||||||
|
|
||||||
|
pr = frappe.get_doc("Payment Reconciliation")
|
||||||
|
pr.company = si.company
|
||||||
|
pr.party_type = "Customer"
|
||||||
|
pr.party = si.customer
|
||||||
|
pr.receivable_payable_account = si.debit_to
|
||||||
|
|
||||||
|
pr.get_unreconciled_entries()
|
||||||
|
|
||||||
|
invoices = [invoice.as_dict() for invoice in pr.invoices if invoice.invoice_number == si.name]
|
||||||
|
payments = [payment.as_dict() for payment in pr.payments if payment.reference_name == cr_note.name]
|
||||||
|
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||||
|
pr.reconcile()
|
||||||
|
|
||||||
|
system_generated_journal = frappe.db.get_all(
|
||||||
|
"Journal Entry",
|
||||||
|
filters={
|
||||||
|
"docstatus": 1,
|
||||||
|
"reference_type": si.doctype,
|
||||||
|
"reference_name": si.name,
|
||||||
|
"voucher_type": "Credit Note",
|
||||||
|
"is_system_generated": True,
|
||||||
|
},
|
||||||
|
fields=["name"],
|
||||||
|
)
|
||||||
|
self.assertEqual(len(system_generated_journal), 1)
|
||||||
|
expected = set([si.name, cr_note.name, system_generated_journal[0].name])
|
||||||
|
# Without ignore_cr_dr_notes
|
||||||
|
columns, data = execute(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": si.company,
|
||||||
|
"from_date": si.posting_date,
|
||||||
|
"to_date": si.posting_date,
|
||||||
|
"account": [si.debit_to],
|
||||||
|
"group_by": "Group by Voucher (Consolidated)",
|
||||||
|
"ignore_cr_dr_notes": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actual = set([x.voucher_no for x in data if x.voucher_no])
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
# Without ignore_cr_dr_notes
|
||||||
|
expected = set([si.name, cr_note.name])
|
||||||
|
columns, data = execute(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"company": si.company,
|
||||||
|
"from_date": si.posting_date,
|
||||||
|
"to_date": si.posting_date,
|
||||||
|
"account": [si.debit_to],
|
||||||
|
"group_by": "Group by Voucher (Consolidated)",
|
||||||
|
"ignore_cr_dr_notes": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
actual = set([x.voucher_no for x in data if x.voucher_no])
|
||||||
|
self.assertEqual(expected, actual)
|
||||||
|
|||||||
@@ -188,11 +188,21 @@ frappe.ui.form.on("Asset", {
|
|||||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||||
|
|
||||||
if (frm.doc.is_composite_asset) {
|
if (frm.doc.is_composite_asset) {
|
||||||
$(".primary-action").prop("hidden", true);
|
frappe.call({
|
||||||
$(".form-message").text("Capitalize this asset to confirm");
|
method: "erpnext.assets.doctype.asset.asset.has_active_capitalization",
|
||||||
|
args: {
|
||||||
|
asset: frm.doc.name,
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (!r.message) {
|
||||||
|
$(".primary-action").prop("hidden", true);
|
||||||
|
$(".form-message").text("Capitalize this asset to confirm");
|
||||||
|
|
||||||
frm.add_custom_button(__("Capitalize Asset"), function () {
|
frm.add_custom_button(__("Capitalize Asset"), function () {
|
||||||
frm.trigger("create_asset_capitalization");
|
frm.trigger("create_asset_capitalization");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,6 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:!doc.is_composite_asset",
|
|
||||||
"fieldname": "gross_purchase_amount",
|
"fieldname": "gross_purchase_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Gross Purchase Amount",
|
"label": "Gross Purchase Amount",
|
||||||
@@ -580,7 +579,7 @@
|
|||||||
"link_fieldname": "target_asset"
|
"link_fieldname": "target_asset"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2024-07-07 22:27:14.733839",
|
"modified": "2024-08-01 16:39:09.340973",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
|
|||||||
@@ -1035,6 +1035,14 @@ def get_asset_value_after_depreciation(asset_name, finance_book=None):
|
|||||||
return asset.get_value_after_depreciation(finance_book)
|
return asset.get_value_after_depreciation(finance_book)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def has_active_capitalization(asset):
|
||||||
|
active_capitalizations = frappe.db.count(
|
||||||
|
"Asset Capitalization", filters={"target_asset": asset, "docstatus": 1}
|
||||||
|
)
|
||||||
|
return active_capitalizations > 0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def split_asset(asset_name, split_qty):
|
def split_asset(asset_name, split_qty):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_
|
|||||||
def update_last_purchase_rate(doc, is_submit) -> None:
|
def update_last_purchase_rate(doc, is_submit) -> None:
|
||||||
"""updates last_purchase_rate in item table for each item"""
|
"""updates last_purchase_rate in item table for each item"""
|
||||||
|
|
||||||
|
if doc.get("is_internal_supplier"):
|
||||||
|
return
|
||||||
|
|
||||||
this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))
|
this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))
|
||||||
|
|
||||||
for d in doc.get("items"):
|
for d in doc.get("items"):
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ force_item_fields = (
|
|||||||
"brand",
|
"brand",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"is_fixed_asset",
|
"is_fixed_asset",
|
||||||
"item_tax_rate",
|
|
||||||
"pricing_rules",
|
"pricing_rules",
|
||||||
"weight_per_unit",
|
"weight_per_unit",
|
||||||
"weight_uom",
|
"weight_uom",
|
||||||
@@ -743,7 +742,6 @@ class AccountsController(TransactionBase):
|
|||||||
args["is_subcontracted"] = self.is_subcontracted
|
args["is_subcontracted"] = self.is_subcontracted
|
||||||
|
|
||||||
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
|
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
|
||||||
|
|
||||||
for fieldname, value in ret.items():
|
for fieldname, value in ret.items():
|
||||||
if item.meta.get_field(fieldname) and value is not None:
|
if item.meta.get_field(fieldname) and value is not None:
|
||||||
if item.get(fieldname) is None or fieldname in force_item_fields:
|
if item.get(fieldname) is None or fieldname in force_item_fields:
|
||||||
@@ -753,7 +751,10 @@ class AccountsController(TransactionBase):
|
|||||||
fieldname
|
fieldname
|
||||||
):
|
):
|
||||||
item.set(fieldname, value)
|
item.set(fieldname, value)
|
||||||
|
elif fieldname == "item_tax_rate" and not (
|
||||||
|
self.get("is_return") and self.get("return_against")
|
||||||
|
):
|
||||||
|
item.set(fieldname, value)
|
||||||
elif fieldname == "serial_no":
|
elif fieldname == "serial_no":
|
||||||
# Ensure that serial numbers are matched against Stock UOM
|
# Ensure that serial numbers are matched against Stock UOM
|
||||||
item_conversion_factor = item.get("conversion_factor") or 1.0
|
item_conversion_factor = item.get("conversion_factor") or 1.0
|
||||||
|
|||||||
@@ -319,6 +319,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
|||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
doc = frappe.get_doc(target)
|
doc = frappe.get_doc(target)
|
||||||
doc.is_return = 1
|
doc.is_return = 1
|
||||||
|
doc.ignore_pricing_rule = 1
|
||||||
|
doc.pricing_rules = []
|
||||||
doc.return_against = source.name
|
doc.return_against = source.name
|
||||||
doc.set_warehouse = ""
|
doc.set_warehouse = ""
|
||||||
if doctype == "Sales Invoice" or doctype == "POS Invoice":
|
if doctype == "Sales Invoice" or doctype == "POS Invoice":
|
||||||
@@ -478,6 +480,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
|
|||||||
|
|
||||||
def update_item(source_doc, target_doc, source_parent):
|
def update_item(source_doc, target_doc, source_parent):
|
||||||
target_doc.qty = -1 * source_doc.qty
|
target_doc.qty = -1 * source_doc.qty
|
||||||
|
target_doc.pricing_rules = None
|
||||||
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
|
||||||
returned_qty_map = get_returned_qty_map_for_row(
|
returned_qty_map = get_returned_qty_map_for_row(
|
||||||
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
source_parent.name, source_parent.supplier, source_doc.name, doctype
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ class calculate_taxes_and_totals:
|
|||||||
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
self.doc.base_tax_withholding_net_total = sum_base_net_amount
|
||||||
|
|
||||||
def validate_item_tax_template(self):
|
def validate_item_tax_template(self):
|
||||||
|
if self.doc.get("is_return") and self.doc.get("return_against"):
|
||||||
|
return
|
||||||
|
|
||||||
for item in self._items:
|
for item in self._items:
|
||||||
if item.item_code and item.get("item_tax_template"):
|
if item.item_code and item.get("item_tax_template"):
|
||||||
item_doc = frappe.get_cached_doc("Item", item.item_code)
|
item_doc = frappe.get_cached_doc("Item", item.item_code)
|
||||||
@@ -241,7 +244,6 @@ class calculate_taxes_and_totals:
|
|||||||
"tax_fraction_for_current_item",
|
"tax_fraction_for_current_item",
|
||||||
"grand_total_fraction_for_current_item",
|
"grand_total_fraction_for_current_item",
|
||||||
]
|
]
|
||||||
|
|
||||||
if tax.charge_type != "Actual" and not (
|
if tax.charge_type != "Actual" and not (
|
||||||
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
|
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from itertools import groupby
|
|||||||
import frappe
|
import frappe
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt, getdate
|
||||||
|
|
||||||
from erpnext.setup.utils import get_exchange_rate
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
|
||||||
@@ -21,7 +21,15 @@ class SalesPipelineAnalytics:
|
|||||||
def __init__(self, filters=None):
|
def __init__(self, filters=None):
|
||||||
self.filters = frappe._dict(filters or {})
|
self.filters = frappe._dict(filters or {})
|
||||||
|
|
||||||
|
def validate_filters(self):
|
||||||
|
if not self.filters.from_date:
|
||||||
|
frappe.throw(_("From Date is mandatory"))
|
||||||
|
|
||||||
|
if not self.filters.to_date:
|
||||||
|
frappe.throw(_("To Date is mandatory"))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
self.validate_filters()
|
||||||
self.get_columns()
|
self.get_columns()
|
||||||
self.get_data()
|
self.get_data()
|
||||||
self.get_chart_data()
|
self.get_chart_data()
|
||||||
@@ -185,7 +193,7 @@ class SalesPipelineAnalytics:
|
|||||||
count_or_amount = info.get(based_on)
|
count_or_amount = info.get(based_on)
|
||||||
|
|
||||||
if self.filters.get("pipeline_by") == "Owner":
|
if self.filters.get("pipeline_by") == "Owner":
|
||||||
if value == "Not Assigned" or value == "[]" or value is None:
|
if value == "Not Assigned" or value == "[]" or value is None or not value:
|
||||||
assigned_to = ["Not Assigned"]
|
assigned_to = ["Not Assigned"]
|
||||||
else:
|
else:
|
||||||
assigned_to = json.loads(value)
|
assigned_to = json.loads(value)
|
||||||
@@ -227,10 +235,9 @@ class SalesPipelineAnalytics:
|
|||||||
|
|
||||||
def get_month_list(self):
|
def get_month_list(self):
|
||||||
month_list = []
|
month_list = []
|
||||||
current_date = date.today()
|
current_date = getdate(self.filters.get("from_date"))
|
||||||
month_number = date.today().month
|
|
||||||
|
|
||||||
for _month in range(month_number, 13):
|
while current_date < getdate(self.filters.get("to_date")):
|
||||||
month_list.append(current_date.strftime("%B"))
|
month_list.append(current_date.strftime("%B"))
|
||||||
current_date = current_date + relativedelta(months=1)
|
current_date = current_date + relativedelta(months=1)
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
|
||||||
from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
|
from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
|
||||||
|
|
||||||
|
|
||||||
class TestSalesPipelineAnalytics(unittest.TestCase):
|
class TestSalesPipelineAnalytics(FrappeTestCase):
|
||||||
@classmethod
|
def setUp(self):
|
||||||
def setUpClass(self):
|
|
||||||
frappe.db.delete("Opportunity")
|
frappe.db.delete("Opportunity")
|
||||||
create_company()
|
create_company()
|
||||||
create_customer()
|
create_customer()
|
||||||
create_opportunity()
|
create_opportunity()
|
||||||
|
|
||||||
def test_sales_pipeline_analytics(self):
|
def test_sales_pipeline_analytics(self):
|
||||||
|
self.from_date = "2021-01-01"
|
||||||
|
self.to_date = "2021-12-31"
|
||||||
self.check_for_monthly_and_number()
|
self.check_for_monthly_and_number()
|
||||||
self.check_for_monthly_and_amount()
|
self.check_for_monthly_and_amount()
|
||||||
self.check_for_quarterly_and_number()
|
self.check_for_quarterly_and_number()
|
||||||
@@ -28,6 +30,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -43,6 +47,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -59,6 +65,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -74,6 +82,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -90,6 +100,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -105,6 +117,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -121,6 +135,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -136,6 +152,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"status": "Open",
|
"status": "Open",
|
||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
@@ -153,8 +171,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
|
|||||||
"opportunity_type": "Sales",
|
"opportunity_type": "Sales",
|
||||||
"company": "Best Test",
|
"company": "Best Test",
|
||||||
"opportunity_source": "Cold Calling",
|
"opportunity_source": "Cold Calling",
|
||||||
"from_date": "2021-08-01",
|
"from_date": self.from_date,
|
||||||
"to_date": "2021-08-31",
|
"to_date": self.to_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
report = execute(filters)
|
report = execute(filters)
|
||||||
|
|||||||
@@ -534,6 +534,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
quotation_to: me.frm.doc.quotation_to,
|
quotation_to: me.frm.doc.quotation_to,
|
||||||
supplier: me.frm.doc.supplier,
|
supplier: me.frm.doc.supplier,
|
||||||
currency: me.frm.doc.currency,
|
currency: me.frm.doc.currency,
|
||||||
|
is_internal_supplier: me.frm.doc.is_internal_supplier,
|
||||||
|
is_internal_customer: me.frm.doc.is_internal_customer,
|
||||||
update_stock: update_stock,
|
update_stock: update_stock,
|
||||||
conversion_rate: me.frm.doc.conversion_rate,
|
conversion_rate: me.frm.doc.conversion_rate,
|
||||||
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
|
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
|
||||||
@@ -826,47 +828,76 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
var me = this;
|
var me = this;
|
||||||
var set_pricing = function() {
|
var set_pricing = function() {
|
||||||
if(me.frm.doc.company && me.frm.fields_dict.currency) {
|
if(me.frm.doc.company && me.frm.fields_dict.currency) {
|
||||||
var company_currency = me.get_company_currency();
|
|
||||||
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
|
|
||||||
|
|
||||||
if (!me.frm.doc.currency) {
|
|
||||||
me.frm.set_value("currency", company_currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.frm.doc.currency == company_currency) {
|
|
||||||
me.frm.set_value("conversion_rate", 1.0);
|
|
||||||
}
|
|
||||||
if (me.frm.doc.price_list_currency == company_currency) {
|
|
||||||
me.frm.set_value('plc_conversion_rate', 1.0);
|
|
||||||
}
|
|
||||||
if (company_doc){
|
|
||||||
if (company_doc.default_letter_head) {
|
|
||||||
if(me.frm.fields_dict.letter_head) {
|
|
||||||
me.frm.set_value("letter_head", company_doc.default_letter_head);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
|
|
||||||
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
|
|
||||||
selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
|
|
||||||
me.frm.set_value("tc_name", company_doc.default_selling_terms);
|
|
||||||
}
|
|
||||||
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
|
|
||||||
"Material Request", "Purchase Receipt"];
|
|
||||||
// Purchase Invoice is excluded as per issue #3345
|
|
||||||
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
|
|
||||||
buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
|
|
||||||
me.frm.set_value("tc_name", company_doc.default_buying_terms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frappe.run_serially([
|
frappe.run_serially([
|
||||||
() => me.frm.script_manager.trigger("currency"),
|
() => get_party_currency(),
|
||||||
() => me.update_item_tax_map(),
|
() => me.update_item_tax_map(),
|
||||||
() => me.apply_default_taxes(),
|
() => me.apply_default_taxes(),
|
||||||
() => me.apply_pricing_rule()
|
() => me.apply_pricing_rule(),
|
||||||
|
() => set_terms(),
|
||||||
|
() => set_letter_head(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var get_party_currency = function() {
|
||||||
|
var party_type = frappe.meta.has_field(me.frm.doc.doctype, "customer") ? "Customer" : "Supplier";
|
||||||
|
var party_name = me.frm.doc[party_type.toLowerCase()];
|
||||||
|
if (party_name) {
|
||||||
|
frappe.call({
|
||||||
|
method: "frappe.client.get_value",
|
||||||
|
args: {
|
||||||
|
doctype: party_type,
|
||||||
|
filters: { name: party_name },
|
||||||
|
fieldname: "default_currency",
|
||||||
|
},
|
||||||
|
callback: function (r) {
|
||||||
|
if (r.message) {
|
||||||
|
set_currency(r.message.default_currency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
set_currency();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var set_currency = function(party_default_currency) {
|
||||||
|
var company_currency = me.get_company_currency();
|
||||||
|
var currency = party_default_currency || company_currency;
|
||||||
|
if (me.frm.doc.currency != currency) {
|
||||||
|
me.frm.set_value("currency", currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (me.frm.doc.currency == company_currency) {
|
||||||
|
me.frm.set_value("conversion_rate", 1.0);
|
||||||
|
}
|
||||||
|
if (me.frm.doc.price_list_currency == company_currency) {
|
||||||
|
me.frm.set_value('plc_conversion_rate', 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
me.frm.script_manager.trigger("currency");
|
||||||
|
}
|
||||||
|
|
||||||
|
var set_terms = function() {
|
||||||
|
if (frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && !me.frm.doc.tc_name) {
|
||||||
|
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
|
||||||
|
var selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"];
|
||||||
|
var company_terms_fieldname = selling_doctypes.includes(me.frm.doc.doctype) ? "default_selling_terms" : "default_buying_terms";
|
||||||
|
if (company_doc && company_doc[company_terms_fieldname]) {
|
||||||
|
me.frm.set_value("tc_name", company_doc[company_terms_fieldname]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var set_letter_head = function() {
|
||||||
|
if(me.frm.fields_dict.letter_head) {
|
||||||
|
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
|
||||||
|
if (company_doc && company_doc.default_letter_head) {
|
||||||
|
me.frm.set_value("letter_head", company_doc.default_letter_head);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var set_party_account = function(set_pricing) {
|
var set_party_account = function(set_pricing) {
|
||||||
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
||||||
if(me.frm.doc.doctype=="Sales Invoice") {
|
if(me.frm.doc.doctype=="Sales Invoice") {
|
||||||
@@ -1621,7 +1652,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
||||||
"conversion_factor": me.frm.doc.conversion_factor,
|
"conversion_factor": me.frm.doc.conversion_factor,
|
||||||
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
||||||
"coupon_code": me.frm.doc.coupon_code
|
"coupon_code": me.frm.doc.coupon_code,
|
||||||
|
"is_internal_supplier": me.frm.doc.is_internal_supplier,
|
||||||
|
"is_internal_customer": me.frm.doc.is_internal_customer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1949,6 +1982,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
let item_rates = {};
|
let item_rates = {};
|
||||||
let item_tax_templates = {};
|
let item_tax_templates = {};
|
||||||
|
|
||||||
|
if (me.frm.doc.is_return && me.frm.doc.return_against) return;
|
||||||
|
|
||||||
$.each(this.frm.doc.items || [], function(i, item) {
|
$.each(this.frm.doc.items || [], function(i, item) {
|
||||||
if (item.item_code) {
|
if (item.item_code) {
|
||||||
// Use combination of name and item code in case same item is added multiple times
|
// Use combination of name and item code in case same item is added multiple times
|
||||||
|
|||||||
@@ -193,6 +193,9 @@ erpnext.SalesFunnel = class SalesFunnel {
|
|||||||
this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0;
|
this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0;
|
||||||
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
|
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
|
||||||
|
|
||||||
|
const min_height = (this.options.height * 0.1) / this.options.data.length;
|
||||||
|
const height = this.options.height * 0.9;
|
||||||
|
|
||||||
// calculate total weightage
|
// calculate total weightage
|
||||||
// as height decreases, area decreases by the square of the reduction
|
// as height decreases, area decreases by the square of the reduction
|
||||||
// hence, compensating by squaring the index value
|
// hence, compensating by squaring the index value
|
||||||
@@ -202,7 +205,7 @@ erpnext.SalesFunnel = class SalesFunnel {
|
|||||||
|
|
||||||
// calculate height for each data
|
// calculate height for each data
|
||||||
$.each(this.options.data, function (i, d) {
|
$.each(this.options.data, function (i, d) {
|
||||||
d.height = (me.options.height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage;
|
d.height = (height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage + min_height;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.elements.canvas = $("<canvas></canvas>")
|
this.elements.canvas = $("<canvas></canvas>")
|
||||||
|
|||||||
@@ -807,6 +807,9 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
if price_list_rate is None or frappe.db.get_single_value(
|
if price_list_rate is None or frappe.db.get_single_value(
|
||||||
"Stock Settings", "update_existing_price_list_rate"
|
"Stock Settings", "update_existing_price_list_rate"
|
||||||
):
|
):
|
||||||
|
if args.get("is_internal_supplier") or args.get("is_internal_customer"):
|
||||||
|
return out
|
||||||
|
|
||||||
if args.price_list and args.rate:
|
if args.price_list and args.rate:
|
||||||
insert_item_price(args)
|
insert_item_price(args)
|
||||||
|
|
||||||
@@ -818,7 +821,11 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
|
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
if not out.price_list_rate and args.transaction_type == "buying":
|
if (
|
||||||
|
not args.get("is_internal_supplier")
|
||||||
|
and not out.price_list_rate
|
||||||
|
and args.transaction_type == "buying"
|
||||||
|
):
|
||||||
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||||
|
|
||||||
out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))
|
out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ def get_columns(filters):
|
|||||||
"width": 150,
|
"width": 150,
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": _("Expiry Date"),
|
||||||
|
"fieldname": "expiry_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"width": 120,
|
||||||
|
},
|
||||||
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
|
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -97,6 +103,7 @@ def get_batchwise_data_from_stock_ledger(filters):
|
|||||||
table.item_code,
|
table.item_code,
|
||||||
table.batch_no,
|
table.batch_no,
|
||||||
table.warehouse,
|
table.warehouse,
|
||||||
|
batch.expiry_date,
|
||||||
Sum(table.actual_qty).as_("balance_qty"),
|
Sum(table.actual_qty).as_("balance_qty"),
|
||||||
)
|
)
|
||||||
.where(table.is_cancelled == 0)
|
.where(table.is_cancelled == 0)
|
||||||
@@ -127,6 +134,7 @@ def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters):
|
|||||||
table.item_code,
|
table.item_code,
|
||||||
ch_table.batch_no,
|
ch_table.batch_no,
|
||||||
table.warehouse,
|
table.warehouse,
|
||||||
|
batch.expiry_date,
|
||||||
Sum(ch_table.qty).as_("balance_qty"),
|
Sum(ch_table.qty).as_("balance_qty"),
|
||||||
)
|
)
|
||||||
.where((table.is_cancelled == 0) & (table.docstatus == 1))
|
.where((table.is_cancelled == 0) & (table.docstatus == 1))
|
||||||
@@ -152,10 +160,14 @@ def get_query_based_on_filters(query, batch, table, filters):
|
|||||||
if filters.batch_no:
|
if filters.batch_no:
|
||||||
query = query.where(batch.name == filters.batch_no)
|
query = query.where(batch.name == filters.batch_no)
|
||||||
|
|
||||||
if not filters.include_expired_batches:
|
if filters.to_date == today():
|
||||||
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
|
if not filters.include_expired_batches:
|
||||||
if filters.to_date == today():
|
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
|
||||||
query = query.where(batch.batch_qty > 0)
|
|
||||||
|
query = query.where(batch.batch_qty > 0)
|
||||||
|
|
||||||
|
else:
|
||||||
|
query = query.where(table.posting_date <= filters.to_date)
|
||||||
|
|
||||||
if filters.warehouse:
|
if filters.warehouse:
|
||||||
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])
|
||||||
|
|||||||
@@ -465,10 +465,13 @@ class FIFOSlots:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
for field in ["item_code", "warehouse"]:
|
for field in ["item_code"]:
|
||||||
if self.filters.get(field):
|
if self.filters.get(field):
|
||||||
query = query.where(bundle[field] == self.filters.get(field))
|
query = query.where(bundle[field] == self.filters.get(field))
|
||||||
|
|
||||||
|
if self.filters.get("warehouse"):
|
||||||
|
query = self.__get_warehouse_conditions(bundle, query)
|
||||||
|
|
||||||
bundle_wise_serial_nos = frappe._dict({})
|
bundle_wise_serial_nos = frappe._dict({})
|
||||||
for bundle_name, serial_no in query.run():
|
for bundle_name, serial_no in query.run():
|
||||||
bundle_wise_serial_nos.setdefault(bundle_name, []).append(serial_no)
|
bundle_wise_serial_nos.setdefault(bundle_name, []).append(serial_no)
|
||||||
|
|||||||
@@ -114,18 +114,23 @@ def validate_filters(filters):
|
|||||||
|
|
||||||
|
|
||||||
def get_warehouse_list(filters):
|
def get_warehouse_list(filters):
|
||||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
if not filters.get("warehouse"):
|
||||||
|
return frappe.get_all(
|
||||||
|
"Warehouse",
|
||||||
|
filters={"company": filters.get("company"), "is_group": 0},
|
||||||
|
fields=["name"],
|
||||||
|
order_by="name",
|
||||||
|
)
|
||||||
|
|
||||||
wh = frappe.qb.DocType("Warehouse")
|
warehouse = frappe.qb.DocType("Warehouse")
|
||||||
query = frappe.qb.from_(wh).select(wh.name).where(wh.is_group == 0)
|
lft, rgt = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"])
|
||||||
|
|
||||||
user_permitted_warehouse = get_permitted_documents("Warehouse")
|
return (
|
||||||
if user_permitted_warehouse:
|
frappe.qb.from_(warehouse)
|
||||||
query = query.where(wh.name.isin(set(user_permitted_warehouse)))
|
.select("name")
|
||||||
elif filters.get("warehouse"):
|
.where((warehouse.lft >= lft) & (warehouse.rgt <= rgt))
|
||||||
query = query.where(wh.name == filters.get("warehouse"))
|
.run(as_dict=True)
|
||||||
|
)
|
||||||
return query.run(as_dict=True)
|
|
||||||
|
|
||||||
|
|
||||||
def add_warehouse_column(columns, warehouse_list):
|
def add_warehouse_column(columns, warehouse_list):
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user