Skip to content

Commit

Permalink
Merge branch 'develop' into mergify/bp/develop/pr-28996
Browse files Browse the repository at this point in the history
  • Loading branch information
deepeshgarg007 authored Dec 28, 2021
2 parents 057cfb2 + 27b157d commit 6405699
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ def test_deferred_expense_via_journal_entry(self):

pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
pi.set_posting_time = 1
pi.posting_date = '2019-03-15'
pi.posting_date = '2019-01-10'
pi.items[0].enable_deferred_expense = 1
pi.items[0].service_start_date = "2019-01-10"
pi.items[0].service_end_date = "2019-03-15"
Expand Down
4 changes: 2 additions & 2 deletions erpnext/non_profit/doctype/membership/membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ def get_plan_from_razorpay_id(plan_id):
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabMembership` SET `status` = 'Expired'
`tabMembership` SET `membership_status` = 'Expired'
WHERE
`status` not in ('Cancelled') AND `to_date` < %s
`membership_status` not in ('Cancelled') AND `to_date` < %s
""", (nowdate()))
79 changes: 58 additions & 21 deletions erpnext/stock/doctype/stock_entry/stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@
from erpnext.stock.utils import get_bin, get_incoming_rate


class IncorrectValuationRateError(frappe.ValidationError): pass
class DuplicateEntryForWorkOrderError(frappe.ValidationError): pass
class OperationsNotCompleteError(frappe.ValidationError): pass
class MaxSampleAlreadyRetainedError(frappe.ValidationError): pass
class FinishedGoodError(frappe.ValidationError):
pass
class IncorrectValuationRateError(frappe.ValidationError):
pass
class DuplicateEntryForWorkOrderError(frappe.ValidationError):
pass
class OperationsNotCompleteError(frappe.ValidationError):
pass
class MaxSampleAlreadyRetainedError(frappe.ValidationError):
pass

from erpnext.controllers.stock_controller import StockController

Expand Down Expand Up @@ -701,6 +707,11 @@ def mark_finished_and_scrap_items(self):

finished_item = self.get_finished_item()

if not finished_item and self.purpose == "Manufacture":
# In case of independent Manufacture entry, don't auto set
# user must decide and set
return

for d in self.items:
if d.t_warehouse and not d.s_warehouse:
if self.purpose=="Repack" or d.item_code == finished_item:
Expand All @@ -721,38 +732,64 @@ def get_finished_item(self):
return finished_item

def validate_finished_goods(self):
"""validation: finished good quantity should be same as manufacturing quantity"""
if not self.work_order: return
"""
1. Check if FG exists
2. Check if Multiple FG Items are present
3. Check FG Item and Qty against WO if present
"""
production_item, wo_qty, finished_items = None, 0, []

production_item, wo_qty = frappe.db.get_value("Work Order",
self.work_order, ["production_item", "qty"])
wo_details = frappe.db.get_value(
"Work Order", self.work_order, ["production_item", "qty"]
)
if wo_details:
production_item, wo_qty = wo_details

finished_items = []
for d in self.get('items'):
if d.is_finished_item:
if not self.work_order:
finished_items.append(d.item_code)
continue # Independent Manufacture Entry, no WO to match against

if d.item_code != production_item:
frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
.format(d.item_code, self.work_order))
.format(d.item_code, self.work_order)
)
elif flt(d.transfer_qty) > flt(self.fg_completed_qty):
frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
format(d.idx, d.transfer_qty, self.fg_completed_qty))
frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}")
.format(d.idx, d.transfer_qty, self.fg_completed_qty)
)

finished_items.append(d.item_code)

if len(set(finished_items)) > 1:
frappe.throw(_("Multiple items cannot be marked as finished item"))
frappe.throw(
msg=_("Multiple items cannot be marked as finished item"),
title=_("Note"),
exc=FinishedGoodError
)

if self.purpose == "Manufacture":
if not finished_items:
frappe.throw(_('Finished Good has not set in the stock entry {0}')
.format(self.name))
frappe.throw(
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
title=_("Missing Finished Good"),
exc=FinishedGoodError
)

allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))
allowance_percentage = flt(
frappe.db.get_single_value(
"Manufacturing Settings","overproduction_percentage_for_work_order"
)
)
allowed_qty = wo_qty + ((allowance_percentage/100) * wo_qty)

allowed_qty = wo_qty + (allowance_percentage/100 * wo_qty)
if self.fg_completed_qty > allowed_qty:
frappe.throw(_("For quantity {0} should not be greater than work order quantity {1}")
.format(flt(self.fg_completed_qty), wo_qty))
# No work order could mean independent Manufacture entry, if so skip validation
if self.work_order and self.fg_completed_qty > allowed_qty:
frappe.throw(
_("For quantity {0} should not be greater than work order quantity {1}")
.format(flt(self.fg_completed_qty), wo_qty)
)

def update_stock_ledger(self):
sl_entries = []
Expand Down
37 changes: 36 additions & 1 deletion erpnext/stock/doctype/stock_entry/test_stock_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
set_item_variant_settings,
)
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
from erpnext.stock.doctype.stock_entry.stock_entry import move_sample_to_retention_warehouse
from erpnext.stock.doctype.stock_entry.stock_entry import (
FinishedGoodError,
move_sample_to_retention_warehouse,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
Expand Down Expand Up @@ -929,6 +932,38 @@ def test_additional_cost_distribution_non_manufacture(self):
distributed_costs = [d.additional_cost for d in se.items]
self.assertEqual([40.0, 60.0], distributed_costs)

def test_independent_manufacture_entry(self):
"Test FG items and incoming rate calculation in Maniufacture Entry without WO or BOM linked."
se = frappe.get_doc(
doctype="Stock Entry",
purpose="Manufacture",
stock_entry_type="Manufacture",
company="_Test Company",
items=[
frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC")
]
)
# SE must have atleast one FG
self.assertRaises(FinishedGoodError, se.save)

se.items[0].is_finished_item = 1
se.items[1].is_finished_item = 1
# SE cannot have multiple FGs
self.assertRaises(FinishedGoodError, se.save)

se.items[0].is_finished_item = 0
se.save()

# Check if FG cost is calculated based on RM total cost
# RM total cost = 200, FG rate = 200/4(FG qty) = 50
self.assertEqual(se.items[1].basic_rate, 50)
self.assertEqual(se.value_difference, 0.0)
self.assertEqual(se.total_incoming_value, se.total_outgoing_value)

# teardown
se.delete()

@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_future_negative_sle(self):
# Initialize item, batch, warehouse, opening qty
Expand Down

0 comments on commit 6405699

Please sign in to comment.