Merge branch 'develop' into gross_profit_memory_and_performance_issue

This commit is contained in:
Deepesh Garg 2023-02-13 15:38:26 +05:30 committed by GitHub
commit ff4ae69408
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1937 additions and 2042 deletions

View File

@ -40,7 +40,7 @@ class Dunning(AccountsController):
def on_cancel(self):
if self.dunning_amount:
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def make_gl_entries(self):

View File

@ -234,7 +234,7 @@ class PaymentReconciliation(Document):
def allocate_entries(self, args):
self.validate_entries()
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"))
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
default_exchange_gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
@ -253,6 +253,9 @@ class PaymentReconciliation(Document):
pay["amount"] = 0
inv["exchange_rate"] = invoice_exchange_map.get(inv.get("invoice_number"))
if pay.get("reference_type") in ["Sales Invoice", "Purchase Invoice"]:
pay["exchange_rate"] = invoice_exchange_map.get(pay.get("reference_name"))
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
@ -407,13 +410,21 @@ class PaymentReconciliation(Document):
if not self.get("payments"):
frappe.throw(_("No records found in the Payments table"))
def get_invoice_exchange_map(self, invoices):
def get_invoice_exchange_map(self, invoices, payments):
sales_invoices = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Sales Invoice"
]
sales_invoices.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Sales Invoice"]
)
purchase_invoices = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Purchase Invoice"
]
purchase_invoices.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Purchase Invoice"]
)
invoice_exchange_map = frappe._dict()
if sales_invoices:

View File

@ -473,6 +473,11 @@ class TestPaymentReconciliation(FrappeTestCase):
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
pr.get_unreconciled_entries()
@ -506,6 +511,11 @@ class TestPaymentReconciliation(FrappeTestCase):
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = allocated_amount
# Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# assert outstanding

View File

@ -1512,9 +1512,12 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
ref_doc = frappe.get_doc(voucher_type, voucher_no)
# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"]
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"] or 0.0
frappe.db.set_value(
voucher_type, voucher_no, "outstanding_amount", outstanding["outstanding_in_account_currency"]
voucher_type,
voucher_no,
"outstanding_amount",
outstanding["outstanding_in_account_currency"] or 0.0,
)
ref_doc.set_status(update=True)

View File

@ -252,6 +252,7 @@ def get_already_returned_items(doc):
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
for update
""".format(
column, doc.doctype, doc.doctype
),

View File

@ -85,11 +85,15 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
}
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
this.frm.add_custom_button(
__("Sales Order"),
this.frm.cscript["Make Sales Order"],
__("Create")
);
if (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation
|| (!doc.valid_till)
|| frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
this.frm.add_custom_button(
__("Sales Order"),
this.frm.cscript["Make Sales Order"],
__("Create")
);
}
if(doc.status!=="Ordered") {
this.frm.add_custom_button(__('Set as Lost'), () => {

View File

@ -195,6 +195,17 @@ def get_list_context(context=None):
@frappe.whitelist()
def make_sales_order(source_name: str, target_doc=None):
if not frappe.db.get_singles_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
):
quotation = frappe.db.get_value(
"Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
)
if quotation.valid_till and (
quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
):
frappe.throw(_("Validity period of this quotation has ended."))
return _make_sales_order(source_name, target_doc)

View File

@ -144,11 +144,21 @@ class TestQuotation(FrappeTestCase):
def test_so_from_expired_quotation(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
frappe.db.set_single_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation", 0
)
quotation = frappe.copy_doc(test_records[0])
quotation.valid_till = add_days(nowdate(), -1)
quotation.insert()
quotation.submit()
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
frappe.db.set_single_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation", 1
)
make_sales_order(quotation.name)
def test_shopping_cart_without_website_item(self):

View File

@ -27,6 +27,7 @@
"column_break_5",
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
"allow_sales_order_creation_for_expired_quotation",
"hide_tax_id",
"enable_discount_accounting"
],
@ -172,6 +173,12 @@
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting for Selling"
},
{
"default": "0",
"fieldname": "allow_sales_order_creation_for_expired_quotation",
"fieldtype": "Check",
"label": "Allow Sales Order Creation For Expired Quotation"
}
],
"icon": "fa fa-cog",
@ -179,7 +186,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-05-31 19:39:48.398738",
"modified": "2023-02-04 12:37:53.380857",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@ -103,6 +103,11 @@ function get_filters() {
return options
}
},
{
"fieldname":"only_immediate_upcoming_term",
"label": __("Show only the Immediate Upcoming Term"),
"fieldtype": "Check",
},
]
return filters;
}

View File

@ -4,6 +4,7 @@
import frappe
from frappe import _, qb, query_builder
from frappe.query_builder import Criterion, functions
from frappe.utils.dateutils import getdate
def get_columns():
@ -208,6 +209,7 @@ def get_so_with_invoices(filters):
)
.where(
(so.docstatus == 1)
& (so.status.isin(["To Deliver and Bill", "To Bill"]))
& (so.payment_terms_template != "NULL")
& (so.company == conditions.company)
& (so.transaction_date[conditions.start_date : conditions.end_date])
@ -291,6 +293,18 @@ def filter_on_calculated_status(filters, sales_orders):
return sales_orders
def filter_for_immediate_upcoming_term(filters, sales_orders):
if filters.only_immediate_upcoming_term and sales_orders:
immediate_term_found = set()
filtered_data = []
for order in sales_orders:
if order.name not in immediate_term_found and order.due_date > getdate():
filtered_data.append(order)
immediate_term_found.add(order.name)
return filtered_data
return sales_orders
def execute(filters=None):
columns = get_columns()
sales_orders, so_invoices = get_so_with_invoices(filters)
@ -298,6 +312,8 @@ def execute(filters=None):
sales_orders = filter_on_calculated_status(filters, sales_orders)
sales_orders = filter_for_immediate_upcoming_term(filters, sales_orders)
prepare_chart(sales_orders)
data = sales_orders

View File

@ -25,6 +25,12 @@ def boot_session(bootinfo):
frappe.db.get_single_value("CRM Settings", "default_valid_till")
)
bootinfo.sysdefaults.allow_sales_order_creation_for_expired_quotation = cint(
frappe.db.get_single_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
)
)
# if no company, show a dialog box to create a new company
bootinfo.customer_count = frappe.db.sql("""SELECT count(*) FROM `tabCustomer`""")[0][0]

File diff suppressed because it is too large Load Diff