mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-19 23:12:13 +00:00
Merge pull request #41773 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -22,8 +22,7 @@ frappe.ui.form.on("Account", {
|
||||
// hide fields if group
|
||||
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0);
|
||||
|
||||
// disable fields
|
||||
frm.toggle_enable(["is_group", "company"], false);
|
||||
frm.toggle_enable(["is_group", "company", "account_number"], frm.is_new());
|
||||
|
||||
if (cint(frm.doc.is_group) == 0) {
|
||||
frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account);
|
||||
|
||||
@@ -55,8 +55,7 @@
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Account Number",
|
||||
"read_only": 1
|
||||
"label": "Account Number"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -72,7 +71,6 @@
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
|
||||
@@ -1525,7 +1525,8 @@
|
||||
"41-Clients et comptes rattach\u00e9s (PASSIF)": {
|
||||
"Clients cr\u00e9diteurs": {
|
||||
"Clients - Avances et acomptes re\u00e7us sur commandes": {
|
||||
"account_number": "4191"
|
||||
"account_number": "4191",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {
|
||||
"account_number": "4196"
|
||||
@@ -3141,4 +3142,4 @@
|
||||
"account_number": "7"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ class AutoMatchbyAccountIBAN:
|
||||
party, or_filters=or_filters, pluck="name", limit_page_length=1
|
||||
)
|
||||
|
||||
if "bank_ac_no" in or_filters:
|
||||
or_filters["bank_account_no"] = or_filters.pop("bank_ac_no")
|
||||
|
||||
if party_result:
|
||||
result = (
|
||||
party,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, format_date, getdate
|
||||
from frappe.utils import add_days, flt, format_date, getdate
|
||||
|
||||
|
||||
class MainCostCenterCantBeChild(frappe.ValidationError):
|
||||
@@ -60,7 +60,7 @@ class CostCenterAllocation(Document):
|
||||
self.validate_child_cost_centers()
|
||||
|
||||
def validate_total_allocation_percentage(self):
|
||||
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
|
||||
total_percentage = sum([flt(d.percentage) for d in self.get("allocation_percentages", [])])
|
||||
|
||||
if total_percentage != 100:
|
||||
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 12:35:38.645968",
|
||||
"modified": "2024-05-27 17:29:55.560840",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year",
|
||||
@@ -127,6 +127,10 @@
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Auditor"
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
|
||||
@@ -1031,6 +1031,17 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def build_gl_map(self):
|
||||
gl_map = []
|
||||
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
if self.multi_currency:
|
||||
for row in self.get("accounts"):
|
||||
if row.account_currency != company_currency:
|
||||
self.currency = row.account_currency
|
||||
self.conversion_rate = row.exchange_rate
|
||||
break
|
||||
else:
|
||||
self.currency = company_currency
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
||||
r = [d.user_remark, self.remark]
|
||||
|
||||
@@ -929,6 +929,30 @@ class TestPricingRule(unittest.TestCase):
|
||||
for doc in [si, si1]:
|
||||
doc.delete()
|
||||
|
||||
def test_pricing_rule_for_transaction_with_condition(self):
|
||||
make_item("PR Transaction Condition")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
|
||||
make_pricing_rule(
|
||||
selling=1,
|
||||
min_qty=0,
|
||||
price_or_product_discount="Product",
|
||||
apply_on="Transaction",
|
||||
free_item="PR Transaction Condition",
|
||||
free_qty=1,
|
||||
free_item_rate=10,
|
||||
condition="customer=='_Test Customer 1'",
|
||||
)
|
||||
|
||||
si = create_sales_invoice(qty=5, customer="_Test Customer 1", do_not_submit=True)
|
||||
self.assertEqual(len(si.items), 2)
|
||||
self.assertEqual(si.items[1].rate, 10)
|
||||
|
||||
si1 = create_sales_invoice(qty=5, customer="_Test Customer 2", do_not_submit=True)
|
||||
self.assertEqual(len(si1.items), 1)
|
||||
|
||||
for doc in [si, si1]:
|
||||
doc.delete()
|
||||
|
||||
def test_remove_pricing_rule(self):
|
||||
item = make_item("Water Flask")
|
||||
make_item_price("Water Flask", "_Test Price List", 100)
|
||||
|
||||
@@ -564,6 +564,7 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
|
||||
if pricing_rules:
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, doc.total, pricing_rules)
|
||||
pricing_rules = filter_pricing_rule_based_on_condition(pricing_rules, doc)
|
||||
|
||||
if not pricing_rules:
|
||||
remove_free_item(doc)
|
||||
|
||||
@@ -448,7 +448,7 @@ class PurchaseInvoice(BuyingController):
|
||||
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
self.asset_received_but_not_billed = None
|
||||
|
||||
if self.update_stock:
|
||||
self.validate_item_code()
|
||||
@@ -534,18 +534,23 @@ class PurchaseInvoice(BuyingController):
|
||||
elif item.is_fixed_asset:
|
||||
account = None
|
||||
if item.pr_detail:
|
||||
if not self.asset_received_but_not_billed:
|
||||
self.asset_received_but_not_billed = self.get_company_default(
|
||||
"asset_received_but_not_billed"
|
||||
)
|
||||
|
||||
# check if 'Asset Received But Not Billed' account is credited in Purchase receipt or not
|
||||
arbnb_booked_in_pr = frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": item.purchase_receipt,
|
||||
"account": asset_received_but_not_billed,
|
||||
"account": self.asset_received_but_not_billed,
|
||||
},
|
||||
"name",
|
||||
)
|
||||
if arbnb_booked_in_pr:
|
||||
account = asset_received_but_not_billed
|
||||
account = self.asset_received_but_not_billed
|
||||
|
||||
if not account:
|
||||
account_type = (
|
||||
|
||||
@@ -53,13 +53,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-23 17:00:42.984798",
|
||||
"modified": "2024-06-03 17:30:37.012593",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Accounting Ledger",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -68,7 +70,9 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
||||
@@ -98,13 +98,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-23 17:00:31.540640",
|
||||
"modified": "2024-06-03 17:31:04.472279",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Repost Payment Ledger",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -113,7 +115,9 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -49,7 +49,6 @@ def get_conditions(filters):
|
||||
|
||||
if filters.account_type:
|
||||
conditions["account_type"] = filters.account_type
|
||||
return conditions
|
||||
|
||||
if filters.company:
|
||||
conditions["company"] = filters.company
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
|
||||
@@ -75,11 +76,24 @@ def get_data(filters):
|
||||
asset_data = assets_details.get(d.against_voucher)
|
||||
if asset_data:
|
||||
if not asset_data.get("accumulated_depreciation_amount"):
|
||||
asset_data.accumulated_depreciation_amount = d.debit + asset_data.get(
|
||||
"opening_accumulated_depreciation"
|
||||
)
|
||||
AssetDepreciationSchedule = DocType("Asset Depreciation Schedule")
|
||||
DepreciationSchedule = DocType("Depreciation Schedule")
|
||||
query = (
|
||||
frappe.qb.from_(DepreciationSchedule)
|
||||
.join(AssetDepreciationSchedule)
|
||||
.on(DepreciationSchedule.parent == AssetDepreciationSchedule.name)
|
||||
.select(DepreciationSchedule.accumulated_depreciation_amount)
|
||||
.where(
|
||||
(AssetDepreciationSchedule.asset == d.against_voucher)
|
||||
& (DepreciationSchedule.parenttype == "Asset Depreciation Schedule")
|
||||
& (DepreciationSchedule.schedule_date == d.posting_date)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
asset_data.accumulated_depreciation_amount = query[0]["accumulated_depreciation_amount"]
|
||||
|
||||
else:
|
||||
asset_data.accumulated_depreciation_amount += d.debit
|
||||
asset_data.opening_accumulated_depreciation = asset_data.accumulated_depreciation_amount - d.debit
|
||||
|
||||
row = frappe._dict(asset_data)
|
||||
row.update(
|
||||
|
||||
@@ -1140,6 +1140,8 @@ def create_new_asset_after_split(asset, split_qty):
|
||||
|
||||
for row in new_asset.get("finance_books"):
|
||||
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
|
||||
if not current_asset_depr_schedule_doc:
|
||||
continue
|
||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||
|
||||
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row)
|
||||
|
||||
@@ -159,8 +159,9 @@ def prepare_chart_data(data, filters):
|
||||
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
|
||||
filters_filter_based_on = "Date Range"
|
||||
date_field = "purchase_date"
|
||||
filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
|
||||
filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
|
||||
filtered_data = [d for d in data if d.get(date_field)]
|
||||
filters_from_date = min(filtered_data, key=lambda a: a.get(date_field)).get(date_field)
|
||||
filters_to_date = max(filtered_data, key=lambda a: a.get(date_field)).get(date_field)
|
||||
else:
|
||||
filters_filter_based_on = filters.filter_based_on
|
||||
date_field = frappe.scrub(filters.date_based_on)
|
||||
|
||||
@@ -902,12 +902,12 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
|
||||
)
|
||||
|
||||
target_doc.populate_items_table()
|
||||
source_doc = frappe.get_doc("Purchase Order", source_name)
|
||||
|
||||
if target_doc.set_warehouse:
|
||||
for item in target_doc.items:
|
||||
item.warehouse = target_doc.set_warehouse
|
||||
else:
|
||||
source_doc = frappe.get_doc("Purchase Order", source_name)
|
||||
if source_doc.set_warehouse:
|
||||
for item in target_doc.items:
|
||||
item.warehouse = source_doc.set_warehouse
|
||||
|
||||
@@ -118,6 +118,8 @@ class Opportunity(TransactionBase, CRMNote):
|
||||
self.title = self.customer_name
|
||||
|
||||
self.calculate_totals()
|
||||
|
||||
def on_update(self):
|
||||
self.update_prospect()
|
||||
|
||||
def map_fields(self):
|
||||
|
||||
@@ -591,13 +591,13 @@
|
||||
"default": "0",
|
||||
"fieldname": "fg_based_operating_cost",
|
||||
"fieldtype": "Check",
|
||||
"label": "FG based Operating Cost"
|
||||
"label": "Finished Goods based Operating Cost"
|
||||
},
|
||||
{
|
||||
"depends_on": "fg_based_operating_cost",
|
||||
"fieldname": "fg_based_section_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "FG Based Operating Cost Section"
|
||||
"label": "Finished Goods Based Operating Cost"
|
||||
},
|
||||
{
|
||||
"depends_on": "fg_based_operating_cost",
|
||||
@@ -637,7 +637,7 @@
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-02 16:22:47.518411",
|
||||
"modified": "2024-06-03 16:24:47.518411",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM",
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "FG Item",
|
||||
"label": "Finished Goods Item",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -203,7 +203,7 @@
|
||||
{
|
||||
"fieldname": "fg_reference_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "FG Reference",
|
||||
"label": "Finished Goods Reference",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -230,7 +230,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-16 13:34:06.321061",
|
||||
"modified": "2024-06-03 18:45:24.339532",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Creator Item",
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "FG Warehouse",
|
||||
"label": "Finished Goods Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
@@ -220,7 +220,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-27 13:24:43.571844",
|
||||
"modified": "2024-06-03 13:10:20.252166",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan Item",
|
||||
|
||||
@@ -102,7 +102,12 @@ def get_columns() -> Columns:
|
||||
"fieldtype": "Float",
|
||||
"width": "150",
|
||||
},
|
||||
{"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"},
|
||||
{
|
||||
"label": _("Finished Goods Value"),
|
||||
"fieldname": "total_fg_value",
|
||||
"fieldtype": "Float",
|
||||
"width": "150",
|
||||
},
|
||||
{
|
||||
"label": _("Raw Material Value"),
|
||||
"fieldname": "total_rm_value",
|
||||
|
||||
@@ -613,7 +613,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
}
|
||||
|
||||
render_data() {
|
||||
if (this.bundle) {
|
||||
if (this.bundle || this.frm.doc.is_return) {
|
||||
frappe
|
||||
.call({
|
||||
method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.get_serial_batch_ledgers",
|
||||
@@ -621,6 +621,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
item_code: this.item.item_code,
|
||||
name: this.bundle,
|
||||
voucher_no: !this.frm.is_new() ? this.item.parent : "",
|
||||
child_row: this.frm.doc.is_return ? this.item : "",
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
|
||||
@@ -867,6 +867,9 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
fields: fields,
|
||||
primary_action: function () {
|
||||
var data = { items: d.fields_dict.items.grid.get_selected_children() };
|
||||
if (!data) {
|
||||
frappe.throw(__("Please select items"));
|
||||
}
|
||||
me.frm.call({
|
||||
method: "make_work_orders",
|
||||
args: {
|
||||
|
||||
@@ -1224,11 +1224,19 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
target.discount_amount = 0.0
|
||||
target.inter_company_order_reference = ""
|
||||
target.shipping_rule = ""
|
||||
target.tc_name = ""
|
||||
target.terms = ""
|
||||
target.payment_terms_template = ""
|
||||
target.payment_schedule = []
|
||||
|
||||
default_price_list = frappe.get_value("Supplier", supplier, "default_price_list")
|
||||
if default_price_list:
|
||||
target.buying_price_list = default_price_list
|
||||
|
||||
default_payment_terms = frappe.get_value("Supplier", supplier, "payment_terms")
|
||||
if default_payment_terms:
|
||||
target.payment_terms_template = default_payment_terms
|
||||
|
||||
if any(item.delivered_by_supplier == 1 for item in source.items):
|
||||
if source.shipping_address_name:
|
||||
target.shipping_address = source.shipping_address_name
|
||||
@@ -1280,7 +1288,6 @@ def make_purchase_order_for_default_supplier(source_name, selected_items=None, t
|
||||
"contact_person",
|
||||
"taxes_and_charges",
|
||||
"shipping_address",
|
||||
"terms",
|
||||
],
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
},
|
||||
@@ -1348,6 +1355,10 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
target.discount_amount = 0.0
|
||||
target.inter_company_order_reference = ""
|
||||
target.shipping_rule = ""
|
||||
target.tc_name = ""
|
||||
target.terms = ""
|
||||
target.payment_terms_template = ""
|
||||
target.payment_schedule = []
|
||||
|
||||
if is_drop_ship_order(target):
|
||||
target.customer = source.customer
|
||||
@@ -1383,7 +1394,6 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
"contact_person",
|
||||
"taxes_and_charges",
|
||||
"shipping_address",
|
||||
"terms",
|
||||
],
|
||||
"validation": {"docstatus": ["=", 1]},
|
||||
},
|
||||
|
||||
@@ -12,10 +12,11 @@ frappe.ui.form.on("Company", {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frm.call("check_if_transactions_exist").then((r) => {
|
||||
frm.toggle_enable("default_currency", !r.message);
|
||||
});
|
||||
if (!frm.doc.__islocal) {
|
||||
frm.call("check_if_transactions_exist").then((r) => {
|
||||
frm.toggle_enable("default_currency", !r.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
setup: function (frm) {
|
||||
frm.__rename_queue = "long";
|
||||
@@ -251,7 +252,10 @@ erpnext.company.setup_queries = function (frm) {
|
||||
["discount_allowed_account", { root_type: "Expense" }],
|
||||
["discount_received_account", { root_type: "Income" }],
|
||||
["exchange_gain_loss_account", { root_type: ["in", ["Expense", "Income"]] }],
|
||||
["unrealized_exchange_gain_loss_account", { root_type: ["in", ["Expense", "Income"]] }],
|
||||
[
|
||||
"unrealized_exchange_gain_loss_account",
|
||||
{ root_type: ["in", ["Expense", "Income", "Equity", "Liability"]] },
|
||||
],
|
||||
[
|
||||
"accumulated_depreciation_account",
|
||||
{ root_type: "Asset", account_type: "Accumulated Depreciation" },
|
||||
|
||||
@@ -795,7 +795,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-16 12:39:54.694232",
|
||||
"modified": "2024-05-27 17:32:49.057386",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
@@ -851,6 +851,10 @@
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"role": "Auditor",
|
||||
"select": 1
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
|
||||
@@ -167,6 +167,7 @@ class TestPickList(FrappeTestCase):
|
||||
"item_code": "_Test Serialized Item",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"valuation_rate": 100,
|
||||
"reconcile_all_serial_batch": 1,
|
||||
"qty": 5,
|
||||
"serial_and_batch_bundle": make_serial_batch_bundle(
|
||||
frappe._dict(
|
||||
|
||||
@@ -1209,31 +1209,44 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None):
|
||||
def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, name=None, child_row=None):
|
||||
filters = get_filters_for_bundle(
|
||||
item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name
|
||||
item_code=item_code, docstatus=docstatus, voucher_no=voucher_no, name=name, child_row=child_row
|
||||
)
|
||||
|
||||
fields = [
|
||||
"`tabSerial and Batch Bundle`.`item_code`",
|
||||
"`tabSerial and Batch Entry`.`qty`",
|
||||
"`tabSerial and Batch Entry`.`warehouse`",
|
||||
"`tabSerial and Batch Entry`.`batch_no`",
|
||||
"`tabSerial and Batch Entry`.`serial_no`",
|
||||
]
|
||||
|
||||
if not child_row:
|
||||
fields.append("`tabSerial and Batch Bundle`.`name`")
|
||||
|
||||
return frappe.get_all(
|
||||
"Serial and Batch Bundle",
|
||||
fields=[
|
||||
"`tabSerial and Batch Bundle`.`name`",
|
||||
"`tabSerial and Batch Bundle`.`item_code`",
|
||||
"`tabSerial and Batch Entry`.`qty`",
|
||||
"`tabSerial and Batch Entry`.`warehouse`",
|
||||
"`tabSerial and Batch Entry`.`batch_no`",
|
||||
"`tabSerial and Batch Entry`.`serial_no`",
|
||||
],
|
||||
fields=fields,
|
||||
filters=filters,
|
||||
order_by="`tabSerial and Batch Entry`.`idx`",
|
||||
)
|
||||
|
||||
|
||||
def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None):
|
||||
def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name=None, child_row=None):
|
||||
filters = [
|
||||
["Serial and Batch Bundle", "is_cancelled", "=", 0],
|
||||
]
|
||||
|
||||
if child_row and isinstance(child_row, str):
|
||||
child_row = parse_json(child_row)
|
||||
|
||||
if not name and child_row and child_row.get("qty") < 0:
|
||||
bundle = get_reference_serial_and_batch_bundle(child_row)
|
||||
if bundle:
|
||||
voucher_no = None
|
||||
filters.append(["Serial and Batch Bundle", "name", "=", bundle])
|
||||
|
||||
if item_code:
|
||||
filters.append(["Serial and Batch Bundle", "item_code", "=", item_code])
|
||||
|
||||
@@ -1257,6 +1270,19 @@ def get_filters_for_bundle(item_code=None, docstatus=None, voucher_no=None, name
|
||||
return filters
|
||||
|
||||
|
||||
def get_reference_serial_and_batch_bundle(child_row):
|
||||
field = {
|
||||
"Sales Invoice Item": "sales_invoice_item",
|
||||
"Delivery Note Item": "dn_detail",
|
||||
"Purchase Receipt Item": "purchase_receipt_item",
|
||||
"Purchase Invoice Item": "purchase_invoice_item",
|
||||
"POS Invoice Item": "pos_invoice_item",
|
||||
}.get(child_row.doctype)
|
||||
|
||||
if field:
|
||||
return frappe.get_cached_value(child_row.doctype, child_row.get(field), "serial_and_batch_bundle")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_serial_batch_ledgers(entries, child_row, doc, warehouse, do_not_save=False) -> object:
|
||||
if isinstance(child_row, str):
|
||||
@@ -1334,9 +1360,6 @@ def get_type_of_transaction(parent_doc, child_row):
|
||||
if parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
type_of_transaction = "Inward"
|
||||
|
||||
if parent_doc.get("is_return"):
|
||||
type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
|
||||
|
||||
if parent_doc.get("doctype") == "Subcontracting Receipt":
|
||||
type_of_transaction = "Outward"
|
||||
if child_row.get("doctype") == "Subcontracting Receipt Item":
|
||||
@@ -1344,6 +1367,14 @@ def get_type_of_transaction(parent_doc, child_row):
|
||||
elif parent_doc.get("doctype") == "Stock Reconciliation":
|
||||
type_of_transaction = "Inward"
|
||||
|
||||
if parent_doc.get("is_return"):
|
||||
type_of_transaction = "Inward"
|
||||
if (
|
||||
parent_doc.get("doctype") in ["Purchase Receipt", "Purchase Invoice"]
|
||||
or child_row.get("doctype") == "Subcontracting Receipt Item"
|
||||
):
|
||||
type_of_transaction = "Outward"
|
||||
|
||||
return type_of_transaction
|
||||
|
||||
|
||||
|
||||
@@ -206,6 +206,7 @@ frappe.ui.form.on("Stock Reconciliation", {
|
||||
posting_date: frm.doc.posting_date,
|
||||
posting_time: frm.doc.posting_time,
|
||||
batch_no: d.batch_no,
|
||||
row: d,
|
||||
},
|
||||
callback: function (r) {
|
||||
const row = frappe.model.get_doc(cdt, cdn);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold, msgprint
|
||||
from frappe import _, bold, json, msgprint
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import add_to_date, cint, cstr, flt
|
||||
|
||||
@@ -162,6 +162,11 @@ class StockReconciliation(StockController):
|
||||
def set_current_serial_and_batch_bundle(self, voucher_detail_no=None, save=False) -> None:
|
||||
"""Set Serial and Batch Bundle for each item"""
|
||||
for item in self.items:
|
||||
if not item.reconcile_all_serial_batch and item.serial_and_batch_bundle:
|
||||
bundle = self.get_bundle_for_specific_serial_batch(item)
|
||||
item.current_serial_and_batch_bundle = bundle
|
||||
continue
|
||||
|
||||
if not save and item.use_serial_batch_fields:
|
||||
continue
|
||||
|
||||
@@ -273,6 +278,75 @@ class StockReconciliation(StockController):
|
||||
}
|
||||
)
|
||||
|
||||
def get_bundle_for_specific_serial_batch(self, row) -> str:
|
||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||
|
||||
if row.current_serial_and_batch_bundle and not self.has_change_in_serial_batch(row):
|
||||
return row.current_serial_and_batch_bundle
|
||||
|
||||
cls_obj = SerialBatchCreation(
|
||||
{
|
||||
"type_of_transaction": "Outward",
|
||||
"serial_and_batch_bundle": row.serial_and_batch_bundle,
|
||||
"item_code": row.get("item_code"),
|
||||
"warehouse": row.get("warehouse"),
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"do_not_save": True,
|
||||
}
|
||||
)
|
||||
|
||||
reco_obj = cls_obj.duplicate_package()
|
||||
|
||||
total_current_qty = 0.0
|
||||
for entry in reco_obj.entries:
|
||||
if not entry.batch_no or entry.serial_no:
|
||||
total_current_qty += entry.qty
|
||||
entry.qty *= -1
|
||||
continue
|
||||
|
||||
current_qty = get_batch_qty(
|
||||
entry.batch_no,
|
||||
row.warehouse,
|
||||
row.item_code,
|
||||
posting_date=self.posting_date,
|
||||
posting_time=self.posting_time,
|
||||
)
|
||||
|
||||
total_current_qty += current_qty
|
||||
entry.qty = current_qty * -1
|
||||
|
||||
reco_obj.flags.ignore_validate = True
|
||||
reco_obj.save()
|
||||
|
||||
row.current_qty = total_current_qty
|
||||
|
||||
return reco_obj.name
|
||||
|
||||
def has_change_in_serial_batch(self, row) -> bool:
|
||||
bundles = {row.serial_and_batch_bundle: [], row.current_serial_and_batch_bundle: []}
|
||||
|
||||
data = frappe.get_all(
|
||||
"Serial and Batch Entry",
|
||||
fields=["serial_no", "batch_no", "parent"],
|
||||
filters={"parent": ("in", [row.serial_and_batch_bundle, row.current_serial_and_batch_bundle])},
|
||||
order_by="idx",
|
||||
)
|
||||
|
||||
for d in data:
|
||||
bundles[d.parent].append(d.serial_no or d.batch_no)
|
||||
|
||||
diff = set(bundles[row.serial_and_batch_bundle]) - set(bundles[row.current_serial_and_batch_bundle])
|
||||
|
||||
if diff:
|
||||
bundle = row.current_serial_and_batch_bundle
|
||||
row.current_serial_and_batch_bundle = None
|
||||
frappe.delete_doc("Serial and Batch Bundle", bundle)
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def set_new_serial_and_batch_bundle(self):
|
||||
for item in self.items:
|
||||
if item.use_serial_batch_fields:
|
||||
@@ -340,6 +414,7 @@ class StockReconciliation(StockController):
|
||||
self.posting_time,
|
||||
batch_no=item.batch_no,
|
||||
inventory_dimensions_dict=inventory_dimensions_dict,
|
||||
row=item,
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -840,6 +915,7 @@ class StockReconciliation(StockController):
|
||||
row.warehouse,
|
||||
self.posting_date,
|
||||
self.posting_time,
|
||||
row=row,
|
||||
)
|
||||
|
||||
current_qty = item_dict.get("qty")
|
||||
@@ -1166,11 +1242,18 @@ def get_stock_balance_for(
|
||||
batch_no: str | None = None,
|
||||
with_valuation_rate: bool = True,
|
||||
inventory_dimensions_dict=None,
|
||||
row=None,
|
||||
):
|
||||
frappe.has_permission("Stock Reconciliation", "write", throw=True)
|
||||
|
||||
item_dict = frappe.get_cached_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1)
|
||||
|
||||
if isinstance(row, str):
|
||||
row = json.loads(row)
|
||||
|
||||
if isinstance(row, dict):
|
||||
row = frappe._dict(row)
|
||||
|
||||
if not item_dict:
|
||||
# In cases of data upload to Items table
|
||||
msg = _("Item {} does not exist.").format(item_code)
|
||||
@@ -1188,7 +1271,7 @@ def get_stock_balance_for(
|
||||
"qty": 0,
|
||||
"rate": 0,
|
||||
"serial_nos": None,
|
||||
"use_serial_batch_fields": use_serial_batch_fields,
|
||||
"use_serial_batch_fields": row.use_serial_batch_fields if row else use_serial_batch_fields,
|
||||
}
|
||||
|
||||
# TODO: fetch only selected batch's values
|
||||
@@ -1214,7 +1297,7 @@ def get_stock_balance_for(
|
||||
"qty": qty,
|
||||
"rate": rate,
|
||||
"serial_nos": serial_nos,
|
||||
"use_serial_batch_fields": use_serial_batch_fields,
|
||||
"use_serial_batch_fields": row.use_serial_batch_fields if row else use_serial_batch_fields,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1070,6 +1070,103 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
self.assertTrue(sr.items[0].serial_and_batch_bundle)
|
||||
self.assertFalse(sr.items[0].current_serial_and_batch_bundle)
|
||||
|
||||
def test_not_reconcile_all_batch(self):
|
||||
from erpnext.stock.doctype.batch.batch import get_batch_qty
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
item = self.make_item(
|
||||
"Test Batch Item Not Reconcile All Serial Batch",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TEST-BATCH-NRALL-SRCOSRWFEE-.###",
|
||||
},
|
||||
)
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
batches = []
|
||||
for qty in [10, 20, 30]:
|
||||
se = make_stock_entry(
|
||||
item_code=item.name,
|
||||
target=warehouse,
|
||||
qty=qty,
|
||||
basic_rate=100 + qty,
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
batches.append(frappe._dict({"batch_no": batch_no, "qty": qty}))
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item.name,
|
||||
warehouse=warehouse,
|
||||
qty=100,
|
||||
rate=1000,
|
||||
reconcile_all_serial_batch=0,
|
||||
batch_no=batches[0].batch_no,
|
||||
)
|
||||
|
||||
sr.reload()
|
||||
current_sabb = sr.items[0].current_serial_and_batch_bundle
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", current_sabb)
|
||||
for row in doc.entries:
|
||||
self.assertEqual(row.batch_no, batches[0].batch_no)
|
||||
self.assertEqual(row.qty, batches[0].qty * -1)
|
||||
|
||||
batch_qty = get_batch_qty(batches[0].batch_no, warehouse, item.name)
|
||||
self.assertEqual(batch_qty, 100)
|
||||
|
||||
def test_not_reconcile_all_serial_nos(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
item = self.make_item(
|
||||
"Test Serial NO Item Not Reconcile All Serial Batch",
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": "SNN-TEST-BATCH-NRALL-S-.###",
|
||||
},
|
||||
)
|
||||
|
||||
warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
serial_nos = []
|
||||
for qty in [5, 5, 5]:
|
||||
se = make_stock_entry(
|
||||
item_code=item.name,
|
||||
target=warehouse,
|
||||
qty=qty,
|
||||
basic_rate=100 + qty,
|
||||
posting_date=nowdate(),
|
||||
)
|
||||
|
||||
serial_nos.extend(get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle))
|
||||
|
||||
sr = create_stock_reconciliation(
|
||||
item_code=item.name,
|
||||
warehouse=warehouse,
|
||||
qty=5,
|
||||
rate=1000,
|
||||
reconcile_all_serial_batch=0,
|
||||
serial_no=serial_nos[0:5],
|
||||
)
|
||||
|
||||
sr.reload()
|
||||
current_sabb = sr.items[0].current_serial_and_batch_bundle
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", current_sabb)
|
||||
for row in doc.entries:
|
||||
self.assertEqual(row.serial_no, serial_nos[row.idx - 1])
|
||||
|
||||
sabb = sr.items[0].serial_and_batch_bundle
|
||||
doc = frappe.get_doc("Serial and Batch Bundle", sabb)
|
||||
for row in doc.entries:
|
||||
self.assertEqual(row.qty, 1)
|
||||
self.assertAlmostEqual(row.incoming_rate, 1000.00)
|
||||
self.assertEqual(row.serial_no, serial_nos[row.idx - 1])
|
||||
|
||||
|
||||
def create_batch_item_with_batch(item_name, batch_id):
|
||||
batch_item_doc = create_item(item_name, is_stock_item=1)
|
||||
@@ -1193,12 +1290,16 @@ def create_stock_reconciliation(**args):
|
||||
)
|
||||
).name
|
||||
|
||||
if args.reconcile_all_serial_batch is None:
|
||||
args.reconcile_all_serial_batch = 1
|
||||
|
||||
sr.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty,
|
||||
"reconcile_all_serial_batch": args.reconcile_all_serial_batch,
|
||||
"valuation_rate": args.rate,
|
||||
"serial_no": args.serial_no if args.use_serial_batch_fields else None,
|
||||
"batch_no": args.batch_no if args.use_serial_batch_fields else None,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"serial_no_and_batch_section",
|
||||
"add_serial_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"reconcile_all_serial_batch",
|
||||
"column_break_11",
|
||||
"serial_and_batch_bundle",
|
||||
"current_serial_and_batch_bundle",
|
||||
@@ -243,11 +244,18 @@
|
||||
{
|
||||
"fieldname": "column_break_eefq",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:!doc.use_serial_batch_fields",
|
||||
"fieldname": "reconcile_all_serial_batch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Reconcile All Serial Nos / Batches"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-04 16:19:44.576022",
|
||||
"modified": "2024-05-30 23:20:00.947243",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Reconciliation Item",
|
||||
@@ -258,4 +266,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ class StockReconciliationItem(Document):
|
||||
parenttype: DF.Data
|
||||
qty: DF.Float
|
||||
quantity_difference: DF.ReadOnly | None
|
||||
reconcile_all_serial_batch: DF.Check
|
||||
serial_and_batch_bundle: DF.Link | None
|
||||
serial_no: DF.LongText | None
|
||||
use_serial_batch_fields: DF.Check
|
||||
|
||||
@@ -84,14 +84,6 @@ class StockSettings(Document):
|
||||
make_mandatory=0,
|
||||
)
|
||||
|
||||
stock_frozen_limit = 356
|
||||
submitted_stock_frozen = self.stock_frozen_upto_days or 0
|
||||
if submitted_stock_frozen > stock_frozen_limit:
|
||||
self.stock_frozen_upto_days = stock_frozen_limit
|
||||
frappe.msgprint(
|
||||
_("`Freeze Stocks Older Than` should be smaller than %d days.") % stock_frozen_limit
|
||||
)
|
||||
|
||||
# show/hide barcode field
|
||||
for name in ["barcode", "barcodes", "scan_barcode"]:
|
||||
frappe.make_property_setter(
|
||||
|
||||
@@ -7,7 +7,7 @@ from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import CombineDatetime
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
||||
@@ -67,9 +67,14 @@ def execute(filters=None):
|
||||
if filters.get("batch_no") or inventory_dimension_filters_applied:
|
||||
actual_qty += flt(sle.actual_qty, precision)
|
||||
stock_value += sle.stock_value_difference
|
||||
batch_balance_dict[sle.batch_no] += sle.actual_qty
|
||||
if sle.batch_no:
|
||||
if not batch_balance_dict.get(sle.batch_no):
|
||||
batch_balance_dict[sle.batch_no] = [0, 0]
|
||||
|
||||
batch_balance_dict[sle.batch_no][0] += sle.actual_qty
|
||||
|
||||
if filters.get("segregate_serial_batch_bundle"):
|
||||
actual_qty = batch_balance_dict[sle.batch_no]
|
||||
actual_qty = batch_balance_dict[sle.batch_no][0]
|
||||
|
||||
if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty:
|
||||
actual_qty = sle.qty_after_transaction
|
||||
@@ -525,7 +530,9 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
|
||||
query_filters = {
|
||||
"batch_no": filters.batch_no,
|
||||
"docstatus": 1,
|
||||
"is_cancelled": 0,
|
||||
"posting_date": ("<", filters.from_date),
|
||||
"company": filters.company,
|
||||
}
|
||||
|
||||
for fields in ["item_code", "warehouse"]:
|
||||
@@ -542,25 +549,30 @@ def get_opening_balance_from_batch(filters, columns, sl_entries):
|
||||
if opening_data.get(field) is None:
|
||||
opening_data[field] = 0.0
|
||||
|
||||
query_filters = [
|
||||
["Serial and Batch Entry", "batch_no", "=", filters.batch_no],
|
||||
["Serial and Batch Bundle", "docstatus", "=", 1],
|
||||
["Serial and Batch Bundle", "posting_date", "<", filters.from_date],
|
||||
]
|
||||
|
||||
for fields in ["item_code", "warehouse"]:
|
||||
if filters.get(fields):
|
||||
query_filters.append(["Serial and Batch Bundle", fields, "=", filters.get(fields)])
|
||||
|
||||
bundle_data = frappe.get_all(
|
||||
"Serial and Batch Bundle",
|
||||
fields=[
|
||||
"sum(`tabSerial and Batch Entry`.`qty`) as qty",
|
||||
"sum(`tabSerial and Batch Entry`.`stock_value_difference`) as stock_value",
|
||||
],
|
||||
filters=query_filters,
|
||||
table = frappe.qb.DocType("Stock Ledger Entry")
|
||||
sabb_table = frappe.qb.DocType("Serial and Batch Entry")
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.inner_join(sabb_table)
|
||||
.on(table.serial_and_batch_bundle == sabb_table.parent)
|
||||
.select(
|
||||
Sum(sabb_table.qty).as_("qty"),
|
||||
Sum(sabb_table.stock_value_difference).as_("stock_value"),
|
||||
)
|
||||
.where(
|
||||
(sabb_table.batch_no == filters.batch_no)
|
||||
& (sabb_table.docstatus == 1)
|
||||
& (table.posting_date < filters.from_date)
|
||||
& (table.is_cancelled == 0)
|
||||
)
|
||||
)
|
||||
|
||||
for field in ["item_code", "warehouse", "company"]:
|
||||
if filters.get(field):
|
||||
query = query.where(table[field] == filters.get(field))
|
||||
|
||||
bundle_data = query.run(as_dict=True)
|
||||
|
||||
if bundle_data:
|
||||
opening_data.qty_after_transaction += flt(bundle_data[0].qty)
|
||||
opening_data.stock_value += flt(bundle_data[0].stock_value)
|
||||
|
||||
@@ -848,10 +848,14 @@ class SerialBatchCreation:
|
||||
new_package.docstatus = 0
|
||||
new_package.warehouse = self.warehouse
|
||||
new_package.voucher_no = ""
|
||||
new_package.posting_date = today()
|
||||
new_package.posting_time = nowtime()
|
||||
new_package.posting_date = self.posting_date if hasattr(self, "posting_date") else today()
|
||||
new_package.posting_time = self.posting_time if hasattr(self, "posting_time") else nowtime()
|
||||
new_package.type_of_transaction = self.type_of_transaction
|
||||
new_package.returned_against = self.get("returned_against")
|
||||
|
||||
if self.get("do_not_save"):
|
||||
return new_package
|
||||
|
||||
new_package.save()
|
||||
|
||||
self.serial_and_batch_bundle = new_package.name
|
||||
|
||||
@@ -235,9 +235,11 @@ erpnext.buying.SubcontractingOrderController = class SubcontractingOrderControll
|
||||
}
|
||||
|
||||
has_unsupplied_items() {
|
||||
return this.frm.doc["supplied_items"].some(
|
||||
(item) => item.required_qty > item.supplied_qty - item.returned_qty
|
||||
);
|
||||
let over_transfer_allowance = this.frm.doc.__onload.over_transfer_allowance;
|
||||
return this.frm.doc["supplied_items"].some((item) => {
|
||||
let required_qty = item.required_qty + (item.required_qty * over_transfer_allowance) / 100;
|
||||
return required_qty > item.supplied_qty - item.returned_qty;
|
||||
});
|
||||
}
|
||||
|
||||
make_subcontracting_receipt() {
|
||||
|
||||
@@ -99,6 +99,12 @@ class SubcontractingOrder(SubcontractingController):
|
||||
}
|
||||
]
|
||||
|
||||
def onload(self):
|
||||
self.set_onload(
|
||||
"over_transfer_allowance",
|
||||
frappe.db.get_single_value("Buying Settings", "over_transfer_allowance"),
|
||||
)
|
||||
|
||||
def before_validate(self):
|
||||
super().before_validate()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user