Merge pull request #35161 from rohitwaghchaure/configure-notify-reposting-role
feat: configuration to notify reposting errors to specific role
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 082128a..3df48e2 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -654,6 +654,28 @@
self.precision("base_received_amount"),
)
+ def calculate_base_allocated_amount_for_reference(self, d) -> float:
+ base_allocated_amount = 0
+ if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
+ # When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
+ # This is so there are no Exchange Gain/Loss generated for such doctypes
+
+ exchange_rate = 1
+ if self.payment_type == "Receive":
+ exchange_rate = self.source_exchange_rate
+ elif self.payment_type == "Pay":
+ exchange_rate = self.target_exchange_rate
+
+ base_allocated_amount += flt(
+ flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
+ )
+ else:
+ base_allocated_amount += flt(
+ flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
+ )
+
+ return base_allocated_amount
+
def set_total_allocated_amount(self):
if self.payment_type == "Internal Transfer":
return
@@ -662,9 +684,7 @@
for d in self.get("references"):
if d.allocated_amount:
total_allocated_amount += flt(d.allocated_amount)
- base_total_allocated_amount += flt(
- flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
- )
+ base_total_allocated_amount += self.calculate_base_allocated_amount_for_reference(d)
self.total_allocated_amount = abs(total_allocated_amount)
self.base_total_allocated_amount = abs(base_total_allocated_amount)
@@ -881,9 +901,7 @@
}
)
- allocated_amount_in_company_currency = flt(
- flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("paid_amount")
- )
+ allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
gle.update(
{
@@ -1715,6 +1733,13 @@
# bank or cash
bank = get_bank_cash_account(doc, bank_account)
+ # if default bank or cash account is not set in company master and party has default company bank account, fetch it
+ if party_type in ["Customer", "Supplier"] and not bank:
+ party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
+ if party_bank_account:
+ account = frappe.db.get_value("Bank Account", party_bank_account, "account")
+ bank = get_bank_cash_account(doc, account)
+
paid_amount, received_amount = set_paid_amount_and_received_amount(
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
)
@@ -1931,19 +1956,27 @@
paid_amount = received_amount = 0
if party_account_currency == bank.account_currency:
paid_amount = received_amount = abs(outstanding_amount)
- elif payment_type == "Receive":
- paid_amount = abs(outstanding_amount)
- if bank_amount:
- received_amount = bank_amount
- else:
- received_amount = paid_amount * doc.get("conversion_rate", 1)
else:
- received_amount = abs(outstanding_amount)
- if bank_amount:
- paid_amount = bank_amount
+ company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
+ if payment_type == "Receive":
+ paid_amount = abs(outstanding_amount)
+ if bank_amount:
+ received_amount = bank_amount
+ else:
+ if company_currency != bank.account_currency:
+ received_amount = paid_amount / doc.get("conversion_rate", 1)
+ else:
+ received_amount = paid_amount * doc.get("conversion_rate", 1)
else:
- # if party account currency and bank currency is different then populate paid amount as well
- paid_amount = received_amount * doc.get("conversion_rate", 1)
+ received_amount = abs(outstanding_amount)
+ if bank_amount:
+ paid_amount = bank_amount
+ else:
+ if company_currency != bank.account_currency:
+ paid_amount = received_amount / doc.get("conversion_rate", 1)
+ else:
+ # if party account currency and bank currency is different then populate paid amount as well
+ paid_amount = received_amount * doc.get("conversion_rate", 1)
return paid_amount, received_amount
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 67049c4..68f333d 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -51,6 +51,38 @@
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
self.assertEqual(so_advance_paid, 0)
+ def test_payment_against_sales_order_usd_to_inr(self):
+ so = make_sales_order(
+ customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True
+ )
+ so.conversion_rate = 50
+ so.submit()
+ pe = get_payment_entry("Sales Order", so.name)
+ pe.source_exchange_rate = 55
+ pe.received_amount = 5500
+ pe.insert()
+ pe.submit()
+
+ # there should be no difference amount
+ pe.reload()
+ self.assertEqual(pe.difference_amount, 0)
+ self.assertEqual(pe.deductions, [])
+
+ expected_gle = dict(
+ (d[0], d)
+ for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], ["Cash - _TC", 5500.0, 0, None]]
+ )
+
+ self.validate_gl_entries(pe.name, expected_gle)
+
+ so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
+ self.assertEqual(so_advance_paid, 100)
+
+ pe.cancel()
+
+ so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
+ self.assertEqual(so_advance_paid, 0)
+
def test_payment_entry_for_blocked_supplier_invoice(self):
supplier = frappe.get_doc("Supplier", "_Test Supplier")
supplier.on_hold = 1
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index b4d369e..f76dfff 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -89,6 +89,7 @@
"column_break8",
"grand_total",
"rounding_adjustment",
+ "use_company_roundoff_cost_center",
"rounded_total",
"in_words",
"total_advance",
@@ -1559,13 +1560,19 @@
"fieldname": "only_include_allocated_payments",
"fieldtype": "Check",
"label": "Only Include Allocated Payments"
+ },
+ {
+ "default": "0",
+ "fieldname": "use_company_roundoff_cost_center",
+ "fieldtype": "Check",
+ "label": "Use Company Default Round Off Cost Center"
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2023-04-03 22:57:14.074982",
+ "modified": "2023-04-28 12:57:50.832598",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index a617447..868a150 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -978,7 +978,7 @@
def make_precision_loss_gl_entry(self, gl_entries):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
- self.company, "Purchase Invoice", self.name
+ self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
)
precision_loss = self.get("base_net_total") - flt(
@@ -992,7 +992,9 @@
"account": round_off_account,
"against": self.supplier,
"credit": precision_loss,
- "cost_center": self.cost_center or round_off_cost_center,
+ "cost_center": round_off_cost_center
+ if self.use_company_roundoff_cost_center
+ else self.cost_center or round_off_cost_center,
"remarks": _("Net total calculation precision loss"),
}
)
@@ -1386,7 +1388,7 @@
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
- self.company, "Purchase Invoice", self.name
+ self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
)
gl_entries.append(
@@ -1396,7 +1398,9 @@
"against": self.supplier,
"debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment,
- "cost_center": self.cost_center or round_off_cost_center,
+ "cost_center": round_off_cost_center
+ if self.use_company_roundoff_cost_center
+ else (self.cost_center or round_off_cost_center),
},
item=self,
)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index a41e13c..6a65b30 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -79,6 +79,7 @@
"column_break5",
"grand_total",
"rounding_adjustment",
+ "use_company_roundoff_cost_center",
"rounded_total",
"in_words",
"total_advance",
@@ -2135,6 +2136,12 @@
"fieldname": "only_include_allocated_payments",
"fieldtype": "Check",
"label": "Only Include Allocated Payments"
+ },
+ {
+ "default": "0",
+ "fieldname": "use_company_roundoff_cost_center",
+ "fieldtype": "Check",
+ "label": "Use Company default Cost Center for Round off"
}
],
"icon": "fa fa-file-text",
@@ -2147,7 +2154,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2023-04-03 22:55:14.206473",
+ "modified": "2023-04-28 14:15:59.901154",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index db61995..e16b1b1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1464,7 +1464,7 @@
and not self.is_internal_transfer()
):
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
- self.company, "Sales Invoice", self.name
+ self.company, "Sales Invoice", self.name, self.use_company_roundoff_cost_center
)
gl_entries.append(
@@ -1476,7 +1476,9 @@
self.rounding_adjustment, self.precision("rounding_adjustment")
),
"credit": flt(self.base_rounding_adjustment, self.precision("base_rounding_adjustment")),
- "cost_center": self.cost_center or round_off_cost_center,
+ "cost_center": round_off_cost_center
+ if self.use_company_roundoff_cost_center
+ else (self.cost_center or round_off_cost_center),
},
item=self,
)
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 6b2546e..a929ff1 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -475,7 +475,9 @@
round_off_gle[dimension] = dimension_values.get(dimension)
-def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
+def get_round_off_account_and_cost_center(
+ company, voucher_type, voucher_no, use_company_default=False
+):
round_off_account, round_off_cost_center = frappe.get_cached_value(
"Company", company, ["round_off_account", "round_off_cost_center"]
) or [None, None]
@@ -483,7 +485,7 @@
meta = frappe.get_meta(voucher_type)
# Give first preference to parent cost center for round off GLE
- if meta.has_field("cost_center"):
+ if not use_company_default and meta.has_field("cost_center"):
parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
if parent_cost_center:
round_off_cost_center = parent_cost_center
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index debe655..76a01db 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -538,13 +538,20 @@
query = query.where(gl_entry.cost_center.isin(filters.cost_center))
if filters.get("include_default_book_entries"):
+ company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
+
+ if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
+ frappe.throw(
+ _("To use a different finance book, please uncheck 'Include Default Book Entries'")
+ )
+
query = query.where(
- (gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
+ (gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb)]))
| (gl_entry.finance_book.isnull())
)
else:
query = query.where(
- (gl_entry.finance_book.isin([cstr(filters.company_fb), ""])) | (gl_entry.finance_book.isnull())
+ (gl_entry.finance_book.isin([cstr(filters.finance_book)])) | (gl_entry.finance_book.isnull())
)
if accounting_dimensions:
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 2100f26..57a9091 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -176,7 +176,8 @@
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
- "fieldtype": "Check"
+ "fieldtype": "Check",
+ "default": 1
},
{
"fieldname": "show_cancelled_entries",
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 27b84c4..0b05c11 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -244,13 +244,23 @@
if filters.get("project"):
conditions.append("project in %(project)s")
- if filters.get("finance_book"):
- if filters.get("include_default_book_entries"):
- conditions.append(
- "(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
- )
+ if filters.get("include_default_book_entries"):
+ if filters.get("finance_book"):
+ if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
+ filters.get("company_fb")
+ ):
+ frappe.throw(
+ _("To use a different finance book, please uncheck 'Include Default Book Entries'")
+ )
+ else:
+ conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
else:
- conditions.append("finance_book in (%(finance_book)s)")
+ conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)")
+ else:
+ if filters.get("finance_book"):
+ conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
+ else:
+ conditions.append("(finance_book IS NULL)")
if not filters.get("show_cancelled_entries"):
conditions.append("is_cancelled = 0")
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 53611ab..57dac2a 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -248,13 +248,20 @@
opening_balance = opening_balance.where(closing_balance.project == filters.project)
if filters.get("include_default_book_entries"):
+ company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
+
+ if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
+ frappe.throw(
+ _("To use a different finance book, please uncheck 'Include Default Book Entries'")
+ )
+
opening_balance = opening_balance.where(
- (closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
+ (closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb)]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
- (closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
+ (closing_balance.finance_book.isin([cstr(filters.finance_book)]))
| (closing_balance.finance_book.isnull())
)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index f899516..877362d 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -87,6 +87,12 @@
frappe.db.get_value("Work Order Operation", self.operation_id, "completed_qty")
)
+ over_production_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
+
+ wo_qty = wo_qty + (wo_qty * over_production_percentage / 100)
+
job_card_qty = frappe.get_all(
"Job Card",
fields=["sum(for_quantity)"],
@@ -101,8 +107,17 @@
job_card_qty = flt(job_card_qty[0][0]) if job_card_qty else 0
if job_card_qty and ((job_card_qty - completed_qty) > wo_qty):
- msg = f"""Job Card quantity cannot be greater than
- Work Order quantity for the operation {self.operation}"""
+ form_link = get_link_to_form("Manufacturing Settings", "Manufacturing Settings")
+
+ msg = f"""
+ Qty To Manufacture in the job card
+ cannot be greater than Qty To Manufacture in the
+ work order for the operation {bold(self.operation)}.
+ <br><br><b>Solution: </b> Either you can reduce the
+ Qty To Manufacture in the job card or set the
+ 'Overproduction Percentage For Work Order'
+ in the {form_link}."""
+
frappe.throw(_(msg), title=_("Extra Job Card Quantity"))
def set_sub_operations(self):
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
index 8c61d54..09bf1d8 100644
--- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
+++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
@@ -16,6 +16,7 @@
"column_break_4",
"quantity",
"uom",
+ "conversion_factor",
"projected_qty",
"reserved_qty_for_production",
"safety_stock",
@@ -169,11 +170,17 @@
"label": "Qty As Per BOM",
"no_copy": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "conversion_factor",
+ "fieldtype": "Float",
+ "label": "Conversion Factor",
+ "read_only": 1
}
],
"istable": 1,
"links": [],
- "modified": "2022-11-26 14:59:25.879631",
+ "modified": "2023-05-03 12:43:29.895754",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Material Request Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 62715e6..ab7aa52 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -336,10 +336,6 @@
},
get_items_for_material_requests(frm, warehouses) {
- let set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
- 'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
- 'reserved_qty_for_production', 'material_request_type'];
-
frappe.call({
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
freeze: true,
@@ -352,11 +348,11 @@
frm.set_value('mr_items', []);
r.message.forEach(row => {
let d = frm.add_child('mr_items');
- set_fields.forEach(field => {
- if (row[field]) {
+ for (let field in row) {
+ if (field !== 'name') {
d[field] = row[field];
}
- });
+ }
});
}
refresh_field('mr_items');
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 0cc0f80..df50cbf 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -28,6 +28,7 @@
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock.get_item_details import get_conversion_factor
+from erpnext.stock.utils import get_or_make_bin
from erpnext.utilities.transaction_base import validate_uom_is_integer
@@ -398,9 +399,20 @@
self.set_status()
self.db_set("status", self.status)
+ def on_submit(self):
+ self.update_bin_qty()
+
def on_cancel(self):
self.db_set("status", "Cancelled")
self.delete_draft_work_order()
+ self.update_bin_qty()
+
+ def update_bin_qty(self):
+ for d in self.mr_items:
+ if d.warehouse:
+ bin_name = get_or_make_bin(d.item_code, d.warehouse)
+ bin = frappe.get_doc("Bin", bin_name, for_update=True)
+ bin.update_reserved_qty_for_production_plan()
def delete_draft_work_order(self):
for d in frappe.get_all(
@@ -1068,6 +1080,7 @@
"item_code": row.item_code,
"item_name": row.item_name,
"quantity": required_qty / conversion_factor,
+ "conversion_factor": conversion_factor,
"required_bom_qty": total_qty,
"stock_uom": row.get("stock_uom"),
"warehouse": warehouse
@@ -1474,3 +1487,34 @@
for field in ["wip_warehouse", "fg_warehouse"]:
if not row.get(field):
row[field] = default_warehouses.get(field)
+
+
+def get_reserved_qty_for_production_plan(item_code, warehouse):
+ from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production
+
+ table = frappe.qb.DocType("Production Plan")
+ child = frappe.qb.DocType("Material Request Plan Item")
+
+ query = (
+ frappe.qb.from_(table)
+ .inner_join(child)
+ .on(table.name == child.parent)
+ .select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
+ .where(
+ (table.docstatus == 1)
+ & (child.item_code == item_code)
+ & (child.warehouse == warehouse)
+ & (table.status.notin(["Completed", "Closed"]))
+ )
+ ).run()
+
+ if not query:
+ return 0.0
+
+ reserved_qty_for_production_plan = flt(query[0][0])
+
+ reserved_qty_for_production = flt(
+ get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True)
+ )
+
+ return reserved_qty_for_production_plan - reserved_qty_for_production
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 2bf14c2..91864d0 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -868,6 +868,27 @@
for item_code in mr_items:
self.assertTrue(item_code in validate_mr_items)
+ def test_resered_qty_for_production_plan_for_material_requests(self):
+ from erpnext.stock.utils import get_or_make_bin
+
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ pln = create_production_plan(item_code="Test Production Item 1")
+
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ self.assertEqual(after_qty - before_qty, 1)
+
+ pln = frappe.get_doc("Production Plan", pln.name)
+ pln.cancel()
+
+ bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
+ after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
+
+ self.assertEqual(after_qty, before_qty)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 540b7dc..bb53c8c 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1649,6 +1649,14 @@
job_card2 = frappe.copy_doc(job_card_doc)
self.assertRaises(frappe.ValidationError, job_card2.save)
+ frappe.db.set_single_value(
+ "Manufacturing Settings", "overproduction_percentage_for_work_order", 100
+ )
+
+ job_card2 = frappe.copy_doc(job_card_doc)
+ job_card2.time_logs = []
+ job_card2.save()
+
def prepare_data_for_workstation_type_check():
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 66b871c..7584522 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -558,12 +558,19 @@
and self.production_plan_item
and not self.production_plan_sub_assembly_item
):
- qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
+ table = frappe.qb.DocType("Work Order")
- if self.docstatus == 1:
- qty += self.qty
- elif self.docstatus == 2:
- qty -= self.qty
+ query = (
+ frappe.qb.from_(table)
+ .select(Sum(table.qty))
+ .where(
+ (table.production_plan == self.production_plan)
+ & (table.production_plan_item == self.production_plan_item)
+ & (table.docstatus == 1)
+ )
+ ).run()
+
+ qty = flt(query[0][0]) if query else 0
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
@@ -1476,12 +1483,14 @@
return doc
-def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
+def get_reserved_qty_for_production(
+ item_code: str, warehouse: str, check_production_plan: bool = False
+) -> float:
"""Get total reserved quantity for any item in specified warehouse"""
wo = frappe.qb.DocType("Work Order")
wo_item = frappe.qb.DocType("Work Order Item")
- return (
+ query = (
frappe.qb.from_(wo)
.from_(wo_item)
.select(
@@ -1502,7 +1511,12 @@
| (wo_item.required_qty > wo_item.consumed_qty)
)
)
- ).run()[0][0] or 0.0
+ )
+
+ if check_production_plan:
+ query = query.where(wo.production_plan.isnotnull())
+
+ return query.run()[0][0] or 0.0
@frappe.whitelist()
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index d822f4a..a115727 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -15,6 +15,7 @@
"projected_qty",
"reserved_qty_for_production",
"reserved_qty_for_sub_contract",
+ "reserved_qty_for_production_plan",
"ma_rate",
"stock_uom",
"fcfs_rate",
@@ -165,13 +166,19 @@
"oldfieldname": "stock_value",
"oldfieldtype": "Currency",
"read_only": 1
+ },
+ {
+ "fieldname": "reserved_qty_for_production_plan",
+ "fieldtype": "Float",
+ "label": "Reserved Qty for Production Plan",
+ "read_only": 1
}
],
"hide_toolbar": 1,
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2022-03-30 07:22:23.868602",
+ "modified": "2023-05-02 23:26:21.806965",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 72654e6..5abea9e 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -24,8 +24,30 @@
- flt(self.reserved_qty)
- flt(self.reserved_qty_for_production)
- flt(self.reserved_qty_for_sub_contract)
+ - flt(self.reserved_qty_for_production_plan)
)
+ def update_reserved_qty_for_production_plan(self, skip_project_qty_update=False):
+ """Update qty reserved for production from Production Plan tables
+ in open production plan"""
+ from erpnext.manufacturing.doctype.production_plan.production_plan import (
+ get_reserved_qty_for_production_plan,
+ )
+
+ self.reserved_qty_for_production_plan = get_reserved_qty_for_production_plan(
+ self.item_code, self.warehouse
+ )
+
+ self.db_set(
+ "reserved_qty_for_production_plan",
+ flt(self.reserved_qty_for_production_plan),
+ update_modified=True,
+ )
+
+ if not skip_project_qty_update:
+ self.set_projected_qty()
+ self.db_set("projected_qty", self.projected_qty, update_modified=True)
+
def update_reserved_qty_for_production(self):
"""Update qty reserved for production from Production Item tables
in open work orders"""
@@ -35,11 +57,13 @@
self.item_code, self.warehouse
)
- self.set_projected_qty()
-
self.db_set(
"reserved_qty_for_production", flt(self.reserved_qty_for_production), update_modified=True
)
+
+ self.update_reserved_qty_for_production_plan(skip_project_qty_update=True)
+
+ self.set_projected_qty()
self.db_set("projected_qty", self.projected_qty, update_modified=True)
def update_reserved_qty_for_sub_contracting(self, subcontract_doctype="Subcontracting Order"):
@@ -141,6 +165,7 @@
"planned_qty",
"reserved_qty_for_production",
"reserved_qty_for_sub_contract",
+ "reserved_qty_for_production_plan",
],
as_dict=1,
)
@@ -188,6 +213,7 @@
- flt(reserved_qty)
- flt(bin_details.reserved_qty_for_production)
- flt(bin_details.reserved_qty_for_sub_contract)
+ - flt(bin_details.reserved_qty_for_production_plan)
)
frappe.db.set_value(
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 5304273..3373d8a 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -379,7 +379,7 @@
)
outgoing_amount = d.base_net_amount
- if self.is_internal_supplier and d.valuation_rate:
+ if self.is_internal_transfer() and d.valuation_rate:
outgoing_amount = abs(
frappe.db.get_value(
"Stock Ledger Entry",
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index f477d8f..31c756d 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -76,6 +76,7 @@
bin.ordered_qty,
bin.reserved_qty,
bin.reserved_qty_for_production,
+ bin.reserved_qty_for_production_plan,
bin.reserved_qty_for_sub_contract,
reserved_qty_for_pos,
bin.projected_qty,
@@ -174,6 +175,13 @@
"convertible": "qty",
},
{
+ "label": _("Reserved for Production Plan"),
+ "fieldname": "reserved_qty_for_production_plan",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
"label": _("Reserved for Sub Contracting"),
"fieldname": "reserved_qty_for_sub_contract",
"fieldtype": "Float",
@@ -232,6 +240,7 @@
bin.reserved_qty,
bin.reserved_qty_for_production,
bin.reserved_qty_for_sub_contract,
+ bin.reserved_qty_for_production_plan,
bin.projected_qty,
)
.orderby(bin.item_code, bin.warehouse)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 8b517bf..103ed4a 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -556,7 +556,7 @@
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no
and sle.actual_qty < 0
- and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
+ and is_internal_transfer(sle)
):
sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle)
@@ -679,7 +679,7 @@
elif (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no
- and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
+ and is_internal_transfer(sle)
):
rate = get_incoming_rate_for_inter_company_transfer(sle)
else:
@@ -1609,3 +1609,15 @@
)
return rate
+
+
+def is_internal_transfer(sle):
+ data = frappe.get_cached_value(
+ sle.voucher_type,
+ sle.voucher_no,
+ ["is_internal_supplier", "represents_company", "company"],
+ as_dict=True,
+ )
+
+ if data.is_internal_supplier and data.represents_company == data.company:
+ return True