mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-23 08:38:30 +00:00
Merge remote-tracking branch 'upstream/version-15-hotfix' into mergify/bp/version-15-hotfix/pr-49875
This commit is contained in:
@@ -189,6 +189,9 @@ class POSInvoice(SalesInvoice):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def validate(self):
|
||||
if not self.customer:
|
||||
frappe.throw(_("Please select Customer first"))
|
||||
|
||||
if not cint(self.is_pos):
|
||||
frappe.throw(
|
||||
_("POS Invoice should have the field {0} checked.").format(frappe.bold(_("Include Payment")))
|
||||
@@ -345,14 +348,14 @@ class POSInvoice(SalesInvoice):
|
||||
):
|
||||
return
|
||||
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
for d in self.get("items"):
|
||||
if not d.serial_and_batch_bundle:
|
||||
if is_negative_stock_allowed(item_code=d.item_code):
|
||||
return
|
||||
available_stock, is_stock_item, is_negative_stock_allowed = get_stock_availability(
|
||||
d.item_code, d.warehouse
|
||||
)
|
||||
|
||||
available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse)
|
||||
if is_negative_stock_allowed:
|
||||
continue
|
||||
|
||||
item_code, warehouse, _qty = (
|
||||
frappe.bold(d.item_code),
|
||||
@@ -760,20 +763,22 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
from erpnext.stock.stock_ledger import is_negative_stock_allowed
|
||||
|
||||
if frappe.db.get_value("Item", item_code, "is_stock_item"):
|
||||
is_stock_item = True
|
||||
bin_qty = get_bin_qty(item_code, warehouse)
|
||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||
|
||||
return bin_qty - pos_sales_qty, is_stock_item
|
||||
return bin_qty - pos_sales_qty, is_stock_item, is_negative_stock_allowed(item_code=item_code)
|
||||
else:
|
||||
is_stock_item = True
|
||||
if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}):
|
||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||
return get_bundle_availability(item_code, warehouse), is_stock_item, False
|
||||
else:
|
||||
is_stock_item = False
|
||||
# Is a service item or non_stock item
|
||||
return 0, is_stock_item
|
||||
return 0, is_stock_item, False
|
||||
|
||||
|
||||
def get_bundle_availability(bundle_item_code, warehouse):
|
||||
|
||||
@@ -26,16 +26,13 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
{
|
||||
fieldname: "cost_center",
|
||||
label: __("Cost Center"),
|
||||
fieldtype: "Link",
|
||||
options: "Cost Center",
|
||||
get_query: () => {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
filters: {
|
||||
company: company,
|
||||
},
|
||||
};
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Cost Center", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
options: "Cost Center",
|
||||
},
|
||||
{
|
||||
fieldname: "party_account",
|
||||
|
||||
@@ -45,16 +45,13 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
{
|
||||
fieldname: "cost_center",
|
||||
label: __("Cost Center"),
|
||||
fieldtype: "Link",
|
||||
options: "Cost Center",
|
||||
get_query: () => {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
filters: {
|
||||
company: company,
|
||||
},
|
||||
};
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Cost Center", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
options: "Cost Center",
|
||||
},
|
||||
{
|
||||
fieldname: "party_type",
|
||||
|
||||
@@ -28,16 +28,13 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
{
|
||||
fieldname: "cost_center",
|
||||
label: __("Cost Center"),
|
||||
fieldtype: "Link",
|
||||
options: "Cost Center",
|
||||
get_query: () => {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
filters: {
|
||||
company: company,
|
||||
},
|
||||
};
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Cost Center", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
options: "Cost Center",
|
||||
},
|
||||
{
|
||||
fieldname: "party_type",
|
||||
|
||||
@@ -15,6 +15,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
|
||||
from erpnext.accounts.utils import (
|
||||
build_qb_match_conditions,
|
||||
get_advance_payment_doctypes,
|
||||
@@ -994,11 +995,7 @@ class ReceivablePayableReport:
|
||||
self.add_accounting_dimensions_filters()
|
||||
|
||||
def get_cost_center_conditions(self):
|
||||
lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
|
||||
cost_center_list = [
|
||||
center.name
|
||||
for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)})
|
||||
]
|
||||
cost_center_list = get_cost_centers_with_children(self.filters.cost_center)
|
||||
self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list))
|
||||
|
||||
def add_common_filters(self):
|
||||
|
||||
@@ -45,16 +45,13 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
{
|
||||
fieldname: "cost_center",
|
||||
label: __("Cost Center"),
|
||||
fieldtype: "Link",
|
||||
options: "Cost Center",
|
||||
get_query: () => {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
filters: {
|
||||
company: company,
|
||||
},
|
||||
};
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Cost Center", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
options: "Cost Center",
|
||||
},
|
||||
{
|
||||
fieldname: "party_type",
|
||||
|
||||
@@ -174,7 +174,7 @@ def add_solvency_ratios(
|
||||
return_on_equity_ratio = {"ratio": _("Return on Equity Ratio")}
|
||||
|
||||
for year in years:
|
||||
profit_after_tax = flt(total_income.get(year)) + flt(total_expense.get(year))
|
||||
profit_after_tax = flt(total_income.get(year)) - flt(total_expense.get(year))
|
||||
share_holder_fund = flt(total_asset.get(year)) - flt(total_liability.get(year))
|
||||
|
||||
debt_equity_ratio[year] = calculate_ratio(total_liability.get(year), share_holder_fund, precision)
|
||||
@@ -199,7 +199,7 @@ def add_turnover_ratios(data, years, period_list, filters, total_asset, net_sale
|
||||
|
||||
avg_data = {}
|
||||
for d in ["Receivable", "Payable", "Stock"]:
|
||||
avg_data[frappe.scrub(d)] = avg_ratio_balance("Receivable", period_list, precision, filters)
|
||||
avg_data[frappe.scrub(d)] = avg_ratio_balance(d, period_list, precision, filters)
|
||||
|
||||
avg_debtors, avg_creditors, avg_stock = (
|
||||
avg_data.get("receivable"),
|
||||
|
||||
@@ -566,6 +566,13 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map, tot
|
||||
else:
|
||||
update_value_in_dict(consolidated_gle, key, gle)
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
dimensions = [*accounting_dimensions, "cost_center", "project"]
|
||||
|
||||
for dimension in dimensions:
|
||||
if val := gle.get(dimension):
|
||||
gle[dimension] = _(val)
|
||||
|
||||
for value in consolidated_gle.values():
|
||||
update_value_in_dict(totals, "total", value)
|
||||
update_value_in_dict(totals, "closing", value)
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
from erpnext.accounts.utils import get_currency_precision
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -43,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
party_map = get_party_pan_map(filters.get("party_type"))
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
precision = get_currency_precision()
|
||||
|
||||
out = []
|
||||
entries = {}
|
||||
@@ -72,17 +75,28 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
tax_withholding_category = party_map.get(party, {}).get("tax_withholding_category")
|
||||
|
||||
rate = get_tax_withholding_rates(tax_rate_map.get(tax_withholding_category, []), posting_date)
|
||||
if net_total_map.get((voucher_type, name)):
|
||||
|
||||
values = net_total_map.get((voucher_type, name))
|
||||
|
||||
if values:
|
||||
if voucher_type == "Journal Entry" and tax_amount and rate:
|
||||
# back calcalute total amount from rate and tax_amount
|
||||
base_total = min(tax_amount / (rate / 100), net_total_map.get((voucher_type, name))[0])
|
||||
# back calculate total amount from rate and tax_amount
|
||||
base_total = min(flt(tax_amount / (rate / 100), precision=precision), values[0])
|
||||
total_amount = grand_total = base_total
|
||||
elif voucher_type == "Purchase Invoice":
|
||||
total_amount, grand_total, base_total, bill_no, bill_date = net_total_map.get(
|
||||
(voucher_type, name)
|
||||
)
|
||||
|
||||
else:
|
||||
total_amount, grand_total, base_total = net_total_map.get((voucher_type, name))
|
||||
if tax_amount and rate:
|
||||
# back calculate total amount from rate and tax_amount
|
||||
total_amount = flt((tax_amount * 100) / rate, precision=precision)
|
||||
else:
|
||||
total_amount = values[0]
|
||||
|
||||
grand_total = values[1]
|
||||
base_total = values[2]
|
||||
|
||||
if voucher_type == "Purchase Invoice":
|
||||
bill_no = values[3]
|
||||
bill_date = values[4]
|
||||
else:
|
||||
total_amount += entry.credit
|
||||
|
||||
|
||||
@@ -47,22 +47,23 @@ frappe.query_reports["Trial Balance"] = {
|
||||
{
|
||||
fieldname: "cost_center",
|
||||
label: __("Cost Center"),
|
||||
fieldtype: "Link",
|
||||
options: "Cost Center",
|
||||
get_query: function () {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
doctype: "Cost Center",
|
||||
filters: {
|
||||
company: company,
|
||||
},
|
||||
};
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Cost Center", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
options: "Cost Center",
|
||||
},
|
||||
{
|
||||
fieldname: "project",
|
||||
label: __("Project"),
|
||||
fieldtype: "Link",
|
||||
fieldtype: "MultiSelectList",
|
||||
get_data: function (txt) {
|
||||
return frappe.db.get_link_options("Project", txt, {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
});
|
||||
},
|
||||
options: "Project",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
filter_accounts,
|
||||
filter_out_zero_value_rows,
|
||||
get_cost_centers_with_children,
|
||||
set_gl_entries_by_account,
|
||||
)
|
||||
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
||||
@@ -103,10 +104,6 @@ def get_data(filters):
|
||||
|
||||
opening_balances = get_opening_balances(filters, ignore_is_opening)
|
||||
|
||||
# add filter inside list so that the query in financial_statements.py doesn't break
|
||||
if filters.project:
|
||||
filters.project = [filters.project]
|
||||
|
||||
set_gl_entries_by_account(
|
||||
filters.company,
|
||||
filters.from_date,
|
||||
@@ -270,18 +267,12 @@ def get_opening_balance(
|
||||
opening_balance = opening_balance.where(closing_balance.voucher_type != "Period Closing Voucher")
|
||||
|
||||
if filters.cost_center:
|
||||
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
|
||||
cost_center = frappe.qb.DocType("Cost Center")
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance.cost_center.isin(
|
||||
frappe.qb.from_(cost_center)
|
||||
.select("name")
|
||||
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
|
||||
)
|
||||
closing_balance.cost_center.isin(get_cost_centers_with_children(filters.get("cost_center")))
|
||||
)
|
||||
|
||||
if filters.project:
|
||||
opening_balance = opening_balance.where(closing_balance.project == filters.project)
|
||||
opening_balance = opening_balance.where(closing_balance.project.isin(filters.project))
|
||||
|
||||
if frappe.db.count("Finance Book"):
|
||||
if filters.get("include_default_book_entries"):
|
||||
|
||||
@@ -371,7 +371,6 @@
|
||||
"label": "Other Details"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
@@ -379,7 +378,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nWork In Progress",
|
||||
"options": "Draft\nSubmitted\nCancelled\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nWork In Progress",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -597,7 +596,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2025-10-23 22:43:33.634452",
|
||||
"modified": "2025-11-17 18:01:51.417942",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -103,6 +103,7 @@ class Asset(AccountsController):
|
||||
status: DF.Literal[
|
||||
"Draft",
|
||||
"Submitted",
|
||||
"Cancelled",
|
||||
"Partially Depreciated",
|
||||
"Fully Depreciated",
|
||||
"Sold",
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Asset",
|
||||
"link_filters": "[[\"Asset\",\"status\",\"not in\",[\"Work In Progress\",\"Capitalized\",\"Fully Depreciated\",\"Sold\",\"Scrapped\",null]]]",
|
||||
"link_filters": "[[\"Asset\",\"status\",\"not in\",[\"Work In Progress\",\"Capitalized\",\"Fully Depreciated\",\"Sold\",\"Scrapped\",\"Cancelled\",null]]]",
|
||||
"options": "Asset",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -250,7 +250,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-29 15:14:34.044564",
|
||||
"modified": "2025-11-17 18:35:54.575265",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
|
||||
@@ -60,6 +60,17 @@ class AssetRepair(AccountsController):
|
||||
if self.get("stock_items"):
|
||||
self.set_stock_items_cost()
|
||||
self.calculate_total_repair_cost()
|
||||
self.validate_purchase_invoice_status()
|
||||
|
||||
def validate_purchase_invoice_status(self):
|
||||
if self.purchase_invoice:
|
||||
docstatus = frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "docstatus")
|
||||
if docstatus == 0:
|
||||
frappe.throw(
|
||||
_("{0} is still in Draft. Please submit it before saving the Asset Repair.").format(
|
||||
get_link_to_form("Purchase Invoice", self.purchase_invoice)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_asset(self):
|
||||
if self.asset_doc.status in ("Sold", "Fully Depreciated", "Scrapped"):
|
||||
|
||||
@@ -95,6 +95,7 @@ class SellingController(StockController):
|
||||
# set contact and address details for customer, if they are not mentioned
|
||||
self.set_missing_lead_customer_details(for_validate=for_validate)
|
||||
self.set_price_list_and_item_details(for_validate=for_validate)
|
||||
self.set_company_contact_person()
|
||||
|
||||
def set_missing_lead_customer_details(self, for_validate=False):
|
||||
customer, lead = None, None
|
||||
@@ -137,6 +138,7 @@ class SellingController(StockController):
|
||||
lead,
|
||||
posting_date=self.get("transaction_date") or self.get("posting_date"),
|
||||
company=self.company,
|
||||
doctype=self.doctype,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -149,6 +151,13 @@ class SellingController(StockController):
|
||||
self.set_price_list_currency("Selling")
|
||||
self.set_missing_item_details(for_validate=for_validate)
|
||||
|
||||
def set_company_contact_person(self):
|
||||
"""Set the Company's Default Sales Contact as Company Contact Person."""
|
||||
if self.company and self.meta.has_field("company_contact_person") and not self.company_contact_person:
|
||||
self.company_contact_person = frappe.get_cached_value(
|
||||
"Company", self.company, "default_sales_contact"
|
||||
)
|
||||
|
||||
def remove_shipping_charge(self):
|
||||
if self.shipping_rule:
|
||||
shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
|
||||
|
||||
@@ -93,6 +93,7 @@ status_map = {
|
||||
["Draft", None],
|
||||
["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"],
|
||||
["Partly Billed", "eval:self.per_billed > 0 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["Return", "eval:self.is_return == 1 and self.per_billed == 0 and self.docstatus == 1"],
|
||||
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
|
||||
[
|
||||
"Completed",
|
||||
|
||||
@@ -432,7 +432,7 @@ def _set_missing_values(source, target):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_lead_details(lead, posting_date=None, company=None):
|
||||
def get_lead_details(lead, posting_date=None, company=None, doctype=None):
|
||||
if not lead:
|
||||
return {}
|
||||
|
||||
@@ -454,7 +454,7 @@ def get_lead_details(lead, posting_date=None, company=None):
|
||||
}
|
||||
)
|
||||
|
||||
set_address_details(out, lead, "Lead", company=company)
|
||||
set_address_details(out, lead, "Lead", doctype=doctype, company=company)
|
||||
|
||||
taxes_and_charges = set_taxes(
|
||||
None,
|
||||
|
||||
@@ -548,12 +548,14 @@
|
||||
{
|
||||
"fieldname": "process_loss_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "% Process Loss"
|
||||
"label": "% Process Loss",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "process_loss_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Process Loss Qty",
|
||||
"non_negative": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -591,7 +593,6 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.track_semi_finished_goods === 0",
|
||||
"fieldname": "fg_based_operating_cost",
|
||||
"fieldtype": "Check",
|
||||
"label": "Finished Goods based Operating Cost"
|
||||
@@ -640,7 +641,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-10-29 17:43:12.966753",
|
||||
"modified": "2025-11-19 16:17:15.925156",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM",
|
||||
|
||||
@@ -465,7 +465,7 @@ class BOM(WebsiteGenerator):
|
||||
)
|
||||
)
|
||||
|
||||
def get_rm_rate(self, arg):
|
||||
def get_rm_rate(self, arg, notify=True):
|
||||
"""Get raw material rate as per selected method, if bom exists takes bom cost"""
|
||||
rate = 0
|
||||
if not self.rm_cost_as_per:
|
||||
@@ -491,7 +491,7 @@ class BOM(WebsiteGenerator):
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
else:
|
||||
elif notify:
|
||||
frappe.msgprint(
|
||||
_("{0} not found for item {1}").format(self.rm_cost_as_per, arg["item_code"]),
|
||||
alert=True,
|
||||
@@ -796,7 +796,8 @@ class BOM(WebsiteGenerator):
|
||||
"stock_uom": d.stock_uom,
|
||||
"conversion_factor": d.conversion_factor,
|
||||
"sourced_by_supplier": d.sourced_by_supplier,
|
||||
}
|
||||
},
|
||||
notify=False,
|
||||
)
|
||||
|
||||
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
|
||||
|
||||
@@ -38,6 +38,15 @@ frappe.ui.form.on("Job Card", {
|
||||
return doc.status === "Complete" ? "green" : "orange";
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("employee", () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
status: "Active",
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -79,18 +79,35 @@ erpnext.financial_statements = {
|
||||
},
|
||||
open_general_ledger: function (data) {
|
||||
if (!data.account && !data.accounts) return;
|
||||
let project = $.grep(frappe.query_report.filters, function (e) {
|
||||
let filters = frappe.query_report.filters;
|
||||
|
||||
let project = $.grep(filters, function (e) {
|
||||
return e.df.fieldname == "project";
|
||||
});
|
||||
|
||||
let cost_center = $.grep(filters, function (e) {
|
||||
return e.df.fieldname == "cost_center";
|
||||
});
|
||||
|
||||
frappe.route_options = {
|
||||
account: data.account || data.accounts,
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
from_date: data.from_date || data.year_start_date,
|
||||
to_date: data.to_date || data.year_end_date,
|
||||
project: project && project.length > 0 ? project[0].$input.val() : "",
|
||||
project: project && project.length > 0 ? project[0].get_value() : "",
|
||||
cost_center: cost_center && cost_center.length > 0 ? cost_center[0].get_value() : "",
|
||||
};
|
||||
|
||||
filters.forEach((f) => {
|
||||
if (f.df.fieldtype == "MultiSelectList") {
|
||||
if (f.df.fieldname in frappe.route_options) return;
|
||||
let value = f.get_value();
|
||||
if (value && value.length > 0) {
|
||||
frappe.route_options[f.df.fieldname] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let report = "General Ledger";
|
||||
|
||||
if (["Payable", "Receivable"].includes(data.account_type)) {
|
||||
|
||||
@@ -420,25 +420,36 @@ $.extend(erpnext.utils, {
|
||||
if (!frappe.boot.setup_complete) {
|
||||
return;
|
||||
}
|
||||
const today = frappe.datetime.get_today();
|
||||
if (!date) {
|
||||
date = frappe.datetime.get_today();
|
||||
date = today;
|
||||
}
|
||||
|
||||
let fiscal_year = "";
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.utils.get_fiscal_year",
|
||||
args: {
|
||||
date: date,
|
||||
boolean: boolean,
|
||||
},
|
||||
async: false,
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (with_dates) fiscal_year = r.message;
|
||||
else fiscal_year = r.message[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
if (
|
||||
frappe.boot.current_fiscal_year &&
|
||||
date >= frappe.boot.current_fiscal_year[1] &&
|
||||
date <= frappe.boot.current_fiscal_year[2]
|
||||
) {
|
||||
if (with_dates) fiscal_year = frappe.boot.current_fiscal_year;
|
||||
else fiscal_year = frappe.boot.current_fiscal_year[0];
|
||||
} else if (today != date) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.utils.get_fiscal_year",
|
||||
type: "GET", // make it cacheable
|
||||
args: {
|
||||
date: date,
|
||||
boolean: boolean,
|
||||
},
|
||||
async: false,
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
if (with_dates) fiscal_year = r.message;
|
||||
else fiscal_year = r.message[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
return fiscal_year;
|
||||
},
|
||||
|
||||
|
||||
@@ -115,6 +115,10 @@ erpnext.sales_common = {
|
||||
company() {
|
||||
super.company();
|
||||
this.set_default_company_address();
|
||||
if (!this.is_onload) {
|
||||
// we don't want to override the mapped contact from prevdoc
|
||||
this.set_default_company_contact_person();
|
||||
}
|
||||
}
|
||||
|
||||
set_default_company_address() {
|
||||
@@ -139,6 +143,24 @@ erpnext.sales_common = {
|
||||
}
|
||||
}
|
||||
|
||||
set_default_company_contact_person() {
|
||||
if (!frappe.meta.has_field(this.frm.doc.doctype, "company_contact_person")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.frm.doc.company) {
|
||||
frappe.db
|
||||
.get_value("Company", this.frm.doc.company, "default_sales_contact")
|
||||
.then((r) => {
|
||||
if (r.message?.default_sales_contact) {
|
||||
this.frm.set_value("company_contact_person", r.message.default_sales_contact);
|
||||
} else {
|
||||
this.frm.set_value("company_contact_person", "");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customer() {
|
||||
var me = this;
|
||||
erpnext.utils.get_party_details(this.frm, null, null, function () {
|
||||
|
||||
@@ -252,6 +252,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
||||
lead: this.frm.doc.party_name,
|
||||
posting_date: this.frm.doc.transaction_date,
|
||||
company: this.frm.doc.company,
|
||||
doctype: this.frm.doc.doctype,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
|
||||
@@ -55,7 +55,7 @@ def search_by_term(search_term, warehouse, price_list):
|
||||
}
|
||||
)
|
||||
|
||||
item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
|
||||
item_stock_qty, is_stock_item, is_negative_stock_allowed = get_stock_availability(item_code, warehouse)
|
||||
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
|
||||
item.update({"actual_qty": item_stock_qty})
|
||||
|
||||
@@ -198,7 +198,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_te
|
||||
current_date = frappe.utils.today()
|
||||
|
||||
for item in items_data:
|
||||
item.actual_qty, _ = get_stock_availability(item.item_code, warehouse)
|
||||
item.actual_qty, _, is_negative_stock_allowed = get_stock_availability(item.item_code, warehouse)
|
||||
|
||||
item_prices = frappe.get_all(
|
||||
"Item Price",
|
||||
|
||||
@@ -759,12 +759,16 @@ erpnext.PointOfSale.Controller = class {
|
||||
const resp = (await this.get_available_stock(item_row.item_code, warehouse)).message;
|
||||
const available_qty = resp[0];
|
||||
const is_stock_item = resp[1];
|
||||
const is_negative_stock_allowed = resp[2];
|
||||
|
||||
frappe.dom.unfreeze();
|
||||
const bold_uom = item_row.stock_uom.bold();
|
||||
const bold_item_code = item_row.item_code.bold();
|
||||
const bold_warehouse = warehouse.bold();
|
||||
const bold_available_qty = available_qty.toString().bold();
|
||||
|
||||
if (is_negative_stock_allowed) return;
|
||||
|
||||
if (!(available_qty > 0)) {
|
||||
if (is_stock_item) {
|
||||
frappe.model.clear_doc(item_row.doctype, item_row.name);
|
||||
|
||||
@@ -37,6 +37,13 @@ frappe.ui.form.on("Company", {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
|
||||
frm.set_query("default_sales_contact", function (doc) {
|
||||
return {
|
||||
query: "frappe.contacts.doctype.contact.contact.contact_query",
|
||||
filters: { link_doctype: "Company", link_name: doc.name },
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("default_buying_terms", function () {
|
||||
return { filters: { buying: 1 } };
|
||||
});
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
"total_monthly_sales",
|
||||
"column_break_goals",
|
||||
"default_selling_terms",
|
||||
"default_sales_contact",
|
||||
"default_warehouse_for_sales_return",
|
||||
"credit_limit",
|
||||
"transactions_annual_history",
|
||||
@@ -851,6 +852,12 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Reconciliation Takes Effect On",
|
||||
"options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_sales_contact",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Sales Contact",
|
||||
"options": "Contact"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-building",
|
||||
@@ -858,7 +865,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-25 18:34:03.602046",
|
||||
"modified": "2025-11-16 16:51:27.624096",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -66,6 +66,7 @@ class Company(NestedSet):
|
||||
default_payable_account: DF.Link | None
|
||||
default_provisional_account: DF.Link | None
|
||||
default_receivable_account: DF.Link | None
|
||||
default_sales_contact: DF.Link | None
|
||||
default_selling_terms: DF.Link | None
|
||||
default_warehouse_for_sales_return: DF.Link | None
|
||||
depreciation_cost_center: DF.Link | None
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe.defaults import get_user_default
|
||||
from frappe.utils import cint
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_years
|
||||
|
||||
|
||||
def boot_session(bootinfo):
|
||||
"""boot session - send website info if guest"""
|
||||
@@ -53,6 +56,11 @@ def boot_session(bootinfo):
|
||||
)
|
||||
|
||||
party_account_types = frappe.db.sql(""" select name, ifnull(account_type, '') from `tabParty Type`""")
|
||||
fiscal_year = get_fiscal_years(
|
||||
frappe.utils.nowdate(), company=get_user_default("company"), boolean=True
|
||||
)
|
||||
if fiscal_year:
|
||||
bootinfo.current_fiscal_year = fiscal_year[0]
|
||||
bootinfo.party_account_types = frappe._dict(party_account_types)
|
||||
|
||||
bootinfo.sysdefaults.demo_company = frappe.db.get_single_value("Global Defaults", "demo_company")
|
||||
|
||||
@@ -55,6 +55,11 @@ class ItemPrice(Document):
|
||||
if not frappe.db.exists("Item", self.item_code):
|
||||
frappe.throw(_("Item {0} not found.").format(self.item_code))
|
||||
|
||||
if self.uom and not frappe.db.exists(
|
||||
"UOM Conversion Detail", {"parenttype": "Item", "parent": self.item_code, "uom": self.uom}
|
||||
):
|
||||
frappe.throw(_("UOM {0} not found in Item {1}").format(self.uom, self.item_code))
|
||||
|
||||
def update_price_list_details(self):
|
||||
if self.price_list:
|
||||
price_list_details = frappe.db.get_value(
|
||||
|
||||
@@ -332,6 +332,9 @@ frappe.ui.form.on("Material Request", {
|
||||
label: __("For Warehouse"),
|
||||
options: "Warehouse",
|
||||
reqd: 1,
|
||||
get_query: function () {
|
||||
return { filters: { company: frm.doc.company } };
|
||||
},
|
||||
},
|
||||
{ fieldname: "qty", fieldtype: "Float", label: __("Quantity"), reqd: 1, default: 1 },
|
||||
{
|
||||
|
||||
@@ -108,7 +108,12 @@ def get_indexed_packed_items_table(doc):
|
||||
"""
|
||||
indexed_table = {}
|
||||
for packed_item in doc.get("packed_items"):
|
||||
key = (packed_item.parent_item, packed_item.item_code, packed_item.parent_detail_docname)
|
||||
key = (
|
||||
packed_item.parent_item,
|
||||
packed_item.item_code,
|
||||
packed_item.idx if doc.is_new() else packed_item.parent_detail_docname,
|
||||
)
|
||||
|
||||
indexed_table[key] = packed_item
|
||||
|
||||
return indexed_table
|
||||
@@ -169,7 +174,11 @@ def add_packed_item_row(doc, packing_item, main_item_row, packed_items_table, re
|
||||
exists, pi_row = False, {}
|
||||
|
||||
# check if row already exists in packed items table
|
||||
key = (main_item_row.item_code, packing_item.item_code, main_item_row.name)
|
||||
key = (
|
||||
main_item_row.item_code,
|
||||
packing_item.item_code,
|
||||
main_item_row.idx if doc.is_new() else main_item_row.name,
|
||||
)
|
||||
if packed_items_table.get(key):
|
||||
pi_row, exists = packed_items_table.get(key), True
|
||||
|
||||
|
||||
@@ -893,7 +893,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nDraft\nPartly Billed\nTo Bill\nCompleted\nReturn Issued\nCancelled\nClosed",
|
||||
"options": "\nDraft\nPartly Billed\nTo Bill\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed",
|
||||
"print_hide": 1,
|
||||
"print_width": "150px",
|
||||
"read_only": 1,
|
||||
@@ -1300,7 +1300,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-06 16:41:02.690658",
|
||||
"modified": "2025-11-12 19:53:48.173096",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -117,7 +117,15 @@ class PurchaseReceipt(BuyingController):
|
||||
shipping_address_display: DF.SmallText | None
|
||||
shipping_rule: DF.Link | None
|
||||
status: DF.Literal[
|
||||
"", "Draft", "Partly Billed", "To Bill", "Completed", "Return Issued", "Cancelled", "Closed"
|
||||
"",
|
||||
"Draft",
|
||||
"Partly Billed",
|
||||
"To Bill",
|
||||
"Completed",
|
||||
"Return",
|
||||
"Return Issued",
|
||||
"Cancelled",
|
||||
"Closed",
|
||||
]
|
||||
subcontracting_receipt: DF.Link | None
|
||||
supplied_items: DF.Table[PurchaseReceiptItemSupplied]
|
||||
|
||||
@@ -18,6 +18,9 @@ def get_data():
|
||||
"Purchase Order": ["items", "purchase_order"],
|
||||
"Project": ["items", "project"],
|
||||
},
|
||||
"internal_and_external_links": {
|
||||
"Purchase Invoice": ["items", "purchase_invoice"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Related"),
|
||||
|
||||
@@ -11,7 +11,7 @@ frappe.listview_settings["Purchase Receipt"] = {
|
||||
"currency",
|
||||
],
|
||||
get_indicator: function (doc) {
|
||||
if (cint(doc.is_return) == 1) {
|
||||
if (cint(doc.is_return) == 1 && doc.status == "Return") {
|
||||
return [__("Return"), "gray", "is_return,=,Yes"];
|
||||
} else if (doc.status === "Closed") {
|
||||
return [__("Closed"), "green", "status,=,Closed"];
|
||||
|
||||
@@ -455,6 +455,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
# Check if Original PR updated
|
||||
self.assertEqual(pr.items[0].returned_qty, 2)
|
||||
self.assertEqual(pr.per_returned, 40)
|
||||
self.assertEqual(returned.status, "Return")
|
||||
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
@@ -2128,7 +2129,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
return_pr.items[0].stock_qty = 0.0
|
||||
return_pr.submit()
|
||||
|
||||
self.assertEqual(return_pr.status, "To Bill")
|
||||
self.assertEqual(return_pr.status, "Return")
|
||||
|
||||
pi = make_purchase_invoice(return_pr.name)
|
||||
pi.submit()
|
||||
|
||||
@@ -1347,7 +1347,36 @@ class SerialandBatchBundle(Document):
|
||||
if self.voucher_type == "POS Invoice":
|
||||
return
|
||||
|
||||
if frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1:
|
||||
child_doctype = self.voucher_type + " Item"
|
||||
mapper = {
|
||||
"Asset Capitalization": "Asset Capitalization Stock Item",
|
||||
"Asset Repair": "Asset Repair Consumed Item",
|
||||
"Stock Entry": "Stock Entry Detail",
|
||||
}.get(self.voucher_type)
|
||||
|
||||
if mapper:
|
||||
child_doctype = mapper
|
||||
|
||||
if self.voucher_type == "Delivery Note" and not frappe.db.exists(
|
||||
"Delivery Note Item", self.voucher_detail_no
|
||||
):
|
||||
child_doctype = "Packed Item"
|
||||
|
||||
elif self.voucher_type == "Sales Invoice" and not frappe.db.exists(
|
||||
"Sales Invoice Item", self.voucher_detail_no
|
||||
):
|
||||
child_doctype = "Packed Item"
|
||||
|
||||
elif self.voucher_type == "Subcontracting Receipt" and not frappe.db.exists(
|
||||
"Subcontracting Receipt Item", self.voucher_detail_no
|
||||
):
|
||||
child_doctype = "Subcontracting Receipt Supplied Item"
|
||||
|
||||
if (
|
||||
frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus") == 1
|
||||
and self.voucher_detail_no
|
||||
and frappe.db.exists(child_doctype, self.voucher_detail_no)
|
||||
):
|
||||
msg = f"""The {self.voucher_type} {bold(self.voucher_no)}
|
||||
is in submitted state, please cancel it first"""
|
||||
frappe.throw(_(msg))
|
||||
@@ -2297,7 +2326,11 @@ def get_auto_batch_nos(kwargs):
|
||||
|
||||
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
|
||||
pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
|
||||
sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
|
||||
|
||||
sre_reserved_batches = frappe._dict()
|
||||
if not kwargs.ignore_reserved_stock:
|
||||
sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
|
||||
|
||||
picked_batches = frappe._dict()
|
||||
if kwargs.get("is_pick_list"):
|
||||
picked_batches = get_picked_batches(kwargs)
|
||||
|
||||
@@ -951,12 +951,10 @@ frappe.ui.form.on("Stock Entry Detail", {
|
||||
no_batch_serial_number_value = true;
|
||||
}
|
||||
|
||||
if (
|
||||
no_batch_serial_number_value &&
|
||||
!frappe.flags.hide_serial_batch_dialog &&
|
||||
!frappe.flags.dialog_set
|
||||
) {
|
||||
frappe.flags.dialog_set = true;
|
||||
if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) {
|
||||
if (!frappe.flags.dialog_set) {
|
||||
frappe.flags.dialog_set = true;
|
||||
}
|
||||
erpnext.stock.select_batch_and_serial_no(frm, d);
|
||||
} else {
|
||||
frappe.flags.dialog_set = false;
|
||||
@@ -1338,8 +1336,8 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["expense_account", "cost_center"]);
|
||||
}
|
||||
|
||||
if (!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
||||
if (!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
||||
if (this.frm.doc.from_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
||||
if (this.frm.doc.to_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
||||
|
||||
if (cint(frappe.user_defaults?.use_serial_batch_fields)) {
|
||||
frappe.model.set_value(row.doctype, row.name, "use_serial_batch_fields", 1);
|
||||
|
||||
@@ -1136,7 +1136,9 @@ class StockEntry(StockController):
|
||||
"qty": row.transfer_qty * -1,
|
||||
}
|
||||
).update_serial_and_batch_entries()
|
||||
elif not row.serial_and_batch_bundle:
|
||||
elif not row.serial_and_batch_bundle and frappe.get_single_value(
|
||||
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
|
||||
):
|
||||
bundle_doc = SerialBatchCreation(
|
||||
{
|
||||
"item_code": row.item_code,
|
||||
|
||||
@@ -979,6 +979,7 @@ class StockReconciliation(StockController):
|
||||
is_customer_item = frappe.db.get_value("Item", d.item_code, "is_customer_provided_item")
|
||||
if is_customer_item and d.valuation_rate:
|
||||
d.valuation_rate = 0.0
|
||||
d.allow_zero_valuation_rate = 1
|
||||
changed_any_values = True
|
||||
|
||||
if changed_any_values:
|
||||
|
||||
@@ -45,6 +45,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
def test_reco_for_moving_average(self):
|
||||
self._test_reco_sle_gle("Moving Average")
|
||||
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 1})
|
||||
def _test_reco_sle_gle(self, valuation_method):
|
||||
item_code = self.make_item(properties={"valuation_method": valuation_method}).name
|
||||
|
||||
|
||||
@@ -185,12 +185,10 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "allow_zero_valuation_rate",
|
||||
"fieldname": "allow_zero_valuation_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Zero Valuation Rate",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "barcode",
|
||||
@@ -256,7 +254,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-03-12 16:34:51.326821",
|
||||
"modified": "2025-11-20 15:27:13.868179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
@@ -267,4 +265,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,6 +678,36 @@ class TestStockReservationEntry(FrappeTestCase):
|
||||
# Test - 1: ValidationError should be thrown as the inwarded stock is reserved.
|
||||
self.assertRaises(frappe.ValidationError, se.cancel)
|
||||
|
||||
@change_settings("Stock Settings", {"allow_negative_stock": 0, "enable_stock_reservation": 1})
|
||||
def test_reserved_stock_validation_for_batch_item(self):
|
||||
item_properties = {
|
||||
"is_stock_item": 1,
|
||||
"valuation_rate": 100,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "SRBV-.#####.",
|
||||
}
|
||||
sr_item = make_item(item_code="Test Reserve Item", properties=item_properties)
|
||||
# inward 100 qty of stock
|
||||
create_material_receipt(items={sr_item.name: sr_item}, warehouse=self.warehouse, qty=100)
|
||||
|
||||
# reserve 80 qty from sales order
|
||||
so = make_sales_order(item_code=sr_item.name, warehouse=self.warehouse, qty=80)
|
||||
so.create_stock_reservation_entries()
|
||||
|
||||
# create a material issue entry including the reserved qty 10
|
||||
se = make_stock_entry(
|
||||
item_code=sr_item.name,
|
||||
qty=30,
|
||||
from_warehouse=self.warehouse,
|
||||
rate=100,
|
||||
purpose="Material Issue",
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
# validation for reserved stock should be thrown
|
||||
self.assertRaises(frappe.ValidationError, se.submit)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
cancel_all_stock_reservation_entries()
|
||||
return super().tearDown()
|
||||
|
||||
@@ -221,7 +221,9 @@ def get_child_warehouses(warehouse):
|
||||
|
||||
def get_warehouses_based_on_account(account, company=None):
|
||||
warehouses = []
|
||||
for d in frappe.get_all("Warehouse", fields=["name", "is_group"], filters={"account": account}):
|
||||
for d in frappe.get_all(
|
||||
"Warehouse", fields=["name", "is_group"], filters={"account": account, "disabled": 0}
|
||||
):
|
||||
if d.is_group:
|
||||
warehouses.extend(get_child_warehouses(d.name))
|
||||
else:
|
||||
|
||||
@@ -259,7 +259,7 @@ class SerialBatchBundle:
|
||||
and not self.sle.serial_and_batch_bundle
|
||||
and self.item_details.has_batch_no == 1
|
||||
and (
|
||||
self.item_details.create_new_batch
|
||||
(self.item_details.create_new_batch and self.sle.actual_qty > 0)
|
||||
or (
|
||||
frappe.db.get_single_value(
|
||||
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
|
||||
|
||||
@@ -2240,9 +2240,11 @@ def validate_reserved_stock(kwargs):
|
||||
kwargs.ignore_voucher_nos = [kwargs.voucher_no]
|
||||
|
||||
if kwargs.serial_no:
|
||||
kwargs.serial_nos = kwargs.serial_no.split("\n")
|
||||
validate_reserved_serial_nos(kwargs)
|
||||
|
||||
elif kwargs.batch_no:
|
||||
kwargs.batch_nos = [kwargs.batch_no]
|
||||
validate_reserved_batch_nos(kwargs)
|
||||
|
||||
elif kwargs.serial_and_batch_bundle:
|
||||
@@ -2311,6 +2313,7 @@ def validate_reserved_batch_nos(kwargs):
|
||||
"posting_date": kwargs.posting_date,
|
||||
"posting_time": kwargs.posting_time,
|
||||
"ignore_voucher_nos": kwargs.ignore_voucher_nos,
|
||||
"ignore_reserved_stock": True,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user