fix: valuation of "finished good" item in purchase receipt (#19268)
* fix: Remove redundant purchase orders and unwanted condition * fix: [WIP] Purchase receipt value * fix: Add raw material cost based on transfered raw material * fix: get_qty_to_be_received * fix: Remove debugger statement * fix: Reset rm_supp_cost before setting subcontracted raw_materials * test: Fix and modify tests for backflush_based_on_stock_entry * fix: Add non stock items to Purchase Receipt from Purchase Order * fix: Ignore valuation rate check for non stock raw material * fix: Rename check all rows * fix: Remove amount from test * test: Fix item rate error * fix: handling of serial nos in backflush * fix: Add serial no. of raw materials * fix: [WIP] Handle Batch nos for purchase reciept backflushed raw material * fix: Raw material batch number selection in purchase receipt * Update test_purchase_order.py
This commit is contained in:
parent
8f48896261
commit
9d6d95c4a2
@ -18,6 +18,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"name": ['!=', frm.doc.supplier_warehouse],
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
@ -283,6 +284,8 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
})
|
||||
}
|
||||
|
||||
me.dialog.get_field('sub_con_rm_items').check_all_rows()
|
||||
|
||||
me.dialog.show()
|
||||
this.dialog.set_primary_action(__('Transfer'), function() {
|
||||
me.values = me.dialog.get_values();
|
||||
|
@ -519,47 +519,62 @@ class TestPurchaseOrder(unittest.TestCase):
|
||||
def test_backflush_based_on_stock_entry(self):
|
||||
item_code = "_Test Subcontracted FG Item 1"
|
||||
make_subcontracted_item(item_code)
|
||||
make_item('Sub Contracted Raw Material 1', {
|
||||
'is_stock_item': 1,
|
||||
'is_sub_contracted_item': 1
|
||||
})
|
||||
|
||||
update_backflush_based_on("Material Transferred for Subcontract")
|
||||
po = create_purchase_order(item_code=item_code, qty=1,
|
||||
|
||||
order_qty = 5
|
||||
po = create_purchase_order(item_code=item_code, qty=order_qty,
|
||||
is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
|
||||
|
||||
make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Test Extra Item 1", qty=100, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Test Extra Item 2", qty=10, basic_rate=100)
|
||||
make_stock_entry(target="_Test Warehouse - _TC",
|
||||
item_code = "Sub Contracted Raw Material 1", qty=10, basic_rate=100)
|
||||
|
||||
rm_item = [
|
||||
{"item_code":item_code,"rm_item_code":"_Test Item","item_name":"_Test Item",
|
||||
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":100,"stock_uom":"Nos"},
|
||||
rm_items = [
|
||||
{"item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 1","item_name":"_Test Item",
|
||||
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
{"item_code":item_code,"rm_item_code":"_Test Item Home Desktop 100","item_name":"_Test Item Home Desktop 100",
|
||||
"qty":2,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"},
|
||||
"qty":20,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
{"item_code":item_code,"rm_item_code":"Test Extra Item 1","item_name":"Test Extra Item 1",
|
||||
"qty":1,"warehouse":"_Test Warehouse - _TC","rate":100,"amount":200,"stock_uom":"Nos"}]
|
||||
"qty":10,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos"},
|
||||
{'item_code': item_code, 'rm_item_code': 'Test Extra Item 2', 'stock_uom':'Nos',
|
||||
'qty': 10, 'warehouse': '_Test Warehouse - _TC', 'item_name':'Test Extra Item 2'}]
|
||||
|
||||
rm_item_string = json.dumps(rm_item)
|
||||
rm_item_string = json.dumps(rm_items)
|
||||
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
|
||||
se.append('items', {
|
||||
'item_code': "Test Extra Item 2",
|
||||
"qty": 1,
|
||||
"rate": 100,
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"t_warehouse": "_Test Warehouse 1 - _TC"
|
||||
})
|
||||
se.set_missing_values()
|
||||
se.submit()
|
||||
|
||||
pr = make_purchase_receipt(po.name)
|
||||
|
||||
received_qty = 2
|
||||
# partial receipt
|
||||
pr.get('items')[0].qty = received_qty
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
se_items = sorted([d.item_code for d in se.get('items')])
|
||||
supplied_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
||||
transferred_items = sorted([d.item_code for d in se.get('items') if se.purchase_order == po.name])
|
||||
issued_items = sorted([d.rm_item_code for d in pr.get('supplied_items')])
|
||||
|
||||
self.assertEquals(transferred_items, issued_items)
|
||||
self.assertEquals(pr.get('items')[0].rm_supp_cost, 2000)
|
||||
|
||||
|
||||
transferred_rm_map = frappe._dict()
|
||||
for item in rm_items:
|
||||
transferred_rm_map[item.get('rm_item_code')] = item
|
||||
|
||||
for item in pr.get('supplied_items'):
|
||||
self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
|
||||
|
||||
self.assertEquals(se_items, supplied_items)
|
||||
update_backflush_based_on("BOM")
|
||||
|
||||
def test_advance_payment_entry_unlink_against_purchase_order(self):
|
||||
|
@ -221,7 +221,7 @@ class BuyingController(StockController):
|
||||
"backflush_raw_materials_of_subcontract_based_on")
|
||||
if (self.doctype == 'Purchase Receipt' and
|
||||
backflush_raw_materials_based_on != 'BOM'):
|
||||
self.update_raw_materials_supplied_based_on_stock_entries(raw_material_table)
|
||||
self.update_raw_materials_supplied_based_on_stock_entries()
|
||||
else:
|
||||
for item in self.get("items"):
|
||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||
@ -241,41 +241,95 @@ class BuyingController(StockController):
|
||||
if self.is_subcontracted == "No" and self.get("supplied_items"):
|
||||
self.set('supplied_items', [])
|
||||
|
||||
def update_raw_materials_supplied_based_on_stock_entries(self, raw_material_table):
|
||||
self.set(raw_material_table, [])
|
||||
purchase_orders = [d.purchase_order for d in self.items]
|
||||
if purchase_orders:
|
||||
items = get_subcontracted_raw_materials_from_se(purchase_orders)
|
||||
backflushed_raw_materials = get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, self.name)
|
||||
def update_raw_materials_supplied_based_on_stock_entries(self):
|
||||
self.set('supplied_items', [])
|
||||
|
||||
for d in items:
|
||||
qty = d.qty - backflushed_raw_materials.get(d.item_code, 0)
|
||||
rm = self.append(raw_material_table, {})
|
||||
rm.rm_item_code = d.item_code
|
||||
rm.item_name = d.item_name
|
||||
rm.main_item_code = d.main_item_code
|
||||
rm.description = d.description
|
||||
rm.stock_uom = d.stock_uom
|
||||
rm.required_qty = qty
|
||||
rm.consumed_qty = qty
|
||||
rm.serial_no = d.serial_no
|
||||
rm.batch_no = d.batch_no
|
||||
purchase_orders = set([d.purchase_order for d in self.items])
|
||||
|
||||
# get raw materials rate
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
rm.rate = get_incoming_rate({
|
||||
"item_code": d.item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * qty,
|
||||
"serial_no": rm.serial_no
|
||||
})
|
||||
if not rm.rate:
|
||||
rm.rate = get_valuation_rate(d.item_code, self.supplier_warehouse,
|
||||
self.doctype, self.name, currency=self.company_currency, company = self.company)
|
||||
# qty of raw materials backflushed (for each item per purchase order)
|
||||
backflushed_raw_materials_map = get_backflushed_subcontracted_raw_materials(purchase_orders)
|
||||
|
||||
rm.amount = qty * flt(rm.rate)
|
||||
# qty of "finished good" item yet to be received
|
||||
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
|
||||
|
||||
for item in self.get('items'):
|
||||
# reset raw_material cost
|
||||
item.rm_supp_cost = 0
|
||||
|
||||
# qty of raw materials transferred to the supplier
|
||||
transferred_raw_materials = get_subcontracted_raw_materials_from_se(item.purchase_order, item.item_code)
|
||||
|
||||
non_stock_items = get_non_stock_items(item.purchase_order, item.item_code)
|
||||
|
||||
item_key = '{}{}'.format(item.item_code, item.purchase_order)
|
||||
|
||||
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
|
||||
|
||||
raw_material_data = backflushed_raw_materials_map.get(item_key, {})
|
||||
|
||||
consumed_qty = raw_material_data.get('qty', 0)
|
||||
consumed_serial_nos = raw_material_data.get('serial_nos', '')
|
||||
consumed_batch_nos = raw_material_data.get('batch_nos', '')
|
||||
|
||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||
|
||||
for raw_material in transferred_raw_materials + non_stock_items:
|
||||
transferred_qty = raw_material.qty
|
||||
|
||||
rm_qty_to_be_consumed = transferred_qty - consumed_qty
|
||||
|
||||
# backflush all remaining transferred qty in the last Purchase Receipt
|
||||
if fg_yet_to_be_received == item.qty:
|
||||
qty = rm_qty_to_be_consumed
|
||||
else:
|
||||
qty = (rm_qty_to_be_consumed / fg_yet_to_be_received) * item.qty
|
||||
|
||||
if frappe.get_cached_value('UOM', raw_material.stock_uom, 'must_be_whole_number'):
|
||||
qty = frappe.utils.ceil(qty)
|
||||
|
||||
if qty > rm_qty_to_be_consumed:
|
||||
qty = rm_qty_to_be_consumed
|
||||
|
||||
if not qty: continue
|
||||
|
||||
if raw_material.serial_nos:
|
||||
set_serial_nos(raw_material, consumed_serial_nos, qty)
|
||||
|
||||
if raw_material.batch_nos:
|
||||
batches_qty = get_batches_with_qty(raw_material.rm_item_code, raw_material.main_item_code,
|
||||
qty, transferred_batch_qty_map, backflushed_batch_qty_map)
|
||||
for batch_data in batches_qty:
|
||||
qty = batch_data['qty']
|
||||
raw_material.batch_no = batch_data['batch']
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
else:
|
||||
self.append_raw_material_to_be_backflushed(item, raw_material, qty)
|
||||
|
||||
def append_raw_material_to_be_backflushed(self, fg_item_doc, raw_material_data, qty):
|
||||
rm = self.append('supplied_items', {})
|
||||
rm.update(raw_material_data)
|
||||
|
||||
rm.required_qty = qty
|
||||
rm.consumed_qty = qty
|
||||
|
||||
if not raw_material_data.get('non_stock_item'):
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
rm.rate = get_incoming_rate({
|
||||
"item_code": raw_material_data.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * qty,
|
||||
"serial_no": rm.serial_no
|
||||
})
|
||||
|
||||
if not rm.rate:
|
||||
rm.rate = get_valuation_rate(raw_material_data.item_code, self.supplier_warehouse,
|
||||
self.doctype, self.name, currency=self.company_currency, company=self.company)
|
||||
|
||||
rm.amount = qty * flt(rm.rate)
|
||||
fg_item_doc.rm_supp_cost += rm.amount
|
||||
|
||||
def update_raw_materials_supplied_based_on_bom(self, item, raw_material_table):
|
||||
exploded_item = 1
|
||||
@ -387,9 +441,11 @@ class BuyingController(StockController):
|
||||
item_codes = list(set(item.item_code for item in
|
||||
self.get("items")))
|
||||
if item_codes:
|
||||
self._sub_contracted_items = [r[0] for r in frappe.db.sql("""select name
|
||||
from `tabItem` where name in (%s) and is_sub_contracted_item=1""" % \
|
||||
(", ".join((["%s"]*len(item_codes))),), item_codes)]
|
||||
items = frappe.get_all('Item', filters={
|
||||
'name': ['in', item_codes],
|
||||
'is_sub_contracted_item': 1
|
||||
})
|
||||
self._sub_contracted_items = [item.name for item in items]
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
@ -722,28 +778,72 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
|
||||
|
||||
return bom_items
|
||||
|
||||
def get_subcontracted_raw_materials_from_se(purchase_orders):
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
sed.item_name, sed.item_code, sum(sed.qty) as qty, sed.description,
|
||||
sed.stock_uom, sed.subcontracted_item as main_item_code, sed.serial_no, sed.batch_no
|
||||
from `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
where
|
||||
se.name = sed.parent and se.docstatus=1 and se.purpose='Send to Subcontractor'
|
||||
and se.purchase_order in (%s) and ifnull(sed.t_warehouse, '') != ''
|
||||
group by sed.item_code, sed.t_warehouse
|
||||
""" % (','.join(['%s'] * len(purchase_orders))), tuple(purchase_orders), as_dict=1)
|
||||
def get_subcontracted_raw_materials_from_se(purchase_order, fg_item):
|
||||
common_query = """
|
||||
SELECT
|
||||
sed.item_code AS rm_item_code,
|
||||
SUM(sed.qty) AS qty,
|
||||
sed.description,
|
||||
sed.stock_uom,
|
||||
sed.subcontracted_item AS main_item_code,
|
||||
{serial_no_concat_syntax} AS serial_nos,
|
||||
{batch_no_concat_syntax} AS batch_nos
|
||||
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
WHERE
|
||||
se.name = sed.parent
|
||||
AND se.docstatus=1
|
||||
AND se.purpose='Send to Subcontractor'
|
||||
AND se.purchase_order = %s
|
||||
AND IFNULL(sed.t_warehouse, '') != ''
|
||||
AND sed.subcontracted_item = %s
|
||||
GROUP BY sed.item_code, sed.subcontracted_item
|
||||
"""
|
||||
raw_materials = frappe.db.multisql({
|
||||
'mariadb': common_query.format(
|
||||
serial_no_concat_syntax="GROUP_CONCAT(sed.serial_no)",
|
||||
batch_no_concat_syntax="GROUP_CONCAT(sed.batch_no)"
|
||||
),
|
||||
'postgres': common_query.format(
|
||||
serial_no_concat_syntax="STRING_AGG(sed.serial_no, ',')",
|
||||
batch_no_concat_syntax="STRING_AGG(sed.batch_no, ',')"
|
||||
)
|
||||
}, (purchase_order, fg_item), as_dict=1)
|
||||
|
||||
def get_backflushed_subcontracted_raw_materials_from_se(purchase_orders, purchase_receipt):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select
|
||||
prsi.rm_item_code as item_code, sum(prsi.consumed_qty) as qty
|
||||
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
||||
where
|
||||
pr.name = pri.parent and pr.name = prsi.parent and pri.purchase_order in (%s)
|
||||
and pri.item_code = prsi.main_item_code and pr.name != '%s' and pr.docstatus = 1
|
||||
group by prsi.rm_item_code
|
||||
""" % (','.join(['%s'] * len(purchase_orders)), purchase_receipt), tuple(purchase_orders)))
|
||||
return raw_materials
|
||||
|
||||
def get_backflushed_subcontracted_raw_materials(purchase_orders):
|
||||
common_query = """
|
||||
SELECT
|
||||
CONCAT(prsi.rm_item_code, pri.purchase_order) AS item_key,
|
||||
SUM(prsi.consumed_qty) AS qty,
|
||||
{serial_no_concat_syntax} AS serial_nos,
|
||||
{batch_no_concat_syntax} AS batch_nos
|
||||
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` prsi
|
||||
WHERE
|
||||
pr.name = pri.parent
|
||||
AND pr.name = prsi.parent
|
||||
AND pri.purchase_order IN %s
|
||||
AND pri.item_code = prsi.main_item_code
|
||||
AND pr.docstatus = 1
|
||||
GROUP BY prsi.rm_item_code, pri.purchase_order
|
||||
"""
|
||||
|
||||
backflushed_raw_materials = frappe.db.multisql({
|
||||
'mariadb': common_query.format(
|
||||
serial_no_concat_syntax="GROUP_CONCAT(prsi.serial_no)",
|
||||
batch_no_concat_syntax="GROUP_CONCAT(prsi.batch_no)"
|
||||
),
|
||||
'postgres': common_query.format(
|
||||
serial_no_concat_syntax="STRING_AGG(prsi.serial_no, ',')",
|
||||
batch_no_concat_syntax="STRING_AGG(prsi.batch_no, ',')"
|
||||
)
|
||||
}, (purchase_orders, ), as_dict=1)
|
||||
|
||||
backflushed_raw_materials_map = frappe._dict()
|
||||
for item in backflushed_raw_materials:
|
||||
backflushed_raw_materials_map.setdefault(item.item_key, item)
|
||||
|
||||
return backflushed_raw_materials_map
|
||||
|
||||
def get_asset_item_details(asset_items):
|
||||
asset_items_data = {}
|
||||
@ -776,3 +876,125 @@ def validate_item_type(doc, fieldname, message):
|
||||
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message))
|
||||
|
||||
frappe.throw(error_message)
|
||||
|
||||
def get_qty_to_be_received(purchase_orders):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
SELECT CONCAT(poi.`item_code`, poi.`parent`) AS item_key,
|
||||
SUM(poi.`qty`) - SUM(poi.`received_qty`) AS qty_to_be_received
|
||||
FROM `tabPurchase Order Item` poi
|
||||
WHERE
|
||||
poi.`parent` in %s
|
||||
GROUP BY poi.`item_code`, poi.`parent`
|
||||
HAVING SUM(poi.`qty`) > SUM(poi.`received_qty`)
|
||||
""", (purchase_orders)))
|
||||
|
||||
def get_non_stock_items(purchase_order, fg_item_code):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
pois.main_item_code,
|
||||
pois.rm_item_code,
|
||||
item.description,
|
||||
pois.required_qty AS qty,
|
||||
pois.rate,
|
||||
1 as non_stock_item,
|
||||
pois.stock_uom
|
||||
FROM `tabPurchase Order Item Supplied` pois, `tabItem` item
|
||||
WHERE
|
||||
pois.`rm_item_code` = item.`name`
|
||||
AND item.is_stock_item = 0
|
||||
AND pois.`parent` = %s
|
||||
AND pois.`main_item_code` = %s
|
||||
""", (purchase_order, fg_item_code), as_dict=1)
|
||||
|
||||
|
||||
def set_serial_nos(raw_material, consumed_serial_nos, qty):
|
||||
serial_nos = set(get_serial_nos(raw_material.serial_nos)) - \
|
||||
set(get_serial_nos(consumed_serial_nos))
|
||||
if serial_nos and qty <= len(serial_nos):
|
||||
raw_material.serial_no = '\n'.join(list(serial_nos)[0:frappe.utils.cint(qty)])
|
||||
|
||||
def get_transferred_batch_qty_map(purchase_order, fg_item):
|
||||
# returns
|
||||
# {
|
||||
# (item_code, fg_code): {
|
||||
# batch1: 10, # qty
|
||||
# batch2: 16
|
||||
# },
|
||||
# }
|
||||
transferred_batch_qty_map = {}
|
||||
transferred_batches = frappe.db.sql("""
|
||||
SELECT
|
||||
sed.batch_no,
|
||||
SUM(sed.qty) AS qty,
|
||||
sed.item_code
|
||||
FROM `tabStock Entry` se,`tabStock Entry Detail` sed
|
||||
WHERE
|
||||
se.name = sed.parent
|
||||
AND se.docstatus=1
|
||||
AND se.purpose='Send to Subcontractor'
|
||||
AND se.purchase_order = %s
|
||||
AND sed.subcontracted_item = %s
|
||||
AND sed.batch_no IS NOT NULL
|
||||
GROUP BY
|
||||
sed.batch_no,
|
||||
sed.item_code
|
||||
""", (purchase_order, fg_item), as_dict=1)
|
||||
|
||||
for batch_data in transferred_batches:
|
||||
transferred_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
||||
transferred_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
||||
|
||||
return transferred_batch_qty_map
|
||||
|
||||
def get_backflushed_batch_qty_map(purchase_order, fg_item):
|
||||
# returns
|
||||
# {
|
||||
# (item_code, fg_code): {
|
||||
# batch1: 10, # qty
|
||||
# batch2: 16
|
||||
# },
|
||||
# }
|
||||
backflushed_batch_qty_map = {}
|
||||
backflushed_batches = frappe.db.sql("""
|
||||
SELECT
|
||||
pris.batch_no,
|
||||
SUM(pris.consumed_qty) AS qty,
|
||||
pris.rm_item_code AS item_code
|
||||
FROM `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri, `tabPurchase Receipt Item Supplied` pris
|
||||
WHERE
|
||||
pr.name = pri.parent
|
||||
AND pri.parent = pris.parent
|
||||
AND pri.purchase_order = %s
|
||||
AND pri.item_code = pris.main_item_code
|
||||
AND pr.docstatus = 1
|
||||
AND pris.main_item_code = %s
|
||||
AND pris.batch_no IS NOT NULL
|
||||
GROUP BY
|
||||
pris.rm_item_code, pris.batch_no
|
||||
""", (purchase_order, fg_item), as_dict=1)
|
||||
|
||||
for batch_data in backflushed_batches:
|
||||
backflushed_batch_qty_map.setdefault((batch_data.item_code, fg_item), {})
|
||||
backflushed_batch_qty_map[(batch_data.item_code, fg_item)][batch_data.batch_no] = batch_data.qty
|
||||
|
||||
return backflushed_batch_qty_map
|
||||
|
||||
def get_batches_with_qty(item_code, fg_item, required_qty, transferred_batch_qty_map, backflushed_batch_qty_map):
|
||||
# Returns available batches to be backflushed based on requirements
|
||||
transferred_batches = transferred_batch_qty_map.get((item_code, fg_item), {})
|
||||
backflushed_batches = backflushed_batch_qty_map.get((item_code, fg_item), {})
|
||||
|
||||
available_batches = []
|
||||
|
||||
for (batch, transferred_qty) in transferred_batches.items():
|
||||
backflushed_qty = backflushed_batches.get(batch, 0)
|
||||
available_qty = transferred_qty - backflushed_qty
|
||||
|
||||
if available_qty >= required_qty:
|
||||
available_batches.append({'batch': batch, 'qty': required_qty})
|
||||
break
|
||||
else:
|
||||
available_batches.append({'batch': batch, 'qty': available_qty})
|
||||
required_qty -= available_qty
|
||||
|
||||
return available_batches
|
@ -9,6 +9,7 @@ from frappe import _
|
||||
from six import string_types
|
||||
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
|
||||
from frappe.model.document import Document
|
||||
import click
|
||||
|
||||
class BOMUpdateTool(Document):
|
||||
def replace_bom(self):
|
||||
@ -17,7 +18,8 @@ class BOMUpdateTool(Document):
|
||||
frappe.cache().delete_key('bom_children')
|
||||
bom_list = self.get_parent_boms(self.new_bom)
|
||||
updated_bom = []
|
||||
|
||||
with click.progressbar(bom_list) as bom_list:
|
||||
pass
|
||||
for bom in bom_list:
|
||||
try:
|
||||
bom_obj = frappe.get_cached_doc('BOM', bom)
|
||||
|
@ -15,7 +15,7 @@
|
||||
"prepared_report": 0,
|
||||
"query": "SELECT\n\t`poi_pri`.`purchase_order` as \"Purchase Order:Link/Purchase Order:120\",\n\t`poi_pri`.`status` as \"Status:Data:120\",\n\t`poi_pri`.`transaction_date` as \"Date:Date:100\",\n\t`poi_pri`.`schedule_date` as \"Reqd by Date:Date:110\",\n\t`poi_pri`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`poi_pri`.`supplier_name` as \"Supplier Name::150\",\n\t`poi_pri`.`item_code` as \"Item Code:Link/Item:120\",\n\t`poi_pri`.`qty` as \"Qty:Float:100\",\n\t`poi_pri`.`base_amount` as \"Base Amount:Currency:100\",\n\t`poi_pri`.`received_qty` as \"Received Qty:Float:100\",\n\t`poi_pri`.`received_amount` as \"Received Qty Amount:Currency:100\",\n\t`poi_pri`.`qty_to_receive` as \"Qty to Receive:Float:100\",\n\t`poi_pri`.`amount_to_be_received` as \"Amount to Receive:Currency:100\",\n\t`poi_pri`.`billed_amount` as \"Billed Amount:Currency:100\",\n\t`poi_pri`.`amount_to_be_billed` as \"Amount To Be Billed:Currency:100\",\n\tSUM(`pii`.`qty`) AS \"Billed Qty:Float:100\",\n\t`poi_pri`.qty - SUM(`pii`.`qty`) AS \"Qty To Be Billed:Float:100\",\n\t`poi_pri`.`warehouse` as \"Warehouse:Link/Warehouse:150\",\n\t`poi_pri`.`item_name` as \"Item Name::150\",\n\t`poi_pri`.`description` as \"Description::200\",\n\t`poi_pri`.`brand` as \"Brand::100\",\n\t`poi_pri`.`project` as \"Project\",\n\t`poi_pri`.`company` as \"Company:Link/Company:\"\nFROM\n\t(SELECT\n\t\t`po`.`name` AS 'purchase_order',\n\t\t`po`.`status`,\n\t\t`po`.`company`,\n\t\t`poi`.`warehouse`,\n\t\t`poi`.`brand`,\n\t\t`poi`.`description`,\n\t\t`po`.`transaction_date`,\n\t\t`poi`.`schedule_date`,\n\t\t`po`.`supplier`,\n\t\t`po`.`supplier_name`,\n\t\t`poi`.`project`,\n\t\t`poi`.`item_code`,\n\t\t`poi`.`item_name`,\n\t\t`poi`.`qty`,\n\t\t`poi`.`base_amount`,\n\t\t`poi`.`received_qty`,\n\t\t(`poi`.billed_amt * ifnull(`po`.conversion_rate, 1)) as billed_amount,\n\t\t(`poi`.base_amount - (`poi`.billed_amt * ifnull(`po`.conversion_rate, 1))) as amount_to_be_billed,\n\t\t`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0) AS 'qty_to_receive',\n\t\t(`poi`.`qty` - IFNULL(`poi`.`received_qty`, 0)) * `poi`.`rate` AS 'amount_to_be_received',\n\t\tSUM(`pri`.`amount`) AS 'received_amount',\n\t\t`poi`.`name` AS 'poi_name',\n\t\t`pri`.`name` AS 'pri_name'\n\tFROM\n\t\t`tabPurchase Order` po\n\t\tLEFT JOIN `tabPurchase Order Item` poi\n\t\tON `poi`.`parent` = `po`.`name`\n\t\tLEFT JOIN `tabPurchase Receipt Item` pri\n\t\tON `pri`.`purchase_order_item` = `poi`.`name`\n\t\t\tAND `pri`.`docstatus`=1\n\tWHERE\n\t\t`po`.`status` not in ('Stopped', 'Closed')\n\t\tAND `po`.`docstatus` = 1\n\t\tAND IFNULL(`poi`.`received_qty`, 0) < IFNULL(`poi`.`qty`, 0)\n\tGROUP BY `poi`.`name`\n\tORDER BY `po`.`transaction_date` ASC\n\t) poi_pri\n\tLEFT JOIN `tabPurchase Invoice Item` pii\n\tON `pii`.`po_detail` = `poi_pri`.`poi_name`\n\t\tAND `pii`.`docstatus`=1\nGROUP BY `poi_pri`.`poi_name`",
|
||||
"ref_doctype": "Purchase Order",
|
||||
"report_name": "Purchase Order Items To Be Received or Billed1",
|
||||
"report_name": "Purchase Order Items To Be Received or Billed",
|
||||
"report_type": "Query Report",
|
||||
"roles": [
|
||||
{
|
||||
|
@ -122,8 +122,8 @@ def get_item_details(items, sl_entries, include_uom):
|
||||
cf_field = cf_join = ""
|
||||
if include_uom:
|
||||
cf_field = ", ucd.conversion_factor"
|
||||
cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom='%s'" \
|
||||
% (include_uom)
|
||||
cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \
|
||||
% frappe.db.escape(include_uom)
|
||||
|
||||
res = frappe.db.sql("""
|
||||
select
|
||||
|
Loading…
x
Reference in New Issue
Block a user