Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions inventory_tools/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

# include js in doctype views
doctype_js = {
"BOM": "public/js/custom/bom_custom.js",
"Item": "public/js/custom/item_custom.js",
"Job Card": "public/js/custom/job_card_custom.js",
"Operation": "public/js/custom/operation_custom.js",
Expand Down Expand Up @@ -125,6 +126,7 @@
# Override standard doctype classes

override_doctype_class = {
"BOM": "inventory_tools.inventory_tools.overrides.bom.InventoryToolsBOM",
"Delivery Note": "inventory_tools.inventory_tools.overrides.delivery_note.InventoryToolsDeliveryNote",
"Pick List": "inventory_tools.inventory_tools.overrides.pick_list.InventoryToolsPickList",
"Quality Inspection": "inventory_tools.inventory_tools.overrides.quality_inspection.InventoryToolsQualityInspection",
Expand Down
77 changes: 77 additions & 0 deletions inventory_tools/inventory_tools/overrides/bom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright (c) 2025, AgriTheory and contributors
# For license information, please see license.txt

import frappe
from erpnext.manufacturing.doctype.bom.bom import BOM
from frappe.utils import flt, getdate, nowdate


class InventoryToolsBOM(BOM):
@frappe.whitelist()
def update_cost(
self,
update_parent=True,
from_child_bom=False,
update_hour_rate=True,
save=True,
as_of_date=None,
):
self._as_of_date = getdate(as_of_date) if as_of_date else getdate(nowdate())
return super().update_cost(
update_parent=update_parent,
from_child_bom=from_child_bom,
update_hour_rate=update_hour_rate,
save=save,
)

def update_rate_and_time(self, row, update_hour_rate=False):
as_of_date = getattr(self, "_as_of_date", getdate(nowdate()))

if not row.hour_rate or update_hour_rate:
hour_rate = self._get_workstation_hour_rate(row.workstation, as_of_date)
if hour_rate is not None:
row.hour_rate = (
hour_rate / flt(self.conversion_rate) if self.conversion_rate and hour_rate else hour_rate
)
else:
cached_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate"))
if cached_rate:
row.hour_rate = (
cached_rate / flt(self.conversion_rate)
if self.conversion_rate and cached_rate
else cached_rate
)

if row.hour_rate and row.time_in_mins:
row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
row.base_operating_cost = flt(row.operating_cost) * flt(self.conversion_rate)
row.cost_per_unit = row.operating_cost / (row.batch_size or 1.0)
row.base_cost_per_unit = row.base_operating_cost / (row.batch_size or 1.0)

if update_hour_rate:
row.db_update()

def _get_workstation_hour_rate(self, workstation_name, as_of_date):
"""Sum costs from workstation_operating_cost table that cover the given date."""
rows = frappe.get_all(
"Workstation Operating Cost",
filters={"parent": workstation_name},
fields=["from_date", "to_date", "qty"],
)

if not rows:
return None

net_hour_rate = 0.0
has_match = False
for row in rows:
from_date = getdate(row.from_date) if row.from_date else None
to_date = getdate(row.to_date) if row.to_date else None

if from_date and from_date <= as_of_date:
if to_date is None or as_of_date <= to_date:
net_hour_rate += flt(row.qty)
has_match = True

return net_hour_rate if has_match else None
47 changes: 47 additions & 0 deletions inventory_tools/public/js/custom/bom_custom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2025, AgriTheory and contributors
// For license information, please see license.txt

frappe.ui.form.on('BOM', {
refresh(frm) {
if (!frm.is_new() && frm.doc.docstatus < 2) {
frm.remove_custom_button(__('Update Cost'))
frm.add_custom_button(__('Update Cost'), function () {
let dialog = new frappe.ui.Dialog({
title: __('Update Cost'),
fields: [
{
fieldname: 'as_of_date',
fieldtype: 'Date',
label: __('As of Date'),
default: frappe.datetime.get_today(),
reqd: 1,
description: __(
'Operating costs from the Workstation Operating Cost table will be applied based on this date.'
),
},
],
primary_action_label: __('Update'),
primary_action(values) {
dialog.hide()
frappe.call({
doc: frm.doc,
method: 'update_cost',
freeze: true,
args: {
update_parent: true,
save: true,
from_child_bom: false,
as_of_date: values.as_of_date,
},
callback(r) {
refresh_field('items')
if (!r.exc) frm.refresh_fields()
},
})
},
})
dialog.show()
})
}
},
})
Loading