mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-23 15:09:20 +00:00
Merge branch 'develop' into fix-no-account-in-gl-entry
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
"show_payment_schedule_in_print",
|
"show_payment_schedule_in_print",
|
||||||
"item_price_settings_section",
|
"item_price_settings_section",
|
||||||
"maintain_same_internal_transaction_rate",
|
"maintain_same_internal_transaction_rate",
|
||||||
|
"fetch_valuation_rate_for_internal_transaction",
|
||||||
"column_break_feyo",
|
"column_break_feyo",
|
||||||
"maintain_same_rate_action",
|
"maintain_same_rate_action",
|
||||||
"role_to_override_stop_action",
|
"role_to_override_stop_action",
|
||||||
@@ -644,6 +645,12 @@
|
|||||||
"fieldname": "drop_ar_procedures",
|
"fieldname": "drop_ar_procedures",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Drop Procedures"
|
"label": "Drop Procedures"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "fetch_valuation_rate_for_internal_transaction",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Fetch Valuation Rate for Internal Transaction"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
@@ -652,7 +659,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-23 15:55:33.346398",
|
"modified": "2025-07-18 13:56:47.192437",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class AccountsSettings(Document):
|
|||||||
enable_immutable_ledger: DF.Check
|
enable_immutable_ledger: DF.Check
|
||||||
enable_party_matching: DF.Check
|
enable_party_matching: DF.Check
|
||||||
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
||||||
|
fetch_valuation_rate_for_internal_transaction: DF.Check
|
||||||
frozen_accounts_modifier: DF.Link | None
|
frozen_accounts_modifier: DF.Link | None
|
||||||
general_ledger_remarks_length: DF.Int
|
general_ledger_remarks_length: DF.Int
|
||||||
ignore_account_closing_balance: DF.Check
|
ignore_account_closing_balance: DF.Check
|
||||||
|
|||||||
@@ -462,4 +462,9 @@ def rename_temporarily_named_docs(doctype):
|
|||||||
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0, modified = %s where name = %s",
|
||||||
(newname, now(), oldname),
|
(newname, now(), oldname),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for hook_type in ("on_gle_rename", "on_sle_rename"):
|
||||||
|
for hook in frappe.get_hooks(hook_type):
|
||||||
|
frappe.call(hook, newname=newname, oldname=oldname)
|
||||||
|
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|||||||
@@ -1145,9 +1145,7 @@ class JournalEntry(AccountsController):
|
|||||||
|
|
||||||
def set_print_format_fields(self):
|
def set_print_format_fields(self):
|
||||||
bank_amount = party_amount = total_amount = 0.0
|
bank_amount = party_amount = total_amount = 0.0
|
||||||
currency = (
|
currency = bank_account_currency = party_account_currency = pay_to_recd_from = None
|
||||||
bank_account_currency
|
|
||||||
) = party_account_currency = pay_to_recd_from = self.pay_to_recd_from = None
|
|
||||||
party_type = None
|
party_type = None
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if d.party_type in ["Customer", "Supplier"] and d.party:
|
if d.party_type in ["Customer", "Supplier"] and d.party:
|
||||||
|
|||||||
@@ -579,6 +579,18 @@ class TestJournalEntry(IntegrationTestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
|
||||||
|
def test_pay_to_recd_from(self):
|
||||||
|
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||||
|
jv.pay_to_recd_from = "_Test Receiver"
|
||||||
|
jv.save()
|
||||||
|
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver")
|
||||||
|
|
||||||
|
jv.pay_to_recd_from = "_Test Receiver 2"
|
||||||
|
jv.save()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
|
||||||
|
|
||||||
|
|
||||||
def make_journal_entry(
|
def make_journal_entry(
|
||||||
account1,
|
account1,
|
||||||
|
|||||||
@@ -30,24 +30,6 @@
|
|||||||
"project",
|
"project",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"customer_po_details",
|
|
||||||
"po_no",
|
|
||||||
"column_break_23",
|
|
||||||
"po_date",
|
|
||||||
"address_and_contact",
|
|
||||||
"customer_address",
|
|
||||||
"address_display",
|
|
||||||
"contact_person",
|
|
||||||
"contact_display",
|
|
||||||
"contact_mobile",
|
|
||||||
"contact_email",
|
|
||||||
"territory",
|
|
||||||
"col_break4",
|
|
||||||
"shipping_address_name",
|
|
||||||
"shipping_address",
|
|
||||||
"company_address",
|
|
||||||
"company_address_display",
|
|
||||||
"company_contact_person",
|
|
||||||
"currency_and_price_list",
|
"currency_and_price_list",
|
||||||
"currency",
|
"currency",
|
||||||
"conversion_rate",
|
"conversion_rate",
|
||||||
@@ -91,14 +73,6 @@
|
|||||||
"base_total_taxes_and_charges",
|
"base_total_taxes_and_charges",
|
||||||
"column_break_47",
|
"column_break_47",
|
||||||
"total_taxes_and_charges",
|
"total_taxes_and_charges",
|
||||||
"loyalty_points_redemption",
|
|
||||||
"loyalty_points",
|
|
||||||
"loyalty_amount",
|
|
||||||
"redeem_loyalty_points",
|
|
||||||
"column_break_77",
|
|
||||||
"loyalty_program",
|
|
||||||
"loyalty_redemption_account",
|
|
||||||
"loyalty_redemption_cost_center",
|
|
||||||
"section_break_49",
|
"section_break_49",
|
||||||
"coupon_code",
|
"coupon_code",
|
||||||
"apply_discount_on",
|
"apply_discount_on",
|
||||||
@@ -118,13 +92,7 @@
|
|||||||
"in_words",
|
"in_words",
|
||||||
"total_advance",
|
"total_advance",
|
||||||
"outstanding_amount",
|
"outstanding_amount",
|
||||||
"advances_section",
|
"payments_tab",
|
||||||
"allocate_advances_automatically",
|
|
||||||
"get_advances",
|
|
||||||
"advances",
|
|
||||||
"payment_schedule_section",
|
|
||||||
"payment_terms_template",
|
|
||||||
"payment_schedule",
|
|
||||||
"payments_section",
|
"payments_section",
|
||||||
"cash_bank_account",
|
"cash_bank_account",
|
||||||
"payments",
|
"payments",
|
||||||
@@ -137,6 +105,10 @@
|
|||||||
"column_break_90",
|
"column_break_90",
|
||||||
"change_amount",
|
"change_amount",
|
||||||
"account_for_change_amount",
|
"account_for_change_amount",
|
||||||
|
"advances_section",
|
||||||
|
"allocate_advances_automatically",
|
||||||
|
"get_advances",
|
||||||
|
"advances",
|
||||||
"column_break4",
|
"column_break4",
|
||||||
"write_off_amount",
|
"write_off_amount",
|
||||||
"base_write_off_amount",
|
"base_write_off_amount",
|
||||||
@@ -144,9 +116,41 @@
|
|||||||
"column_break_74",
|
"column_break_74",
|
||||||
"write_off_account",
|
"write_off_account",
|
||||||
"write_off_cost_center",
|
"write_off_cost_center",
|
||||||
|
"loyalty_points_redemption",
|
||||||
|
"loyalty_points",
|
||||||
|
"loyalty_amount",
|
||||||
|
"redeem_loyalty_points",
|
||||||
|
"column_break_77",
|
||||||
|
"loyalty_program",
|
||||||
|
"loyalty_redemption_account",
|
||||||
|
"loyalty_redemption_cost_center",
|
||||||
|
"contact_and_address_tab",
|
||||||
|
"address_and_contact",
|
||||||
|
"customer_address",
|
||||||
|
"address_display",
|
||||||
|
"contact_person",
|
||||||
|
"contact_display",
|
||||||
|
"contact_mobile",
|
||||||
|
"contact_email",
|
||||||
|
"territory",
|
||||||
|
"col_break4",
|
||||||
|
"shipping_address_name",
|
||||||
|
"shipping_address",
|
||||||
|
"company_address",
|
||||||
|
"company_address_display",
|
||||||
|
"company_contact_person",
|
||||||
|
"terms_tab",
|
||||||
|
"payment_schedule_section",
|
||||||
|
"payment_terms_template",
|
||||||
|
"payment_schedule",
|
||||||
"terms_section_break",
|
"terms_section_break",
|
||||||
"tc_name",
|
"tc_name",
|
||||||
"terms",
|
"terms",
|
||||||
|
"more_info_tab",
|
||||||
|
"customer_po_details",
|
||||||
|
"po_no",
|
||||||
|
"column_break_23",
|
||||||
|
"po_date",
|
||||||
"edit_printing_settings",
|
"edit_printing_settings",
|
||||||
"letter_head",
|
"letter_head",
|
||||||
"group_same_items",
|
"group_same_items",
|
||||||
@@ -398,7 +402,6 @@
|
|||||||
"label": "Customer's Purchase Order Date"
|
"label": "Customer's Purchase Order Date"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
|
||||||
"fieldname": "address_and_contact",
|
"fieldname": "address_and_contact",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Address and Contact"
|
"label": "Address and Contact"
|
||||||
@@ -1050,7 +1053,6 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
|
||||||
"collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)",
|
"collapsible_depends_on": "eval:(!doc.is_pos && !doc.is_return)",
|
||||||
"fieldname": "payment_schedule_section",
|
"fieldname": "payment_schedule_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -1130,8 +1132,10 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"collapsible": 1,
|
||||||
"fieldname": "section_break_88",
|
"fieldname": "section_break_88",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Changes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "is_pos",
|
"depends_on": "is_pos",
|
||||||
@@ -1218,7 +1222,6 @@
|
|||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
|
||||||
"collapsible_depends_on": "terms",
|
"collapsible_depends_on": "terms",
|
||||||
"fieldname": "terms_section_break",
|
"fieldname": "terms_section_break",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
@@ -1570,12 +1573,32 @@
|
|||||||
"label": "Company Contact Person",
|
"label": "Company Contact Person",
|
||||||
"options": "Contact",
|
"options": "Contact",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payments_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Payments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "contact_and_address_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Address & Contact"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "terms_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Terms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "more_info_tab",
|
||||||
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "More Info"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-07-17 16:51:40.886083",
|
"modified": "2025-07-18 16:50:30.516162",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_ac
|
|||||||
"finance_book": cstr(filters.get("finance_book")),
|
"finance_book": cstr(filters.get("finance_book")),
|
||||||
}
|
}
|
||||||
|
|
||||||
gl_filters["dimensions"] = set(dimension_list)
|
gl_filters["dimensions"] = tuple(set(dimension_list))
|
||||||
|
|
||||||
if filters.get("include_default_book_entries"):
|
if filters.get("include_default_book_entries"):
|
||||||
gl_filters["company_fb"] = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
gl_filters["company_fb"] = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||||
@@ -179,7 +179,7 @@ def accumulate_values_into_parents(accounts, accounts_by_name, dimension_list):
|
|||||||
def get_condition(dimension):
|
def get_condition(dimension):
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
conditions.append(f"{frappe.scrub(dimension)} in (%(dimensions)s)")
|
conditions.append(f"{frappe.scrub(dimension)} in %(dimensions)s")
|
||||||
|
|
||||||
return " and {}".format(" and ".join(conditions)) if conditions else ""
|
return " and {}".format(" and ".join(conditions)) if conditions else ""
|
||||||
|
|
||||||
|
|||||||
@@ -210,7 +210,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if filters.get("presentation_currency"):
|
if filters.get("presentation_currency"):
|
||||||
return convert_to_presentation_currency(gl_entries, currency_map)
|
return convert_to_presentation_currency(gl_entries, currency_map, filters)
|
||||||
else:
|
else:
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
"fieldname": "invoice",
|
"fieldname": "invoice",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Purchase Invoice",
|
"options": "Purchase Invoice",
|
||||||
"width": 120,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||||
]
|
]
|
||||||
@@ -310,8 +310,8 @@ def apply_conditions(query, pi, pii, filters):
|
|||||||
|
|
||||||
def get_items(filters, additional_table_columns):
|
def get_items(filters, additional_table_columns):
|
||||||
doctype = "Purchase Invoice"
|
doctype = "Purchase Invoice"
|
||||||
pi = frappe.qb.DocType(doctype)
|
pi = frappe.qb.DocType(doctype).as_("invoice")
|
||||||
pii = frappe.qb.DocType(f"{doctype} Item")
|
pii = frappe.qb.DocType(f"{doctype} Item").as_("invoice_item")
|
||||||
Item = frappe.qb.DocType("Item")
|
Item = frappe.qb.DocType("Item")
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(pi)
|
frappe.qb.from_(pi)
|
||||||
@@ -331,6 +331,7 @@ def get_items(filters, additional_table_columns):
|
|||||||
pi.unrealized_profit_loss_account,
|
pi.unrealized_profit_loss_account,
|
||||||
pii.item_code,
|
pii.item_code,
|
||||||
pii.description,
|
pii.description,
|
||||||
|
pii.item_name,
|
||||||
pii.item_group,
|
pii.item_group,
|
||||||
pii.item_name.as_("pi_item_name"),
|
pii.item_name.as_("pi_item_name"),
|
||||||
pii.item_group.as_("pi_item_group"),
|
pii.item_group.as_("pi_item_group"),
|
||||||
@@ -374,7 +375,7 @@ def get_items(filters, additional_table_columns):
|
|||||||
if match_conditions:
|
if match_conditions:
|
||||||
query += " and " + match_conditions
|
query += " and " + match_conditions
|
||||||
|
|
||||||
query = apply_order_by_conditions(query, pi, pii, filters)
|
query = apply_order_by_conditions(query, filters)
|
||||||
|
|
||||||
return frappe.db.sql(query, params, as_dict=True)
|
return frappe.db.sql(query, params, as_dict=True)
|
||||||
|
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
"fieldname": "invoice",
|
"fieldname": "invoice",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Sales Invoice",
|
"options": "Sales Invoice",
|
||||||
"width": 120,
|
"width": 150,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||||
]
|
]
|
||||||
@@ -395,15 +395,15 @@ def apply_conditions(query, si, sii, sip, filters, additional_conditions=None):
|
|||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
def apply_order_by_conditions(query, si, ii, filters):
|
def apply_order_by_conditions(query, filters):
|
||||||
if not filters.get("group_by"):
|
if not filters.get("group_by"):
|
||||||
query += f" order by {si.posting_date} desc, {ii.item_group} desc"
|
query += "order by invoice.posting_date desc, invoice_item.item_group desc"
|
||||||
elif filters.get("group_by") == "Invoice":
|
elif filters.get("group_by") == "Invoice":
|
||||||
query += f" order by {ii.parent} desc"
|
query += "order by invoice_item.parent desc"
|
||||||
elif filters.get("group_by") == "Item":
|
elif filters.get("group_by") == "Item":
|
||||||
query += f" order by {ii.item_code}"
|
query += "order by invoice_item.item_code"
|
||||||
elif filters.get("group_by") == "Item Group":
|
elif filters.get("group_by") == "Item Group":
|
||||||
query += f" order by {ii.item_group}"
|
query += "order by invoice_item.item_group"
|
||||||
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
|
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
|
||||||
filter_field = frappe.scrub(filters.get("group_by"))
|
filter_field = frappe.scrub(filters.get("group_by"))
|
||||||
query += f" order by {filter_field} desc"
|
query += f" order by {filter_field} desc"
|
||||||
@@ -413,9 +413,9 @@ def apply_order_by_conditions(query, si, ii, filters):
|
|||||||
|
|
||||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||||
doctype = "Sales Invoice"
|
doctype = "Sales Invoice"
|
||||||
si = frappe.qb.DocType(doctype)
|
si = frappe.qb.DocType("Sales Invoice").as_("invoice")
|
||||||
sip = frappe.qb.DocType(f"{doctype} Payment")
|
sii = frappe.qb.DocType("Sales Invoice Item").as_("invoice_item")
|
||||||
sii = frappe.qb.DocType(f"{doctype} Item")
|
sip = frappe.qb.DocType("Sales Invoice Payment")
|
||||||
item = frappe.qb.DocType("Item")
|
item = frappe.qb.DocType("Item")
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
@@ -493,7 +493,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
|||||||
if match_conditions:
|
if match_conditions:
|
||||||
query += " and " + match_conditions
|
query += " and " + match_conditions
|
||||||
|
|
||||||
query = apply_order_by_conditions(query, si, sii, filters)
|
query = apply_order_by_conditions(query, filters)
|
||||||
|
|
||||||
return frappe.db.sql(query, params, as_dict=True)
|
return frappe.db.sql(query, params, as_dict=True)
|
||||||
|
|
||||||
@@ -763,25 +763,13 @@ def add_total_row(
|
|||||||
def get_display_value(filters, group_by_field, item):
|
def get_display_value(filters, group_by_field, item):
|
||||||
if filters.get("group_by") == "Item":
|
if filters.get("group_by") == "Item":
|
||||||
if item.get("item_code") != item.get("item_name"):
|
if item.get("item_code") != item.get("item_name"):
|
||||||
value = (
|
value = f"{item.get('item_code')}: {item.get('item_name')}"
|
||||||
cstr(item.get("item_code"))
|
|
||||||
+ "<br><br>"
|
|
||||||
+ "<span style='font-weight: normal'>"
|
|
||||||
+ cstr(item.get("item_name"))
|
|
||||||
+ "</span>"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
value = item.get("item_code", "")
|
value = item.get("item_code", "")
|
||||||
elif filters.get("group_by") in ("Customer", "Supplier"):
|
elif filters.get("group_by") in ("Customer", "Supplier"):
|
||||||
party = frappe.scrub(filters.get("group_by"))
|
party = frappe.scrub(filters.get("group_by"))
|
||||||
if item.get(party) != item.get(party + "_name"):
|
if item.get(party) != item.get(party + "_name"):
|
||||||
value = (
|
value = f"{item.get(party)}: {item.get(party + '_name')}"
|
||||||
item.get(party)
|
|
||||||
+ "<br><br>"
|
|
||||||
+ "<span style='font-weight: normal'>"
|
|
||||||
+ item.get(party + "_name")
|
|
||||||
+ "</span>"
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
value = item.get(party)
|
value = item.get(party)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ def get_rate_as_at(date, from_currency, to_currency):
|
|||||||
return rate
|
return rate
|
||||||
|
|
||||||
|
|
||||||
def convert_to_presentation_currency(gl_entries, currency_info):
|
def convert_to_presentation_currency(gl_entries, currency_info, filters=None):
|
||||||
"""
|
"""
|
||||||
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
||||||
in `currency_info`.
|
in `currency_info`.
|
||||||
@@ -99,6 +99,13 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
|||||||
company_currency = currency_info["company_currency"]
|
company_currency = currency_info["company_currency"]
|
||||||
|
|
||||||
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
|
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
|
||||||
|
exchange_gain_or_loss = False
|
||||||
|
|
||||||
|
if filters and isinstance(filters.get("account"), list):
|
||||||
|
account_filter = filters.get("account")
|
||||||
|
gain_loss_account = frappe.db.get_value("Company", filters.company, "exchange_gain_loss_account")
|
||||||
|
|
||||||
|
exchange_gain_or_loss = len(account_filter) == 1 and account_filter[0] == gain_loss_account
|
||||||
|
|
||||||
for entry in gl_entries:
|
for entry in gl_entries:
|
||||||
debit = flt(entry["debit"])
|
debit = flt(entry["debit"])
|
||||||
@@ -107,7 +114,11 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
|||||||
credit_in_account_currency = flt(entry["credit_in_account_currency"])
|
credit_in_account_currency = flt(entry["credit_in_account_currency"])
|
||||||
account_currency = entry["account_currency"]
|
account_currency = entry["account_currency"]
|
||||||
|
|
||||||
if len(account_currencies) == 1 and account_currency == presentation_currency:
|
if (
|
||||||
|
len(account_currencies) == 1
|
||||||
|
and account_currency == presentation_currency
|
||||||
|
and not exchange_gain_or_loss
|
||||||
|
):
|
||||||
entry["debit"] = debit_in_account_currency
|
entry["debit"] = debit_in_account_currency
|
||||||
entry["credit"] = credit_in_account_currency
|
entry["credit"] = credit_in_account_currency
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -522,7 +522,8 @@ def reconcile_against_document(
|
|||||||
skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
|
skip_ref_details_update_for_pe=skip_ref_details_update_for_pe,
|
||||||
dimensions_dict=dimensions_dict,
|
dimensions_dict=dimensions_dict,
|
||||||
)
|
)
|
||||||
|
if referenced_row.get("outstanding_amount"):
|
||||||
|
referenced_row.outstanding_amount -= flt(entry.allocated_amount)
|
||||||
doc.save(ignore_permissions=True)
|
doc.save(ignore_permissions=True)
|
||||||
# re-submit advance entry
|
# re-submit advance entry
|
||||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
||||||
|
|||||||
@@ -659,7 +659,7 @@ class TestAssetDepreciationSchedule(IntegrationTestCase):
|
|||||||
|
|
||||||
self.assertEqual(schedules, expected_depreciation_before_repair)
|
self.assertEqual(schedules, expected_depreciation_before_repair)
|
||||||
|
|
||||||
def test_daily_prorata_based_depreciation_schedule_after_cancelling_asset_repair_for(self):
|
def test_daily_prorata_based_depreciation_schedule_after_cancelling_asset_repair(self):
|
||||||
asset = create_asset(
|
asset = create_asset(
|
||||||
item_code="Macbook Pro",
|
item_code="Macbook Pro",
|
||||||
gross_purchase_amount=500,
|
gross_purchase_amount=500,
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
"fieldname": "completion_date",
|
"fieldname": "completion_date",
|
||||||
"fieldtype": "Datetime",
|
"fieldtype": "Datetime",
|
||||||
"label": "Completion Date",
|
"label": "Completion Date",
|
||||||
|
"mandatory_depends_on": "eval:doc.repair_status==\"Completed\"",
|
||||||
"no_copy": 1
|
"no_copy": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -260,7 +261,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-29 22:30:00.589597",
|
"modified": "2025-07-18 15:59:53.981224",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Repair",
|
"name": "Asset Repair",
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class AssetRepair(AccountsController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
if self.completion_date and (self.failure_date > self.completion_date):
|
if self.completion_date and (getdate(self.failure_date) > getdate(self.completion_date)):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Completion Date can not be before Failure Date. Please adjust the dates accordingly.")
|
_("Completion Date can not be before Failure Date. Please adjust the dates accordingly.")
|
||||||
)
|
)
|
||||||
@@ -292,7 +292,7 @@ class AssetRepair(AccountsController):
|
|||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"posting_date": getdate(),
|
"posting_date": self.completion_date,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
@@ -309,7 +309,7 @@ class AssetRepair(AccountsController):
|
|||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"posting_date": getdate(),
|
"posting_date": self.completion_date,
|
||||||
"against_voucher_type": "Purchase Invoice",
|
"against_voucher_type": "Purchase Invoice",
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
},
|
},
|
||||||
@@ -344,7 +344,7 @@ class AssetRepair(AccountsController):
|
|||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"posting_date": getdate(),
|
"posting_date": self.completion_date,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
@@ -361,7 +361,7 @@ class AssetRepair(AccountsController):
|
|||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"posting_date": getdate(),
|
"posting_date": self.completion_date,
|
||||||
"against_voucher_type": "Stock Entry",
|
"against_voucher_type": "Stock Entry",
|
||||||
"against_voucher": stock_entry.name,
|
"against_voucher": stock_entry.name,
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests import IntegrationTestCase
|
from frappe.tests import IntegrationTestCase
|
||||||
from frappe.utils import add_months, flt, get_first_day, nowdate, nowtime, today
|
from frappe.utils import add_days, add_months, flt, get_first_day, nowdate, nowtime, today
|
||||||
|
|
||||||
from erpnext.assets.doctype.asset.asset import (
|
from erpnext.assets.doctype.asset.asset import (
|
||||||
get_asset_account,
|
get_asset_account,
|
||||||
@@ -388,6 +388,7 @@ def create_asset_repair(**args):
|
|||||||
|
|
||||||
if args.submit:
|
if args.submit:
|
||||||
asset_repair.repair_status = "Completed"
|
asset_repair.repair_status = "Completed"
|
||||||
|
asset_repair.completion_date = add_days(args.failure_date, 1)
|
||||||
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
|
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
|
||||||
|
|
||||||
if args.stock_consumption:
|
if args.stock_consumption:
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ class BuyingController(SubcontractingController):
|
|||||||
tax_accounts, total_valuation_amount, total_actual_tax_amount = self.get_tax_details()
|
tax_accounts, total_valuation_amount, total_actual_tax_amount = self.get_tax_details()
|
||||||
|
|
||||||
for i, item in enumerate(self.get("items")):
|
for i, item in enumerate(self.get("items")):
|
||||||
if item.item_code and item.qty:
|
if item.item_code and (item.qty or item.get("rejected_qty")):
|
||||||
item_tax_amount, actual_tax_amount = 0.0, 0.0
|
item_tax_amount, actual_tax_amount = 0.0, 0.0
|
||||||
if i == (last_item_idx - 1):
|
if i == (last_item_idx - 1):
|
||||||
item_tax_amount = total_valuation_amount
|
item_tax_amount = total_valuation_amount
|
||||||
@@ -387,7 +387,19 @@ class BuyingController(SubcontractingController):
|
|||||||
if item.sales_incoming_rate: # for internal transfer
|
if item.sales_incoming_rate: # for internal transfer
|
||||||
net_rate = item.qty * item.sales_incoming_rate
|
net_rate = item.qty * item.sales_incoming_rate
|
||||||
|
|
||||||
|
if (
|
||||||
|
not net_rate
|
||||||
|
and item.get("rejected_qty")
|
||||||
|
and frappe.get_single_value(
|
||||||
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
|
)
|
||||||
|
):
|
||||||
|
net_rate = item.rejected_qty * item.net_rate
|
||||||
|
|
||||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||||
|
if not qty_in_stock_uom and item.get("rejected_qty"):
|
||||||
|
qty_in_stock_uom = flt(item.rejected_qty * item.conversion_factor)
|
||||||
|
|
||||||
if self.get("is_old_subcontracting_flow"):
|
if self.get("is_old_subcontracting_flow"):
|
||||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||||
item.valuation_rate = (
|
item.valuation_rate = (
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1132
erpnext/locale/fa.po
1132
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
95902
erpnext/locale/id.po
95902
erpnext/locale/id.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10353
erpnext/locale/zh.po
10353
erpnext/locale/zh.po
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,16 @@ frappe.ui.form.on("Job Card", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.events.set_company_filters(frm, "source_warehouse");
|
||||||
|
frm.events.set_company_filters(frm, "wip_warehouse");
|
||||||
|
frm.set_query("source_warehouse", "items", () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_indicator_formatter("sub_operation", function (doc) {
|
frm.set_indicator_formatter("sub_operation", function (doc) {
|
||||||
if (doc.status == "Pending") {
|
if (doc.status == "Pending") {
|
||||||
return "red";
|
return "red";
|
||||||
@@ -32,6 +42,16 @@ frappe.ui.form.on("Job Card", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
set_company_filters(frm, fieldname) {
|
||||||
|
frm.set_query(fieldname, () => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: frm.doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
make_fields_read_only(frm) {
|
make_fields_read_only(frm) {
|
||||||
if (frm.doc.docstatus === 1) {
|
if (frm.doc.docstatus === 1) {
|
||||||
frm.set_df_property("employee", "read_only", 1);
|
frm.set_df_property("employee", "read_only", 1);
|
||||||
|
|||||||
@@ -47,6 +47,14 @@ frappe.ui.form.on("Production Plan", {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frm.set_query("sub_assembly_warehouse", function (doc) {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: doc.company,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("material_request", "material_requests", function () {
|
frm.set_query("material_request", "material_requests", function () {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -2965,56 +2965,45 @@ class TestWorkOrder(IntegrationTestCase):
|
|||||||
fg_warehouse="_Test Warehouse 2 - _TC",
|
fg_warehouse="_Test Warehouse 2 - _TC",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initial check
|
|
||||||
self.assertEqual(wo.operations[0].operation, "Test Operation A")
|
|
||||||
self.assertEqual(wo.operations[1].operation, "Test Operation B")
|
|
||||||
self.assertEqual(wo.operations[2].operation, "Test Operation C")
|
|
||||||
self.assertEqual(wo.operations[3].operation, "Test Operation D")
|
|
||||||
|
|
||||||
wo = frappe.copy_doc(wo)
|
wo = frappe.copy_doc(wo)
|
||||||
wo.operations[3].sequence_id = 2
|
wo.operations[3].sequence_id = None
|
||||||
|
|
||||||
|
# Test 1 : If any one operation does not have sequence ID then error will be thrown
|
||||||
|
self.assertRaises(frappe.ValidationError, wo.submit)
|
||||||
|
|
||||||
|
for op in wo.operations:
|
||||||
|
op.sequence_id = None
|
||||||
wo.submit()
|
wo.submit()
|
||||||
|
|
||||||
# Test 2 : Sort line items in child table based on sequence ID
|
# Test 2 : If none of the operations have sequence ID then they will be sequenced as per their idx
|
||||||
self.assertEqual(wo.operations[0].operation, "Test Operation A")
|
for op in wo.operations:
|
||||||
self.assertEqual(wo.operations[1].operation, "Test Operation B")
|
self.assertEqual(op.sequence_id, op.idx)
|
||||||
self.assertEqual(wo.operations[2].operation, "Test Operation D")
|
|
||||||
self.assertEqual(wo.operations[3].operation, "Test Operation C")
|
|
||||||
|
|
||||||
wo = frappe.copy_doc(wo)
|
wo = frappe.copy_doc(wo)
|
||||||
wo.operations[3].sequence_id = 1
|
wo.operations[0].sequence_id = 2
|
||||||
wo.submit()
|
|
||||||
|
|
||||||
self.assertEqual(wo.operations[0].operation, "Test Operation A")
|
# Test 3 : Sequence IDs should not miss the correct sequence of numbers
|
||||||
self.assertEqual(wo.operations[1].operation, "Test Operation C")
|
self.assertRaises(frappe.ValidationError, wo.submit)
|
||||||
self.assertEqual(wo.operations[2].operation, "Test Operation B")
|
|
||||||
self.assertEqual(wo.operations[3].operation, "Test Operation D")
|
|
||||||
|
|
||||||
wo = frappe.copy_doc(wo)
|
wo.operations[1].sequence_id = 1
|
||||||
wo.operations[0].sequence_id = 3
|
|
||||||
wo.submit()
|
|
||||||
|
|
||||||
self.assertEqual(wo.operations[0].operation, "Test Operation C")
|
# Test 4 : Sequence IDs should be in the correct ascending order
|
||||||
self.assertEqual(wo.operations[1].operation, "Test Operation B")
|
|
||||||
self.assertEqual(wo.operations[2].operation, "Test Operation D")
|
|
||||||
self.assertEqual(wo.operations[3].operation, "Test Operation A")
|
|
||||||
|
|
||||||
wo = frappe.copy_doc(wo)
|
|
||||||
wo.operations[1].sequence_id = 0
|
|
||||||
|
|
||||||
# Test 3 - Error should be thrown if any one operation does not have sequence id but others do
|
|
||||||
self.assertRaises(frappe.ValidationError, wo.submit)
|
self.assertRaises(frappe.ValidationError, wo.submit)
|
||||||
|
|
||||||
workstation = frappe.get_doc("Workstation", "Test Workstation A")
|
workstation = frappe.get_doc("Workstation", "Test Workstation A")
|
||||||
workstation.production_capacity = 4
|
workstation.production_capacity = 4
|
||||||
workstation.save()
|
workstation.save()
|
||||||
|
|
||||||
wo = frappe.copy_doc(wo)
|
wo = frappe.copy_doc(wo)
|
||||||
|
wo.operations[0].sequence_id = 1
|
||||||
wo.operations[1].sequence_id = 2
|
wo.operations[1].sequence_id = 2
|
||||||
|
wo.operations[2].sequence_id = 2
|
||||||
|
wo.operations[3].sequence_id = 3
|
||||||
wo.submit()
|
wo.submit()
|
||||||
|
|
||||||
# Test 4 - If Sequence ID is same then planned start time for both operations should be same
|
# Test 5 : If two operations have the same sequence ID then the next operation will start 10 mins after the longest previous operation ends
|
||||||
self.assertEqual(wo.operations[1].planned_start_time, wo.operations[2].planned_start_time)
|
self.assertEqual(
|
||||||
|
wo.operations[3].planned_start_time, add_to_date(wo.operations[1].planned_end_time, minutes=10)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse):
|
def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse):
|
||||||
|
|||||||
@@ -349,13 +349,18 @@ frappe.ui.form.on("Work Order", {
|
|||||||
return operations_data;
|
return operations_data;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
function (data) {
|
function () {
|
||||||
|
const selected_rows = dialog.fields_dict["operations"].grid.get_selected_children();
|
||||||
|
if (selected_rows.length == 0) {
|
||||||
|
frappe.msgprint(__("Please select atleast one operation to create Job Card"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
|
method: "erpnext.manufacturing.doctype.work_order.work_order.make_job_card",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
args: {
|
args: {
|
||||||
work_order: frm.doc.name,
|
work_order: frm.doc.name,
|
||||||
operations: data.operations,
|
operations: selected_rows,
|
||||||
},
|
},
|
||||||
callback: function () {
|
callback: function () {
|
||||||
frm.reload_doc();
|
frm.reload_doc();
|
||||||
@@ -366,7 +371,7 @@ frappe.ui.form.on("Work Order", {
|
|||||||
__("Create")
|
__("Create")
|
||||||
);
|
);
|
||||||
|
|
||||||
dialog.fields_dict["operations"].grid.wrapper.find(".grid-add-row").hide();
|
dialog.fields_dict["operations"].grid.grid_buttons.hide();
|
||||||
|
|
||||||
var pending_qty = 0;
|
var pending_qty = 0;
|
||||||
frm.doc.operations.forEach((data) => {
|
frm.doc.operations.forEach((data) => {
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
self.set_required_items(reset_only_qty=len(self.get("required_items")))
|
self.set_required_items(reset_only_qty=len(self.get("required_items")))
|
||||||
self.enable_auto_reserve_stock()
|
self.enable_auto_reserve_stock()
|
||||||
|
self.validate_operations_sequence()
|
||||||
|
|
||||||
def validate_dates(self):
|
def validate_dates(self):
|
||||||
if self.actual_start_date and self.actual_end_date:
|
if self.actual_start_date and self.actual_end_date:
|
||||||
@@ -229,6 +230,30 @@ class WorkOrder(Document):
|
|||||||
if self.is_new() and frappe.db.get_single_value("Stock Settings", "auto_reserve_stock"):
|
if self.is_new() and frappe.db.get_single_value("Stock Settings", "auto_reserve_stock"):
|
||||||
self.reserve_stock = 1
|
self.reserve_stock = 1
|
||||||
|
|
||||||
|
def validate_operations_sequence(self):
|
||||||
|
if all([not op.sequence_id for op in self.operations]):
|
||||||
|
for op in self.operations:
|
||||||
|
op.sequence_id = op.idx
|
||||||
|
else:
|
||||||
|
sequence_id = 1
|
||||||
|
for op in self.operations:
|
||||||
|
if op.idx == 1 and op.sequence_id != 1:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #1: Sequence ID must be 1 for Operation {0}.").format(
|
||||||
|
frappe.bold(op.operation)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif op.sequence_id != sequence_id and op.sequence_id != sequence_id + 1:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{0}: Sequence ID must be {1} or {2} for Operation {3}.").format(
|
||||||
|
op.idx,
|
||||||
|
frappe.bold(sequence_id),
|
||||||
|
frappe.bold(sequence_id + 1),
|
||||||
|
frappe.bold(op.operation),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
sequence_id = op.sequence_id
|
||||||
|
|
||||||
def set_warehouses(self):
|
def set_warehouses(self):
|
||||||
for row in self.required_items:
|
for row in self.required_items:
|
||||||
if not row.source_warehouse:
|
if not row.source_warehouse:
|
||||||
@@ -726,17 +751,6 @@ class WorkOrder(Document):
|
|||||||
enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
|
enable_capacity_planning = not cint(manufacturing_settings_doc.disable_capacity_planning)
|
||||||
plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
|
plan_days = cint(manufacturing_settings_doc.capacity_planning_for_days) or 30
|
||||||
|
|
||||||
if all([op.sequence_id for op in self.operations]):
|
|
||||||
self.operations = sorted(self.operations, key=lambda op: op.sequence_id)
|
|
||||||
for idx, op in enumerate(self.operations):
|
|
||||||
op.idx = idx + 1
|
|
||||||
elif any([op.sequence_id for op in self.operations]):
|
|
||||||
frappe.throw(
|
|
||||||
_(
|
|
||||||
"Row #{0}: Incorrect Sequence ID. If any single operation has a Sequence ID then all other operations must have one too."
|
|
||||||
).format(next((op.idx for op in self.operations if not op.sequence_id), None))
|
|
||||||
)
|
|
||||||
|
|
||||||
for idx, row in enumerate(self.operations):
|
for idx, row in enumerate(self.operations):
|
||||||
qty = self.qty
|
qty = self.qty
|
||||||
while qty > 0:
|
while qty > 0:
|
||||||
|
|||||||
@@ -202,6 +202,7 @@
|
|||||||
"fieldname": "sequence_id",
|
"fieldname": "sequence_id",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Sequence ID",
|
"label": "Sequence ID",
|
||||||
|
"non_negative": 1,
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -299,7 +300,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-04-09 16:21:47.110564",
|
"modified": "2025-05-15 15:10:06.885440",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order Operation",
|
"name": "Work Order Operation",
|
||||||
|
|||||||
@@ -427,4 +427,5 @@ erpnext.patches.v15_0.set_status_cancelled_on_cancelled_pos_opening_entry_and_po
|
|||||||
erpnext.patches.v15_0.set_company_on_pos_inv_merge_log
|
erpnext.patches.v15_0.set_company_on_pos_inv_merge_log
|
||||||
erpnext.patches.v15_0.rename_price_list_to_buying_price_list
|
erpnext.patches.v15_0.rename_price_list_to_buying_price_list
|
||||||
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting
|
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting
|
||||||
|
erpnext.patches.v15_0.patch_missing_buying_price_list_in_material_request
|
||||||
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
|
erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import frappe
|
||||||
|
import frappe.defaults
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
if frappe.db.has_column("Material Request", "buying_price_list") and (
|
||||||
|
default_buying_price_list := frappe.defaults.get_defaults().buying_price_list
|
||||||
|
):
|
||||||
|
docs = frappe.get_all(
|
||||||
|
"Material Request", filters={"buying_price_list": ["is", "not set"], "docstatus": 1}, pluck="name"
|
||||||
|
)
|
||||||
|
for doc in docs:
|
||||||
|
frappe.db.set_value("Material Request", doc, "buying_price_list", default_buying_price_list)
|
||||||
@@ -787,7 +787,8 @@ class SalesOrder(SellingController):
|
|||||||
|
|
||||||
if self.delivery_date:
|
if self.delivery_date:
|
||||||
for item in self.items:
|
for item in self.items:
|
||||||
item.delivery_date = self.delivery_date
|
if not item.delivery_date:
|
||||||
|
item.delivery_date = self.delivery_date
|
||||||
|
|
||||||
|
|
||||||
def get_unreserved_qty(item: object, reserved_qty_details: dict) -> float:
|
def get_unreserved_qty(item: object, reserved_qty_details: dict) -> float:
|
||||||
|
|||||||
@@ -713,6 +713,10 @@ class PurchaseReceipt(BuyingController):
|
|||||||
warehouse_with_no_account = []
|
warehouse_with_no_account = []
|
||||||
|
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
|
remarks = self.get("remarks") or _("Accounting Entry for {0}").format(
|
||||||
|
"Asset" if d.is_fixed_asset else "Stock"
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
provisional_accounting_for_non_stock_items
|
provisional_accounting_for_non_stock_items
|
||||||
and d.item_code not in stock_items
|
and d.item_code not in stock_items
|
||||||
@@ -724,10 +728,6 @@ class PurchaseReceipt(BuyingController):
|
|||||||
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
d, gl_entries, self.posting_date, d.get("provisional_expense_account")
|
||||||
)
|
)
|
||||||
elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return):
|
elif flt(d.qty) and (flt(d.valuation_rate) or self.is_return):
|
||||||
remarks = self.get("remarks") or _("Accounting Entry for {0}").format(
|
|
||||||
"Asset" if d.is_fixed_asset else "Stock"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not (
|
if not (
|
||||||
(erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items)
|
(erpnext.is_perpetual_inventory_enabled(self.company) and d.item_code in stock_items)
|
||||||
or (d.is_fixed_asset and not d.purchase_invoice)
|
or (d.is_fixed_asset and not d.purchase_invoice)
|
||||||
@@ -772,7 +772,7 @@ class PurchaseReceipt(BuyingController):
|
|||||||
make_amount_difference_entry(d)
|
make_amount_difference_entry(d)
|
||||||
make_sub_contracting_gl_entries(d)
|
make_sub_contracting_gl_entries(d)
|
||||||
make_divisional_loss_gl_entry(d, outgoing_amount)
|
make_divisional_loss_gl_entry(d, outgoing_amount)
|
||||||
elif (d.warehouse and d.warehouse not in warehouse_with_no_account) or (
|
elif (d.warehouse and d.qty and d.warehouse not in warehouse_with_no_account) or (
|
||||||
not frappe.db.get_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials")
|
not frappe.db.get_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials")
|
||||||
and d.rejected_warehouse
|
and d.rejected_warehouse
|
||||||
and d.rejected_warehouse not in warehouse_with_no_account
|
and d.rejected_warehouse not in warehouse_with_no_account
|
||||||
@@ -785,10 +785,18 @@ class PurchaseReceipt(BuyingController):
|
|||||||
if d.rejected_qty and frappe.db.get_single_value(
|
if d.rejected_qty and frappe.db.get_single_value(
|
||||||
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
"Buying Settings", "set_valuation_rate_for_rejected_materials"
|
||||||
):
|
):
|
||||||
|
stock_asset_rbnb = (
|
||||||
|
self.get_company_default("asset_received_but_not_billed")
|
||||||
|
if d.is_fixed_asset
|
||||||
|
else self.get_company_default("stock_received_but_not_billed")
|
||||||
|
)
|
||||||
|
|
||||||
stock_value_diff = get_stock_value_difference(self.name, d.name, d.rejected_warehouse)
|
stock_value_diff = get_stock_value_difference(self.name, d.name, d.rejected_warehouse)
|
||||||
stock_asset_account_name = warehouse_account[d.rejected_warehouse]["account"]
|
stock_asset_account_name = warehouse_account[d.rejected_warehouse]["account"]
|
||||||
|
|
||||||
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
make_item_asset_inward_gl_entry(d, stock_value_diff, stock_asset_account_name)
|
||||||
|
if not d.qty:
|
||||||
|
make_stock_received_but_not_billed_entry(d)
|
||||||
|
|
||||||
if warehouse_with_no_account:
|
if warehouse_with_no_account:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
|
|||||||
@@ -3693,7 +3693,7 @@ class TestPurchaseReceipt(IntegrationTestCase):
|
|||||||
|
|
||||||
columns, data = execute(
|
columns, data = execute(
|
||||||
filters=frappe._dict(
|
filters=frappe._dict(
|
||||||
{"item_code": item_code, "warehouse": pr.items[0].warehouse, "company": pr.company}
|
{"item_code": [item_code], "warehouse": [pr.items[0].warehouse], "company": pr.company}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -4286,6 +4286,47 @@ class TestPurchaseReceipt(IntegrationTestCase):
|
|||||||
|
|
||||||
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)
|
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 0)
|
||||||
|
|
||||||
|
def test_valuation_rate_for_rejected_materials_withoout_accepted_materials(self):
|
||||||
|
item = make_item("Test Item with Rej Material Valuation WO Accepted", {"is_stock_item": 1})
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
warehouse = create_warehouse(
|
||||||
|
"_Test In-ward Warehouse",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
)
|
||||||
|
|
||||||
|
rej_warehouse = create_warehouse(
|
||||||
|
"_Test Warehouse - Rejected Material",
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice", 1)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "set_valuation_rate_for_rejected_materials", 1)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
item_code=item.name,
|
||||||
|
qty=0,
|
||||||
|
rate=100,
|
||||||
|
company=company,
|
||||||
|
warehouse=warehouse,
|
||||||
|
rejected_qty=5,
|
||||||
|
rejected_warehouse=rej_warehouse,
|
||||||
|
)
|
||||||
|
|
||||||
|
gl_entry = frappe.get_all(
|
||||||
|
"GL Entry", filters={"debit": (">", 0), "voucher_no": pr.name}, pluck="name"
|
||||||
|
)
|
||||||
|
|
||||||
|
stock_value_diff = frappe.db.get_value(
|
||||||
|
"Stock Ledger Entry",
|
||||||
|
{"warehouse": rej_warehouse, "voucher_no": pr.name},
|
||||||
|
"stock_value_difference",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(gl_entry)
|
||||||
|
self.assertEqual(stock_value_diff, 500.00)
|
||||||
|
|
||||||
def test_no_valuation_rate_for_rejected_materials(self):
|
def test_no_valuation_rate_for_rejected_materials(self):
|
||||||
item = make_item("Test Item with Rej Material No Valuation", {"is_stock_item": 1})
|
item = make_item("Test Item with Rej Material No Valuation", {"is_stock_item": 1})
|
||||||
company = "_Test Company with perpetual inventory"
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|||||||
@@ -253,19 +253,35 @@ def get_warehouses_based_on_account(account, company=None):
|
|||||||
|
|
||||||
# Will be use for frappe.qb
|
# Will be use for frappe.qb
|
||||||
def apply_warehouse_filter(query, sle, filters):
|
def apply_warehouse_filter(query, sle, filters):
|
||||||
if warehouse := filters.get("warehouse"):
|
if not (warehouses := filters.get("warehouse")):
|
||||||
warehouse_table = frappe.qb.DocType("Warehouse")
|
return query
|
||||||
|
|
||||||
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
|
warehouse_table = frappe.qb.DocType("Warehouse")
|
||||||
chilren_subquery = (
|
|
||||||
frappe.qb.from_(warehouse_table)
|
if isinstance(warehouses, str):
|
||||||
.select(warehouse_table.name)
|
warehouses = [warehouses]
|
||||||
.where(
|
|
||||||
(warehouse_table.lft >= lft)
|
warehouse_range = frappe.get_all(
|
||||||
& (warehouse_table.rgt <= rgt)
|
"Warehouse",
|
||||||
& (warehouse_table.name == sle.warehouse)
|
filters={
|
||||||
)
|
"name": ("in", warehouses),
|
||||||
)
|
},
|
||||||
query = query.where(ExistsCriterion(chilren_subquery))
|
fields=["lft", "rgt"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
child_query = frappe.qb.from_(warehouse_table).select(warehouse_table.name)
|
||||||
|
|
||||||
|
range_conditions = [
|
||||||
|
(warehouse_table.lft >= lft) & (warehouse_table.rgt <= rgt) for lft, rgt in warehouse_range
|
||||||
|
]
|
||||||
|
|
||||||
|
combined_condition = range_conditions[0]
|
||||||
|
for condition in range_conditions[1:]:
|
||||||
|
combined_condition = combined_condition | condition
|
||||||
|
|
||||||
|
child_query = child_query.where(combined_condition).where(warehouse_table.name == sle.warehouse)
|
||||||
|
|
||||||
|
query = query.where(ExistsCriterion(child_query))
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|||||||
@@ -36,38 +36,57 @@ frappe.query_reports["Stock Balance"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_code",
|
fieldname: "item_code",
|
||||||
label: __("Item"),
|
label: __("Items"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
width: "80",
|
width: "80",
|
||||||
options: "Item",
|
options: "Item",
|
||||||
get_query: function () {
|
get_data: async function (txt) {
|
||||||
let item_group = frappe.query_report.get_filter_value("item_group");
|
let item_group = frappe.query_report.get_filter_value("item_group");
|
||||||
|
|
||||||
return {
|
let filters = {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
...(item_group && { item_group }),
|
||||||
filters: {
|
is_stock_item: 1,
|
||||||
...(item_group && { item_group }),
|
|
||||||
is_stock_item: 1,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let { message: data } = await frappe.call({
|
||||||
|
method: "erpnext.controllers.queries.item_query",
|
||||||
|
args: {
|
||||||
|
doctype: "Item",
|
||||||
|
txt: txt,
|
||||||
|
searchfield: "name",
|
||||||
|
start: 0,
|
||||||
|
page_len: 10,
|
||||||
|
filters: filters,
|
||||||
|
as_dict: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
data = data.map(({ name, description }) => {
|
||||||
|
return {
|
||||||
|
value: name,
|
||||||
|
description: description,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return data || [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "warehouse",
|
fieldname: "warehouse",
|
||||||
label: __("Warehouse"),
|
label: __("Warehouses"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
width: "80",
|
width: "80",
|
||||||
options: "Warehouse",
|
options: "Warehouse",
|
||||||
get_query: () => {
|
get_data: (txt) => {
|
||||||
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
let warehouse_type = frappe.query_report.get_filter_value("warehouse_type");
|
||||||
let company = frappe.query_report.get_filter_value("company");
|
let company = frappe.query_report.get_filter_value("company");
|
||||||
|
|
||||||
return {
|
let filters = {
|
||||||
filters: {
|
...(warehouse_type && { warehouse_type }),
|
||||||
...(warehouse_type && { warehouse_type }),
|
...(company && { company }),
|
||||||
...(company && { company }),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return frappe.db.get_link_options("Warehouse", txt, filters);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from typing import Any, TypedDict
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.query_builder import Order
|
|
||||||
from frappe.query_builder.functions import Coalesce
|
from frappe.query_builder.functions import Coalesce
|
||||||
from frappe.utils import add_days, cint, date_diff, flt, getdate
|
from frappe.utils import add_days, cint, date_diff, flt, getdate
|
||||||
from frappe.utils.nestedset import get_descendants_of
|
from frappe.utils.nestedset import get_descendants_of
|
||||||
@@ -26,8 +25,8 @@ class StockBalanceFilter(TypedDict):
|
|||||||
from_date: str
|
from_date: str
|
||||||
to_date: str
|
to_date: str
|
||||||
item_group: str | None
|
item_group: str | None
|
||||||
item: str | None
|
item: list[str] | None
|
||||||
warehouse: str | None
|
warehouse: list[str] | None
|
||||||
warehouse_type: str | None
|
warehouse_type: str | None
|
||||||
include_uom: str | None # include extra info in converted UOM
|
include_uom: str | None # include extra info in converted UOM
|
||||||
show_stock_ageing_data: bool
|
show_stock_ageing_data: bool
|
||||||
@@ -361,6 +360,7 @@ class StockBalanceReport:
|
|||||||
|
|
||||||
if self.filters.get("warehouse"):
|
if self.filters.get("warehouse"):
|
||||||
query = apply_warehouse_filter(query, sle, self.filters)
|
query = apply_warehouse_filter(query, sle, self.filters)
|
||||||
|
|
||||||
elif warehouse_type := self.filters.get("warehouse_type"):
|
elif warehouse_type := self.filters.get("warehouse_type"):
|
||||||
query = (
|
query = (
|
||||||
query.join(warehouse_table)
|
query.join(warehouse_table)
|
||||||
@@ -375,13 +375,11 @@ class StockBalanceReport:
|
|||||||
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
|
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
|
||||||
query = query.where(item_table.item_group.isin([*children, item_group]))
|
query = query.where(item_table.item_group.isin([*children, item_group]))
|
||||||
|
|
||||||
for field in ["item_code", "brand"]:
|
if item_codes := self.filters.get("item_code"):
|
||||||
if not self.filters.get(field):
|
query = query.where(item_table.name.isin(item_codes))
|
||||||
continue
|
|
||||||
elif field == "item_code":
|
if brand := self.filters.get("brand"):
|
||||||
query = query.where(item_table.name == self.filters.get(field))
|
query = query.where(item_table.brand == brand)
|
||||||
else:
|
|
||||||
query = query.where(item_table[field] == self.filters.get(field))
|
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class TestStockBalance(IntegrationTestCase):
|
|||||||
self.filters = _dict(
|
self.filters = _dict(
|
||||||
{
|
{
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"item_code": self.item.name,
|
"item_code": [self.item.name],
|
||||||
"from_date": "2020-01-01",
|
"from_date": "2020-01-01",
|
||||||
"to_date": str(today()),
|
"to_date": str(today()),
|
||||||
}
|
}
|
||||||
@@ -165,6 +165,6 @@ class TestStockBalance(IntegrationTestCase):
|
|||||||
variant.save()
|
variant.save()
|
||||||
|
|
||||||
self.generate_stock_ledger(variant.name, [_dict(qty=5, rate=10)])
|
self.generate_stock_ledger(variant.name, [_dict(qty=5, rate=10)])
|
||||||
rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": variant.name}))
|
rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": [variant.name]}))
|
||||||
self.assertPartialDictEq(attributes, rows[0])
|
self.assertPartialDictEq(attributes, rows[0])
|
||||||
self.assertInvariants(rows)
|
self.assertInvariants(rows)
|
||||||
|
|||||||
@@ -27,25 +27,44 @@ frappe.query_reports["Stock Ledger"] = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "warehouse",
|
fieldname: "warehouse",
|
||||||
label: __("Warehouse"),
|
label: __("Warehouses"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
options: "Warehouse",
|
options: "Warehouse",
|
||||||
get_query: function () {
|
get_data: function (txt) {
|
||||||
const company = frappe.query_report.get_filter_value("company");
|
const company = frappe.query_report.get_filter_value("company");
|
||||||
return {
|
|
||||||
filters: { company: company },
|
return frappe.db.get_link_options("Warehouse", txt, {
|
||||||
};
|
company: company,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "item_code",
|
fieldname: "item_code",
|
||||||
label: __("Item"),
|
label: __("Items"),
|
||||||
fieldtype: "Link",
|
fieldtype: "MultiSelectList",
|
||||||
options: "Item",
|
options: "Item",
|
||||||
get_query: function () {
|
get_data: async function (txt) {
|
||||||
return {
|
let { message: data } = await frappe.call({
|
||||||
query: "erpnext.controllers.queries.item_query",
|
method: "erpnext.controllers.queries.item_query",
|
||||||
};
|
args: {
|
||||||
|
doctype: "Item",
|
||||||
|
txt: txt,
|
||||||
|
searchfield: "name",
|
||||||
|
start: 0,
|
||||||
|
page_len: 10,
|
||||||
|
filters: {},
|
||||||
|
as_dict: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
data = data.map(({ name, description }) => {
|
||||||
|
return {
|
||||||
|
value: name,
|
||||||
|
description: description,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return data || [];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -456,19 +456,23 @@ def get_items(filters):
|
|||||||
query = frappe.qb.from_(item).select(item.name)
|
query = frappe.qb.from_(item).select(item.name)
|
||||||
conditions = []
|
conditions = []
|
||||||
|
|
||||||
if item_code := filters.get("item_code"):
|
if item_codes := filters.get("item_code"):
|
||||||
conditions.append(item.name == item_code)
|
conditions.append(item.name.isin(item_codes))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if brand := filters.get("brand"):
|
if brand := filters.get("brand"):
|
||||||
conditions.append(item.brand == brand)
|
conditions.append(item.brand == brand)
|
||||||
if item_group := filters.get("item_group"):
|
|
||||||
if condition := get_item_group_condition(item_group, item):
|
if filters.get("item_group") and (
|
||||||
conditions.append(condition)
|
condition := get_item_group_condition(filters.get("item_group"), item)
|
||||||
|
):
|
||||||
|
conditions.append(condition)
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
if conditions:
|
if conditions:
|
||||||
for condition in conditions:
|
for condition in conditions:
|
||||||
query = query.where(condition)
|
query = query.where(condition)
|
||||||
|
|
||||||
items = [r[0] for r in query.run()]
|
items = [r[0] for r in query.run()]
|
||||||
|
|
||||||
return items
|
return items
|
||||||
@@ -505,6 +509,7 @@ def get_item_details(items, sl_entries, include_uom):
|
|||||||
return item_details
|
return item_details
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: THIS IS NOT USED
|
||||||
def get_sle_conditions(filters):
|
def get_sle_conditions(filters):
|
||||||
conditions = []
|
conditions = []
|
||||||
if filters.get("warehouse"):
|
if filters.get("warehouse"):
|
||||||
@@ -535,8 +540,8 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for fields in ["item_code", "warehouse"]:
|
for fields in ["item_code", "warehouse"]:
|
||||||
if filters.get(fields):
|
if value := filters.get(fields):
|
||||||
query_filters[fields] = filters.get(fields)
|
query_filters[fields] = ("in", value)
|
||||||
|
|
||||||
opening_data = frappe.get_all(
|
opening_data = frappe.get_all(
|
||||||
"Stock Ledger Entry",
|
"Stock Ledger Entry",
|
||||||
@@ -567,8 +572,16 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for field in ["item_code", "warehouse", "company"]:
|
for field in ["item_code", "warehouse", "company"]:
|
||||||
if filters.get(field):
|
value = filters.get(field)
|
||||||
query = query.where(table[field] == filters.get(field))
|
|
||||||
|
if not value:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(value, list | tuple):
|
||||||
|
query = query.where(table[field].isin(value))
|
||||||
|
|
||||||
|
else:
|
||||||
|
query = query.where(table[field] == value)
|
||||||
|
|
||||||
bundle_data = query.run(as_dict=True)
|
bundle_data = query.run(as_dict=True)
|
||||||
|
|
||||||
@@ -623,13 +636,34 @@ def get_opening_balance(filters, columns, sl_entries):
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
def get_warehouse_condition(warehouse):
|
def get_warehouse_condition(warehouses):
|
||||||
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
|
if not warehouses:
|
||||||
if warehouse_details:
|
return ""
|
||||||
return f" exists (select name from `tabWarehouse` wh \
|
|
||||||
where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
|
|
||||||
|
|
||||||
return ""
|
if isinstance(warehouses, str):
|
||||||
|
warehouses = [warehouses]
|
||||||
|
|
||||||
|
warehouse_range = frappe.get_all(
|
||||||
|
"Warehouse",
|
||||||
|
filters={
|
||||||
|
"name": ("in", warehouses),
|
||||||
|
},
|
||||||
|
fields=["lft", "rgt"],
|
||||||
|
as_list=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not warehouse_range:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
alias = "wh"
|
||||||
|
conditions = []
|
||||||
|
for lft, rgt in warehouse_range:
|
||||||
|
conditions.append(f"({alias}.lft >= {lft} and {alias}.rgt <= {rgt})")
|
||||||
|
|
||||||
|
conditions = " or ".join(conditions)
|
||||||
|
|
||||||
|
return f" exists (select name from `tabWarehouse` {alias} \
|
||||||
|
where ({conditions}) and warehouse = {alias}.name)"
|
||||||
|
|
||||||
|
|
||||||
def get_item_group_condition(item_group, item_table=None):
|
def get_item_group_condition(item_group, item_table=None):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class TestStockLedgerReeport(IntegrationTestCase):
|
|||||||
company="_Test Company",
|
company="_Test Company",
|
||||||
from_date=today(),
|
from_date=today(),
|
||||||
to_date=add_days(today(), 30),
|
to_date=add_days(today(), 30),
|
||||||
item_code="_Test Stock Report Serial Item",
|
item_code=["_Test Stock Report Serial Item"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
|
|||||||
@@ -18,8 +18,15 @@ batch = get_random("Batch")
|
|||||||
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
||||||
("Stock Ledger", {"_optional": True}),
|
("Stock Ledger", {"_optional": True}),
|
||||||
("Stock Ledger", {"batch_no": batch}),
|
("Stock Ledger", {"batch_no": batch}),
|
||||||
("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}),
|
("Stock Ledger", {"item_code": ["_Test Item"], "warehouse": ["_Test Warehouse - _TC"]}),
|
||||||
("Stock Balance", {"_optional": True}),
|
(
|
||||||
|
"Stock Balance",
|
||||||
|
{
|
||||||
|
"item_code": ["_Test Item"],
|
||||||
|
"warehouse": ["_Test Warehouse - _TC"],
|
||||||
|
"item_group": "_Test Item Group",
|
||||||
|
},
|
||||||
|
),
|
||||||
("Stock Projected Qty", {"_optional": True}),
|
("Stock Projected Qty", {"_optional": True}),
|
||||||
("Batch-Wise Balance History", {}),
|
("Batch-Wise Balance History", {}),
|
||||||
("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}),
|
("Itemwise Recommended Reorder Level", {"item_group": "All Item Groups"}),
|
||||||
|
|||||||
@@ -1683,8 +1683,20 @@ def get_stock_ledger_entries(
|
|||||||
):
|
):
|
||||||
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
"""get stock ledger entries filtered by specific posting datetime conditions"""
|
||||||
conditions = f" and posting_datetime {operator} %(posting_datetime)s"
|
conditions = f" and posting_datetime {operator} %(posting_datetime)s"
|
||||||
if previous_sle.get("warehouse"):
|
|
||||||
conditions += " and warehouse = %(warehouse)s"
|
if item_code := previous_sle.get("item_code"):
|
||||||
|
if isinstance(item_code, list | tuple):
|
||||||
|
conditions += " and item_code in %(item_code)s"
|
||||||
|
else:
|
||||||
|
conditions += " and item_code = %(item_code)s"
|
||||||
|
|
||||||
|
if warehouse := previous_sle.get("warehouse"):
|
||||||
|
if isinstance(warehouse, list | tuple):
|
||||||
|
conditions += " and warehouse in %(warehouse)s"
|
||||||
|
|
||||||
|
else:
|
||||||
|
conditions += " and warehouse = %(warehouse)s"
|
||||||
|
|
||||||
elif previous_sle.get("warehouse_condition"):
|
elif previous_sle.get("warehouse_condition"):
|
||||||
conditions += " and " + previous_sle.get("warehouse_condition")
|
conditions += " and " + previous_sle.get("warehouse_condition")
|
||||||
|
|
||||||
@@ -1727,8 +1739,7 @@ def get_stock_ledger_entries(
|
|||||||
"""
|
"""
|
||||||
select *, posting_datetime as "timestamp"
|
select *, posting_datetime as "timestamp"
|
||||||
from `tabStock Ledger Entry`
|
from `tabStock Ledger Entry`
|
||||||
where item_code = %(item_code)s
|
where is_cancelled = 0
|
||||||
and is_cancelled = 0
|
|
||||||
{conditions}
|
{conditions}
|
||||||
order by posting_datetime {order}, creation {order}
|
order by posting_datetime {order}, creation {order}
|
||||||
{limit} {for_update}""".format(
|
{limit} {for_update}""".format(
|
||||||
|
|||||||
@@ -316,9 +316,12 @@ class TransactionBase(StatusUpdater):
|
|||||||
setattr(item_obj, k, v)
|
setattr(item_obj, k, v)
|
||||||
|
|
||||||
def handle_internal_parties(self, item_obj: object, item_details: dict) -> None:
|
def handle_internal_parties(self, item_obj: object, item_details: dict) -> None:
|
||||||
|
fetch_valuation_rate_for_internal_transaction = cint(
|
||||||
|
frappe.get_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction")
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
self.get("is_internal_customer") or self.get("is_internal_supplier")
|
self.get("is_internal_customer") or self.get("is_internal_supplier")
|
||||||
) and self.represents_company == self.company:
|
) and fetch_valuation_rate_for_internal_transaction:
|
||||||
args = frappe._dict(
|
args = frappe._dict(
|
||||||
{
|
{
|
||||||
"item_code": item_obj.item_code,
|
"item_code": item_obj.item_code,
|
||||||
|
|||||||
Reference in New Issue
Block a user