mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-20 23:35:11 +00:00
Merge pull request #49116 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -169,7 +169,7 @@ class Account(NestedSet):
|
||||
if par.root_type:
|
||||
self.root_type = par.root_type
|
||||
|
||||
if self.is_group:
|
||||
if cint(self.is_group):
|
||||
db_value = self.get_doc_before_save()
|
||||
if db_value:
|
||||
if self.report_type != db_value.report_type:
|
||||
@@ -212,7 +212,7 @@ class Account(NestedSet):
|
||||
if doc_before_save and not doc_before_save.parent_account:
|
||||
throw(_("Root cannot be edited."), RootNotEditable)
|
||||
|
||||
if not self.parent_account and not self.is_group:
|
||||
if not self.parent_account and not cint(self.is_group):
|
||||
throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
|
||||
|
||||
def validate_root_company_and_sync_account_to_children(self):
|
||||
@@ -261,7 +261,7 @@ class Account(NestedSet):
|
||||
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction cannot be converted to ledger"))
|
||||
elif self.is_group:
|
||||
elif cint(self.is_group):
|
||||
if self.account_type and not self.flags.exclude_account_type_check:
|
||||
throw(_("Cannot covert to Group because Account Type is selected."))
|
||||
elif self.check_if_child_exists():
|
||||
|
||||
@@ -252,10 +252,6 @@ frappe.treeview_settings["Account"] = {
|
||||
root_company,
|
||||
]);
|
||||
} else {
|
||||
const node = treeview.tree.get_selected_node();
|
||||
if (node.is_root) {
|
||||
frappe.throw(__("Cannot create root account."));
|
||||
}
|
||||
treeview.new_node();
|
||||
}
|
||||
},
|
||||
@@ -274,8 +270,7 @@ frappe.treeview_settings["Account"] = {
|
||||
].treeview.page.fields_dict.root_company.get_value() ||
|
||||
frappe.flags.ignore_root_company_validation) &&
|
||||
node.expandable &&
|
||||
!node.hide_add &&
|
||||
!node.is_root
|
||||
!node.hide_add
|
||||
);
|
||||
},
|
||||
click: function () {
|
||||
|
||||
@@ -550,7 +550,7 @@ def send_auto_email():
|
||||
selected = frappe.get_list(
|
||||
"Process Statement Of Accounts",
|
||||
filters={"enable_auto_email": 1},
|
||||
or_filters={"to_date": format_date(today()), "posting_date": format_date(today())},
|
||||
or_filters={"to_date": today(), "posting_date": today()},
|
||||
)
|
||||
for entry in selected:
|
||||
send_emails(entry.name, from_scheduler=True)
|
||||
|
||||
@@ -178,7 +178,7 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if not repost_doc.delete_cancelled_entries:
|
||||
doc.docstatus = 2
|
||||
doc.make_gl_entries_on_cancel()
|
||||
doc.make_gl_entries_on_cancel(from_repost=True)
|
||||
|
||||
doc.docstatus = 1
|
||||
if doc.doctype == "Sales Invoice":
|
||||
@@ -190,7 +190,7 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
elif doc.doctype == "Purchase Receipt":
|
||||
if not repost_doc.delete_cancelled_entries:
|
||||
doc.docstatus = 2
|
||||
doc.make_gl_entries_on_cancel()
|
||||
doc.make_gl_entries_on_cancel(from_repost=True)
|
||||
|
||||
doc.docstatus = 1
|
||||
doc.make_gl_entries(from_repost=True)
|
||||
|
||||
@@ -1206,7 +1206,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.make_exchange_gain_loss_journal()
|
||||
elif self.docstatus == 2:
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||
|
||||
if update_outstanding == "No":
|
||||
|
||||
@@ -4668,6 +4668,59 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
doc.db_set("do_not_use_batchwise_valuation", original_value)
|
||||
|
||||
def test_system_generated_exchange_gain_or_loss_je_after_repost(self):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
|
||||
update_repost_settings,
|
||||
)
|
||||
|
||||
update_repost_settings()
|
||||
|
||||
si = create_sales_invoice(
|
||||
customer="_Test Customer USD",
|
||||
debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD",
|
||||
conversion_rate=80,
|
||||
)
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name)
|
||||
pe.reference_no = "10"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_from_account_currency = si.currency
|
||||
pe.paid_to_account_currency = "INR"
|
||||
pe.source_exchange_rate = 85
|
||||
pe.target_exchange_rate = 1
|
||||
pe.paid_amount = si.outstanding_amount
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||
ral.company = si.company
|
||||
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
||||
ral.save()
|
||||
ral.submit()
|
||||
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
jea = frappe.qb.DocType("Journal Entry Account")
|
||||
q = (
|
||||
(
|
||||
frappe.qb.from_(je)
|
||||
.join(jea)
|
||||
.on(je.name == jea.parent)
|
||||
.select(je.docstatus)
|
||||
.where(
|
||||
(je.voucher_type == "Exchange Gain Or Loss")
|
||||
& (jea.reference_name == si.name)
|
||||
& (jea.reference_type == "Sales Invoice")
|
||||
& (je.is_system_generated == 1)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
self.assertEqual(q[0][0], 1)
|
||||
|
||||
|
||||
def make_item_for_si(item_code, properties=None):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
@@ -203,6 +203,12 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
party_name_map = get_party_name_map()
|
||||
|
||||
for gl_entry in gl_entries:
|
||||
if gl_entry.party_type and gl_entry.party:
|
||||
gl_entry.party_name = party_name_map.get(gl_entry.party_type, {}).get(gl_entry.party)
|
||||
|
||||
if filters.get("presentation_currency"):
|
||||
return convert_to_presentation_currency(gl_entries, currency_map, filters)
|
||||
else:
|
||||
@@ -337,6 +343,20 @@ def get_conditions(filters):
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
|
||||
def get_party_name_map():
|
||||
party_map = {}
|
||||
|
||||
customers = frappe.get_all("Customer", fields=["name", "customer_name"])
|
||||
party_map["Customer"] = {c.name: c.customer_name for c in customers}
|
||||
|
||||
suppliers = frappe.get_all("Supplier", fields=["name", "supplier_name"])
|
||||
party_map["Supplier"] = {s.name: s.supplier_name for s in suppliers}
|
||||
|
||||
employees = frappe.get_all("Employee", fields=["name", "employee_name"])
|
||||
party_map["Employee"] = {e.name: e.employee_name for e in employees}
|
||||
return party_map
|
||||
|
||||
|
||||
def get_accounts_with_children(accounts):
|
||||
if not isinstance(accounts, list):
|
||||
accounts = [d.strip() for d in accounts.strip().split(",") if d]
|
||||
@@ -701,6 +721,19 @@ def get_columns(filters):
|
||||
{"label": _("Party"), "fieldname": "party", "width": 100},
|
||||
]
|
||||
|
||||
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
|
||||
if supplier_master_name != "Supplier Name" or customer_master_name != "Customer Name":
|
||||
columns.append(
|
||||
{
|
||||
"label": _("Party Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 150,
|
||||
}
|
||||
)
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100})
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ def execute(filters=None):
|
||||
"invoice_or_item",
|
||||
"customer",
|
||||
"customer_group",
|
||||
"customer_name",
|
||||
"posting_date",
|
||||
"item_code",
|
||||
"item_name",
|
||||
@@ -95,6 +96,7 @@ def execute(filters=None):
|
||||
"customer": [
|
||||
"customer",
|
||||
"customer_group",
|
||||
"customer_name",
|
||||
"qty",
|
||||
"base_rate",
|
||||
"buying_rate",
|
||||
@@ -250,6 +252,10 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
|
||||
|
||||
def get_columns(group_wise_columns, filters):
|
||||
columns = []
|
||||
|
||||
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
|
||||
column_map = frappe._dict(
|
||||
{
|
||||
"parent": {
|
||||
@@ -395,6 +401,12 @@ def get_columns(group_wise_columns, filters):
|
||||
"options": "Customer Group",
|
||||
"width": 100,
|
||||
},
|
||||
"customer_name": {
|
||||
"label": _("Customer Name"),
|
||||
"fieldname": "customer_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 150,
|
||||
},
|
||||
"territory": {
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
@@ -419,6 +431,10 @@ def get_columns(group_wise_columns, filters):
|
||||
)
|
||||
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
if col == "customer_name" and (
|
||||
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
|
||||
):
|
||||
continue
|
||||
columns.append(column_map.get(col))
|
||||
|
||||
columns.append(
|
||||
@@ -440,6 +456,7 @@ def get_column_names():
|
||||
"invoice_or_item": "sales_invoice",
|
||||
"customer": "customer",
|
||||
"customer_group": "customer_group",
|
||||
"customer_name": "customer_name",
|
||||
"posting_date": "posting_date",
|
||||
"item_code": "item_code",
|
||||
"item_name": "item_name",
|
||||
@@ -905,7 +922,7 @@ class GrossProfitGenerator:
|
||||
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.customer_name,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
||||
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
|
||||
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||
@@ -1003,6 +1020,7 @@ class GrossProfitGenerator:
|
||||
"update_stock": row.update_stock,
|
||||
"customer": row.customer,
|
||||
"customer_group": row.customer_group,
|
||||
"customer_name": row.customer_name,
|
||||
"item_code": None,
|
||||
"item_name": None,
|
||||
"description": None,
|
||||
@@ -1032,6 +1050,7 @@ class GrossProfitGenerator:
|
||||
"project": row.project,
|
||||
"customer": row.customer,
|
||||
"customer_group": row.customer_group,
|
||||
"customer_name": row.customer_name,
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"description": item.description,
|
||||
|
||||
@@ -578,7 +578,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "qty", "ordered_qty"],
|
||||
child_columns: ["item_code", "item_name", "qty", "ordered_qty"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
|
||||
@@ -959,8 +959,9 @@ class StockController(AccountsController):
|
||||
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
|
||||
update_batch_qty(self.doctype, self.name, via_landed_cost_voucher=via_landed_cost_voucher)
|
||||
|
||||
def make_gl_entries_on_cancel(self):
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
def make_gl_entries_on_cancel(self, from_repost=False):
|
||||
if not from_repost:
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
if frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type=%s
|
||||
and voucher_no=%s""",
|
||||
|
||||
@@ -284,6 +284,9 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
|
||||
this.frm.set_value("currency", frappe.defaults.get_user_default("Currency"));
|
||||
}
|
||||
|
||||
if (this.frm.is_new() && this.frm.doc.opportunity_type === undefined) {
|
||||
this.frm.doc.opportunity_type = __("Sales");
|
||||
}
|
||||
this.setup_queries();
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"default": "Sales",
|
||||
"fieldname": "opportunity_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@@ -670,4 +669,4 @@
|
||||
"title_field": "title",
|
||||
"track_seen": 1,
|
||||
"track_views": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ class Opportunity(TransactionBase, CRMNote):
|
||||
link_communications(self.opportunity_from, self.party_name, self)
|
||||
|
||||
def validate(self):
|
||||
self.set_opportunity_type()
|
||||
self.make_new_lead_if_required()
|
||||
self.validate_item_details()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
@@ -150,6 +151,10 @@ class Opportunity(TransactionBase, CRMNote):
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def set_opportunity_type(self):
|
||||
if self.is_new() and not self.opportunity_type:
|
||||
self.opportunity_type = _("Sales")
|
||||
|
||||
def set_exchange_rate(self):
|
||||
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
if self.currency == company_currency:
|
||||
|
||||
@@ -23,6 +23,14 @@ frappe.ui.form.on("Job Card", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("item_code", "scrap_items", () => {
|
||||
return {
|
||||
filters: {
|
||||
disabled: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_indicator_formatter("sub_operation", function (doc) {
|
||||
if (doc.status == "Pending") {
|
||||
return "red";
|
||||
|
||||
@@ -419,3 +419,4 @@ erpnext.patches.v15_0.remove_sales_partner_from_consolidated_sales_invoice
|
||||
erpnext.patches.v15_0.repost_gl_entries_with_no_account_subcontracting #2025-08-04
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_for_internal_transaction", 1)
|
||||
erpnext.patches.v15_0.add_company_payment_gateway_account
|
||||
erpnext.patches.v15_0.update_uae_zero_rated_fetch
|
||||
|
||||
10
erpnext/patches/v15_0/update_uae_zero_rated_fetch.py
Normal file
10
erpnext/patches/v15_0/update_uae_zero_rated_fetch.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.regional.united_arab_emirates.setup import make_custom_fields
|
||||
|
||||
|
||||
def execute():
|
||||
if not frappe.db.get_value("Company", {"country": "United Arab Emirates"}):
|
||||
return
|
||||
|
||||
make_custom_fields()
|
||||
@@ -143,7 +143,7 @@ def get_total_emiratewise(filters):
|
||||
on
|
||||
i.parent = s.name
|
||||
where
|
||||
s.docstatus = 1 and i.is_exempt != 1 and i.is_zero_rated != 1
|
||||
s.docstatus = 1 and i.is_exempt != 1 and i.is_zero_rated != 1
|
||||
{conditions}
|
||||
group by
|
||||
s.vat_emirate;
|
||||
|
||||
@@ -20,6 +20,7 @@ def make_custom_fields():
|
||||
label="Is Zero Rated",
|
||||
fieldtype="Check",
|
||||
fetch_from="item_code.is_zero_rated",
|
||||
fetch_if_empty=1,
|
||||
insert_after="description",
|
||||
print_hide=1,
|
||||
)
|
||||
|
||||
@@ -7,10 +7,6 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax
|
||||
|
||||
|
||||
def update_itemised_tax_data(doc):
|
||||
# maybe this should be a standard function rather than a regional one
|
||||
if not doc.taxes:
|
||||
return
|
||||
|
||||
if not doc.items:
|
||||
return
|
||||
|
||||
@@ -20,6 +16,29 @@ def update_itemised_tax_data(doc):
|
||||
|
||||
itemised_tax = get_itemised_tax(doc.taxes)
|
||||
|
||||
def determine_if_export(doc):
|
||||
if doc.doctype != "Sales Invoice":
|
||||
return False
|
||||
|
||||
if not doc.customer_address:
|
||||
if not doc.total_taxes_and_charges:
|
||||
frappe.msgprint(
|
||||
_("Please set Customer Address to determine if the transaction is an export."),
|
||||
alert=True,
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
company_country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
customer_country = frappe.db.get_value("Address", doc.customer_address, "country")
|
||||
|
||||
if company_country != customer_country:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
is_export = determine_if_export(doc)
|
||||
|
||||
for row in doc.items:
|
||||
tax_rate, tax_amount = 0.0, 0.0
|
||||
# dont even bother checking in item tax template as it contains both input and output accounts - double the tax rate
|
||||
@@ -30,6 +49,9 @@ def update_itemised_tax_data(doc):
|
||||
tax_amount += flt((row.net_amount * _tax_rate) / 100, row.precision("tax_amount"))
|
||||
tax_rate += _tax_rate
|
||||
|
||||
if not tax_rate or row.get("is_zero_rated"):
|
||||
row.is_zero_rated = is_export or frappe.get_cached_value("Item", row.item_code, "is_zero_rated")
|
||||
|
||||
row.tax_rate = flt(tax_rate, row.precision("tax_rate"))
|
||||
row.tax_amount = flt(tax_amount, row.precision("tax_amount"))
|
||||
row.total_amount = flt((row.net_amount + row.tax_amount), row.precision("total_amount"))
|
||||
|
||||
@@ -1219,6 +1219,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
read_only: 1,
|
||||
fieldname: "uom",
|
||||
label: __("UOM"),
|
||||
options: "UOM",
|
||||
in_list_view: 1,
|
||||
},
|
||||
{
|
||||
@@ -1292,7 +1293,6 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
let pending_qty = (flt(d.stock_qty) - ordered_qty) / flt(d.conversion_factor);
|
||||
if (pending_qty > 0) {
|
||||
po_items.push({
|
||||
doctype: "Sales Order Item",
|
||||
name: d.name,
|
||||
item_name: d.item_name,
|
||||
item_code: d.item_code,
|
||||
|
||||
@@ -1785,7 +1785,9 @@ def create_pick_list(source_name, target_doc=None):
|
||||
|
||||
doc.purpose = "Delivery"
|
||||
|
||||
doc.set_item_locations()
|
||||
# Only auto-assign serial numbers if not picking manually
|
||||
if not doc.pick_manually:
|
||||
doc.set_item_locations()
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ def get_brand_defaults(item, company):
|
||||
|
||||
for d in brand.brand_defaults or []:
|
||||
if d.company == company:
|
||||
row = copy.deepcopy(d.as_dict())
|
||||
row = d.as_dict(no_private_properties=True)
|
||||
row.pop("name")
|
||||
return row
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ def get_item_group_defaults(item, company):
|
||||
|
||||
for d in item_group.item_group_defaults or []:
|
||||
if d.company == company:
|
||||
row = copy.deepcopy(d.as_dict())
|
||||
row = d.as_dict(no_private_properties=True)
|
||||
row.pop("name")
|
||||
return row
|
||||
|
||||
|
||||
@@ -26,13 +26,27 @@ class PartyType(Document):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_party_type(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ""
|
||||
account_type = None
|
||||
|
||||
if filters and filters.get("account"):
|
||||
account_type = frappe.db.get_value("Account", filters.get("account"), "account_type")
|
||||
cond = "and account_type = '%s'" % account_type
|
||||
if account_type:
|
||||
if account_type in ["Receivable", "Payable"]:
|
||||
# Include Employee regardless of its configured account_type, but still respect the text filter
|
||||
cond = "and (account_type = %(account_type)s or name = 'Employee')"
|
||||
else:
|
||||
cond = "and account_type = %(account_type)s"
|
||||
|
||||
return frappe.db.sql(
|
||||
# Build parameters dictionary
|
||||
params = {"txt": "%" + txt + "%", "start": start, "page_len": page_len}
|
||||
if account_type:
|
||||
params["account_type"] = account_type
|
||||
|
||||
result = frappe.db.sql(
|
||||
f"""select name from `tabParty Type`
|
||||
where `{searchfield}` LIKE %(txt)s {cond}
|
||||
order by name limit %(page_len)s offset %(start)s""",
|
||||
{"txt": "%" + txt + "%", "start": start, "page_len": page_len},
|
||||
where `{searchfield}` LIKE %(txt)s {cond}
|
||||
order by name limit %(page_len)s offset %(start)s""",
|
||||
params,
|
||||
)
|
||||
|
||||
return result or []
|
||||
|
||||
@@ -318,7 +318,8 @@
|
||||
"fieldname": "shelf_life_in_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Shelf Life In Days",
|
||||
"mandatory_depends_on": "eval:doc.has_batch_no && doc.has_expiry_date"
|
||||
"mandatory_depends_on": "eval:doc.has_batch_no && doc.has_expiry_date",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"default": "2099-12-31",
|
||||
@@ -362,7 +363,8 @@
|
||||
"depends_on": "is_stock_item",
|
||||
"fieldname": "weight_per_unit",
|
||||
"fieldtype": "Float",
|
||||
"label": "Weight Per Unit"
|
||||
"label": "Weight Per Unit",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_stock_item",
|
||||
@@ -534,13 +536,15 @@
|
||||
"fieldname": "min_order_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Minimum Order Qty",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "min_order_qty",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "safety_stock",
|
||||
"fieldtype": "Float",
|
||||
"label": "Safety Stock"
|
||||
"label": "Safety Stock",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_details_cb",
|
||||
@@ -551,6 +555,7 @@
|
||||
"fieldname": "lead_time_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Lead Time in days",
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "lead_time_days",
|
||||
"oldfieldtype": "Int"
|
||||
},
|
||||
@@ -559,6 +564,7 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Last Purchase Rate",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "last_purchase_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"read_only": 1
|
||||
@@ -889,7 +895,7 @@
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2025-02-03 23:43:57.253667",
|
||||
"modified": "2025-08-08 14:58:48.674193",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
@@ -954,6 +960,7 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "item_name,description,item_group,customer_code",
|
||||
"show_name_in_global_search": 1,
|
||||
"show_preview_popup": 1,
|
||||
|
||||
@@ -1281,7 +1281,7 @@ def get_item_defaults(item_code, company):
|
||||
|
||||
for d in item.item_defaults:
|
||||
if d.company == company:
|
||||
row = copy.deepcopy(d.as_dict())
|
||||
row = d.as_dict(no_private_properties=True)
|
||||
row.pop("name")
|
||||
out.update(row)
|
||||
return out
|
||||
|
||||
@@ -81,6 +81,7 @@ frappe.ui.form.on("Pick List", {
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
set_item_locations: (frm, save) => {
|
||||
if (!(frm.doc.locations && frm.doc.locations.length)) {
|
||||
frappe.msgprint(__("Add items in the Item Locations table"));
|
||||
@@ -101,11 +102,34 @@ frappe.ui.form.on("Pick List", {
|
||||
},
|
||||
|
||||
pick_manually: function (frm) {
|
||||
// Update warehouse field read-only property
|
||||
frm.fields_dict.locations.grid.update_docfield_property(
|
||||
"warehouse",
|
||||
"read_only",
|
||||
!frm.doc.pick_manually
|
||||
);
|
||||
|
||||
// Clear auto-assigned serial numbers and related fields when switching to manual picking
|
||||
if (frm.doc.pick_manually && frm.doc.locations) {
|
||||
let has_changes = false;
|
||||
frm.doc.locations.forEach((row) => {
|
||||
if (row.serial_no || row.batch_no || row.serial_and_batch_bundle) {
|
||||
row.serial_no = "";
|
||||
row.batch_no = "";
|
||||
row.serial_and_batch_bundle = "";
|
||||
row.picked_qty = 0;
|
||||
has_changes = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (has_changes) {
|
||||
frappe.show_alert(
|
||||
__("Cleared auto-assigned serial numbers and batch numbers for manual picking"),
|
||||
3
|
||||
);
|
||||
frm.refresh_field("locations");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get_item_locations: (frm) => {
|
||||
@@ -273,7 +297,7 @@ frappe.ui.form.on("Pick List", {
|
||||
max_qty_field: "qty",
|
||||
dont_allow_new_row: true,
|
||||
prompt_qty: frm.doc.prompt_qty,
|
||||
serial_no_field: "not_supported", // doesn't make sense for picklist without a separate field.
|
||||
serial_no_field: "serial_no",
|
||||
};
|
||||
const barcode_scanner = new erpnext.utils.BarcodeScanner(opts);
|
||||
barcode_scanner.process_scan();
|
||||
|
||||
@@ -572,10 +572,18 @@ class PickList(TransactionBase):
|
||||
|
||||
if not item.item_code:
|
||||
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
|
||||
if not cint(
|
||||
frappe.get_cached_value("Item", item.item_code, "is_stock_item")
|
||||
) and not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0}):
|
||||
continue
|
||||
|
||||
# Check if item is stock item or product bundle
|
||||
is_stock_item = cint(frappe.get_cached_value("Item", item.item_code, "is_stock_item"))
|
||||
is_product_bundle = frappe.db.exists(
|
||||
"Product Bundle", {"new_item_code": item.item_code, "disabled": 0}
|
||||
)
|
||||
|
||||
# Include non-stock items for delivery purposes, but skip them for warehouse assignment
|
||||
if not is_stock_item and not is_product_bundle:
|
||||
# For non-stock items, set warehouse to None and continue processing
|
||||
item.warehouse = None
|
||||
|
||||
item_code = item.item_code
|
||||
reference = item.sales_order_item or item.material_request_item
|
||||
key = (item_code, item.uom, item.warehouse, item.batch_no, reference)
|
||||
|
||||
@@ -567,20 +567,15 @@ def get_item_warehouse(item, args, overwrite_warehouse, defaults=None):
|
||||
or args.get("warehouse")
|
||||
)
|
||||
|
||||
if not warehouse:
|
||||
defaults = frappe.defaults.get_defaults() or {}
|
||||
warehouse_exists = frappe.db.exists(
|
||||
"Warehouse", {"name": defaults.default_warehouse, "company": args.company}
|
||||
)
|
||||
if defaults.get("default_warehouse") and warehouse_exists:
|
||||
warehouse = defaults.default_warehouse
|
||||
|
||||
else:
|
||||
warehouse = args.get("warehouse")
|
||||
|
||||
if not warehouse:
|
||||
default_warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse")
|
||||
if frappe.db.get_value("Warehouse", default_warehouse, "company") == args.company:
|
||||
default_warehouse = frappe.get_single_value("Stock Settings", "default_warehouse")
|
||||
if (
|
||||
default_warehouse
|
||||
and frappe.get_cached_value("Warehouse", default_warehouse, "company") == args.company
|
||||
):
|
||||
return default_warehouse
|
||||
|
||||
return warehouse
|
||||
|
||||
Reference in New Issue
Block a user