mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-05 21:29:11 +00:00
Merge branch 'frappe:develop' into add-employee-name-to-session-user
This commit is contained in:
@@ -212,7 +212,7 @@ def get_opening_balance(
|
|||||||
ignore_is_opening=0,
|
ignore_is_opening=0,
|
||||||
):
|
):
|
||||||
closing_balance = frappe.qb.DocType(doctype)
|
closing_balance = frappe.qb.DocType(doctype)
|
||||||
account = frappe.qb.DocType("Account")
|
accounts = frappe.db.get_all("Account", filters={"report_type": report_type}, pluck="name")
|
||||||
|
|
||||||
opening_balance = (
|
opening_balance = (
|
||||||
frappe.qb.from_(closing_balance)
|
frappe.qb.from_(closing_balance)
|
||||||
@@ -224,14 +224,7 @@ def get_opening_balance(
|
|||||||
Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
|
Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
|
||||||
Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
|
Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
|
||||||
)
|
)
|
||||||
.where(
|
.where((closing_balance.company == filters.company) & (closing_balance.account.isin(accounts)))
|
||||||
(closing_balance.company == filters.company)
|
|
||||||
& (
|
|
||||||
closing_balance.account.isin(
|
|
||||||
frappe.qb.from_(account).select("name").where(account.report_type == report_type)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.groupby(closing_balance.account)
|
.groupby(closing_balance.account)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -286,21 +279,24 @@ def get_opening_balance(
|
|||||||
if filters.project:
|
if filters.project:
|
||||||
opening_balance = opening_balance.where(closing_balance.project == filters.project)
|
opening_balance = opening_balance.where(closing_balance.project == filters.project)
|
||||||
|
|
||||||
if filters.get("include_default_book_entries"):
|
if frappe.db.count("Finance Book"):
|
||||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
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):
|
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 FB Entries'"))
|
frappe.throw(
|
||||||
|
_("To use a different finance book, please uncheck 'Include Default FB Entries'")
|
||||||
|
)
|
||||||
|
|
||||||
opening_balance = opening_balance.where(
|
opening_balance = opening_balance.where(
|
||||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||||
| (closing_balance.finance_book.isnull())
|
| (closing_balance.finance_book.isnull())
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
opening_balance = opening_balance.where(
|
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())
|
| (closing_balance.finance_book.isnull())
|
||||||
)
|
)
|
||||||
|
|
||||||
if accounting_dimensions:
|
if accounting_dimensions:
|
||||||
for dimension in accounting_dimensions:
|
for dimension in accounting_dimensions:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from frappe.utils import cint, flt, format_datetime, get_datetime
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
|
from erpnext.stock.serial_batch_bundle import get_batches_from_bundle
|
||||||
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
from erpnext.stock.serial_batch_bundle import get_serial_nos as get_serial_nos_from_bundle
|
||||||
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
|
from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method
|
||||||
|
|
||||||
|
|
||||||
class StockOverReturnError(frappe.ValidationError):
|
class StockOverReturnError(frappe.ValidationError):
|
||||||
@@ -1082,8 +1082,7 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
|
|||||||
"batches": data.get("batches"),
|
"batches": data.get("batches"),
|
||||||
"serial_nos_valuation": data.get("serial_nos_valuation"),
|
"serial_nos_valuation": data.get("serial_nos_valuation"),
|
||||||
"batches_valuation": data.get("batches_valuation"),
|
"batches_valuation": data.get("batches_valuation"),
|
||||||
"posting_date": parent_doc.posting_date,
|
"posting_datetime": get_combine_datetime(parent_doc.posting_date, parent_doc.posting_time),
|
||||||
"posting_time": parent_doc.posting_time,
|
|
||||||
"voucher_type": parent_doc.doctype,
|
"voucher_type": parent_doc.doctype,
|
||||||
"voucher_no": parent_doc.name,
|
"voucher_no": parent_doc.name,
|
||||||
"voucher_detail_no": child_doc.name,
|
"voucher_detail_no": child_doc.name,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
|
|||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
from erpnext.stock.doctype.item.item import set_item_default
|
from erpnext.stock.doctype.item.item import set_item_default
|
||||||
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
|
from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor
|
||||||
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
|
from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method
|
||||||
|
|
||||||
|
|
||||||
class SellingController(StockController):
|
class SellingController(StockController):
|
||||||
@@ -1017,8 +1017,7 @@ def get_serial_and_batch_bundle(child, parent, delivery_note_child=None):
|
|||||||
"voucher_type": parent.doctype,
|
"voucher_type": parent.doctype,
|
||||||
"voucher_no": parent.name if parent.docstatus < 2 else None,
|
"voucher_no": parent.name if parent.docstatus < 2 else None,
|
||||||
"voucher_detail_no": delivery_note_child.name if delivery_note_child else child.name,
|
"voucher_detail_no": delivery_note_child.name if delivery_note_child else child.name,
|
||||||
"posting_date": parent.posting_date,
|
"posting_datetime": get_combine_datetime(parent.posting_date, parent.posting_time),
|
||||||
"posting_time": parent.posting_time,
|
|
||||||
"qty": child.qty,
|
"qty": child.qty,
|
||||||
"type_of_transaction": "Outward" if child.qty > 0 and parent.docstatus < 2 else "Inward",
|
"type_of_transaction": "Outward" if child.qty > 0 and parent.docstatus < 2 else "Inward",
|
||||||
"company": parent.company,
|
"company": parent.company,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
|
|||||||
get_evaluated_inventory_dimension,
|
get_evaluated_inventory_dimension,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
combine_datetime,
|
||||||
get_type_of_transaction,
|
get_type_of_transaction,
|
||||||
)
|
)
|
||||||
from erpnext.stock.stock_ledger import get_items_to_be_repost
|
from erpnext.stock.stock_ledger import get_items_to_be_repost
|
||||||
@@ -266,8 +267,7 @@ class StockController(AccountsController):
|
|||||||
):
|
):
|
||||||
bundle_details = {
|
bundle_details = {
|
||||||
"item_code": row.get("rm_item_code") or row.item_code,
|
"item_code": row.get("rm_item_code") or row.item_code,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": combine_datetime(self.posting_date, self.posting_time),
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": row.name,
|
"voucher_detail_no": row.name,
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ from frappe.utils import cint, flt, get_link_to_form
|
|||||||
|
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
combine_datetime,
|
||||||
|
get_auto_batch_nos,
|
||||||
|
get_available_serial_nos,
|
||||||
get_voucher_wise_serial_batch_from_bundle,
|
get_voucher_wise_serial_batch_from_bundle,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
@@ -52,9 +55,42 @@ class SubcontractingController(StockController):
|
|||||||
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
|
||||||
self.validate_items()
|
self.validate_items()
|
||||||
self.create_raw_materials_supplied()
|
self.create_raw_materials_supplied()
|
||||||
|
self.set_valuation_rate_for_rm()
|
||||||
else:
|
else:
|
||||||
super().validate()
|
super().validate()
|
||||||
|
|
||||||
|
def set_valuation_rate_for_rm(self):
|
||||||
|
rate_changed = False
|
||||||
|
if self.doctype == "Subcontracting Receipt":
|
||||||
|
for row in self.supplied_items:
|
||||||
|
kwargs = frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": row.rm_item_code,
|
||||||
|
"warehouse": self.supplier_warehouse,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"qty": flt(row.consumed_qty) * (-1 if not self.is_return else 1),
|
||||||
|
"voucher_type": self.doctype,
|
||||||
|
"voucher_no": self.name,
|
||||||
|
"company": self.company,
|
||||||
|
"serial_and_batch_bundle": row.serial_and_batch_bundle,
|
||||||
|
"voucher_detail_no": row.name,
|
||||||
|
"batch_no": row.batch_no,
|
||||||
|
"serial_no": row.serial_no,
|
||||||
|
"use_serial_batch_fields": row.use_serial_batch_fields,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
rate = get_incoming_rate(kwargs)
|
||||||
|
precision = frappe.get_precision("Subcontracting Receipt Supplied Item", "rate")
|
||||||
|
if flt(rate, precision) != flt(row.rate, precision):
|
||||||
|
row.rate = rate
|
||||||
|
row.amount = flt(row.consumed_qty) * flt(rate)
|
||||||
|
rate_changed = True
|
||||||
|
|
||||||
|
if rate_changed:
|
||||||
|
self.calculate_items_qty_and_amount()
|
||||||
|
|
||||||
def validate_rejected_warehouse(self):
|
def validate_rejected_warehouse(self):
|
||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.rejected_qty) and not item.rejected_warehouse:
|
if flt(item.rejected_qty) and not item.rejected_warehouse:
|
||||||
@@ -537,8 +573,7 @@ class SubcontractingController(StockController):
|
|||||||
"qty": qty,
|
"qty": qty,
|
||||||
"serial_nos": serial_nos,
|
"serial_nos": serial_nos,
|
||||||
"batches": batches,
|
"batches": batches,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": combine_datetime(self.posting_date, self.posting_time),
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"voucher_type": "Subcontracting Receipt",
|
"voucher_type": "Subcontracting Receipt",
|
||||||
"do_not_submit": True,
|
"do_not_submit": True,
|
||||||
"type_of_transaction": "Outward" if qty > 0 else "Inward",
|
"type_of_transaction": "Outward" if qty > 0 else "Inward",
|
||||||
@@ -616,6 +651,64 @@ class SubcontractingController(StockController):
|
|||||||
self.set_rate_for_supplied_items(rm_obj, item_row)
|
self.set_rate_for_supplied_items(rm_obj, item_row)
|
||||||
elif self.backflush_based_on == "BOM":
|
elif self.backflush_based_on == "BOM":
|
||||||
self.update_rate_for_supplied_items()
|
self.update_rate_for_supplied_items()
|
||||||
|
self.set_batch_for_supplied_items()
|
||||||
|
|
||||||
|
def set_batch_for_supplied_items(self):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
|
||||||
|
from erpnext.stock.get_item_details import get_filtered_serial_nos
|
||||||
|
|
||||||
|
for row in self.supplied_items:
|
||||||
|
item_details = frappe.get_cached_value(
|
||||||
|
"Item", row.rm_item_code, ["has_batch_no", "has_serial_no"], as_dict=1
|
||||||
|
)
|
||||||
|
|
||||||
|
if not item_details.has_batch_no and not item_details.has_serial_no:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not row.use_serial_batch_fields:
|
||||||
|
continue
|
||||||
|
|
||||||
|
kwargs = frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": row.rm_item_code,
|
||||||
|
"warehouse": self.supplier_warehouse,
|
||||||
|
"posting_date": self.posting_date,
|
||||||
|
"posting_time": self.posting_time,
|
||||||
|
"qty": flt(row.consumed_qty),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if item_details.has_serial_no and not row.serial_and_batch_bundle and not row.serial_no:
|
||||||
|
serial_nos = get_available_serial_nos(kwargs)
|
||||||
|
if serial_nos:
|
||||||
|
serial_nos = [sn.get("serial_no") for sn in serial_nos]
|
||||||
|
serial_nos = get_filtered_serial_nos(serial_nos, self, "supplied_items")
|
||||||
|
row.serial_no = "\n".join(serial_nos)
|
||||||
|
|
||||||
|
elif item_details.has_batch_no and not row.serial_and_batch_bundle and not row.batch_no:
|
||||||
|
batches = get_auto_batch_nos(kwargs)
|
||||||
|
if batches:
|
||||||
|
consumed_qty = row.consumed_qty
|
||||||
|
for index, d in enumerate(batches):
|
||||||
|
if consumed_qty <= 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
if index == 0:
|
||||||
|
row.batch_no = d.get("batch_no")
|
||||||
|
row.consumed_qty = d.get("qty")
|
||||||
|
consumed_qty -= d.get("qty")
|
||||||
|
else:
|
||||||
|
new_row = self.append("supplied_items", {})
|
||||||
|
new_row.update(frappe.copy_doc(row).as_dict())
|
||||||
|
new_row.update(
|
||||||
|
{
|
||||||
|
"consumed_qty": d.get("qty"),
|
||||||
|
"batch_no": d.get("batch_no"),
|
||||||
|
"rate": row.rate,
|
||||||
|
"amount": flt(d.get("qty")) * flt(row.rate),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
consumed_qty -= d.get("qty")
|
||||||
|
|
||||||
def update_rate_for_supplied_items(self):
|
def update_rate_for_supplied_items(self):
|
||||||
if self.doctype != "Subcontracting Receipt":
|
if self.doctype != "Subcontracting Receipt":
|
||||||
|
|||||||
@@ -1308,6 +1308,7 @@ def make_subcontracted_items():
|
|||||||
"Subcontracted Item SA7": {},
|
"Subcontracted Item SA7": {},
|
||||||
"Subcontracted Item SA8": {},
|
"Subcontracted Item SA8": {},
|
||||||
"Subcontracted Item SA9": {"stock_uom": "Litre"},
|
"Subcontracted Item SA9": {"stock_uom": "Litre"},
|
||||||
|
"Subcontracted Item SA10": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for item, properties in sub_contracted_items.items():
|
for item, properties in sub_contracted_items.items():
|
||||||
@@ -1329,6 +1330,7 @@ def make_raw_materials():
|
|||||||
"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"},
|
"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"},
|
||||||
"Subcontracted SRM Item 8": {},
|
"Subcontracted SRM Item 8": {},
|
||||||
"Subcontracted SRM Item 9": {"stock_uom": "Litre"},
|
"Subcontracted SRM Item 9": {"stock_uom": "Litre"},
|
||||||
|
"Subcontracted SRM Item 10": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for item, properties in raw_materials.items():
|
for item, properties in raw_materials.items():
|
||||||
@@ -1357,6 +1359,7 @@ def make_service_items():
|
|||||||
"Subcontracted Service Item 7": {},
|
"Subcontracted Service Item 7": {},
|
||||||
"Subcontracted Service Item 8": {},
|
"Subcontracted Service Item 8": {},
|
||||||
"Subcontracted Service Item 9": {},
|
"Subcontracted Service Item 9": {},
|
||||||
|
"Subcontracted Service Item 10": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for item, properties in service_items.items():
|
for item, properties in service_items.items():
|
||||||
@@ -1381,6 +1384,7 @@ def make_bom_for_subcontracted_items():
|
|||||||
"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
|
"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
|
||||||
"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
|
"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
|
||||||
"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
|
"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
|
||||||
|
"Subcontracted Item SA10": ["Subcontracted SRM Item 10"],
|
||||||
}
|
}
|
||||||
|
|
||||||
for item_code, raw_materials in boms.items():
|
for item_code, raw_materials in boms.items():
|
||||||
|
|||||||
@@ -1487,7 +1487,9 @@ def add_operating_cost_component_wise(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for wc in workstation_cost:
|
for wc in workstation_cost:
|
||||||
expense_account = get_component_account(wc.operating_component) or op_expense_account
|
expense_account = (
|
||||||
|
get_component_account(wc.operating_component, stock_entry.company) or op_expense_account
|
||||||
|
)
|
||||||
actual_cp_operating_cost = flt(
|
actual_cp_operating_cost = flt(
|
||||||
flt(wc.operating_cost) * flt(flt(row.actual_operation_time) / 60.0),
|
flt(wc.operating_cost) * flt(flt(row.actual_operation_time) / 60.0),
|
||||||
row.precision("actual_operating_cost"),
|
row.precision("actual_operating_cost"),
|
||||||
@@ -1513,8 +1515,10 @@ def add_operating_cost_component_wise(
|
|||||||
|
|
||||||
|
|
||||||
@frappe.request_cache
|
@frappe.request_cache
|
||||||
def get_component_account(parent):
|
def get_component_account(parent, company):
|
||||||
return frappe.db.get_value("Workstation Operating Component Account", parent, "expense_account")
|
return frappe.db.get_value(
|
||||||
|
"Workstation Operating Component Account", {"parent": parent, "company": company}, "expense_account"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_operations_cost(stock_entry, work_order=None, expense_account=None, job_card=None):
|
def add_operations_cost(stock_entry, work_order=None, expense_account=None, job_card=None):
|
||||||
|
|||||||
@@ -437,3 +437,4 @@ erpnext.patches.v16_0.set_invoice_type_in_pos_settings
|
|||||||
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
|
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
|
||||||
erpnext.patches.v16_0.make_workstation_operating_components #1
|
erpnext.patches.v16_0.make_workstation_operating_components #1
|
||||||
erpnext.patches.v16_0.set_reporting_currency
|
erpnext.patches.v16_0.set_reporting_currency
|
||||||
|
erpnext.patches.v16_0.set_posting_datetime_for_sabb_and_drop_indexes
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import click
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.db.sql(
|
||||||
|
"""
|
||||||
|
UPDATE `tabSerial and Batch Bundle`
|
||||||
|
JOIN `tabStock Ledger Entry`
|
||||||
|
ON `tabSerial and Batch Bundle`.`name` = `tabStock Ledger Entry`.`serial_and_batch_bundle`
|
||||||
|
SET `tabSerial and Batch Bundle`.`posting_datetime` = `tabStock Ledger Entry`.`posting_datetime`
|
||||||
|
WHERE `tabStock Ledger Entry`.`is_cancelled` = 0
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
drop_indexes()
|
||||||
|
|
||||||
|
|
||||||
|
def drop_indexes():
|
||||||
|
table = "tabSerial and Batch Bundle"
|
||||||
|
index_list = ["voucher_no_index", "item_code_index", "warehouse_index", "company_index"]
|
||||||
|
|
||||||
|
for index in index_list:
|
||||||
|
if not frappe.db.has_index(table, index):
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
|
||||||
|
click.echo(f"✓ dropped {index} index from {table}")
|
||||||
|
except Exception:
|
||||||
|
frappe.log_error("Failed to drop index")
|
||||||
@@ -53,6 +53,11 @@ class DeprecatedSerialNoValuation:
|
|||||||
|
|
||||||
# get rate from serial nos within same company
|
# get rate from serial nos within same company
|
||||||
incoming_values = 0.0
|
incoming_values = 0.0
|
||||||
|
posting_datetime = self.sle.posting_datetime
|
||||||
|
|
||||||
|
if not posting_datetime and self.sle.posting_date:
|
||||||
|
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
||||||
|
|
||||||
for serial_no in serial_nos:
|
for serial_no in serial_nos:
|
||||||
sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1)
|
sn_details = frappe.db.get_value("Serial No", serial_no, ["purchase_rate", "company"], as_dict=1)
|
||||||
if sn_details and sn_details.purchase_rate and sn_details.company == self.sle.company:
|
if sn_details and sn_details.purchase_rate and sn_details.company == self.sle.company:
|
||||||
@@ -77,7 +82,7 @@ class DeprecatedSerialNoValuation:
|
|||||||
& (table.actual_qty > 0)
|
& (table.actual_qty > 0)
|
||||||
& (table.is_cancelled == 0)
|
& (table.is_cancelled == 0)
|
||||||
& table.posting_datetime
|
& table.posting_datetime
|
||||||
<= get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
<= posting_datetime
|
||||||
)
|
)
|
||||||
.orderby(table.posting_datetime, order=Order.desc)
|
.orderby(table.posting_datetime, order=Order.desc)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
@@ -132,11 +137,8 @@ class DeprecatedBatchNoValuation:
|
|||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
timestamp_condition = None
|
timestamp_condition = None
|
||||||
if self.sle.posting_date:
|
if self.sle.posting_datetime:
|
||||||
if self.sle.posting_time is None:
|
posting_datetime = self.sle.posting_datetime
|
||||||
self.sle.posting_time = nowtime()
|
|
||||||
|
|
||||||
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
|
||||||
if not self.sle.creation:
|
if not self.sle.creation:
|
||||||
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
||||||
|
|
||||||
@@ -245,7 +247,11 @@ class DeprecatedBatchNoValuation:
|
|||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
batch = frappe.qb.DocType("Batch")
|
batch = frappe.qb.DocType("Batch")
|
||||||
|
|
||||||
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
posting_datetime = self.sle.posting_datetime
|
||||||
|
|
||||||
|
if not posting_datetime and self.sle.posting_date:
|
||||||
|
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
||||||
|
|
||||||
if not self.sle.creation:
|
if not self.sle.creation:
|
||||||
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
||||||
|
|
||||||
@@ -293,7 +299,10 @@ class DeprecatedBatchNoValuation:
|
|||||||
|
|
||||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
posting_datetime = self.sle.posting_datetime
|
||||||
|
if not posting_datetime and self.sle.posting_date:
|
||||||
|
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
||||||
|
|
||||||
if not self.sle.creation:
|
if not self.sle.creation:
|
||||||
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
||||||
|
|
||||||
@@ -343,19 +352,22 @@ class DeprecatedBatchNoValuation:
|
|||||||
"No known instructions.",
|
"No known instructions.",
|
||||||
)
|
)
|
||||||
def set_balance_value_from_bundle(self) -> None:
|
def set_balance_value_from_bundle(self) -> None:
|
||||||
|
from erpnext.stock.utils import get_combine_datetime
|
||||||
|
|
||||||
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
bundle_child = frappe.qb.DocType("Serial and Batch Entry")
|
bundle_child = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
batch = frappe.qb.DocType("Batch")
|
batch = frappe.qb.DocType("Batch")
|
||||||
|
|
||||||
timestamp_condition = CombineDatetime(bundle.posting_date, bundle.posting_time) < CombineDatetime(
|
posting_datetime = self.sle.posting_datetime
|
||||||
self.sle.posting_date, self.sle.posting_time
|
if not posting_datetime and self.sle.posting_date:
|
||||||
)
|
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
||||||
|
|
||||||
|
timestamp_condition = bundle.posting_datetime < posting_datetime
|
||||||
|
|
||||||
if self.sle.creation:
|
if self.sle.creation:
|
||||||
timestamp_condition |= (
|
timestamp_condition |= (bundle.posting_datetime == posting_datetime) & (
|
||||||
CombineDatetime(bundle.posting_date, bundle.posting_time)
|
bundle.creation < self.sle.creation
|
||||||
== CombineDatetime(self.sle.posting_date, self.sle.posting_time)
|
)
|
||||||
) & (bundle.creation < self.sle.creation)
|
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(bundle)
|
frappe.qb.from_(bundle)
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ def get_batch_qty(
|
|||||||
warehouse=None,
|
warehouse=None,
|
||||||
item_code=None,
|
item_code=None,
|
||||||
creation=None,
|
creation=None,
|
||||||
|
posting_datetime=None,
|
||||||
posting_date=None,
|
posting_date=None,
|
||||||
posting_time=None,
|
posting_time=None,
|
||||||
ignore_voucher_nos=None,
|
ignore_voucher_nos=None,
|
||||||
@@ -237,6 +238,7 @@ def get_batch_qty(
|
|||||||
:param for_stock_levels: True consider expired batches"""
|
:param for_stock_levels: True consider expired batches"""
|
||||||
|
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
combine_datetime,
|
||||||
get_auto_batch_nos,
|
get_auto_batch_nos,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -246,8 +248,6 @@ def get_batch_qty(
|
|||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"creation": creation,
|
"creation": creation,
|
||||||
"posting_date": posting_date,
|
|
||||||
"posting_time": posting_time,
|
|
||||||
"batch_no": batch_no,
|
"batch_no": batch_no,
|
||||||
"ignore_voucher_nos": ignore_voucher_nos,
|
"ignore_voucher_nos": ignore_voucher_nos,
|
||||||
"for_stock_levels": for_stock_levels,
|
"for_stock_levels": for_stock_levels,
|
||||||
@@ -256,6 +256,10 @@ def get_batch_qty(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kwargs["posting_datetime"] = posting_datetime
|
||||||
|
if not kwargs.get("posting_datetime") and posting_date:
|
||||||
|
kwargs["posting_datetime"] = combine_datetime(posting_date, posting_time)
|
||||||
|
|
||||||
batches = get_auto_batch_nos(kwargs)
|
batches = get_auto_batch_nos(kwargs)
|
||||||
|
|
||||||
if not (batch_no and warehouse):
|
if not (batch_no and warehouse):
|
||||||
@@ -337,6 +341,7 @@ def make_batch_bundle(
|
|||||||
):
|
):
|
||||||
from frappe.utils import nowtime, today
|
from frappe.utils import nowtime, today
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import combine_datetime
|
||||||
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -344,8 +349,7 @@ def make_batch_bundle(
|
|||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"posting_date": today(),
|
"posting_datetime": combine_datetime(today(), nowtime()),
|
||||||
"posting_time": nowtime(),
|
|
||||||
"voucher_type": "Stock Entry",
|
"voucher_type": "Stock Entry",
|
||||||
"qty": qty,
|
"qty": qty,
|
||||||
"type_of_transaction": type_of_transaction,
|
"type_of_transaction": type_of_transaction,
|
||||||
@@ -456,9 +460,13 @@ def get_pos_reserved_batch_qty(filters):
|
|||||||
|
|
||||||
def get_available_batches(kwargs):
|
def get_available_batches(kwargs):
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
combine_datetime,
|
||||||
get_auto_batch_nos,
|
get_auto_batch_nos,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if kwargs.get("posting_date"):
|
||||||
|
kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
|
||||||
|
|
||||||
batchwise_qty = OrderedDict()
|
batchwise_qty = OrderedDict()
|
||||||
|
|
||||||
batches = get_auto_batch_nos(kwargs)
|
batches = get_auto_batch_nos(kwargs)
|
||||||
|
|||||||
@@ -1438,7 +1438,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_details(item_code, uom=None, warehouse=None, company=None):
|
def get_item_details(item_code, uom=None, warehouse=None, company=None):
|
||||||
details = frappe.db.get_value("Item", item_code, ["stock_uom", "name"], as_dict=1)
|
details = frappe.db.get_value("Item", item_code, "stock_uom", as_dict=1)
|
||||||
details.uom = uom or details.stock_uom
|
details.uom = uom or details.stock_uom
|
||||||
if uom:
|
if uom:
|
||||||
details.update(get_conversion_factor(item_code, uom))
|
details.update(get_conversion_factor(item_code, uom))
|
||||||
|
|||||||
@@ -29,8 +29,7 @@
|
|||||||
"voucher_no",
|
"voucher_no",
|
||||||
"voucher_detail_no",
|
"voucher_detail_no",
|
||||||
"column_break_aouy",
|
"column_break_aouy",
|
||||||
"posting_date",
|
"posting_datetime",
|
||||||
"posting_time",
|
|
||||||
"returned_against",
|
"returned_against",
|
||||||
"section_break_wzou",
|
"section_break_wzou",
|
||||||
"is_cancelled",
|
"is_cancelled",
|
||||||
@@ -50,8 +49,7 @@
|
|||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Company",
|
"label": "Company",
|
||||||
"options": "Company",
|
"options": "Company",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.item_group",
|
"fetch_from": "item_code.item_group",
|
||||||
@@ -80,8 +78,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Item Code",
|
"label": "Item Code",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"reqd": 1,
|
"reqd": 1
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "item_code.item_name",
|
"fetch_from": "item_code.item_name",
|
||||||
@@ -118,8 +115,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Voucher No",
|
"label": "Voucher No",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "voucher_type",
|
"options": "voucher_type"
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -189,8 +185,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Warehouse",
|
"label": "Warehouse",
|
||||||
"mandatory_depends_on": "eval:doc.type_of_transaction != \"Maintenance\"",
|
"mandatory_depends_on": "eval:doc.type_of_transaction != \"Maintenance\"",
|
||||||
"options": "Warehouse",
|
"options": "Warehouse"
|
||||||
"search_index": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "type_of_transaction",
|
"fieldname": "type_of_transaction",
|
||||||
@@ -212,18 +207,6 @@
|
|||||||
"fieldname": "section_break_wzou",
|
"fieldname": "section_break_wzou",
|
||||||
"fieldtype": "Section Break"
|
"fieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "posting_date",
|
|
||||||
"fieldtype": "Date",
|
|
||||||
"label": "Posting Date",
|
|
||||||
"no_copy": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "posting_time",
|
|
||||||
"fieldtype": "Time",
|
|
||||||
"label": "Posting Time",
|
|
||||||
"no_copy": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "voucher_detail_no",
|
"fieldname": "voucher_detail_no",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
@@ -258,13 +241,18 @@
|
|||||||
"fieldname": "is_packed",
|
"fieldname": "is_packed",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Is Packed"
|
"label": "Is Packed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posting_datetime",
|
||||||
|
"fieldtype": "Datetime",
|
||||||
|
"label": "Posting Datetime"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-09-15 14:37:26.441742",
|
"modified": "2025-09-24 16:24:48.154853",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Serial and Batch Bundle",
|
"name": "Serial and Batch Bundle",
|
||||||
|
|||||||
@@ -73,8 +73,7 @@ class SerialandBatchBundle(Document):
|
|||||||
item_group: DF.Link | None
|
item_group: DF.Link | None
|
||||||
item_name: DF.Data | None
|
item_name: DF.Data | None
|
||||||
naming_series: DF.Literal["", "SABB-.########"]
|
naming_series: DF.Literal["", "SABB-.########"]
|
||||||
posting_date: DF.Date | None
|
posting_datetime: DF.Datetime | None
|
||||||
posting_time: DF.Time | None
|
|
||||||
returned_against: DF.Data | None
|
returned_against: DF.Data | None
|
||||||
total_amount: DF.Float
|
total_amount: DF.Float
|
||||||
total_qty: DF.Float
|
total_qty: DF.Float
|
||||||
@@ -252,8 +251,7 @@ class SerialandBatchBundle(Document):
|
|||||||
kwargs.update(
|
kwargs.update(
|
||||||
{
|
{
|
||||||
"voucher_no": self.voucher_no,
|
"voucher_no": self.voucher_no,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": self.posting_datetime,
|
||||||
"posting_time": self.posting_time,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -291,8 +289,7 @@ class SerialandBatchBundle(Document):
|
|||||||
kwargs = frappe._dict(
|
kwargs = frappe._dict(
|
||||||
{
|
{
|
||||||
"item_code": self.item_code,
|
"item_code": self.item_code,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": self.posting_datetime,
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"serial_nos": serial_nos,
|
"serial_nos": serial_nos,
|
||||||
"check_serial_nos": True,
|
"check_serial_nos": True,
|
||||||
}
|
}
|
||||||
@@ -560,8 +557,7 @@ class SerialandBatchBundle(Document):
|
|||||||
def get_sle_for_outward_transaction(self):
|
def get_sle_for_outward_transaction(self):
|
||||||
sle = frappe._dict(
|
sle = frappe._dict(
|
||||||
{
|
{
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": self.posting_datetime,
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"item_code": self.item_code,
|
"item_code": self.item_code,
|
||||||
"warehouse": self.warehouse,
|
"warehouse": self.warehouse,
|
||||||
"serial_and_batch_bundle": self.name,
|
"serial_and_batch_bundle": self.name,
|
||||||
@@ -662,11 +658,10 @@ class SerialandBatchBundle(Document):
|
|||||||
if not self.voucher_detail_no or self.voucher_detail_no != row.name:
|
if not self.voucher_detail_no or self.voucher_detail_no != row.name:
|
||||||
values_to_set["voucher_detail_no"] = row.name
|
values_to_set["voucher_detail_no"] = row.name
|
||||||
|
|
||||||
if parent.get("posting_date") and (not self.posting_date or self.posting_date != parent.posting_date):
|
if parent.get("posting_date") and parent.get("posting_time"):
|
||||||
values_to_set["posting_date"] = parent.posting_date or today()
|
posting_datetime = combine_datetime(parent.posting_date, parent.posting_time)
|
||||||
|
if not self.posting_datetime or self.posting_datetime != posting_datetime:
|
||||||
if parent.get("posting_time") and (not self.posting_time or self.posting_time != parent.posting_time):
|
values_to_set["posting_datetime"] = posting_datetime
|
||||||
values_to_set["posting_time"] = parent.posting_time
|
|
||||||
|
|
||||||
if parent.doctype in [
|
if parent.doctype in [
|
||||||
"Delivery Note",
|
"Delivery Note",
|
||||||
@@ -741,9 +736,7 @@ class SerialandBatchBundle(Document):
|
|||||||
parent = frappe.qb.DocType("Serial and Batch Bundle")
|
parent = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
child = frappe.qb.DocType("Serial and Batch Entry")
|
child = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) > CombineDatetime(
|
timestamp_condition = parent.posting_datetime > self.posting_datetime
|
||||||
self.posting_date, self.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
future_entries = (
|
future_entries = (
|
||||||
frappe.qb.from_(parent)
|
frappe.qb.from_(parent)
|
||||||
@@ -1251,7 +1244,7 @@ class SerialandBatchBundle(Document):
|
|||||||
frappe.qb.update(sn_table)
|
frappe.qb.update(sn_table)
|
||||||
.set(sn_table.reference_doctype, self.voucher_type)
|
.set(sn_table.reference_doctype, self.voucher_type)
|
||||||
.set(sn_table.reference_name, self.voucher_no)
|
.set(sn_table.reference_name, self.voucher_no)
|
||||||
.set(sn_table.posting_date, self.posting_date)
|
.set(sn_table.posting_date, getdate(self.posting_datetime))
|
||||||
.where((sn_table.name.isin(serial_nos)) & (sn_table.reference_name.isnull()))
|
.where((sn_table.name.isin(serial_nos)) & (sn_table.reference_name.isnull()))
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
@@ -1700,6 +1693,8 @@ def create_serial_batch_no_ledgers(
|
|||||||
if parent_doc.get("doctype") == "Stock Entry":
|
if parent_doc.get("doctype") == "Stock Entry":
|
||||||
warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
|
warehouse = warehouse or child_row.s_warehouse or child_row.t_warehouse
|
||||||
|
|
||||||
|
posting_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time"))
|
||||||
|
|
||||||
doc = frappe.get_doc(
|
doc = frappe.get_doc(
|
||||||
{
|
{
|
||||||
"doctype": "Serial and Batch Bundle",
|
"doctype": "Serial and Batch Bundle",
|
||||||
@@ -1708,8 +1703,7 @@ def create_serial_batch_no_ledgers(
|
|||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"is_rejected": child_row.is_rejected,
|
"is_rejected": child_row.is_rejected,
|
||||||
"type_of_transaction": type_of_transaction,
|
"type_of_transaction": type_of_transaction,
|
||||||
"posting_date": parent_doc.get("posting_date"),
|
"posting_datetime": posting_datetime,
|
||||||
"posting_time": parent_doc.get("posting_time"),
|
|
||||||
"company": parent_doc.get("company"),
|
"company": parent_doc.get("company"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1745,6 +1739,12 @@ def create_serial_batch_no_ledgers(
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def combine_datetime(date, time=None):
|
||||||
|
from erpnext.stock.utils import get_combine_datetime
|
||||||
|
|
||||||
|
return get_combine_datetime(date, time)
|
||||||
|
|
||||||
|
|
||||||
def get_batch(item_code):
|
def get_batch(item_code):
|
||||||
from erpnext.stock.doctype.batch.batch import make_batch
|
from erpnext.stock.doctype.batch.batch import make_batch
|
||||||
|
|
||||||
@@ -1788,8 +1788,8 @@ def get_type_of_transaction(parent_doc, child_row):
|
|||||||
def update_serial_batch_no_ledgers(bundle, entries, child_row, parent_doc, warehouse=None) -> object:
|
def update_serial_batch_no_ledgers(bundle, entries, child_row, parent_doc, warehouse=None) -> object:
|
||||||
doc = frappe.get_doc("Serial and Batch Bundle", bundle)
|
doc = frappe.get_doc("Serial and Batch Bundle", bundle)
|
||||||
doc.voucher_detail_no = child_row.name
|
doc.voucher_detail_no = child_row.name
|
||||||
doc.posting_date = parent_doc.posting_date
|
doc.posting_datetime = combine_datetime(parent_doc.get("posting_date"), parent_doc.get("posting_time"))
|
||||||
doc.posting_time = parent_doc.posting_time
|
|
||||||
doc.warehouse = warehouse or doc.warehouse
|
doc.warehouse = warehouse or doc.warehouse
|
||||||
doc.set("entries", [])
|
doc.set("entries", [])
|
||||||
|
|
||||||
@@ -1896,6 +1896,9 @@ def get_available_serial_nos(kwargs):
|
|||||||
elif kwargs.based_on == "Expiry":
|
elif kwargs.based_on == "Expiry":
|
||||||
order_by = "amc_expiry_date asc"
|
order_by = "amc_expiry_date asc"
|
||||||
|
|
||||||
|
if not kwargs.get("posting_datetime") and kwargs.get("posting_date"):
|
||||||
|
kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
|
||||||
|
|
||||||
filters = {"item_code": kwargs.item_code}
|
filters = {"item_code": kwargs.item_code}
|
||||||
|
|
||||||
# ignore_warehouse is used for backdated stock transactions
|
# ignore_warehouse is used for backdated stock transactions
|
||||||
@@ -1941,10 +1944,7 @@ def get_available_serial_nos(kwargs):
|
|||||||
ignore_serial_nos.extend(kwargs.get("ignore_serial_nos"))
|
ignore_serial_nos.extend(kwargs.get("ignore_serial_nos"))
|
||||||
|
|
||||||
ignore_serial_nos = list(set(ignore_serial_nos))
|
ignore_serial_nos = list(set(ignore_serial_nos))
|
||||||
if kwargs.get("posting_date"):
|
if kwargs.get("posting_datetime"):
|
||||||
if kwargs.get("posting_time") is None:
|
|
||||||
kwargs.posting_time = nowtime()
|
|
||||||
|
|
||||||
time_based_serial_nos = get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos)
|
time_based_serial_nos = get_serial_nos_based_on_posting_date(kwargs, ignore_serial_nos)
|
||||||
|
|
||||||
if not time_based_serial_nos:
|
if not time_based_serial_nos:
|
||||||
@@ -2343,11 +2343,13 @@ def get_auto_batch_nos(kwargs):
|
|||||||
kwargs.batch_no = batches
|
kwargs.batch_no = batches
|
||||||
kwargs.warehouse = warehouses
|
kwargs.warehouse = warehouses
|
||||||
|
|
||||||
|
if not kwargs.get("posting_datetime") and kwargs.get("posting_date"):
|
||||||
|
kwargs["posting_datetime"] = combine_datetime(kwargs.get("posting_date"), kwargs.get("posting_time"))
|
||||||
|
|
||||||
available_batches = get_available_batches(kwargs)
|
available_batches = get_available_batches(kwargs)
|
||||||
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
|
stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
|
||||||
pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
|
pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
|
||||||
sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
|
sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
|
||||||
|
|
||||||
if kwargs.against_sales_order and only_consider_batches:
|
if kwargs.against_sales_order and only_consider_batches:
|
||||||
kwargs.batch_no = kwargs.warehouse = None
|
kwargs.batch_no = kwargs.warehouse = None
|
||||||
|
|
||||||
@@ -2367,7 +2369,7 @@ def get_auto_batch_nos(kwargs):
|
|||||||
if kwargs.based_on == "Expiry":
|
if kwargs.based_on == "Expiry":
|
||||||
available_batches = sorted(available_batches, key=lambda x: (x.expiry_date or getdate("9999-12-31")))
|
available_batches = sorted(available_batches, key=lambda x: (x.expiry_date or getdate("9999-12-31")))
|
||||||
|
|
||||||
if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_date"):
|
if not kwargs.get("do_not_check_future_batches") and available_batches and kwargs.get("posting_datetime"):
|
||||||
filter_zero_near_batches(available_batches, kwargs)
|
filter_zero_near_batches(available_batches, kwargs)
|
||||||
|
|
||||||
if not kwargs.consider_negative_batches:
|
if not kwargs.consider_negative_batches:
|
||||||
@@ -2404,8 +2406,7 @@ def get_batches_to_be_considered(sales_order_name):
|
|||||||
def filter_zero_near_batches(available_batches, kwargs):
|
def filter_zero_near_batches(available_batches, kwargs):
|
||||||
kwargs.batch_no = [d.batch_no for d in available_batches]
|
kwargs.batch_no = [d.batch_no for d in available_batches]
|
||||||
|
|
||||||
del kwargs["posting_date"]
|
del kwargs["posting_datetime"]
|
||||||
del kwargs["posting_time"]
|
|
||||||
|
|
||||||
kwargs.do_not_check_future_batches = 1
|
kwargs.do_not_check_future_batches = 1
|
||||||
available_batches_in_future = get_auto_batch_nos(kwargs)
|
available_batches_in_future = get_auto_batch_nos(kwargs)
|
||||||
@@ -2471,8 +2472,6 @@ def update_available_batches(available_batches, *reserved_batches) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def get_available_batches(kwargs):
|
def get_available_batches(kwargs):
|
||||||
from erpnext.stock.utils import get_combine_datetime
|
|
||||||
|
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
|
batch_ledger = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
batch_table = frappe.qb.DocType("Batch")
|
batch_table = frappe.qb.DocType("Batch")
|
||||||
@@ -2497,23 +2496,15 @@ def get_available_batches(kwargs):
|
|||||||
if not kwargs.get("for_stock_levels"):
|
if not kwargs.get("for_stock_levels"):
|
||||||
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
|
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
|
||||||
|
|
||||||
if kwargs.get("posting_date"):
|
if kwargs.get("posting_datetime"):
|
||||||
if kwargs.get("posting_time") is None:
|
timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime
|
||||||
kwargs.posting_time = nowtime()
|
|
||||||
|
|
||||||
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
|
|
||||||
kwargs.posting_date, kwargs.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
if kwargs.get("creation"):
|
if kwargs.get("creation"):
|
||||||
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime(
|
timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime
|
||||||
kwargs.posting_date, kwargs.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
timestamp_condition |= (
|
timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & (
|
||||||
stock_ledger_entry.posting_datetime
|
stock_ledger_entry.creation < kwargs.creation
|
||||||
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time)
|
)
|
||||||
) & (stock_ledger_entry.creation < kwargs.creation)
|
|
||||||
|
|
||||||
query = query.where(timestamp_condition)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
@@ -2693,15 +2684,14 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
|
|||||||
serial_batch_table.incoming_rate,
|
serial_batch_table.incoming_rate,
|
||||||
bundle_table.voucher_detail_no,
|
bundle_table.voucher_detail_no,
|
||||||
bundle_table.voucher_no,
|
bundle_table.voucher_no,
|
||||||
bundle_table.posting_date,
|
bundle_table.posting_datetime,
|
||||||
bundle_table.posting_time,
|
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
(bundle_table.docstatus == 1)
|
(bundle_table.docstatus == 1)
|
||||||
& (bundle_table.is_cancelled == 0)
|
& (bundle_table.is_cancelled == 0)
|
||||||
& (bundle_table.type_of_transaction.isin(["Inward", "Outward"]))
|
& (bundle_table.type_of_transaction.isin(["Inward", "Outward"]))
|
||||||
)
|
)
|
||||||
.orderby(bundle_table.posting_date, bundle_table.posting_time)
|
.orderby(bundle_table.posting_datetime)
|
||||||
)
|
)
|
||||||
|
|
||||||
for key, val in kwargs.items():
|
for key, val in kwargs.items():
|
||||||
@@ -2719,7 +2709,7 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
|
|||||||
query = query.where(bundle_table[key].isin(val))
|
query = query.where(bundle_table[key].isin(val))
|
||||||
else:
|
else:
|
||||||
query = query.where(bundle_table[key] == val)
|
query = query.where(bundle_table[key] == val)
|
||||||
elif key in ["posting_date", "posting_time"]:
|
elif key in ["posting_datetime"]:
|
||||||
query = query.where(bundle_table[key] >= val)
|
query = query.where(bundle_table[key] >= val)
|
||||||
else:
|
else:
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
@@ -2731,8 +2721,6 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
|
|||||||
|
|
||||||
|
|
||||||
def get_stock_ledgers_for_serial_nos(kwargs):
|
def get_stock_ledgers_for_serial_nos(kwargs):
|
||||||
from erpnext.stock.utils import get_combine_datetime
|
|
||||||
|
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
@@ -2748,23 +2736,15 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
|||||||
.orderby(stock_ledger_entry.creation)
|
.orderby(stock_ledger_entry.creation)
|
||||||
)
|
)
|
||||||
|
|
||||||
if kwargs.get("posting_date"):
|
if kwargs.get("posting_datetime"):
|
||||||
if kwargs.get("posting_time") is None:
|
timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime
|
||||||
kwargs.posting_time = nowtime()
|
|
||||||
|
|
||||||
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
|
|
||||||
kwargs.posting_date, kwargs.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
if kwargs.get("creation"):
|
if kwargs.get("creation"):
|
||||||
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime(
|
timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime
|
||||||
kwargs.posting_date, kwargs.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
timestamp_condition |= (
|
timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & (
|
||||||
stock_ledger_entry.posting_datetime
|
stock_ledger_entry.creation < kwargs.creation
|
||||||
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time)
|
)
|
||||||
) & (stock_ledger_entry.creation < kwargs.creation)
|
|
||||||
|
|
||||||
query = query.where(timestamp_condition)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
@@ -2784,8 +2764,6 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def get_stock_ledgers_batches(kwargs):
|
def get_stock_ledgers_batches(kwargs):
|
||||||
from erpnext.stock.utils import get_combine_datetime
|
|
||||||
|
|
||||||
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
|
||||||
batch_table = frappe.qb.DocType("Batch")
|
batch_table = frappe.qb.DocType("Batch")
|
||||||
|
|
||||||
@@ -2816,23 +2794,15 @@ def get_stock_ledgers_batches(kwargs):
|
|||||||
if not kwargs.get("for_stock_levels"):
|
if not kwargs.get("for_stock_levels"):
|
||||||
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
|
query = query.where((batch_table.expiry_date >= today()) | (batch_table.expiry_date.isnull()))
|
||||||
|
|
||||||
if kwargs.get("posting_date"):
|
if kwargs.get("posting_datetime"):
|
||||||
if kwargs.get("posting_time") is None:
|
timestamp_condition = stock_ledger_entry.posting_datetime <= kwargs.posting_datetime
|
||||||
kwargs.posting_time = nowtime()
|
|
||||||
|
|
||||||
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
|
|
||||||
kwargs.posting_date, kwargs.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
if kwargs.get("creation"):
|
if kwargs.get("creation"):
|
||||||
timestamp_condition = stock_ledger_entry.posting_datetime < get_combine_datetime(
|
timestamp_condition = stock_ledger_entry.posting_datetime < kwargs.posting_datetime
|
||||||
kwargs.posting_date, kwargs.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
timestamp_condition |= (
|
timestamp_condition |= (stock_ledger_entry.posting_datetime == kwargs.posting_datetime) & (
|
||||||
stock_ledger_entry.posting_datetime
|
stock_ledger_entry.creation < kwargs.creation
|
||||||
== get_combine_datetime(kwargs.posting_date, kwargs.posting_time)
|
)
|
||||||
) & (stock_ledger_entry.creation < kwargs.creation)
|
|
||||||
|
|
||||||
query = query.where(timestamp_condition)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
@@ -2920,3 +2890,7 @@ def get_stock_reco_details(voucher_detail_no):
|
|||||||
],
|
],
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def on_doctype_update():
|
||||||
|
frappe.db.add_index("Serial and Batch Bundle", ["item_code", "warehouse", "posting_datetime", "creation"])
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from frappe.utils import flt, nowtime, today
|
|||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
add_serial_batch_ledgers,
|
add_serial_batch_ledgers,
|
||||||
|
combine_datetime,
|
||||||
make_batch_nos,
|
make_batch_nos,
|
||||||
make_serial_nos,
|
make_serial_nos,
|
||||||
)
|
)
|
||||||
@@ -932,14 +933,17 @@ def make_serial_batch_bundle(kwargs):
|
|||||||
if kwargs.get("type_of_transaction"):
|
if kwargs.get("type_of_transaction"):
|
||||||
type_of_transaction = kwargs.get("type_of_transaction")
|
type_of_transaction = kwargs.get("type_of_transaction")
|
||||||
|
|
||||||
|
posting_datetime = None
|
||||||
|
if kwargs.get("posting_date"):
|
||||||
|
posting_datetime = combine_datetime(kwargs.posting_date, kwargs.posting_time or nowtime())
|
||||||
|
|
||||||
sb = SerialBatchCreation(
|
sb = SerialBatchCreation(
|
||||||
{
|
{
|
||||||
"item_code": kwargs.item_code,
|
"item_code": kwargs.item_code,
|
||||||
"warehouse": kwargs.warehouse,
|
"warehouse": kwargs.warehouse,
|
||||||
"voucher_type": kwargs.voucher_type,
|
"voucher_type": kwargs.voucher_type,
|
||||||
"voucher_no": kwargs.voucher_no,
|
"voucher_no": kwargs.voucher_no,
|
||||||
"posting_date": kwargs.posting_date,
|
"posting_datetime": posting_datetime,
|
||||||
"posting_time": kwargs.posting_time,
|
|
||||||
"qty": kwargs.qty,
|
"qty": kwargs.qty,
|
||||||
"avg_rate": kwargs.rate,
|
"avg_rate": kwargs.rate,
|
||||||
"batches": kwargs.batches,
|
"batches": kwargs.batches,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ from erpnext.stock.serial_batch_bundle import (
|
|||||||
get_serial_or_batch_items,
|
get_serial_or_batch_items,
|
||||||
)
|
)
|
||||||
from erpnext.stock.stock_ledger import NegativeStockError, get_previous_sle, get_valuation_rate
|
from erpnext.stock.stock_ledger import NegativeStockError, get_previous_sle, get_valuation_rate
|
||||||
from erpnext.stock.utils import get_bin, get_incoming_rate
|
from erpnext.stock.utils import get_bin, get_combine_datetime, get_incoming_rate
|
||||||
|
|
||||||
|
|
||||||
class FinishedGoodError(frappe.ValidationError):
|
class FinishedGoodError(frappe.ValidationError):
|
||||||
@@ -1122,8 +1122,7 @@ class StockEntry(StockController):
|
|||||||
{
|
{
|
||||||
"item_code": row.item_code,
|
"item_code": row.item_code,
|
||||||
"warehouse": row.s_warehouse,
|
"warehouse": row.s_warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": get_combine_datetime(self.posting_date, self.posting_time),
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_detail_no": row.name,
|
"voucher_detail_no": row.name,
|
||||||
"qty": row.transfer_qty * -1,
|
"qty": row.transfer_qty * -1,
|
||||||
@@ -2263,6 +2262,9 @@ class StockEntry(StockController):
|
|||||||
# in case of BOM
|
# in case of BOM
|
||||||
to_warehouse = item.get("default_warehouse")
|
to_warehouse = item.get("default_warehouse")
|
||||||
|
|
||||||
|
expense_account = item.get("expense_account")
|
||||||
|
if not expense_account:
|
||||||
|
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
|
||||||
args = {
|
args = {
|
||||||
"to_warehouse": to_warehouse,
|
"to_warehouse": to_warehouse,
|
||||||
"from_warehouse": "",
|
"from_warehouse": "",
|
||||||
@@ -2270,7 +2272,7 @@ class StockEntry(StockController):
|
|||||||
"item_name": item.item_name,
|
"item_name": item.item_name,
|
||||||
"description": item.description,
|
"description": item.description,
|
||||||
"stock_uom": item.stock_uom,
|
"stock_uom": item.stock_uom,
|
||||||
"expense_account": item.get("expense_account"),
|
"expense_account": expense_account,
|
||||||
"cost_center": item.get("buying_cost_center"),
|
"cost_center": item.get("buying_cost_center"),
|
||||||
"is_finished_item": 1,
|
"is_finished_item": 1,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
from typing import TYPE_CHECKING, overload
|
from typing import TYPE_CHECKING, overload
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cint, flt
|
from frappe.utils import cint, flt, today
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
|
from erpnext.stock.utils import get_combine_datetime
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry
|
from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry
|
||||||
@@ -76,6 +77,9 @@ def make_stock_entry(**args):
|
|||||||
if args.inspection_required:
|
if args.inspection_required:
|
||||||
s.inspection_required = args.inspection_required
|
s.inspection_required = args.inspection_required
|
||||||
|
|
||||||
|
if not args.posting_date:
|
||||||
|
s.posting_date = today()
|
||||||
|
|
||||||
# map names
|
# map names
|
||||||
if args.from_warehouse:
|
if args.from_warehouse:
|
||||||
args.source = args.from_warehouse
|
args.source = args.from_warehouse
|
||||||
@@ -140,6 +144,10 @@ def make_stock_entry(**args):
|
|||||||
elif args.batches:
|
elif args.batches:
|
||||||
batches = args.batches
|
batches = args.batches
|
||||||
|
|
||||||
|
posting_datetime = None
|
||||||
|
if s.posting_date and s.posting_time:
|
||||||
|
posting_datetime = get_combine_datetime(s.posting_date, s.posting_time)
|
||||||
|
|
||||||
bundle_id = (
|
bundle_id = (
|
||||||
SerialBatchCreation(
|
SerialBatchCreation(
|
||||||
{
|
{
|
||||||
@@ -151,8 +159,7 @@ def make_stock_entry(**args):
|
|||||||
"serial_nos": args.serial_no,
|
"serial_nos": args.serial_no,
|
||||||
"type_of_transaction": "Outward" if args.source else "Inward",
|
"type_of_transaction": "Outward" if args.source else "Inward",
|
||||||
"company": s.company,
|
"company": s.company,
|
||||||
"posting_date": s.posting_date,
|
"posting_datetime": posting_datetime,
|
||||||
"posting_time": s.posting_time,
|
|
||||||
"rate": args.rate or args.basic_rate,
|
"rate": args.rate or args.basic_rate,
|
||||||
"do_not_submit": True,
|
"do_not_submit": True,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from erpnext.controllers.stock_controller import StockController, create_repost_
|
|||||||
from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty
|
from erpnext.stock.doctype.batch.batch import get_available_batches, get_batch_qty
|
||||||
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
combine_datetime,
|
||||||
get_available_serial_nos,
|
get_available_serial_nos,
|
||||||
)
|
)
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
@@ -135,8 +136,7 @@ class StockReconciliation(StockController):
|
|||||||
{
|
{
|
||||||
"item_code": row.item_code,
|
"item_code": row.item_code,
|
||||||
"warehouse": row.warehouse,
|
"warehouse": row.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": combine_datetime(self.posting_date, self.posting_time),
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": row.name,
|
"voucher_detail_no": row.name,
|
||||||
@@ -242,8 +242,7 @@ class StockReconciliation(StockController):
|
|||||||
"doctype": "Serial and Batch Bundle",
|
"doctype": "Serial and Batch Bundle",
|
||||||
"item_code": item.item_code,
|
"item_code": item.item_code,
|
||||||
"warehouse": item.warehouse,
|
"warehouse": item.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": combine_datetime(self.posting_date, self.posting_time),
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"voucher_type": self.doctype,
|
"voucher_type": self.doctype,
|
||||||
"type_of_transaction": "Outward",
|
"type_of_transaction": "Outward",
|
||||||
}
|
}
|
||||||
@@ -261,8 +260,7 @@ class StockReconciliation(StockController):
|
|||||||
{
|
{
|
||||||
"item_code": item.item_code,
|
"item_code": item.item_code,
|
||||||
"warehouse": item.warehouse,
|
"warehouse": item.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": combine_datetime(self.posting_date, self.posting_time),
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"ignore_warehouse": 1,
|
"ignore_warehouse": 1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1154,8 +1152,7 @@ class StockReconciliation(StockController):
|
|||||||
{
|
{
|
||||||
"item_code": doc.item_code,
|
"item_code": doc.item_code,
|
||||||
"warehouse": doc.warehouse,
|
"warehouse": doc.warehouse,
|
||||||
"posting_date": self.posting_date,
|
"posting_datetime": doc.posting_datetime,
|
||||||
"posting_time": self.posting_time,
|
|
||||||
"creation": sle_creation,
|
"creation": sle_creation,
|
||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"ignore_warehouse": 1,
|
"ignore_warehouse": 1,
|
||||||
@@ -1195,8 +1192,7 @@ class StockReconciliation(StockController):
|
|||||||
d.batch_no,
|
d.batch_no,
|
||||||
doc.warehouse,
|
doc.warehouse,
|
||||||
creation=sle_creation,
|
creation=sle_creation,
|
||||||
posting_date=doc.posting_date,
|
posting_datetime=doc.posting_datetime,
|
||||||
posting_time=doc.posting_time,
|
|
||||||
ignore_voucher_nos=[doc.voucher_no],
|
ignore_voucher_nos=[doc.voucher_no],
|
||||||
for_stock_levels=True,
|
for_stock_levels=True,
|
||||||
consider_negative_batches=True,
|
consider_negative_batches=True,
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
|
|||||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||||
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
|
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
|
||||||
from erpnext.stock.tests.test_utils import StockTestMixin
|
from erpnext.stock.tests.test_utils import StockTestMixin
|
||||||
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
|
from erpnext.stock.utils import (
|
||||||
|
get_combine_datetime,
|
||||||
|
get_incoming_rate,
|
||||||
|
get_stock_value_on,
|
||||||
|
get_valuation_method,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestStockReconciliation(IntegrationTestCase, StockTestMixin):
|
class TestStockReconciliation(IntegrationTestCase, StockTestMixin):
|
||||||
@@ -716,6 +721,13 @@ class TestStockReconciliation(IntegrationTestCase, StockTestMixin):
|
|||||||
rate=100,
|
rate=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stock_reco.reload()
|
||||||
|
stock_reco_sabb = stock_reco.items[0].serial_and_batch_bundle
|
||||||
|
posting_datetime = frappe.db.get_value("Serial and Batch Bundle", stock_reco_sabb, "posting_datetime")
|
||||||
|
self.assertEqual(
|
||||||
|
posting_datetime, get_combine_datetime(stock_reco.posting_date, stock_reco.posting_time)
|
||||||
|
)
|
||||||
|
|
||||||
sle = frappe.get_all(
|
sle = frappe.get_all(
|
||||||
"Stock Ledger Entry",
|
"Stock Ledger Entry",
|
||||||
filters={"is_cancelled": 0, "voucher_no": stock_reco.name, "actual_qty": ("<", 0)},
|
filters={"is_cancelled": 0, "voucher_no": stock_reco.name, "actual_qty": ("<", 0)},
|
||||||
|
|||||||
@@ -283,10 +283,13 @@ def filter_batches(batches, doc):
|
|||||||
del batches[row.get("batch_no")]
|
del batches[row.get("batch_no")]
|
||||||
|
|
||||||
|
|
||||||
def get_filtered_serial_nos(serial_nos, doc):
|
def get_filtered_serial_nos(serial_nos, doc, table=None):
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
for row in doc.get("items"):
|
if not table:
|
||||||
|
table = "items"
|
||||||
|
|
||||||
|
for row in doc.get(table):
|
||||||
if row.get("serial_no"):
|
if row.get("serial_no"):
|
||||||
for serial_no in get_serial_nos(row.get("serial_no")):
|
for serial_no in get_serial_nos(row.get("serial_no")):
|
||||||
if serial_no in serial_nos:
|
if serial_no in serial_nos:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ def get_data(filters):
|
|||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
fields=[
|
fields=[
|
||||||
"`tabSerial and Batch Bundle`.`voucher_type`",
|
"`tabSerial and Batch Bundle`.`voucher_type`",
|
||||||
"`tabSerial and Batch Bundle`.`posting_date`",
|
"`tabSerial and Batch Bundle`.`posting_datetime` as posting_date",
|
||||||
"`tabSerial and Batch Bundle`.`name`",
|
"`tabSerial and Batch Bundle`.`name`",
|
||||||
"`tabSerial and Batch Bundle`.`company`",
|
"`tabSerial and Batch Bundle`.`company`",
|
||||||
"`tabSerial and Batch Bundle`.`voucher_no`",
|
"`tabSerial and Batch Bundle`.`voucher_no`",
|
||||||
@@ -33,7 +33,7 @@ def get_data(filters):
|
|||||||
"`tabSerial and Batch Entry`.`qty`",
|
"`tabSerial and Batch Entry`.`qty`",
|
||||||
],
|
],
|
||||||
filters=filter_conditions,
|
filters=filter_conditions,
|
||||||
order_by="posting_date",
|
order_by="posting_datetime",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ def get_filter_conditions(filters):
|
|||||||
filter_conditions.append(
|
filter_conditions.append(
|
||||||
[
|
[
|
||||||
"Serial and Batch Bundle",
|
"Serial and Batch Bundle",
|
||||||
"posting_date",
|
"posting_datetime",
|
||||||
"between",
|
"between",
|
||||||
[filters.get("from_date"), filters.get("to_date")],
|
[filters.get("from_date"), filters.get("to_date")],
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -492,6 +492,7 @@ class FIFOSlots:
|
|||||||
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
bundle = frappe.qb.DocType("Serial and Batch Bundle")
|
||||||
entry = frappe.qb.DocType("Serial and Batch Entry")
|
entry = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
|
to_date = get_datetime(self.filters.get("to_date") + " 23:59:59")
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(bundle)
|
frappe.qb.from_(bundle)
|
||||||
.join(entry)
|
.join(entry)
|
||||||
@@ -501,7 +502,7 @@ class FIFOSlots:
|
|||||||
(bundle.docstatus == 1)
|
(bundle.docstatus == 1)
|
||||||
& (entry.serial_no.isnotnull())
|
& (entry.serial_no.isnotnull())
|
||||||
& (bundle.company == self.filters.get("company"))
|
& (bundle.company == self.filters.get("company"))
|
||||||
& (bundle.posting_date <= self.filters.get("to_date"))
|
& (bundle.posting_datetime <= to_date)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import frappe
|
|||||||
from frappe import _, bold
|
from frappe import _, bold
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
|
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
|
||||||
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today
|
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowtime, today
|
||||||
from pypika import Order
|
from pypika import Order
|
||||||
from pypika.terms import ExistsCriterion
|
from pypika.terms import ExistsCriterion
|
||||||
|
|
||||||
@@ -100,8 +100,7 @@ class SerialBatchBundle:
|
|||||||
{
|
{
|
||||||
"item_code": self.item_code,
|
"item_code": self.item_code,
|
||||||
"warehouse": self.warehouse,
|
"warehouse": self.warehouse,
|
||||||
"posting_date": self.sle.posting_date,
|
"posting_datetime": self.sle.posting_datetime,
|
||||||
"posting_time": self.sle.posting_time,
|
|
||||||
"voucher_type": self.sle.voucher_type,
|
"voucher_type": self.sle.voucher_type,
|
||||||
"voucher_no": self.sle.voucher_no,
|
"voucher_no": self.sle.voucher_no,
|
||||||
"voucher_detail_no": self.sle.voucher_detail_no,
|
"voucher_detail_no": self.sle.voucher_detail_no,
|
||||||
@@ -463,7 +462,7 @@ class SerialBatchBundle:
|
|||||||
if status == "Delivered":
|
if status == "Delivered":
|
||||||
warranty_period = frappe.get_cached_value("Item", sle.item_code, "warranty_period")
|
warranty_period = frappe.get_cached_value("Item", sle.item_code, "warranty_period")
|
||||||
if warranty_period:
|
if warranty_period:
|
||||||
warranty_expiry_date = add_days(sle.posting_date, cint(warranty_period))
|
warranty_expiry_date = add_days(getdate(sle.posting_datetime), cint(warranty_period))
|
||||||
query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date)
|
query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date)
|
||||||
query = query.set(sn_table.warranty_period, warranty_period)
|
query = query.set(sn_table.warranty_period, warranty_period)
|
||||||
else:
|
else:
|
||||||
@@ -488,7 +487,7 @@ class SerialBatchBundle:
|
|||||||
sle_doctype.voucher_no,
|
sle_doctype.voucher_no,
|
||||||
sle_doctype.is_cancelled,
|
sle_doctype.is_cancelled,
|
||||||
sle_doctype.item_code,
|
sle_doctype.item_code,
|
||||||
sle_doctype.posting_date,
|
sle_doctype.posting_datetime,
|
||||||
sle_doctype.company,
|
sle_doctype.company,
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
@@ -644,7 +643,7 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
& (bundle.item_code == self.sle.item_code)
|
& (bundle.item_code == self.sle.item_code)
|
||||||
& (bundle_child.warehouse == self.sle.warehouse)
|
& (bundle_child.warehouse == self.sle.warehouse)
|
||||||
)
|
)
|
||||||
.orderby(Timestamp(bundle.posting_date, bundle.posting_time), order=Order.desc)
|
.orderby(bundle.posting_datetime, order=Order.desc)
|
||||||
.limit(1)
|
.limit(1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -652,13 +651,8 @@ class SerialNoValuation(DeprecatedSerialNoValuation):
|
|||||||
if self.sle.voucher_no:
|
if self.sle.voucher_no:
|
||||||
query = query.where(bundle.voucher_no != self.sle.voucher_no)
|
query = query.where(bundle.voucher_no != self.sle.voucher_no)
|
||||||
|
|
||||||
if self.sle.posting_date:
|
if self.sle.posting_datetime:
|
||||||
if self.sle.posting_time is None:
|
timestamp_condition = bundle.posting_datetime <= self.sle.posting_datetime
|
||||||
self.sle.posting_time = nowtime()
|
|
||||||
|
|
||||||
timestamp_condition = CombineDatetime(
|
|
||||||
bundle.posting_date, bundle.posting_time
|
|
||||||
) <= CombineDatetime(self.sle.posting_date, self.sle.posting_time)
|
|
||||||
|
|
||||||
query = query.where(timestamp_condition)
|
query = query.where(timestamp_condition)
|
||||||
|
|
||||||
@@ -754,19 +748,13 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
|
|||||||
child = frappe.qb.DocType("Serial and Batch Entry")
|
child = frappe.qb.DocType("Serial and Batch Entry")
|
||||||
|
|
||||||
timestamp_condition = ""
|
timestamp_condition = ""
|
||||||
if self.sle.posting_date:
|
if self.sle.posting_datetime:
|
||||||
if self.sle.posting_time is None:
|
timestamp_condition = parent.posting_datetime < self.sle.posting_datetime
|
||||||
self.sle.posting_time = nowtime()
|
|
||||||
|
|
||||||
timestamp_condition = CombineDatetime(parent.posting_date, parent.posting_time) < CombineDatetime(
|
|
||||||
self.sle.posting_date, self.sle.posting_time
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.sle.creation:
|
if self.sle.creation:
|
||||||
timestamp_condition |= (
|
timestamp_condition |= (parent.posting_datetime == self.sle.posting_datetime) & (
|
||||||
CombineDatetime(parent.posting_date, parent.posting_time)
|
parent.creation < self.sle.creation
|
||||||
== CombineDatetime(self.sle.posting_date, self.sle.posting_time)
|
)
|
||||||
) & (parent.creation < self.sle.creation)
|
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(parent)
|
frappe.qb.from_(parent)
|
||||||
@@ -989,9 +977,9 @@ class SerialBatchCreation:
|
|||||||
self.__dict__.update(item_details)
|
self.__dict__.update(item_details)
|
||||||
|
|
||||||
def set_other_details(self):
|
def set_other_details(self):
|
||||||
if not self.get("posting_date"):
|
if not self.get("posting_datetime"):
|
||||||
self.posting_date = today()
|
self.posting_datetime = now()
|
||||||
self.__dict__["posting_date"] = self.posting_date
|
self.__dict__["posting_datetime"] = self.posting_datetime
|
||||||
|
|
||||||
if not self.get("actual_qty"):
|
if not self.get("actual_qty"):
|
||||||
qty = self.get("qty") or self.get("total_qty")
|
qty = self.get("qty") or self.get("total_qty")
|
||||||
@@ -1016,8 +1004,7 @@ class SerialBatchCreation:
|
|||||||
new_package.docstatus = 0
|
new_package.docstatus = 0
|
||||||
new_package.warehouse = self.warehouse
|
new_package.warehouse = self.warehouse
|
||||||
new_package.voucher_no = ""
|
new_package.voucher_no = ""
|
||||||
new_package.posting_date = self.posting_date if hasattr(self, "posting_date") else today()
|
new_package.posting_datetime = self.posting_datetime if hasattr(self, "posting_datetime") else now()
|
||||||
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.type_of_transaction = self.type_of_transaction
|
||||||
new_package.returned_against = self.get("returned_against")
|
new_package.returned_against = self.get("returned_against")
|
||||||
|
|
||||||
@@ -1144,9 +1131,8 @@ class SerialBatchCreation:
|
|||||||
elif self.has_serial_no and not self.get("serial_nos"):
|
elif self.has_serial_no and not self.get("serial_nos"):
|
||||||
self.serial_nos = get_serial_nos_for_outward(kwargs)
|
self.serial_nos = get_serial_nos_for_outward(kwargs)
|
||||||
elif not self.has_serial_no and self.has_batch_no and not self.get("batches"):
|
elif not self.has_serial_no and self.has_batch_no and not self.get("batches"):
|
||||||
if self.get("posting_date"):
|
if self.get("posting_datetime"):
|
||||||
kwargs["posting_date"] = self.get("posting_date")
|
kwargs["posting_datetime"] = self.get("posting_datetime")
|
||||||
kwargs["posting_time"] = self.get("posting_time")
|
|
||||||
|
|
||||||
self.batches = get_available_batches(kwargs)
|
self.batches = get_available_batches(kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -729,32 +729,16 @@ class update_entries_after:
|
|||||||
self.distinct_item_warehouses[key] = val
|
self.distinct_item_warehouses[key] = val
|
||||||
self.new_items_found = True
|
self.new_items_found = True
|
||||||
else:
|
else:
|
||||||
# Check if the dependent voucher is reposted
|
existing_sle = self.distinct_item_warehouses[key].get("sle", {})
|
||||||
# If not, then do not add it to the list
|
if getdate(existing_sle.get("posting_date")) > getdate(dependant_sle.posting_date):
|
||||||
if not self.is_dependent_voucher_reposted(dependant_sle):
|
|
||||||
return
|
|
||||||
|
|
||||||
existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
|
|
||||||
|
|
||||||
dependent_voucher_detail_nos = self.get_dependent_voucher_detail_nos(key)
|
|
||||||
if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date):
|
|
||||||
if dependent_voucher_detail_nos and dependant_sle.voucher_detail_no in set(
|
|
||||||
dependent_voucher_detail_nos
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
val.sle_changed = True
|
|
||||||
dependent_voucher_detail_nos.append(dependant_sle.voucher_detail_no)
|
|
||||||
val.dependent_voucher_detail_nos = dependent_voucher_detail_nos
|
|
||||||
self.distinct_item_warehouses[key] = val
|
self.distinct_item_warehouses[key] = val
|
||||||
self.new_items_found = True
|
self.new_items_found = True
|
||||||
elif dependant_sle.voucher_detail_no not in set(dependent_voucher_detail_nos):
|
elif dependant_sle.voucher_type == "Stock Entry" and is_transfer_stock_entry(
|
||||||
# Future dependent voucher needs to be repost to get the correct stock value
|
dependant_sle.voucher_no
|
||||||
# If dependent voucher has not reposted, then add it to the list
|
):
|
||||||
dependent_voucher_detail_nos.append(dependant_sle.voucher_detail_no)
|
print(dependant_sle.voucher_no)
|
||||||
self.new_items_found = True
|
|
||||||
val.dependent_voucher_detail_nos = dependent_voucher_detail_nos
|
|
||||||
self.distinct_item_warehouses[key] = val
|
self.distinct_item_warehouses[key] = val
|
||||||
|
self.new_items_found = True
|
||||||
|
|
||||||
def is_dependent_voucher_reposted(self, dependant_sle) -> bool:
|
def is_dependent_voucher_reposted(self, dependant_sle) -> bool:
|
||||||
# Return False if the dependent voucher is not reposted
|
# Return False if the dependent voucher is not reposted
|
||||||
@@ -1785,6 +1769,8 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
|
|||||||
"posting_time",
|
"posting_time",
|
||||||
"voucher_detail_no",
|
"voucher_detail_no",
|
||||||
"posting_datetime as timestamp",
|
"posting_datetime as timestamp",
|
||||||
|
"voucher_type",
|
||||||
|
"voucher_no",
|
||||||
],
|
],
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
@@ -1863,8 +1849,7 @@ def get_valuation_rate(
|
|||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"actual_qty": -1,
|
"actual_qty": -1,
|
||||||
"serial_and_batch_bundle": serial_and_batch_bundle,
|
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||||
"posting_date": sabb.posting_date,
|
"posting_datetime": get_combine_datetime(sabb.posting_date, sabb.posting_time),
|
||||||
"posting_time": sabb.posting_time,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2208,8 +2193,7 @@ def validate_reserved_batch_nos(item_code, warehouse, batch_nos):
|
|||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"posting_date": nowdate(),
|
"posting_datetime": get_combine_datetime(nowdate(), nowtime()),
|
||||||
"posting_time": nowtime(),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -2293,3 +2277,10 @@ def get_stock_value_difference(item_code, warehouse, posting_date, posting_time,
|
|||||||
|
|
||||||
difference_amount = query.run()
|
difference_amount = query.run()
|
||||||
return flt(difference_amount[0][0]) if difference_amount else 0
|
return flt(difference_amount[0][0]) if difference_amount else 0
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.request_cache
|
||||||
|
def is_transfer_stock_entry(voucher_no):
|
||||||
|
purpose = frappe.get_cached_value("Stock Entry", voucher_no, "purpose")
|
||||||
|
|
||||||
|
return purpose in ["Material Transfer", "Material Transfer for Manufacture", "Send to Subcontractor"]
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
|
|||||||
from frappe.utils import cstr, flt, get_link_to_form, get_time, getdate, nowdate, nowtime
|
from frappe.utils import cstr, flt, get_link_to_form, get_time, getdate, nowdate, nowtime
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import get_available_serial_nos
|
||||||
get_available_serial_nos,
|
|
||||||
)
|
|
||||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||||
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
|
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
|
||||||
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
|
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
|
||||||
@@ -139,8 +137,7 @@ def get_stock_balance(
|
|||||||
{
|
{
|
||||||
"item_code": item_code,
|
"item_code": item_code,
|
||||||
"warehouse": warehouse,
|
"warehouse": warehouse,
|
||||||
"posting_date": posting_date,
|
"posting_datetime": get_combine_datetime(posting_date, posting_time),
|
||||||
"posting_time": posting_time,
|
|
||||||
"ignore_warehouse": 1,
|
"ignore_warehouse": 1,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -247,6 +244,9 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
|||||||
if isinstance(args, str):
|
if isinstance(args, str):
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
|
||||||
|
if not args.get("posting_datetime") and args.get("posting_date"):
|
||||||
|
args["posting_datetime"] = get_combine_datetime(args.get("posting_date"), args.get("posting_time"))
|
||||||
|
|
||||||
in_rate = None
|
in_rate = None
|
||||||
|
|
||||||
item_details = frappe.get_cached_value(
|
item_details = frappe.get_cached_value(
|
||||||
|
|||||||
@@ -117,7 +117,13 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.validate_items_qty()
|
self.validate_items_qty()
|
||||||
self.set_items_bom()
|
self.set_items_bom()
|
||||||
self.set_items_cost_center()
|
self.set_items_cost_center()
|
||||||
self.set_items_expense_account()
|
|
||||||
|
if self.company:
|
||||||
|
default_expense_account = self.get_company_default(
|
||||||
|
"default_expense_account", ignore_validation=True
|
||||||
|
)
|
||||||
|
self.set_service_expense_account(default_expense_account)
|
||||||
|
self.set_expense_account_for_subcontracted_items(default_expense_account)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.reset_supplied_items()
|
self.reset_supplied_items()
|
||||||
@@ -205,6 +211,39 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
doc = frappe.get_doc("Job Card", row.job_card)
|
doc = frappe.get_doc("Job Card", row.job_card)
|
||||||
doc.set_manufactured_qty()
|
doc.set_manufactured_qty()
|
||||||
|
|
||||||
|
def set_service_expense_account(self, default_expense_account):
|
||||||
|
for row in self.get("items"):
|
||||||
|
if not row.service_expense_account and row.purchase_order_item:
|
||||||
|
service_item = frappe.db.get_value(
|
||||||
|
"Purchase Order Item", row.purchase_order_item, "item_code"
|
||||||
|
)
|
||||||
|
|
||||||
|
if service_item:
|
||||||
|
if default := (
|
||||||
|
get_item_defaults(service_item, self.company)
|
||||||
|
or get_item_group_defaults(service_item, self.company)
|
||||||
|
or get_brand_defaults(service_item, self.company)
|
||||||
|
):
|
||||||
|
if service_expense_account := default.get("expense_account"):
|
||||||
|
row.service_expense_account = service_expense_account
|
||||||
|
|
||||||
|
if not row.service_expense_account:
|
||||||
|
row.service_expense_account = default_expense_account
|
||||||
|
|
||||||
|
def set_expense_account_for_subcontracted_items(self, default_expense_account):
|
||||||
|
for row in self.get("items"):
|
||||||
|
if not row.expense_account:
|
||||||
|
if default := (
|
||||||
|
get_item_defaults(row.item_code, self.company)
|
||||||
|
or get_item_group_defaults(row.item_code, self.company)
|
||||||
|
or get_brand_defaults(row.item_code, self.company)
|
||||||
|
):
|
||||||
|
if expense_account := default.get("expense_account"):
|
||||||
|
row.expense_account = expense_account
|
||||||
|
|
||||||
|
if not row.expense_account:
|
||||||
|
row.expense_account = default_expense_account
|
||||||
|
|
||||||
def get_manufactured_qty(self, job_card):
|
def get_manufactured_qty(self, job_card):
|
||||||
table = frappe.qb.DocType("Subcontracting Receipt Item")
|
table = frappe.qb.DocType("Subcontracting Receipt Item")
|
||||||
query = (
|
query = (
|
||||||
@@ -262,14 +301,6 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
self.company,
|
self.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_items_expense_account(self):
|
|
||||||
if self.company:
|
|
||||||
expense_account = self.get_company_default("default_expense_account", ignore_validation=True)
|
|
||||||
|
|
||||||
for item in self.items:
|
|
||||||
if not item.expense_account:
|
|
||||||
item.expense_account = expense_account
|
|
||||||
|
|
||||||
def set_supplied_items_expense_account(self):
|
def set_supplied_items_expense_account(self):
|
||||||
for item in self.supplied_items:
|
for item in self.supplied_items:
|
||||||
if not item.expense_account:
|
if not item.expense_account:
|
||||||
@@ -625,13 +656,17 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
project=item.project,
|
project=item.project,
|
||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
service_cost = flt(
|
||||||
|
item.service_cost_per_qty, item.precision("service_cost_per_qty")
|
||||||
|
) * flt(item.qty, item.precision("qty"))
|
||||||
# Expense Account (Credit)
|
# Expense Account (Credit)
|
||||||
self.add_gl_entry(
|
self.add_gl_entry(
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
account=item.expense_account,
|
account=item.expense_account,
|
||||||
cost_center=item.cost_center,
|
cost_center=item.cost_center,
|
||||||
debit=0.0,
|
debit=0.0,
|
||||||
credit=stock_value_diff,
|
credit=flt(stock_value_diff) - service_cost,
|
||||||
remarks=remarks,
|
remarks=remarks,
|
||||||
against_account=accepted_warehouse_account,
|
against_account=accepted_warehouse_account,
|
||||||
account_currency=get_account_currency(item.expense_account),
|
account_currency=get_account_currency(item.expense_account),
|
||||||
@@ -639,6 +674,21 @@ class SubcontractingReceipt(SubcontractingController):
|
|||||||
item=item,
|
item=item,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
service_account = item.service_expense_account or item.expense_account
|
||||||
|
# Expense Account (Credit)
|
||||||
|
self.add_gl_entry(
|
||||||
|
gl_entries=gl_entries,
|
||||||
|
account=service_account,
|
||||||
|
cost_center=item.cost_center,
|
||||||
|
debit=0.0,
|
||||||
|
credit=service_cost,
|
||||||
|
remarks=remarks,
|
||||||
|
against_account=accepted_warehouse_account,
|
||||||
|
account_currency=get_account_currency(service_account),
|
||||||
|
project=item.project,
|
||||||
|
item=item,
|
||||||
|
)
|
||||||
|
|
||||||
if flt(item.rm_supp_cost) and supplier_warehouse_account:
|
if flt(item.rm_supp_cost) and supplier_warehouse_account:
|
||||||
for rm_item in supplied_items_details.get(item.name):
|
for rm_item in supplied_items_details.get(item.name):
|
||||||
# Supplier Warehouse Account (Credit)
|
# Supplier Warehouse Account (Credit)
|
||||||
|
|||||||
@@ -421,6 +421,79 @@ class TestSubcontractingReceipt(IntegrationTestCase):
|
|||||||
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
||||||
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||||
|
|
||||||
|
def test_subcontracting_receipt_for_service_expense_account(self):
|
||||||
|
service_expense_account = (
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "_Test Service Expense",
|
||||||
|
"account_type": "Expense Account",
|
||||||
|
"company": "_Test Company with perpetual inventory",
|
||||||
|
"is_group": 0,
|
||||||
|
"parent_account": "Indirect Expenses - TCP1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.insert(ignore_if_duplicate=True)
|
||||||
|
.name
|
||||||
|
)
|
||||||
|
|
||||||
|
service_item_doc = frappe.get_doc("Item", "Subcontracted Service Item 10")
|
||||||
|
service_item_doc.append(
|
||||||
|
"item_defaults",
|
||||||
|
{
|
||||||
|
"company": "_Test Company with perpetual inventory",
|
||||||
|
"expense_account": service_expense_account,
|
||||||
|
"default_warehouse": "Stores - TCP1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
service_item_doc.save()
|
||||||
|
|
||||||
|
service_items = [
|
||||||
|
{
|
||||||
|
"warehouse": "Stores - TCP1",
|
||||||
|
"item_code": "Subcontracted Service Item 10",
|
||||||
|
"qty": 10,
|
||||||
|
"rate": 100,
|
||||||
|
"fg_item": "Subcontracted Item SA10",
|
||||||
|
"fg_item_qty": 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
sco = get_subcontracting_order(
|
||||||
|
company="_Test Company with perpetual inventory",
|
||||||
|
warehouse="Stores - TCP1",
|
||||||
|
supplier_warehouse="Work In Progress - TCP1",
|
||||||
|
service_items=service_items,
|
||||||
|
)
|
||||||
|
rm_items = get_rm_items(sco.supplied_items)
|
||||||
|
itemwise_details = make_stock_in_entry(rm_items=rm_items)
|
||||||
|
make_stock_transfer_entry(
|
||||||
|
sco_no=sco.name,
|
||||||
|
rm_items=rm_items,
|
||||||
|
itemwise_details=copy.deepcopy(itemwise_details),
|
||||||
|
)
|
||||||
|
|
||||||
|
scr = make_subcontracting_receipt(sco.name)
|
||||||
|
scr.submit()
|
||||||
|
|
||||||
|
for item in scr.items:
|
||||||
|
self.assertEqual(item.service_expense_account, service_expense_account)
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
|
||||||
|
self.assertTrue(gl_entries)
|
||||||
|
|
||||||
|
fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
|
||||||
|
expense_account = scr.items[0].expense_account
|
||||||
|
expected_values = {
|
||||||
|
fg_warehouse_ac: [2000, 1000],
|
||||||
|
expense_account: [1000, 1000],
|
||||||
|
service_expense_account: [0, 1000],
|
||||||
|
}
|
||||||
|
|
||||||
|
for gle in gl_entries:
|
||||||
|
self.assertEqual(expected_values[gle.account][0], gle.debit)
|
||||||
|
self.assertEqual(expected_values[gle.account][1], gle.credit)
|
||||||
|
|
||||||
@IntegrationTestCase.change_settings("Stock Settings", {"use_serial_batch_fields": 0})
|
@IntegrationTestCase.change_settings("Stock Settings", {"use_serial_batch_fields": 0})
|
||||||
def test_subcontracting_receipt_with_zero_service_cost(self):
|
def test_subcontracting_receipt_with_zero_service_cost(self):
|
||||||
warehouse = "Stores - TCP1"
|
warehouse = "Stores - TCP1"
|
||||||
@@ -739,13 +812,13 @@ class TestSubcontractingReceipt(IntegrationTestCase):
|
|||||||
for row in scr.supplied_items:
|
for row in scr.supplied_items:
|
||||||
self.assertEqual(row.rate, 300.00)
|
self.assertEqual(row.rate, 300.00)
|
||||||
self.assertTrue(row.serial_and_batch_bundle)
|
self.assertTrue(row.serial_and_batch_bundle)
|
||||||
auto_created_serial_batch = frappe.db.get_value(
|
serial_and_batch_bundle = frappe.db.get_value(
|
||||||
"Stock Ledger Entry",
|
"Stock Ledger Entry",
|
||||||
{"voucher_no": scr.name, "voucher_detail_no": row.name},
|
{"voucher_no": scr.name, "voucher_detail_no": row.name},
|
||||||
"auto_created_serial_and_batch_bundle",
|
"serial_and_batch_bundle",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(auto_created_serial_batch)
|
self.assertTrue(serial_and_batch_bundle)
|
||||||
|
|
||||||
self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
|
self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
|
||||||
self.assertEqual(scr.items[0].service_cost_per_qty, 100)
|
self.assertEqual(scr.items[0].service_cost_per_qty, 100)
|
||||||
|
|||||||
@@ -66,6 +66,8 @@
|
|||||||
"manufacturer_part_no",
|
"manufacturer_part_no",
|
||||||
"accounting_details_section",
|
"accounting_details_section",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
|
"column_break_exht",
|
||||||
|
"service_expense_account",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"cost_center",
|
"cost_center",
|
||||||
"dimension_col_break",
|
"dimension_col_break",
|
||||||
@@ -597,13 +599,23 @@
|
|||||||
"label": "Landed Cost Voucher Amount",
|
"label": "Landed Cost Voucher Amount",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_exht",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "service_expense_account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Service Expense Account",
|
||||||
|
"options": "Account"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-11 08:45:18.903036",
|
"modified": "2025-09-26 12:00:38.877638",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Subcontracting",
|
"module": "Subcontracting",
|
||||||
"name": "Subcontracting Receipt Item",
|
"name": "Subcontracting Receipt Item",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class SubcontractingReceiptItem(Document):
|
|||||||
serial_and_batch_bundle: DF.Link | None
|
serial_and_batch_bundle: DF.Link | None
|
||||||
serial_no: DF.SmallText | None
|
serial_no: DF.SmallText | None
|
||||||
service_cost_per_qty: DF.Currency
|
service_cost_per_qty: DF.Currency
|
||||||
|
service_expense_account: DF.Link | None
|
||||||
stock_uom: DF.Link
|
stock_uom: DF.Link
|
||||||
subcontracting_order: DF.Link | None
|
subcontracting_order: DF.Link | None
|
||||||
subcontracting_order_item: DF.Data | None
|
subcontracting_order_item: DF.Data | None
|
||||||
|
|||||||
Reference in New Issue
Block a user