blob: a8e056c9924c966442618b17a6da4f0e193fa30a [file] [log] [blame]
Anand Doshi885e0742015-03-03 14:55:30 +05301# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
Nabin Haiteea2b342013-10-11 18:31:33 +05302# License: GNU General Public License v3. See license.txt
3
Chillar Anand915b3432021-09-02 16:44:59 +05304
Rushabh Mehta793ba6b2014-02-14 15:47:51 +05305import frappe
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -04006from frappe.query_builder import DocType
7from frappe.query_builder.functions import Sum
8from frappe.query_builder.utils import Table
Ankush Menat77be9822022-02-11 11:29:37 +05309from frappe.utils import cstr, flt, now, nowdate, nowtime
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -040010from pypika.queries import QueryBuilder
Chillar Anand915b3432021-09-02 16:44:59 +053011
Nabin Haita77b8c92020-12-21 14:45:50 +053012from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
Chillar Anand915b3432021-09-02 16:44:59 +053013
Nabin Haiteea2b342013-10-11 18:31:33 +053014
Nabin Haitb7e46c42015-10-12 16:46:29 +053015def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False):
Nabin Haiteea2b342013-10-11 18:31:33 +053016 """
17 Repost everything!
18 """
Anand Doshie9baaa62014-02-26 12:35:33 +053019 frappe.db.auto_commit_on_many_writes = 1
Nabin Hait62985362014-04-04 12:05:16 +053020
Nabin Haitca471f42013-11-20 13:14:12 +053021 if allow_negative_stock:
Ankush Menat494bd9e2022-03-28 18:52:46 +053022 existing_allow_negative_stock = frappe.db.get_value(
23 "Stock Settings", None, "allow_negative_stock"
24 )
Nabin Hait249bbbc2014-11-26 15:35:08 +053025 frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
Nabin Hait62985362014-04-04 12:05:16 +053026
Ankush Menat494bd9e2022-03-28 18:52:46 +053027 item_warehouses = frappe.db.sql(
28 """
Nabin Hait001edb42019-09-26 16:44:35 +053029 select distinct item_code, warehouse
30 from
31 (select item_code, warehouse from tabBin
32 union
33 select item_code, warehouse from `tabStock Ledger Entry`) a
Ankush Menat494bd9e2022-03-28 18:52:46 +053034 """
35 )
Nabin Hait001edb42019-09-26 16:44:35 +053036 for d in item_warehouses:
37 try:
38 repost_stock(d[0], d[1], allow_zero_rate, only_actual, only_bin, allow_negative_stock)
39 frappe.db.commit()
Ankush Menat694ae812021-09-01 14:40:56 +053040 except Exception:
Nabin Hait001edb42019-09-26 16:44:35 +053041 frappe.db.rollback()
Nabin Hait62985362014-04-04 12:05:16 +053042
Nabin Haitca471f42013-11-20 13:14:12 +053043 if allow_negative_stock:
Ankush Menat494bd9e2022-03-28 18:52:46 +053044 frappe.db.set_value(
45 "Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock
46 )
Anand Doshie9baaa62014-02-26 12:35:33 +053047 frappe.db.auto_commit_on_many_writes = 0
Nabin Haiteea2b342013-10-11 18:31:33 +053048
Ankush Menat494bd9e2022-03-28 18:52:46 +053049
50def repost_stock(
51 item_code,
52 warehouse,
53 allow_zero_rate=False,
54 only_actual=False,
55 only_bin=False,
56 allow_negative_stock=False,
57):
Nabin Hait001edb42019-09-26 16:44:35 +053058
Nabin Haitb7e46c42015-10-12 16:46:29 +053059 if not only_bin:
Nabin Hait001edb42019-09-26 16:44:35 +053060 repost_actual_qty(item_code, warehouse, allow_zero_rate, allow_negative_stock)
Nabin Hait62985362014-04-04 12:05:16 +053061
Nabin Hait2348a5f2014-10-15 15:31:33 +053062 if item_code and warehouse and not only_actual:
Nabin Haitb7e46c42015-10-12 16:46:29 +053063 qty_dict = {
Nabin Haiteea2b342013-10-11 18:31:33 +053064 "reserved_qty": get_reserved_qty(item_code, warehouse),
65 "indented_qty": get_indented_qty(item_code, warehouse),
66 "ordered_qty": get_ordered_qty(item_code, warehouse),
Ankush Menat494bd9e2022-03-28 18:52:46 +053067 "planned_qty": get_planned_qty(item_code, warehouse),
Nabin Haitb7e46c42015-10-12 16:46:29 +053068 }
69 if only_bin:
Ankush Menat494bd9e2022-03-28 18:52:46 +053070 qty_dict.update({"actual_qty": get_balance_qty_from_sle(item_code, warehouse)})
Rushabh Mehtac4d4c7f2015-10-14 17:37:28 +053071
Nabin Haitb7e46c42015-10-12 16:46:29 +053072 update_bin_qty(item_code, warehouse, qty_dict)
Nabin Haiteea2b342013-10-11 18:31:33 +053073
Ankush Menat494bd9e2022-03-28 18:52:46 +053074
Deepesh Garg2a9c5ba2020-04-30 10:38:58 +053075def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False):
Ankush Menat494bd9e2022-03-28 18:52:46 +053076 create_repost_item_valuation_entry(
77 {
78 "item_code": item_code,
79 "warehouse": warehouse,
80 "posting_date": "1900-01-01",
81 "posting_time": "00:01",
82 "allow_negative_stock": allow_negative_stock,
83 "allow_zero_rate": allow_zero_rate,
84 }
85 )
86
Rushabh Mehtac4d4c7f2015-10-14 17:37:28 +053087
Nabin Haitb7e46c42015-10-12 16:46:29 +053088def get_balance_qty_from_sle(item_code, warehouse):
Ankush Menat494bd9e2022-03-28 18:52:46 +053089 balance_qty = frappe.db.sql(
90 """select qty_after_transaction from `tabStock Ledger Entry`
Nabin Haita77b8c92020-12-21 14:45:50 +053091 where item_code=%s and warehouse=%s and is_cancelled=0
Aditya Hase0c164242019-01-07 22:07:13 +053092 order by posting_date desc, posting_time desc, creation desc
Ankush Menat494bd9e2022-03-28 18:52:46 +053093 limit 1""",
94 (item_code, warehouse),
95 )
Rushabh Mehtac4d4c7f2015-10-14 17:37:28 +053096
Nabin Haitb7e46c42015-10-12 16:46:29 +053097 return flt(balance_qty[0][0]) if balance_qty else 0.0
Nabin Hait62985362014-04-04 12:05:16 +053098
Ankush Menat494bd9e2022-03-28 18:52:46 +053099
Nabin Haiteea2b342013-10-11 18:31:33 +0530100def get_reserved_qty(item_code, warehouse):
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400101 def append_open_so_query(q: QueryBuilder, child_table: Table) -> QueryBuilder:
102 return (
103 q.inner_join(SalesOrder)
104 .on(SalesOrder.name == child_table.parent)
105 .where(SalesOrder.docstatus == 1)
106 .where(SalesOrder.status != "Closed")
107 )
108
Devin Slauenwhite494bbf02022-06-17 13:51:33 -0400109 SalesOrder = DocType("Sales Order")
110 SalesOrderItem = DocType("Sales Order Item")
111 PackedItem = DocType("Packed Item")
112
113 dont_reserve_qty_on_sales_return = frappe.db.get_single_value(
114 "Selling Settings", "dont_reserve_sales_order_qty_on_sales_return"
115 )
116
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400117 tab = (
118 frappe.qb.from_(SalesOrderItem)
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400119 .where(SalesOrderItem.item_code == item_code)
120 .where(SalesOrderItem.warehouse == warehouse)
121 )
Devin Slauenwhite494bbf02022-06-17 13:51:33 -0400122 for field, cond in [
123 (SalesOrderItem.stock_qty.as_("dnpi_qty"), 1),
124 (SalesOrderItem.qty.as_("so_item_qty"), 1),
125 (SalesOrderItem.delivered_qty.as_("so_item_delivered_qty"), 1),
126 (SalesOrderItem.returned_qty.as_("so_item_returned_qty"), dont_reserve_qty_on_sales_return),
127 (SalesOrderItem.parent, 1),
128 (SalesOrderItem.name, 1),
129 ]:
130 if cond:
131 tab = tab.select(field)
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400132 tab = append_open_so_query(tab, SalesOrderItem)
133
134 dnpi = (
135 frappe.qb.from_(PackedItem)
136 .select(PackedItem.qty, PackedItem.parent_detail_docname, PackedItem.parent, PackedItem.name)
137 .where(PackedItem.item_code == item_code)
138 .where(PackedItem.warehouse == warehouse)
139 )
Devin Slauenwhite591b5912022-06-17 15:27:17 -0400140 dnpi = append_open_so_query(dnpi, PackedItem)
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400141
Devin Slauenwhite494bbf02022-06-17 13:51:33 -0400142 dnpi_parent = frappe.qb.from_(dnpi).select(dnpi.qty.as_("dnpi_qty"))
143 for key, so_item_field, cond in [
144 ("so_item_qty", "qty", 1),
145 ("so_item_delivered_qty", "delivered_qty", 1),
146 ("so_item_returned_qty", "returned_qty", dont_reserve_qty_on_sales_return),
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400147 ]:
Devin Slauenwhite494bbf02022-06-17 13:51:33 -0400148 if cond:
149 dnpi_parent = dnpi_parent.select(
150 (
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400151 frappe.qb.from_(SalesOrderItem)
152 .select(SalesOrderItem[so_item_field])
153 .where(SalesOrderItem.name == dnpi.parent_detail_docname)
154 .where(SalesOrderItem.delivered_by_supplier == 0)
Devin Slauenwhite494bbf02022-06-17 13:51:33 -0400155 ).as_(key)
156 )
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400157 dnpi_parent = dnpi_parent.select(dnpi.parent, dnpi.name)
158
159 dnpi_parent = dnpi_parent + tab
160
161 q = (
162 frappe.qb.from_(dnpi_parent)
163 .select(
164 Sum(
165 dnpi_parent.dnpi_qty
166 * (
Nabin Haiteea2b342013-10-11 18:31:33 +0530167 (
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400168 dnpi_parent.so_item_qty
169 - dnpi_parent.so_item_delivered_qty
Devin Slauenwhite494bbf02022-06-17 13:51:33 -0400170 - (dnpi_parent.so_item_returned_qty if dont_reserve_qty_on_sales_return else 0)
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400171 )
172 / dnpi_parent.so_item_qty
173 )
174 )
175 )
176 .where(dnpi_parent.so_item_qty >= dnpi_parent.so_item_delivered_qty)
Devin Slauenwhite03288742022-06-17 14:45:33 -0400177 )
Nabin Haiteea2b342013-10-11 18:31:33 +0530178
Devin Slauenwhitef3f26d22022-06-17 11:51:21 -0400179 reserved_qty = q.run()
Nabin Haiteea2b342013-10-11 18:31:33 +0530180 return flt(reserved_qty[0][0]) if reserved_qty else 0
Nabin Hait62985362014-04-04 12:05:16 +0530181
Ankush Menat494bd9e2022-03-28 18:52:46 +0530182
Nabin Haiteea2b342013-10-11 18:31:33 +0530183def get_indented_qty(item_code, warehouse):
Nabin Hait7eaa7f22020-04-17 10:52:28 +0530184 # Ordered Qty is always maintained in stock UOM
Ankush Menat494bd9e2022-03-28 18:52:46 +0530185 inward_qty = frappe.db.sql(
186 """
Nabin Hait03af0292020-04-16 20:11:32 +0530187 select sum(mr_item.stock_qty - mr_item.ordered_qty)
Nabin Hait4acd4312014-11-04 15:32:31 +0530188 from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
189 where mr_item.item_code=%s and mr_item.warehouse=%s
Nabin Hait7eaa7f22020-04-17 10:52:28 +0530190 and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer')
Nabin Hait03af0292020-04-16 20:11:32 +0530191 and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
192 and mr.status!='Stopped' and mr.docstatus=1
Ankush Menat494bd9e2022-03-28 18:52:46 +0530193 """,
194 (item_code, warehouse),
195 )
Nabin Hait7eaa7f22020-04-17 10:52:28 +0530196 inward_qty = flt(inward_qty[0][0]) if inward_qty else 0
Nabin Hait62985362014-04-04 12:05:16 +0530197
Ankush Menat494bd9e2022-03-28 18:52:46 +0530198 outward_qty = frappe.db.sql(
199 """
Nabin Hait03af0292020-04-16 20:11:32 +0530200 select sum(mr_item.stock_qty - mr_item.ordered_qty)
201 from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
202 where mr_item.item_code=%s and mr_item.warehouse=%s
Nabin Hait7eaa7f22020-04-17 10:52:28 +0530203 and mr.material_request_type = 'Material Issue'
Nabin Hait03af0292020-04-16 20:11:32 +0530204 and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
205 and mr.status!='Stopped' and mr.docstatus=1
Ankush Menat494bd9e2022-03-28 18:52:46 +0530206 """,
207 (item_code, warehouse),
208 )
Nabin Hait7eaa7f22020-04-17 10:52:28 +0530209 outward_qty = flt(outward_qty[0][0]) if outward_qty else 0
marination815c36a2020-03-26 14:49:28 +0530210
Nabin Hait7eaa7f22020-04-17 10:52:28 +0530211 requested_qty = inward_qty - outward_qty
Nabin Hait03af0292020-04-16 20:11:32 +0530212
213 return requested_qty
Nabin Haiteea2b342013-10-11 18:31:33 +0530214
Ankush Menat494bd9e2022-03-28 18:52:46 +0530215
Nabin Haiteea2b342013-10-11 18:31:33 +0530216def get_ordered_qty(item_code, warehouse):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530217 ordered_qty = frappe.db.sql(
218 """
Anand Doshi602e8252015-11-16 19:05:46 +0530219 select sum((po_item.qty - po_item.received_qty)*po_item.conversion_factor)
Nabin Haiteea2b342013-10-11 18:31:33 +0530220 from `tabPurchase Order Item` po_item, `tabPurchase Order` po
Nabin Hait62985362014-04-04 12:05:16 +0530221 where po_item.item_code=%s and po_item.warehouse=%s
Anand Doshi602e8252015-11-16 19:05:46 +0530222 and po_item.qty > po_item.received_qty and po_item.parent=po.name
patilsangrambf2b5112016-02-22 16:24:23 +0530223 and po.status not in ('Closed', 'Delivered') and po.docstatus=1
Ankush Menat494bd9e2022-03-28 18:52:46 +0530224 and po_item.delivered_by_supplier = 0""",
225 (item_code, warehouse),
226 )
Nabin Hait62985362014-04-04 12:05:16 +0530227
Nabin Haiteea2b342013-10-11 18:31:33 +0530228 return flt(ordered_qty[0][0]) if ordered_qty else 0
Nabin Hait62985362014-04-04 12:05:16 +0530229
Ankush Menat494bd9e2022-03-28 18:52:46 +0530230
Nabin Haiteea2b342013-10-11 18:31:33 +0530231def get_planned_qty(item_code, warehouse):
Ankush Menat494bd9e2022-03-28 18:52:46 +0530232 planned_qty = frappe.db.sql(
233 """
Zarrar13ddc7e2018-03-20 12:38:43 +0530234 select sum(qty - produced_qty) from `tabWork Order`
Conor74a782d2022-06-17 06:31:27 -0500235 where production_item = %s and fg_warehouse = %s and status not in ('Stopped', 'Completed', 'Closed')
Ankush Menat494bd9e2022-03-28 18:52:46 +0530236 and docstatus=1 and qty > produced_qty""",
237 (item_code, warehouse),
238 )
Nabin Haiteea2b342013-10-11 18:31:33 +0530239
240 return flt(planned_qty[0][0]) if planned_qty else 0
Nabin Hait62985362014-04-04 12:05:16 +0530241
242
Nabin Hait7f3f2a02014-09-01 18:16:05 +0530243def update_bin_qty(item_code, warehouse, qty_dict=None):
Rushabh Mehta1f847992013-12-12 19:12:19 +0530244 from erpnext.stock.utils import get_bin
Ankush Menat494bd9e2022-03-28 18:52:46 +0530245
Nabin Haiteea2b342013-10-11 18:31:33 +0530246 bin = get_bin(item_code, warehouse)
247 mismatch = False
marination815c36a2020-03-26 14:49:28 +0530248 for field, value in qty_dict.items():
249 if flt(bin.get(field)) != flt(value):
250 bin.set(field, flt(value))
Nabin Haiteea2b342013-10-11 18:31:33 +0530251 mismatch = True
Nabin Hait62985362014-04-04 12:05:16 +0530252
Ankush Menat77be9822022-02-11 11:29:37 +0530253 bin.modified = now()
Nabin Haiteea2b342013-10-11 18:31:33 +0530254 if mismatch:
Himanshu Mishraf56aac62018-08-08 18:32:03 +0530255 bin.set_projected_qty()
256 bin.db_update()
257 bin.clear_cache()
Nabin Hait62985362014-04-04 12:05:16 +0530258
Nabin Hait62985362014-04-04 12:05:16 +0530259
Ankush Menat494bd9e2022-03-28 18:52:46 +0530260def set_stock_balance_as_per_serial_no(
261 item_code=None, posting_date=None, posting_time=None, fiscal_year=None
262):
263 if not posting_date:
264 posting_date = nowdate()
265 if not posting_time:
266 posting_time = nowtime()
Nabin Hait62985362014-04-04 12:05:16 +0530267
Ankush Menat494bd9e2022-03-28 18:52:46 +0530268 condition = " and item.name='%s'" % item_code.replace("'", "'") if item_code else ""
269
270 bin = frappe.db.sql(
271 """select bin.item_code, bin.warehouse, bin.actual_qty, item.stock_uom
Nabin Hait62985362014-04-04 12:05:16 +0530272 from `tabBin` bin, tabItem item
Ankush Menat494bd9e2022-03-28 18:52:46 +0530273 where bin.item_code = item.name and item.has_serial_no = 1 %s"""
274 % condition
275 )
Nabin Hait62985362014-04-04 12:05:16 +0530276
277 for d in bin:
Ankush Menat494bd9e2022-03-28 18:52:46 +0530278 serial_nos = frappe.db.sql(
279 """select count(name) from `tabSerial No`
280 where item_code=%s and warehouse=%s and docstatus < 2""",
281 (d[0], d[1]),
282 )
Nabin Hait62985362014-04-04 12:05:16 +0530283
Ankush Menat494bd9e2022-03-28 18:52:46 +0530284 sle = frappe.db.sql(
285 """select valuation_rate, company from `tabStock Ledger Entry`
Nabin Haita77b8c92020-12-21 14:45:50 +0530286 where item_code = %s and warehouse = %s and is_cancelled = 0
Ankush Menat494bd9e2022-03-28 18:52:46 +0530287 order by posting_date desc limit 1""",
288 (d[0], d[1]),
289 )
Nabin Hait62985362014-04-04 12:05:16 +0530290
291 sle_dict = {
Ankush Menat494bd9e2022-03-28 18:52:46 +0530292 "doctype": "Stock Ledger Entry",
293 "item_code": d[0],
294 "warehouse": d[1],
295 "transaction_date": nowdate(),
296 "posting_date": posting_date,
297 "posting_time": posting_time,
298 "voucher_type": "Stock Reconciliation (Manual)",
299 "voucher_no": "",
300 "voucher_detail_no": "",
301 "actual_qty": flt(serial_nos[0][0]) - flt(d[2]),
302 "stock_uom": d[3],
303 "incoming_rate": sle and flt(serial_nos[0][0]) > flt(d[2]) and flt(sle[0][0]) or 0,
304 "company": sle and cstr(sle[0][1]) or 0,
305 "batch_no": "",
306 "serial_no": "",
Nabin Hait62985362014-04-04 12:05:16 +0530307 }
308
309 sle_doc = frappe.get_doc(sle_dict)
Anand Doshi6dfd4302015-02-10 14:41:27 +0530310 sle_doc.flags.ignore_validate = True
311 sle_doc.flags.ignore_links = True
Nabin Hait62985362014-04-04 12:05:16 +0530312 sle_doc.insert()
313
314 args = sle_dict.copy()
Ankush Menat494bd9e2022-03-28 18:52:46 +0530315 args.update({"sle_id": sle_doc.name})
Nabin Hait62985362014-04-04 12:05:16 +0530316
Ankush Menat494bd9e2022-03-28 18:52:46 +0530317 create_repost_item_valuation_entry(
318 {
319 "item_code": d[0],
320 "warehouse": d[1],
321 "posting_date": posting_date,
322 "posting_time": posting_time,
323 }
324 )
325
nabinhait5c384882014-07-14 11:43:00 +0530326
nabinhait7700c622014-07-14 14:21:21 +0530327def reset_serial_no_status_and_warehouse(serial_nos=None):
nabinhait5c384882014-07-14 11:43:00 +0530328 if not serial_nos:
Nabin Haitc865f222015-09-21 09:18:43 +0530329 serial_nos = frappe.db.sql_list("""select name from `tabSerial No` where docstatus = 0""")
nabinhait5c384882014-07-14 11:43:00 +0530330 for serial_no in serial_nos:
331 try:
332 sr = frappe.get_doc("Serial No", serial_no)
nabinhaitb0a8d002014-07-14 11:56:03 +0530333 last_sle = sr.get_last_sle()
334 if flt(last_sle.actual_qty) > 0:
335 sr.warehouse = last_sle.warehouse
Nabin Hait7f3f2a02014-09-01 18:16:05 +0530336
nabinhait5c384882014-07-14 11:43:00 +0530337 sr.via_stock_ledger = True
338 sr.save()
Ankush Menat694ae812021-09-01 14:40:56 +0530339 except Exception:
nabinhait5c384882014-07-14 11:43:00 +0530340 pass