“To Whom It May Concern, I have made some customizations in Stock Entry with the type ‘Repack’:”
“I added a Client Script and a Server Script for the ‘Before Save’ event.”
“Here it is.”
“Server Script.”
# Server Script: Stock Entry (Before Save)
if doc.purpose == "Repack":
# 1) Cost - (Raw)
total_raw_cost = 0
for row in doc.items:
if not row.is_finished_item and not row.get("is_scrap_item", 0):
total_raw_cost = total_raw_cost + (row.qty or 0) * (row.valuation_rate or 0)
# 2) Total qt without scraped
total_finished_qty = 0
for row in doc.items:
if row.is_finished_item and not row.get("is_scrap_item", 0):
total_finished_qty = total_finished_qty + (row.qty or 0)
if total_finished_qty == 0:
frappe.throw(
f"Stock Entry {doc.name}: Cannot complete Repack because no finished goods quantity found."
)
# 3) allocated_cost
for row in doc.items:
if row.is_finished_item and not row.get("is_scrap_item", 0):
share = (row.qty or 0) / total_finished_qty
allocated_cost = total_raw_cost * share
basic_rate = allocated_cost / (row.qty or 1)
extra_cost = (doc.total_additional_costs or 0) * share
extra_rate = extra_cost / (row.qty or 1)
# update value
row.basic_rate = basic_rate
row.additional_cost = extra_cost
row.valuation_rate = basic_rate + extra_rate
row.basic_amount = (basic_rate * (row.qty or 0)) + extra_cost
row.amount = (basic_rate * (row.qty or 0)) + extra_cost
elif row.get("is_scrap_item", 0):
# Scrap = zero
row.basic_rate = 0
row.valuation_rate = 0
row.is_finished_item = 0
row.basic_amount = 0
row.amount = 0
row.additional_cost = 0
row.allow_zero_valuation_rate = 1
else:
row.additional_cost = 0
# 4) Totals
total_incoming = 0
total_outgoing = 0
for row in doc.items:
if row.t_warehouse:
total_incoming = total_incoming + row.basic_amount or 0
if row.s_warehouse:
total_outgoing = total_outgoing + row.basic_amount or 0
doc.total_incoming_value = total_incoming
doc.total_outgoing_value = total_outgoing
doc.total_amount = total_incoming
doc.value_difference = (doc.total_incoming_value or 0) - (doc.total_outgoing_value or 0)
“Client Script.”
// Custom Script For Repack Transaction:
function highlight_scrap_rows(frm) {
if (frm.doc.purpose !== "Repack") return;
// rest css
$(frm.fields_dict["items"].grid.wrapper)
.find(".grid-row")
.css("background-color", "");
// if item scrap → set background-color in grey
(frm.doc.items || []).forEach(row => {
if (row.is_scrap_item) {
let $row = $(frm.fields_dict["items"].grid.wrapper)
.find(`[data-name='${row.name}']`);
$row.css("background-color", "#e0e0e0"); // grey color
}
});
}
function recalc_cost(frm) {
if (frm.doc.purpose !== "Repack") return;
let total_raw_cost = 0;
let total_finished_qty = 0;
// 1) raw material cost
(frm.doc.items || []).forEach(row => {
if (!row.is_finished_item && !row.is_scrap_item) {
total_raw_cost += (row.qty || 0) * (row.valuation_rate || 0);
}
});
// 2) total qt of FG
(frm.doc.items || []).forEach(row => {
if (row.is_finished_item && !row.is_scrap_item) {
total_finished_qty += (row.qty || 0);
}
});
// 3) cost allocation for FG
if (total_finished_qty > 0) {
(frm.doc.items || []).forEach(row => {
if (row.is_finished_item && !row.is_scrap_item) {
let share = (row.qty || 0) / total_finished_qty;
// Rw Cost
let allocated_cost = total_raw_cost * share;
let basic_rate = allocated_cost / (row.qty || 1);
// FG Cost from Addational Cost
let extra_cost = (frm.doc.total_additional_costs || 0) * share;
let extra_rate = extra_cost / (row.qty || 1);
// Update Fields Value
frappe.model.set_value(row.doctype, row.name, "basic_rate", basic_rate);
frappe.model.set_value(row.doctype, row.name, "additional_cost", extra_cost);
frappe.model.set_value(row.doctype, row.name, "valuation_rate", basic_rate + extra_rate);
frappe.model.set_value(row.doctype, row.name, "basic_amount", (basic_rate * row.qty) + extra_cost);
frappe.model.set_value(row.doctype, row.name, "amount", (basic_rate * row.qty) + extra_cost);
}
// (Scrap)
if (row.is_scrap_item) {
frappe.model.set_value(row.doctype, row.name, "basic_rate", 0);
frappe.model.set_value(row.doctype, row.name, "valuation_rate", 0);
frappe.model.set_value(row.doctype, row.name, "is_finished_item", 0);
frappe.model.set_value(row.doctype, row.name, "basic_amount", 0);
frappe.model.set_value(row.doctype, row.name, "amount", 0);
frappe.model.set_value(row.doctype, row.name, "allow_zero_valuation_rate", 1);
frappe.model.set_value(row.doctype, row.name, "additional_cost", 0);
}
});
}
// 4) Totals
let total_incoming = 0, total_outgoing = 0;
(frm.doc.items || []).forEach(row => {
if (row.t_warehouse) total_incoming += (row.basic_amount || 0);
if (row.s_warehouse) total_outgoing += (row.basic_amount || 0);
});
frm.set_value("total_incoming_value", total_incoming);
frm.set_value("total_outgoing_value", total_outgoing);
frm.set_value("total_amount", total_incoming);
frm.set_value("value_difference", (total_incoming || 0) - (total_outgoing || 0));
}
// Events
frappe.ui.form.on("Stock Entry", {
refresh(frm) { highlight_scrap_rows(frm); },
after_save(frm) { highlight_scrap_rows(frm); },
purpose(frm) { recalc_cost(frm); },
items_add(frm) { recalc_cost(frm); },
items_remove(frm) { recalc_cost(frm); },
items_on_form_rendered(frm) { recalc_cost(frm); },
total_additional_costs(frm) { recalc_cost(frm); }
});
frappe.ui.form.on("Stock Entry Detail", {
qty(frm, cdt, cdn) { recalc_cost(frm); },
is_finished_item(frm, cdt, cdn) { recalc_cost(frm); },
is_scrap_item(frm, cdt, cdn) { recalc_cost(frm); highlight_scrap_rows(frm); },
valuation_rate(frm, cdt, cdn) { recalc_cost(frm); }
});
The Final Output is:
** I also cover the addational cost:**