mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-02 21:18:27 +00:00
Merge pull request #46582 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -139,7 +139,7 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
self.cancel_gl_entries()
|
self.cancel_gl_entries()
|
||||||
|
|
||||||
def make_gl_entries(self):
|
def make_gl_entries(self):
|
||||||
if self.get_gle_count_in_selected_period() > 5000:
|
if frappe.db.estimate_count("GL Entry") > 100_000:
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
process_gl_and_closing_entries,
|
process_gl_and_closing_entries,
|
||||||
doc=self,
|
doc=self,
|
||||||
@@ -154,16 +154,6 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
else:
|
else:
|
||||||
process_gl_and_closing_entries(self)
|
process_gl_and_closing_entries(self)
|
||||||
|
|
||||||
def get_gle_count_in_selected_period(self):
|
|
||||||
return frappe.db.count(
|
|
||||||
"GL Entry",
|
|
||||||
{
|
|
||||||
"posting_date": ["between", [self.period_start_date, self.period_end_date]],
|
|
||||||
"company": self.company,
|
|
||||||
"is_cancelled": 0,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_pcv_gl_entries(self):
|
def get_pcv_gl_entries(self):
|
||||||
self.pl_accounts_reverse_gle = []
|
self.pl_accounts_reverse_gle = []
|
||||||
self.closing_account_gle = []
|
self.closing_account_gle = []
|
||||||
|
|||||||
@@ -1318,7 +1318,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": cost_of_goods_sold_account,
|
"account": cost_of_goods_sold_account,
|
||||||
"against": item.expense_account,
|
"against": item.expense_account,
|
||||||
"debit": stock_adjustment_amt,
|
"debit": stock_adjustment_amt,
|
||||||
"debit_in_transaction_currency": item.net_amount,
|
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
|
||||||
"remarks": self.get("remarks") or _("Stock Adjustment"),
|
"remarks": self.get("remarks") or _("Stock Adjustment"),
|
||||||
"cost_center": item.cost_center,
|
"cost_center": item.cost_center,
|
||||||
"project": item.project or self.project,
|
"project": item.project or self.project,
|
||||||
@@ -1330,6 +1330,38 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
warehouse_debit_amount = stock_amount
|
warehouse_debit_amount = stock_amount
|
||||||
|
|
||||||
|
elif self.is_return and self.update_stock and self.is_internal_supplier and warehouse_debit_amount:
|
||||||
|
net_rate = item.base_net_amount
|
||||||
|
if item.sales_incoming_rate: # for internal transfer
|
||||||
|
net_rate = item.qty * item.sales_incoming_rate
|
||||||
|
|
||||||
|
stock_amount = (
|
||||||
|
net_rate
|
||||||
|
+ item.item_tax_amount
|
||||||
|
+ flt(item.landed_cost_voucher_amount)
|
||||||
|
+ flt(item.get("amount_difference_with_purchase_invoice"))
|
||||||
|
)
|
||||||
|
|
||||||
|
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
|
||||||
|
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
|
||||||
|
stock_adjustment_amt = stock_amount - warehouse_debit_amount
|
||||||
|
|
||||||
|
gl_entries.append(
|
||||||
|
self.get_gl_dict(
|
||||||
|
{
|
||||||
|
"account": cost_of_goods_sold_account,
|
||||||
|
"against": item.expense_account,
|
||||||
|
"debit": stock_adjustment_amt,
|
||||||
|
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
|
||||||
|
"remarks": self.get("remarks") or _("Stock Adjustment"),
|
||||||
|
"cost_center": item.cost_center,
|
||||||
|
"project": item.project or self.project,
|
||||||
|
},
|
||||||
|
account_currency,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return warehouse_debit_amount
|
return warehouse_debit_amount
|
||||||
|
|
||||||
def make_tax_gl_entries(self, gl_entries):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
"autoname": "naming_series:",
|
"autoname": "naming_series:",
|
||||||
"creation": "2022-01-25 10:29:57.771398",
|
"creation": "2022-01-25 10:29:57.771398",
|
||||||
"default_print_format": "Sales Invoice Print",
|
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
@@ -2189,7 +2188,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-03-05 17:06:59.720616",
|
"modified": "2025-03-17 19:32:31.809658",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
@@ -2245,4 +2244,4 @@
|
|||||||
"title_field": "title",
|
"title_field": "title",
|
||||||
"track_changes": 1,
|
"track_changes": 1,
|
||||||
"track_seen": 1
|
"track_seen": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ def make_acc_dimensions_offsetting_entry(gl_map):
|
|||||||
"credit_in_account_currency": credit,
|
"credit_in_account_currency": credit,
|
||||||
"remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}",
|
"remarks": _("Offsetting for Accounting Dimension") + f" - {dimension.name}",
|
||||||
"against_voucher": None,
|
"against_voucher": None,
|
||||||
|
"account_currency": dimension.account_currency,
|
||||||
|
# Party Type and Party are restricted to Receivable and Payable accounts
|
||||||
|
"party_type": None,
|
||||||
|
"party": None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
offsetting_entry["against_voucher_type"] = None
|
offsetting_entry["against_voucher_type"] = None
|
||||||
@@ -108,6 +112,9 @@ def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
|
|||||||
accounting_dimensions_to_offset = []
|
accounting_dimensions_to_offset = []
|
||||||
for acc_dimension in acc_dimensions:
|
for acc_dimension in acc_dimensions:
|
||||||
values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
|
values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
|
||||||
|
acc_dimension.account_currency = frappe.get_cached_value(
|
||||||
|
"Account", acc_dimension.offsetting_account, "account_currency"
|
||||||
|
)
|
||||||
if len(values) > 1:
|
if len(values) > 1:
|
||||||
accounting_dimensions_to_offset.append(acc_dimension)
|
accounting_dimensions_to_offset.append(acc_dimension)
|
||||||
|
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ class ReceivablePayableReport:
|
|||||||
ps.description, ps.paid_amount, ps.discounted_amount
|
ps.description, ps.paid_amount, ps.discounted_amount
|
||||||
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
|
from `tab{row.voucher_type}` si, `tabPayment Schedule` ps
|
||||||
where
|
where
|
||||||
si.name = ps.parent and
|
si.name = ps.parent and ps.parenttype = '{row.voucher_type}' and
|
||||||
si.name = %s and
|
si.name = %s and
|
||||||
si.is_return = 0
|
si.is_return = 0
|
||||||
order by ps.paid_amount desc, due_date
|
order by ps.paid_amount desc, due_date
|
||||||
@@ -729,11 +729,13 @@ class ReceivablePayableReport:
|
|||||||
"company": self.filters.company,
|
"company": self.filters.company,
|
||||||
"update_outstanding_for_self": 0,
|
"update_outstanding_for_self": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
or_filters = {}
|
or_filters = {}
|
||||||
for party_type in self.party_type:
|
if party_type := self.filters.party_type:
|
||||||
party_field = scrub(party_type)
|
party_field = scrub(party_type)
|
||||||
if self.filters.get(party_field):
|
if parties := self.filters.get("party"):
|
||||||
or_filters.update({party_field: self.filters.get(party_field)})
|
or_filters.update({party_field: ["in", parties]})
|
||||||
|
|
||||||
self.return_entries = frappe._dict(
|
self.return_entries = frappe._dict(
|
||||||
frappe.get_all(
|
frappe.get_all(
|
||||||
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1
|
doctype, filters=filters, or_filters=or_filters, fields=["name", "return_against"], as_list=1
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class AssetCapitalization(StockController):
|
|||||||
self.make_bundle_using_old_serial_batch_fields()
|
self.make_bundle_using_old_serial_batch_fields()
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
self.update_target_asset()
|
self.update_target_asset()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@@ -136,6 +137,7 @@ class AssetCapitalization(StockController):
|
|||||||
)
|
)
|
||||||
self.update_stock_ledger()
|
self.update_stock_ledger()
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
|
self.repost_future_sle_and_gle()
|
||||||
self.restore_consumed_asset_items()
|
self.restore_consumed_asset_items()
|
||||||
|
|
||||||
def set_title(self):
|
def set_title(self):
|
||||||
|
|||||||
@@ -913,6 +913,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
|
"depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
|
||||||
"fieldname": "subcontracted_quantity",
|
"fieldname": "subcontracted_quantity",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Subcontracted Quantity",
|
"label": "Subcontracted Quantity",
|
||||||
@@ -926,7 +927,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-03-02 16:58:26.059601",
|
"modified": "2025-03-13 17:27:43.468602",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order Item",
|
"name": "Purchase Order Item",
|
||||||
@@ -940,4 +941,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ class AccountsController(TransactionBase):
|
|||||||
self.set_total_in_words()
|
self.set_total_in_words()
|
||||||
self.set_default_letter_head()
|
self.set_default_letter_head()
|
||||||
self.validate_company_in_accounting_dimension()
|
self.validate_company_in_accounting_dimension()
|
||||||
|
self.validate_party_address_and_contact()
|
||||||
|
|
||||||
def set_default_letter_head(self):
|
def set_default_letter_head(self):
|
||||||
if hasattr(self, "letter_head") and not self.letter_head:
|
if hasattr(self, "letter_head") and not self.letter_head:
|
||||||
@@ -441,6 +442,45 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def validate_party_address_and_contact(self):
|
||||||
|
party, party_type = None, None
|
||||||
|
if self.get("customer"):
|
||||||
|
party, party_type = self.customer, "Customer"
|
||||||
|
billing_address, shipping_address = (
|
||||||
|
self.get("customer_address"),
|
||||||
|
self.get("shipping_address_name"),
|
||||||
|
)
|
||||||
|
self.validate_party_address(party, party_type, billing_address, shipping_address)
|
||||||
|
elif self.get("supplier"):
|
||||||
|
party, party_type = self.supplier, "Supplier"
|
||||||
|
billing_address = self.get("supplier_address")
|
||||||
|
self.validate_party_address(party, party_type, billing_address)
|
||||||
|
|
||||||
|
if party and party_type:
|
||||||
|
self.validate_party_contact(party, party_type)
|
||||||
|
|
||||||
|
def validate_party_address(self, party, party_type, billing_address, shipping_address=None):
|
||||||
|
if billing_address or shipping_address:
|
||||||
|
party_address = frappe.get_all(
|
||||||
|
"Dynamic Link",
|
||||||
|
{"link_doctype": party_type, "link_name": party, "parenttype": "Address"},
|
||||||
|
pluck="parent",
|
||||||
|
)
|
||||||
|
if billing_address and billing_address not in party_address:
|
||||||
|
frappe.throw(_("Billing Address does not belong to the {0}").format(party))
|
||||||
|
elif shipping_address and shipping_address not in party_address:
|
||||||
|
frappe.throw(_("Shipping Address does not belong to the {0}").format(party))
|
||||||
|
|
||||||
|
def validate_party_contact(self, party, party_type):
|
||||||
|
if self.get("contact_person"):
|
||||||
|
contact = frappe.get_all(
|
||||||
|
"Dynamic Link",
|
||||||
|
{"link_doctype": party_type, "link_name": party, "parenttype": "Contact"},
|
||||||
|
pluck="parent",
|
||||||
|
)
|
||||||
|
if self.contact_person and self.contact_person not in contact:
|
||||||
|
frappe.throw(_("Contact Person does not belong to the {0}").format(party))
|
||||||
|
|
||||||
def validate_return_against_account(self):
|
def validate_return_against_account(self):
|
||||||
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against:
|
||||||
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
|
cr_dr_account_field = "debit_to" if self.doctype == "Sales Invoice" else "credit_to"
|
||||||
@@ -3666,6 +3706,9 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
if d.get("schedule_date") and parent_doctype == "Purchase Order":
|
if d.get("schedule_date") and parent_doctype == "Purchase Order":
|
||||||
child_item.schedule_date = d.get("schedule_date")
|
child_item.schedule_date = d.get("schedule_date")
|
||||||
|
|
||||||
|
if d.get("bom_no") and parent_doctype == "Sales Order":
|
||||||
|
child_item.bom_no = d.get("bom_no")
|
||||||
|
|
||||||
if flt(child_item.price_list_rate):
|
if flt(child_item.price_list_rate):
|
||||||
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
if flt(child_item.rate) > flt(child_item.price_list_rate):
|
||||||
# if rate is greater than price_list_rate, set margin
|
# if rate is greater than price_list_rate, set margin
|
||||||
|
|||||||
@@ -2175,3 +2175,59 @@ class TestAccountsController(FrappeTestCase):
|
|||||||
si_1 = create_sales_invoice(do_not_submit=True)
|
si_1 = create_sales_invoice(do_not_submit=True)
|
||||||
si_1.items[0].project = project.name
|
si_1.items[0].project = project.name
|
||||||
self.assertRaises(frappe.ValidationError, si_1.save)
|
self.assertRaises(frappe.ValidationError, si_1.save)
|
||||||
|
|
||||||
|
def test_party_billing_and_shipping_address(self):
|
||||||
|
from erpnext.crm.doctype.prospect.test_prospect import make_address
|
||||||
|
|
||||||
|
customer_billing = make_address(address_title="Customer")
|
||||||
|
customer_billing.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||||
|
customer_billing.save()
|
||||||
|
supplier_billing = make_address(address_title="Supplier", address_line1="2", city="Ahmedabad")
|
||||||
|
supplier_billing.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
|
||||||
|
supplier_billing.save()
|
||||||
|
|
||||||
|
customer_shipping = make_address(
|
||||||
|
address_title="Customer", address_type="Shipping", address_line1="10"
|
||||||
|
)
|
||||||
|
customer_shipping.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||||
|
customer_shipping.save()
|
||||||
|
supplier_shipping = make_address(
|
||||||
|
address_title="Supplier", address_type="Shipping", address_line1="20", city="Ahmedabad"
|
||||||
|
)
|
||||||
|
supplier_shipping.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
|
||||||
|
supplier_shipping.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save=True)
|
||||||
|
si.customer_address = supplier_billing.name
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
si.customer_address = customer_billing.name
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
si.shipping_address_name = supplier_shipping.name
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
si.shipping_address_name = customer_shipping.name
|
||||||
|
si.reload()
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(do_not_save=True)
|
||||||
|
pi.supplier_address = customer_shipping.name
|
||||||
|
self.assertRaises(frappe.ValidationError, pi.save)
|
||||||
|
pi.supplier_address = supplier_shipping.name
|
||||||
|
pi.save()
|
||||||
|
|
||||||
|
def test_party_contact(self):
|
||||||
|
from frappe.contacts.doctype.contact.test_contact import create_contact
|
||||||
|
|
||||||
|
customer_contact = create_contact(name="Customer", salutation="Mr", save=False)
|
||||||
|
customer_contact.append("links", {"link_doctype": "Customer", "link_name": "_Test Customer"})
|
||||||
|
customer_contact.save()
|
||||||
|
|
||||||
|
supplier_contact = create_contact(name="Supplier", salutation="Mr", save=False)
|
||||||
|
supplier_contact.append("links", {"link_doctype": "Supplier", "link_name": "_Test Supplier"})
|
||||||
|
supplier_contact.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save=True)
|
||||||
|
si.contact_person = supplier_contact.name
|
||||||
|
self.assertRaises(frappe.ValidationError, si.save)
|
||||||
|
si.contact_person = customer_contact.name
|
||||||
|
si.save()
|
||||||
|
|||||||
@@ -440,8 +440,8 @@ class BOM(WebsiteGenerator):
|
|||||||
"description": item and args["description"] or "",
|
"description": item and args["description"] or "",
|
||||||
"image": item and args["image"] or "",
|
"image": item and args["image"] or "",
|
||||||
"stock_uom": item and args["stock_uom"] or "",
|
"stock_uom": item and args["stock_uom"] or "",
|
||||||
"uom": args["uom"] if hasattr(args, "uom") else item and args["stock_uom"] or "",
|
"uom": args["uom"] if args.get("uom") else item and args["stock_uom"] or "",
|
||||||
"conversion_factor": args["conversion_factor"] if hasattr(args, "conversion_factor") else 1,
|
"conversion_factor": args["conversion_factor"] if args.get("conversion_factor") else 1,
|
||||||
"bom_no": args["bom_no"],
|
"bom_no": args["bom_no"],
|
||||||
"rate": rate,
|
"rate": rate,
|
||||||
"qty": args.get("qty") or args.get("stock_qty") or 1,
|
"qty": args.get("qty") or args.get("stock_qty") or 1,
|
||||||
|
|||||||
@@ -86,7 +86,6 @@
|
|||||||
"search_index": 1
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "work_order.bom_no",
|
|
||||||
"fieldname": "bom_no",
|
"fieldname": "bom_no",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "BOM No",
|
"label": "BOM No",
|
||||||
@@ -281,7 +280,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "work_order.production_item",
|
"fetch_from": "bom_no.item",
|
||||||
"fieldname": "production_item",
|
"fieldname": "production_item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Production Item",
|
"label": "Production Item",
|
||||||
@@ -511,7 +510,7 @@
|
|||||||
],
|
],
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-28 19:23:14.345214",
|
"modified": "2025-03-17 15:55:11.143456",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Job Card",
|
"name": "Job Card",
|
||||||
|
|||||||
@@ -64,9 +64,7 @@ class JobCard(Document):
|
|||||||
from erpnext.manufacturing.doctype.job_card_scheduled_time.job_card_scheduled_time import (
|
from erpnext.manufacturing.doctype.job_card_scheduled_time.job_card_scheduled_time import (
|
||||||
JobCardScheduledTime,
|
JobCardScheduledTime,
|
||||||
)
|
)
|
||||||
from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import (
|
from erpnext.manufacturing.doctype.job_card_scrap_item.job_card_scrap_item import JobCardScrapItem
|
||||||
JobCardScrapItem,
|
|
||||||
)
|
|
||||||
from erpnext.manufacturing.doctype.job_card_time_log.job_card_time_log import JobCardTimeLog
|
from erpnext.manufacturing.doctype.job_card_time_log.job_card_time_log import JobCardTimeLog
|
||||||
|
|
||||||
actual_end_date: DF.Datetime | None
|
actual_end_date: DF.Datetime | None
|
||||||
@@ -91,7 +89,7 @@ class JobCard(Document):
|
|||||||
naming_series: DF.Literal["PO-JOB.#####"]
|
naming_series: DF.Literal["PO-JOB.#####"]
|
||||||
operation: DF.Link
|
operation: DF.Link
|
||||||
operation_id: DF.Data | None
|
operation_id: DF.Data | None
|
||||||
operation_row_number: DF.Literal
|
operation_row_number: DF.Literal[None]
|
||||||
posting_date: DF.Date | None
|
posting_date: DF.Date | None
|
||||||
process_loss_qty: DF.Float
|
process_loss_qty: DF.Float
|
||||||
production_item: DF.Link | None
|
production_item: DF.Link | None
|
||||||
|
|||||||
@@ -363,8 +363,8 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
item.pending_qty = (
|
item.pending_qty = (
|
||||||
flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0) * item.conversion_factor
|
flt(item.qty) - max(item.work_order_qty, item.delivered_qty, 0)
|
||||||
)
|
) * item.conversion_factor
|
||||||
|
|
||||||
pi = frappe.qb.DocType("Packed Item")
|
pi = frappe.qb.DocType("Packed Item")
|
||||||
|
|
||||||
@@ -425,6 +425,7 @@ class ProductionPlan(Document):
|
|||||||
mr_item.item_code,
|
mr_item.item_code,
|
||||||
mr_item.warehouse,
|
mr_item.warehouse,
|
||||||
mr_item.description,
|
mr_item.description,
|
||||||
|
mr_item.bom_no,
|
||||||
((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"),
|
((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor).as_("pending_qty"),
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
prepare_data_for_backflush_based_on_materials_transferred()
|
prepare_data_for_backflush_based_on_materials_transferred()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
frappe.local.future_sle = {}
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
|
|
||||||
def check_planned_qty(self):
|
def check_planned_qty(self):
|
||||||
|
|||||||
@@ -301,6 +301,12 @@ frappe.ui.form.on("Work Order", {
|
|||||||
label: __("Sequence Id"),
|
label: __("Sequence Id"),
|
||||||
read_only: 1,
|
read_only: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Link",
|
||||||
|
fieldname: "bom",
|
||||||
|
label: __("BOM"),
|
||||||
|
read_only: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
data: operations_data,
|
data: operations_data,
|
||||||
in_place_edit: true,
|
in_place_edit: true,
|
||||||
@@ -341,6 +347,7 @@ frappe.ui.form.on("Work Order", {
|
|||||||
qty: pending_qty,
|
qty: pending_qty,
|
||||||
pending_qty: pending_qty,
|
pending_qty: pending_qty,
|
||||||
sequence_id: data.sequence_id,
|
sequence_id: data.sequence_id,
|
||||||
|
bom: data.bom,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1616,7 +1616,7 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create
|
|||||||
"posting_date": nowdate(),
|
"posting_date": nowdate(),
|
||||||
"for_quantity": row.job_card_qty or work_order.get("qty", 0),
|
"for_quantity": row.job_card_qty or work_order.get("qty", 0),
|
||||||
"operation_id": row.get("name"),
|
"operation_id": row.get("name"),
|
||||||
"bom_no": work_order.bom_no,
|
"bom_no": row.get("bom"),
|
||||||
"project": work_order.project,
|
"project": work_order.project,
|
||||||
"company": work_order.company,
|
"company": work_order.company,
|
||||||
"sequence_id": row.get("sequence_id"),
|
"sequence_id": row.get("sequence_id"),
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment
|
|||||||
erpnext.patches.v14_0.update_posting_datetime
|
erpnext.patches.v14_0.update_posting_datetime
|
||||||
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
||||||
erpnext.patches.v15_0.update_query_report
|
erpnext.patches.v15_0.update_query_report
|
||||||
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference
|
erpnext.patches.v15_0.rename_field_from_rate_difference_to_amount_difference #2025-03-18
|
||||||
erpnext.patches.v15_0.recalculate_amount_difference_field
|
erpnext.patches.v15_0.recalculate_amount_difference_field
|
||||||
erpnext.patches.v15_0.rename_sla_fields #2025-03-12
|
erpnext.patches.v15_0.rename_sla_fields #2025-03-12
|
||||||
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
erpnext.patches.v15_0.set_purchase_receipt_row_item_to_capitalization_stock_item
|
||||||
|
|||||||
@@ -27,11 +27,7 @@ def execute():
|
|||||||
table.qty,
|
table.qty,
|
||||||
parent.conversion_rate,
|
parent.conversion_rate,
|
||||||
)
|
)
|
||||||
.where(
|
.where((table.docstatus == 1) & (parent.company == company))
|
||||||
(table.amount_difference_with_purchase_invoice != 0)
|
|
||||||
& (table.docstatus == 1)
|
|
||||||
& (parent.company == company)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
posting_date = "2024-04-01"
|
posting_date = "2024-04-01"
|
||||||
@@ -121,6 +117,7 @@ def get_billed_qty_against_purchase_receipt(pr_names):
|
|||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.select(table.pr_detail, Sum(table.qty).as_("qty"))
|
.select(table.pr_detail, Sum(table.qty).as_("qty"))
|
||||||
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
||||||
|
.groupby(table.pr_detail)
|
||||||
)
|
)
|
||||||
invoice_data = query.run(as_list=1)
|
invoice_data = query.run(as_list=1)
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,12 @@ from frappe.model.utils.rename_field import rename_field
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity")
|
if frappe.db.table_exists("Purchase Order Item") and frappe.db.has_column(
|
||||||
rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor")
|
"Purchase Order Item", "sco_qty"
|
||||||
|
):
|
||||||
|
rename_field("Purchase Order Item", "sco_qty", "subcontracted_quantity")
|
||||||
|
|
||||||
|
if frappe.db.table_exists("Subcontracting Order Item") and frappe.db.has_column(
|
||||||
|
"Subcontracting Order Item", "sc_conversion_factor"
|
||||||
|
):
|
||||||
|
rename_field("Subcontracting Order Item", "sc_conversion_factor", "subcontracting_conversion_factor")
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ erpnext.buying = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
qty(doc, cdt, cdn) {
|
qty(doc, cdt, cdn) {
|
||||||
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && (doc.update_stock || doc.is_return))) {
|
if ((doc.doctype == "Purchase Receipt") || (doc.doctype == "Purchase Invoice" && doc.update_stock)) {
|
||||||
this.calculate_received_qty(doc, cdt, cdn)
|
this.calculate_received_qty(doc, cdt, cdn)
|
||||||
}
|
}
|
||||||
super.qty(doc, cdt, cdn);
|
super.qty(doc, cdt, cdn);
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
let d = locals[cdt][cdn];
|
let d = locals[cdt][cdn];
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
docstatus: 1,
|
docstatus: ("<", 2),
|
||||||
inspection_type: inspection_type,
|
inspection_type: inspection_type,
|
||||||
reference_name: doc.name,
|
reference_name: doc.name,
|
||||||
item_code: d.item_code
|
item_code: d.item_code
|
||||||
|
|||||||
@@ -692,7 +692,7 @@ erpnext.utils.update_child_items = function (opts) {
|
|||||||
},
|
},
|
||||||
callback: function (r) {
|
callback: function (r) {
|
||||||
if (r.message) {
|
if (r.message) {
|
||||||
const { qty, price_list_rate: rate, uom, conversion_factor } = r.message;
|
const { qty, price_list_rate: rate, uom, conversion_factor, bom_no } = r.message;
|
||||||
|
|
||||||
const row = dialog.fields_dict.trans_items.df.data.find(
|
const row = dialog.fields_dict.trans_items.df.data.find(
|
||||||
(doc) => doc.idx == me.doc.idx
|
(doc) => doc.idx == me.doc.idx
|
||||||
@@ -703,6 +703,7 @@ erpnext.utils.update_child_items = function (opts) {
|
|||||||
uom: me.doc.uom || uom,
|
uom: me.doc.uom || uom,
|
||||||
qty: me.doc.qty || qty,
|
qty: me.doc.qty || qty,
|
||||||
rate: me.doc.rate || rate,
|
rate: me.doc.rate || rate,
|
||||||
|
bom_no: bom_no,
|
||||||
});
|
});
|
||||||
dialog.fields_dict.trans_items.grid.refresh();
|
dialog.fields_dict.trans_items.grid.refresh();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) {
|
|||||||
(frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype))
|
(frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype))
|
||||||
) {
|
) {
|
||||||
let party_type = "Customer";
|
let party_type = "Customer";
|
||||||
if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) {
|
if (frm.doc.quotation_to && ["Lead", "Prospect", "CRM Deal"].includes(frm.doc.quotation_to)) {
|
||||||
party_type = frm.doc.quotation_to;
|
party_type = frm.doc.quotation_to;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ class TestUaeVat201(TestCase):
|
|||||||
make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0})
|
make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0})
|
||||||
make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1})
|
make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1})
|
||||||
|
|
||||||
|
def test_uae_vat_201_report(self):
|
||||||
make_sales_invoices()
|
make_sales_invoices()
|
||||||
|
|
||||||
create_purchase_invoices()
|
create_purchase_invoices()
|
||||||
|
|
||||||
def test_uae_vat_201_report(self):
|
|
||||||
filters = {"company": "_Test Company UAE VAT"}
|
filters = {"company": "_Test Company UAE VAT"}
|
||||||
total_emiratewise = get_total_emiratewise(filters)
|
total_emiratewise = get_total_emiratewise(filters)
|
||||||
amounts_by_emirate = {}
|
amounts_by_emirate = {}
|
||||||
@@ -64,6 +63,37 @@ class TestUaeVat201(TestCase):
|
|||||||
self.assertEqual(get_standard_rated_expenses_total(filters), 250)
|
self.assertEqual(get_standard_rated_expenses_total(filters), 250)
|
||||||
self.assertEqual(get_standard_rated_expenses_tax(filters), 1)
|
self.assertEqual(get_standard_rated_expenses_tax(filters), 1)
|
||||||
|
|
||||||
|
def test_uae_vat_201_report_with_foreign_transaction(self):
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
company="_Test Company UAE VAT",
|
||||||
|
supplier="_Test UAE Supplier",
|
||||||
|
supplier_warehouse="_Test UAE VAT Supplier Warehouse - _TCUV",
|
||||||
|
warehouse="_Test UAE VAT Supplier Warehouse - _TCUV",
|
||||||
|
currency="USD",
|
||||||
|
conversion_rate=3.67,
|
||||||
|
cost_center="Main - _TCUV",
|
||||||
|
expense_account="Cost of Goods Sold - _TCUV",
|
||||||
|
item="_Test UAE VAT Item",
|
||||||
|
do_not_save=1,
|
||||||
|
uom="Nos",
|
||||||
|
)
|
||||||
|
pi.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "VAT 5% - _TCUV",
|
||||||
|
"cost_center": "Main - _TCUV",
|
||||||
|
"description": "VAT 5% @ 5.0",
|
||||||
|
"rate": 5.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pi.recoverable_standard_rated_expenses = 50
|
||||||
|
pi.save().submit()
|
||||||
|
|
||||||
|
filters = {"company": "_Test Company UAE VAT"}
|
||||||
|
self.assertEqual(get_standard_rated_expenses_total(filters), 917.5)
|
||||||
|
self.assertEqual(get_standard_rated_expenses_tax(filters), 50)
|
||||||
|
|
||||||
|
|
||||||
def make_company(company_name, abbr):
|
def make_company(company_name, abbr):
|
||||||
if not frappe.db.exists("Company", company_name):
|
if not frappe.db.exists("Company", company_name):
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ def get_reverse_charge_total(filters):
|
|||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
frappe.db.get_all(
|
frappe.db.get_all(
|
||||||
"Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
"Purchase Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1
|
||||||
)[0][0]
|
)[0][0]
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
@@ -219,7 +219,7 @@ def get_reverse_charge_recoverable_total(filters):
|
|||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
frappe.db.get_all(
|
frappe.db.get_all(
|
||||||
"Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
"Purchase Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1
|
||||||
)[0][0]
|
)[0][0]
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
@@ -274,7 +274,7 @@ def get_standard_rated_expenses_total(filters):
|
|||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
frappe.db.get_all(
|
frappe.db.get_all(
|
||||||
"Purchase Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
"Purchase Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1
|
||||||
)[0][0]
|
)[0][0]
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
@@ -310,7 +310,7 @@ def get_tourist_tax_return_total(filters):
|
|||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
frappe.db.get_all(
|
frappe.db.get_all(
|
||||||
"Sales Invoice", filters=query_filters, fields=["sum(total)"], as_list=True, limit=1
|
"Sales Invoice", filters=query_filters, fields=["sum(base_total)"], as_list=True, limit=1
|
||||||
)[0][0]
|
)[0][0]
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -876,6 +876,7 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
"name": "sales_order_item",
|
"name": "sales_order_item",
|
||||||
"parent": "sales_order",
|
"parent": "sales_order",
|
||||||
"delivery_date": "required_by",
|
"delivery_date": "required_by",
|
||||||
|
"bom_no": "bom_no",
|
||||||
},
|
},
|
||||||
"condition": lambda item: not frappe.db.exists(
|
"condition": lambda item: not frappe.db.exists(
|
||||||
"Product Bundle", {"name": item.item_code, "disabled": 0}
|
"Product Bundle", {"name": item.item_code, "disabled": 0}
|
||||||
|
|||||||
@@ -2136,7 +2136,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(dn.items[0].rate, 90)
|
self.assertEqual(dn.items[0].rate, 90)
|
||||||
|
|
||||||
def test_credit_limit_on_so_reopning(self):
|
def test_credit_limit_on_so_reopening(self):
|
||||||
# set credit limit
|
# set credit limit
|
||||||
company = "_Test Company"
|
company = "_Test Company"
|
||||||
customer = frappe.get_doc("Customer", self.customer)
|
customer = frappe.get_doc("Customer", self.customer)
|
||||||
@@ -2148,12 +2148,14 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
|||||||
|
|
||||||
so1 = make_sales_order(qty=9, rate=100, do_not_submit=True)
|
so1 = make_sales_order(qty=9, rate=100, do_not_submit=True)
|
||||||
so1.customer = self.customer
|
so1.customer = self.customer
|
||||||
|
so1.customer_address = so1.shipping_address_name = None
|
||||||
so1.save().submit()
|
so1.save().submit()
|
||||||
|
|
||||||
so1.update_status("Closed")
|
so1.update_status("Closed")
|
||||||
|
|
||||||
so2 = make_sales_order(qty=9, rate=100, do_not_submit=True)
|
so2 = make_sales_order(qty=9, rate=100, do_not_submit=True)
|
||||||
so2.customer = self.customer
|
so2.customer = self.customer
|
||||||
|
so2.customer_address = so2.shipping_address_name = None
|
||||||
so2.save().submit()
|
so2.save().submit()
|
||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, so1.update_status, "Draft")
|
self.assertRaises(frappe.ValidationError, so1.update_status, "Draft")
|
||||||
|
|||||||
@@ -449,6 +449,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
init_order_summary() {
|
init_order_summary() {
|
||||||
this.order_summary = new erpnext.PointOfSale.PastOrderSummary({
|
this.order_summary = new erpnext.PointOfSale.PastOrderSummary({
|
||||||
wrapper: this.$components_wrapper,
|
wrapper: this.$components_wrapper,
|
||||||
|
settings: this.settings,
|
||||||
events: {
|
events: {
|
||||||
get_frm: () => this.frm,
|
get_frm: () => this.frm,
|
||||||
|
|
||||||
@@ -485,7 +486,6 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pos_profile: this.pos_profile,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
erpnext.PointOfSale.PastOrderSummary = class {
|
erpnext.PointOfSale.PastOrderSummary = class {
|
||||||
constructor({ wrapper, events, pos_profile }) {
|
constructor({ wrapper, settings, events }) {
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
this.events = events;
|
this.events = events;
|
||||||
this.pos_profile = pos_profile;
|
this.print_receipt_on_order_complete = settings.print_receipt_on_order_complete;
|
||||||
|
|
||||||
this.init_component();
|
this.init_component();
|
||||||
}
|
}
|
||||||
@@ -357,8 +357,8 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
|
|
||||||
this.add_summary_btns(condition_btns_map);
|
this.add_summary_btns(condition_btns_map);
|
||||||
|
|
||||||
if (after_submission) {
|
if (after_submission && this.print_receipt_on_order_complete) {
|
||||||
this.print_receipt_on_order_complete();
|
this.print_receipt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,16 +426,4 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
|||||||
toggle_component(show) {
|
toggle_component(show) {
|
||||||
show ? this.$component.css("display", "flex") : this.$component.css("display", "none");
|
show ? this.$component.css("display", "flex") : this.$component.css("display", "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
async print_receipt_on_order_complete() {
|
|
||||||
const res = await frappe.db.get_value(
|
|
||||||
"POS Profile",
|
|
||||||
this.pos_profile,
|
|
||||||
"print_receipt_on_order_complete"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.message.print_receipt_on_order_complete) {
|
|
||||||
this.print_receipt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -220,35 +220,42 @@ class TransactionDeletionRecord(Document):
|
|||||||
"""Delete addresses to which leads are linked"""
|
"""Delete addresses to which leads are linked"""
|
||||||
self.validate_doc_status()
|
self.validate_doc_status()
|
||||||
if not self.delete_leads_and_addresses:
|
if not self.delete_leads_and_addresses:
|
||||||
leads = frappe.get_all("Lead", filters={"company": self.company})
|
leads = frappe.db.get_all("Lead", filters={"company": self.company}, pluck="name")
|
||||||
leads = ["'%s'" % row.get("name") for row in leads]
|
|
||||||
addresses = []
|
addresses = []
|
||||||
if leads:
|
if leads:
|
||||||
addresses = frappe.db.sql_list(
|
addresses = frappe.db.get_all(
|
||||||
"""select parent from `tabDynamic Link` where link_name
|
"Dynamic Link", filters={"link_name": ("in", leads)}, pluck="parent"
|
||||||
in ({leads})""".format(leads=",".join(leads))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if addresses:
|
if addresses:
|
||||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||||
|
|
||||||
frappe.db.sql(
|
address = qb.DocType("Address")
|
||||||
"""delete from `tabAddress` where name in ({addresses}) and
|
dl1 = qb.DocType("Dynamic Link")
|
||||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
dl2 = qb.DocType("Dynamic Link")
|
||||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
|
||||||
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses))
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.sql(
|
qb.from_(address).delete().where(
|
||||||
"""delete from `tabDynamic Link` where link_doctype='Lead'
|
(address.name.isin(addresses))
|
||||||
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads))
|
& (
|
||||||
)
|
address.name.notin(
|
||||||
|
qb.from_(dl1)
|
||||||
|
.join(dl2)
|
||||||
|
.on((dl1.parent == dl2.parent) & (dl1.link_doctype != dl2.link_doctype))
|
||||||
|
.select(dl1.parent)
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).run()
|
||||||
|
|
||||||
|
dynamic_link = qb.DocType("Dynamic Link")
|
||||||
|
qb.from_(dynamic_link).delete().where(
|
||||||
|
(dynamic_link.link_doctype == "Lead")
|
||||||
|
& (dynamic_link.parenttype == "Address")
|
||||||
|
& (dynamic_link.link_name.isin(leads))
|
||||||
|
).run()
|
||||||
|
|
||||||
|
customer = qb.DocType("Customer")
|
||||||
|
qb.update(customer).set(customer.lead_name, None).where(customer.lead_name.isin(leads)).run()
|
||||||
|
|
||||||
frappe.db.sql(
|
|
||||||
"""update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format(
|
|
||||||
leads=",".join(leads)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.db_set("delete_leads_and_addresses", 1)
|
self.db_set("delete_leads_and_addresses", 1)
|
||||||
self.enqueue_task(task="Reset Company Values")
|
self.enqueue_task(task="Reset Company Values")
|
||||||
|
|
||||||
|
|||||||
@@ -228,7 +228,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
(sle.item_code == self.sle.item_code)
|
(sle.item_code == self.sle.item_code)
|
||||||
& (sle.warehouse == self.sle.warehouse)
|
& (sle.warehouse == self.sle.warehouse)
|
||||||
& (sle.batch_no.isnotnull())
|
& (sle.batch_no.isnotnull())
|
||||||
& (batch.use_batchwise_valuation == 0)
|
|
||||||
& (sle.is_cancelled == 0)
|
& (sle.is_cancelled == 0)
|
||||||
& (sle.batch_no.isin(self.non_batchwise_valuation_batches))
|
& (sle.batch_no.isin(self.non_batchwise_valuation_batches))
|
||||||
)
|
)
|
||||||
@@ -278,7 +277,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
(sle.item_code == self.sle.item_code)
|
(sle.item_code == self.sle.item_code)
|
||||||
& (sle.warehouse == self.sle.warehouse)
|
& (sle.warehouse == self.sle.warehouse)
|
||||||
& (sle.batch_no.isnotnull())
|
& (sle.batch_no.isnotnull())
|
||||||
& (batch.use_batchwise_valuation == 0)
|
|
||||||
& (sle.is_cancelled == 0)
|
& (sle.is_cancelled == 0)
|
||||||
)
|
)
|
||||||
.where(timestamp_condition)
|
.where(timestamp_condition)
|
||||||
@@ -318,7 +316,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
(sabb.item_code == self.sle.item_code)
|
(sabb.item_code == self.sle.item_code)
|
||||||
& (sabb.warehouse == self.sle.warehouse)
|
& (sabb.warehouse == self.sle.warehouse)
|
||||||
& (sabb_entry.batch_no.isnotnull())
|
& (sabb_entry.batch_no.isnotnull())
|
||||||
& (batch.use_batchwise_valuation == 0)
|
|
||||||
& (sabb.is_cancelled == 0)
|
& (sabb.is_cancelled == 0)
|
||||||
& (sabb.docstatus == 1)
|
& (sabb.docstatus == 1)
|
||||||
)
|
)
|
||||||
@@ -378,7 +375,6 @@ class DeprecatedBatchNoValuation:
|
|||||||
(bundle.item_code == self.sle.item_code)
|
(bundle.item_code == self.sle.item_code)
|
||||||
& (bundle.warehouse == self.sle.warehouse)
|
& (bundle.warehouse == self.sle.warehouse)
|
||||||
& (bundle_child.batch_no.isnotnull())
|
& (bundle_child.batch_no.isnotnull())
|
||||||
& (batch.use_batchwise_valuation == 0)
|
|
||||||
& (bundle.is_cancelled == 0)
|
& (bundle.is_cancelled == 0)
|
||||||
& (bundle.docstatus == 1)
|
& (bundle.docstatus == 1)
|
||||||
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
|
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
|
||||||
|
|||||||
@@ -157,7 +157,13 @@ class Batch(Document):
|
|||||||
frappe.throw(_("The selected item cannot have Batch"))
|
frappe.throw(_("The selected item cannot have Batch"))
|
||||||
|
|
||||||
def set_batchwise_valuation(self):
|
def set_batchwise_valuation(self):
|
||||||
|
from erpnext.stock.utils import get_valuation_method
|
||||||
|
|
||||||
if self.is_new():
|
if self.is_new():
|
||||||
|
if get_valuation_method(self.item) == "Moving Average":
|
||||||
|
self.use_batchwise_valuation = 0
|
||||||
|
return
|
||||||
|
|
||||||
if frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation"):
|
if frappe.db.get_single_value("Stock Settings", "do_not_use_batchwise_valuation"):
|
||||||
self.use_batchwise_valuation = 0
|
self.use_batchwise_valuation = 0
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -40,7 +40,8 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Warehouse",
|
"label": "Default Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse",
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_3",
|
"fieldname": "column_break_3",
|
||||||
@@ -139,7 +140,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-04 12:33:14.607267",
|
"modified": "2025-03-17 13:46:09.719105",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Item Default",
|
"name": "Item Default",
|
||||||
@@ -150,4 +151,4 @@
|
|||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -813,7 +813,7 @@ def create_pick_list(source_name, target_doc=None):
|
|||||||
},
|
},
|
||||||
"Material Request Item": {
|
"Material Request Item": {
|
||||||
"doctype": "Pick List Item",
|
"doctype": "Pick List Item",
|
||||||
"field_map": {"name": "material_request_item", "qty": "stock_qty"},
|
"field_map": {"name": "material_request_item", "stock_qty": "stock_qty"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
target_doc,
|
target_doc,
|
||||||
|
|||||||
@@ -1189,6 +1189,7 @@ def create_delivery_note(source_name, target_doc=None):
|
|||||||
if not all(item.sales_order for item in pick_list.locations):
|
if not all(item.sales_order for item in pick_list.locations):
|
||||||
delivery_note = create_dn_wo_so(pick_list)
|
delivery_note = create_dn_wo_so(pick_list)
|
||||||
|
|
||||||
|
frappe.msgprint(_("Delivery Note(s) created for the Pick List"))
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|
||||||
@@ -1205,6 +1206,7 @@ def create_dn_wo_so(pick_list):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
|
||||||
|
delivery_note.insert(ignore_mandatory=True)
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
@@ -1232,7 +1234,10 @@ def create_dn_with_so(sales_dict, pick_list):
|
|||||||
# map all items of all sales orders of that customer
|
# map all items of all sales orders of that customer
|
||||||
for so in sales_dict[customer]:
|
for so in sales_dict[customer]:
|
||||||
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
|
||||||
|
delivery_note.flags.ignore_mandatory = True
|
||||||
|
delivery_note.insert()
|
||||||
update_packed_item_details(pick_list, delivery_note)
|
update_packed_item_details(pick_list, delivery_note)
|
||||||
|
delivery_note.save()
|
||||||
|
|
||||||
return delivery_note
|
return delivery_note
|
||||||
|
|
||||||
|
|||||||
@@ -1083,6 +1083,9 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
total_amount, total_billed_amount = 0, 0
|
total_amount, total_billed_amount = 0, 0
|
||||||
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
|
item_wise_returned_qty = get_item_wise_returned_qty(pr_doc)
|
||||||
|
|
||||||
|
if adjust_incoming_rate:
|
||||||
|
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
|
||||||
|
|
||||||
for item in pr_doc.items:
|
for item in pr_doc.items:
|
||||||
returned_qty = flt(item_wise_returned_qty.get(item.name))
|
returned_qty = flt(item_wise_returned_qty.get(item.name))
|
||||||
returned_amount = flt(returned_qty) * flt(item.rate)
|
returned_amount = flt(returned_qty) * flt(item.rate)
|
||||||
@@ -1102,7 +1105,6 @@ def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate
|
|||||||
|
|
||||||
if adjust_incoming_rate:
|
if adjust_incoming_rate:
|
||||||
adjusted_amt = 0.0
|
adjusted_amt = 0.0
|
||||||
item_wise_billed_qty = get_billed_qty_against_purchase_receipt(pr_doc)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
item.billed_amt is not None
|
item.billed_amt is not None
|
||||||
@@ -1134,6 +1136,7 @@ def get_billed_qty_against_purchase_receipt(pr_doc):
|
|||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.select(table.pr_detail, fn.Sum(table.qty).as_("qty"))
|
.select(table.pr_detail, fn.Sum(table.qty).as_("qty"))
|
||||||
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
.where((table.pr_detail.isin(pr_names)) & (table.docstatus == 1))
|
||||||
|
.groupby(table.pr_detail)
|
||||||
)
|
)
|
||||||
invoice_data = query.run(as_list=1)
|
invoice_data = query.run(as_list=1)
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ def get_data():
|
|||||||
"Auto Repeat": "reference_document",
|
"Auto Repeat": "reference_document",
|
||||||
"Purchase Receipt": "return_against",
|
"Purchase Receipt": "return_against",
|
||||||
"Stock Reservation Entry": "from_voucher_no",
|
"Stock Reservation Entry": "from_voucher_no",
|
||||||
|
"Quality Inspection": "reference_name",
|
||||||
},
|
},
|
||||||
"internal_links": {
|
"internal_links": {
|
||||||
"Material Request": ["items", "material_request"],
|
"Material Request": ["items", "material_request"],
|
||||||
"Purchase Order": ["items", "purchase_order"],
|
"Purchase Order": ["items", "purchase_order"],
|
||||||
"Project": ["items", "project"],
|
"Project": ["items", "project"],
|
||||||
"Quality Inspection": ["items", "quality_inspection"],
|
|
||||||
},
|
},
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3327,7 +3327,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
bundle = dn.items[0].serial_and_batch_bundle
|
bundle = dn.items[0].serial_and_batch_bundle
|
||||||
|
|
||||||
valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
|
valuation_rate = frappe.db.get_value("Serial and Batch Bundle", bundle, "avg_rate")
|
||||||
self.assertEqual(valuation_rate, 100)
|
self.assertEqual(valuation_rate, 150)
|
||||||
|
|
||||||
doc = frappe.get_doc("Stock Settings")
|
doc = frappe.get_doc("Stock Settings")
|
||||||
doc.do_not_use_batchwise_valuation = 1
|
doc.do_not_use_batchwise_valuation = 1
|
||||||
|
|||||||
@@ -181,6 +181,9 @@ class QualityInspection(Document):
|
|||||||
child = self.append("readings", {})
|
child = self.append("readings", {})
|
||||||
child.update(d)
|
child.update(d)
|
||||||
child.status = "Accepted"
|
child.status = "Accepted"
|
||||||
|
child.parameter_group = frappe.get_value(
|
||||||
|
"Quality Inspection Parameter", d.specification, "parameter_group"
|
||||||
|
)
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_quality_inspection_template(self):
|
def get_quality_inspection_template(self):
|
||||||
|
|||||||
@@ -145,6 +145,11 @@ class SerialandBatchBundle(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}):
|
elif not frappe.db.exists("Stock Ledger Entry", {"voucher_detail_no": self.voucher_detail_no}):
|
||||||
|
if self.voucher_type == "Delivery Note" and frappe.db.exists(
|
||||||
|
"Packed Item", self.voucher_detail_no
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("The serial and batch bundle {0} not linked to {1} {2}").format(
|
_("The serial and batch bundle {0} not linked to {1} {2}").format(
|
||||||
bold(self.name), self.voucher_type, bold(self.voucher_no)
|
bold(self.name), self.voucher_type, bold(self.voucher_no)
|
||||||
|
|||||||
@@ -913,7 +913,12 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
if frappe.db.exists(
|
if frappe.db.exists(
|
||||||
"Stock Entry",
|
"Stock Entry",
|
||||||
{"docstatus": 1, "work_order": self.work_order, "purpose": "Manufacture"},
|
{
|
||||||
|
"docstatus": 1,
|
||||||
|
"work_order": self.work_order,
|
||||||
|
"purpose": "Manufacture",
|
||||||
|
"name": ("!=", self.name),
|
||||||
|
},
|
||||||
):
|
):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Only one {0} entry can be created against the Work Order {1}").format(
|
_("Only one {0} entry can be created against the Work Order {1}").format(
|
||||||
@@ -1593,17 +1598,38 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_details(self, args=None, for_update=False):
|
def get_item_details(self, args=None, for_update=False):
|
||||||
item = frappe.db.sql(
|
item = frappe.qb.DocType("Item")
|
||||||
"""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
|
item_default = frappe.qb.DocType("Item Default")
|
||||||
i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,
|
|
||||||
id.expense_account, id.buying_cost_center
|
query = (
|
||||||
from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s
|
frappe.qb.from_(item)
|
||||||
where i.name=%s
|
.left_join(item_default)
|
||||||
and i.disabled=0
|
.on((item.name == item_default.parent) & (item_default.company == self.company))
|
||||||
and (i.end_of_life is null or i.end_of_life<'1900-01-01' or i.end_of_life > %s)""",
|
.select(
|
||||||
(self.company, args.get("item_code"), nowdate()),
|
item.name,
|
||||||
as_dict=1,
|
item.stock_uom,
|
||||||
|
item.description,
|
||||||
|
item.image,
|
||||||
|
item.item_name,
|
||||||
|
item.item_group,
|
||||||
|
item.has_batch_no,
|
||||||
|
item.sample_quantity,
|
||||||
|
item.has_serial_no,
|
||||||
|
item.allow_alternative_item,
|
||||||
|
item_default.expense_account,
|
||||||
|
item_default.buying_cost_center,
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(item.name == args.get("item_code"))
|
||||||
|
& (item.disabled == 0)
|
||||||
|
& (
|
||||||
|
(item.end_of_life.isnull())
|
||||||
|
| (item.end_of_life < "1900-01-01")
|
||||||
|
| (item.end_of_life > nowdate())
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
item = query.run(as_dict=True)
|
||||||
|
|
||||||
if not item:
|
if not item:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
@@ -1646,6 +1672,11 @@ class StockEntry(StockController):
|
|||||||
if self.purpose == "Material Issue":
|
if self.purpose == "Material Issue":
|
||||||
ret["expense_account"] = item.get("expense_account") or item_group_defaults.get("expense_account")
|
ret["expense_account"] = item.get("expense_account") or item_group_defaults.get("expense_account")
|
||||||
|
|
||||||
|
if self.purpose == "Manufacture":
|
||||||
|
ret["expense_account"] = frappe.get_cached_value(
|
||||||
|
"Company", self.company, "stock_adjustment_account"
|
||||||
|
)
|
||||||
|
|
||||||
for company_field, field in {
|
for company_field, field in {
|
||||||
"stock_adjustment_account": "expense_account",
|
"stock_adjustment_account": "expense_account",
|
||||||
"cost_center": "cost_center",
|
"cost_center": "cost_center",
|
||||||
|
|||||||
@@ -456,6 +456,45 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
user.remove_roles("Stock Manager")
|
user.remove_roles("Stock Manager")
|
||||||
|
|
||||||
|
def test_batchwise_item_valuation_fifo(self):
|
||||||
|
item, warehouses, batches = setup_item_valuation_test(valuation_method="FIFO")
|
||||||
|
|
||||||
|
# Incoming Entries for Stock Value check
|
||||||
|
pr_entry_list = [
|
||||||
|
(item, warehouses[0], batches[0], 1, 100),
|
||||||
|
(item, warehouses[0], batches[1], 1, 50),
|
||||||
|
(item, warehouses[0], batches[0], 1, 150),
|
||||||
|
(item, warehouses[0], batches[1], 1, 100),
|
||||||
|
]
|
||||||
|
prs = create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_list)
|
||||||
|
sle_details = fetch_sle_details_for_doc_list(prs, ["stock_value"])
|
||||||
|
sv_list = [d["stock_value"] for d in sle_details]
|
||||||
|
expected_sv = [100, 150, 300, 400]
|
||||||
|
self.assertEqual(expected_sv, sv_list, "Incorrect 'Stock Value' values")
|
||||||
|
|
||||||
|
# Outgoing Entries for Stock Value Difference check
|
||||||
|
dn_entry_list = [
|
||||||
|
(item, warehouses[0], batches[1], 1, 200),
|
||||||
|
(item, warehouses[0], batches[0], 1, 200),
|
||||||
|
(item, warehouses[0], batches[1], 1, 200),
|
||||||
|
(item, warehouses[0], batches[0], 1, 200),
|
||||||
|
]
|
||||||
|
|
||||||
|
frappe.flags.use_serial_and_batch_fields = True
|
||||||
|
dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
|
||||||
|
sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
|
||||||
|
svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
|
||||||
|
expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0]
|
||||||
|
|
||||||
|
self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
|
||||||
|
for dn, _incoming_rate in zip(dns, expected_incoming_rates, strict=False):
|
||||||
|
self.assertTrue(
|
||||||
|
dn.items[0].incoming_rate in expected_abs_svd,
|
||||||
|
"Incorrect 'Incoming Rate' values fetched for DN items",
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.flags.use_serial_and_batch_fields = False
|
||||||
|
|
||||||
def test_batchwise_item_valuation_moving_average(self):
|
def test_batchwise_item_valuation_moving_average(self):
|
||||||
item, warehouses, batches = setup_item_valuation_test(valuation_method="Moving Average")
|
item, warehouses, batches = setup_item_valuation_test(valuation_method="Moving Average")
|
||||||
|
|
||||||
@@ -484,7 +523,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
|
|||||||
dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
|
dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
|
||||||
sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
|
sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
|
||||||
svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
|
svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
|
||||||
expected_incoming_rates = expected_abs_svd = [75.0, 125.0, 75.0, 125.0]
|
expected_incoming_rates = expected_abs_svd = [100.0, 100.0, 100.0, 100.0]
|
||||||
|
|
||||||
self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
|
self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
|
||||||
for dn, _incoming_rate in zip(dns, expected_incoming_rates, strict=False):
|
for dn, _incoming_rate in zip(dns, expected_incoming_rates, strict=False):
|
||||||
|
|||||||
@@ -10,25 +10,14 @@ from pypika import functions as fn
|
|||||||
|
|
||||||
from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter
|
from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter
|
||||||
|
|
||||||
SLE_COUNT_LIMIT = 10_000
|
SLE_COUNT_LIMIT = 100_000
|
||||||
|
|
||||||
|
|
||||||
def _estimate_table_row_count(doctype: str):
|
|
||||||
table = get_table_name(doctype)
|
|
||||||
return cint(
|
|
||||||
frappe.db.sql(
|
|
||||||
f"""select table_rows
|
|
||||||
from information_schema.tables
|
|
||||||
where table_name = '{table}' ;"""
|
|
||||||
)[0][0]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = {}
|
filters = {}
|
||||||
|
|
||||||
sle_count = _estimate_table_row_count("Stock Ledger Entry")
|
sle_count = frappe.db.estimate_count("Stock Ledger Entry")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
sle_count > SLE_COUNT_LIMIT
|
sle_count > SLE_COUNT_LIMIT
|
||||||
|
|||||||
@@ -683,6 +683,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
return query.run(as_dict=True)
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
def prepare_batches(self):
|
def prepare_batches(self):
|
||||||
|
from erpnext.stock.utils import get_valuation_method
|
||||||
|
|
||||||
self.batches = self.batch_nos
|
self.batches = self.batch_nos
|
||||||
if isinstance(self.batch_nos, dict):
|
if isinstance(self.batch_nos, dict):
|
||||||
self.batches = list(self.batch_nos.keys())
|
self.batches = list(self.batch_nos.keys())
|
||||||
@@ -690,6 +692,10 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
self.batchwise_valuation_batches = []
|
self.batchwise_valuation_batches = []
|
||||||
self.non_batchwise_valuation_batches = []
|
self.non_batchwise_valuation_batches = []
|
||||||
|
|
||||||
|
if get_valuation_method(self.sle.item_code) == "Moving Average":
|
||||||
|
self.non_batchwise_valuation_batches = self.batches
|
||||||
|
return
|
||||||
|
|
||||||
batches = frappe.get_all(
|
batches = frappe.get_all(
|
||||||
"Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"]
|
"Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"]
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user