I want to edit rate manually inside BOM for a raw material, but I cannot make it happen. Perhaps there is some back-end connection due to which I cannot manually update the price if I do not have setup Valuation Rate for the Item. Any help?
Go and change it on the Item master data then run Update Rate on BOM to reflect the new cost.
That’s what I do not want to do! I want to be able to manually enter the rate for every raw material from BOM itself without needing to go to every Item and update the Valuation Rate there.
It’s because the Raw Materials price keep changing too often in my case.
That is the world economy right now. Everyone has the same problem with single source assignment problems of the ERPNext BOM structure. Cannot throw away the need for using spreadsheets…
I’ve faced this today in a demo with the lead.
I think we can create a new field for that purpose. Second option is using Price List as a cost method and create an item price in the Price List.
I have made a custom client script in order to make it work! Here is what it does:
Prerequisites-
In order to edit the rate, inside the BOM Item Doctype, I removed the Mandatory Depends On (JS) section code of rate field, after which the rate became editable.
Buying Price List Validation:
The code checks if a “Buying Price List” is selected in the form. If not, it tries to fetch a default price list. If neither is found, an error message prompts the user to select one, and the form submission is halted.
Duplicate Item-UOM Check:
It checks that no two rows in the form contain the same item-UOM combination. If any duplicates are found, a detailed message shows which items and which rows contain the offending item-UOM combinations and it halts the form’s submission.
Unique Item-UOM Rates Collection:
It collects the rates of all the unique item-UOM combinations into a collection for further processing.
Update or Create Item Prices
For each unique item-UOM combination:
It checks if a record of “Item Price” already exists for that item, UOM and price list.
If found, it updates the existing record with the new rate.
If not found, it creates a new “Item Price” record with the details provided
Success notifications are displayed to the user for both updations and new entries.
Asynchronous Processing:
Updates and insertions are asynchronous with promises, which enables the code to run smoothly while multiple requests are being processed concurrently. The process errors are logged to the console.
frappe.ui.form.on('BOM', {
validate: function(frm) {
let price_list = frm.doc.buying_price_list;
if (!price_list) {
price_list = frappe.defaults.get_default('price_list');
}
if (!price_list) {
frappe.msgprint(__('Please select a Buying Price List before saving'));
frappe.validated = false;
return;
}
// Track duplicate item-UOM combinations
let duplicateItemUomRows = {};
let duplicatesFound = false;
frm.doc.items.forEach(function(item, idx) {
if (item.item_code && item.uom) {
let key = `${item.item_code}|${item.uom}`;
if (duplicateItemUomRows[key]) {
// If duplicate found, add this row number
duplicateItemUomRows[key].push(idx + 1);
duplicatesFound = true;
} else {
duplicateItemUomRows[key] = [idx + 1];
}
}
});
// Show message if duplicates found
if (duplicatesFound) {
let message = __('You have entered the following items with the same UOM in different rows. Please combine them into a single row:') + '<br><br>';
Object.entries(duplicateItemUomRows).forEach(([key, rows]) => {
if (rows.length > 1) {
let [itemCode, uom] = key.split('|');
message += `- ${__('Item')}: <b>${itemCode}</b>, ${__('UOM')}: <b>${uom}</b>, ${__('Rows')}: ${rows.join(', ')}<br>`;
}
});
frappe.msgprint({
title: __('Duplicate Items with Same UOM'),
indicator: 'orange',
message: message
});
frappe.validated = false; // Prevent saving
return;
}
let uniqueItemUomRates = {};
// Collect unique item-UOM rates
frm.doc.items.forEach(function(item) {
if (item.item_code && item.rate && item.uom) {
let key = `${item.item_code}|${item.uom}`;
uniqueItemUomRates[key] = {
item_code: item.item_code,
uom: item.uom,
rate: parseFloat(item.rate)
};
}
});
let promises = [];
// Process each unique item-UOM combination
Object.values(uniqueItemUomRates).forEach(function(itemData) {
promises.push(
new Promise((resolve, reject) => {
frappe.call({
method: 'frappe.client.get_list',
args: {
doctype: 'Item Price',
filters: {
'item_code': itemData.item_code,
'price_list': price_list,
'uom': itemData.uom
},
fields: ['name']
}
}).then(response => {
if (response.message && response.message.length > 0) {
// Update existing Item Price
return frappe.call({
method: 'frappe.client.set_value',
args: {
doctype: 'Item Price',
name: response.message[0].name,
fieldname: 'price_list_rate',
value: itemData.rate,
update_modified: false
}
}).then(updateResponse => {
if (updateResponse.message) {
frappe.show_alert({
message: __('Price Updated for ' + itemData.item_code + ' in ' + price_list + ' for UOM ' + itemData.uom),
indicator: 'green'
});
}
resolve();
});
} else {
// Create new Item Price
return frappe.call({
method: 'frappe.client.insert',
args: {
doc: {
'doctype': 'Item Price',
'item_code': itemData.item_code,
'price_list': price_list,
'price_list_rate': itemData.rate,
'currency': frappe.defaults.get_default('currency'),
'uom': itemData.uom
}
}
}).then(insertResponse => {
if (insertResponse.message) {
frappe.show_alert({
message: __('Price List Entry Created for ' + itemData.item_code + ' in ' + price_list + ' for UOM ' + itemData.uom),
indicator: 'green'
});
}
resolve();
});
}
}).catch(err => {
console.error(`Error processing item ${itemData.item_code}:`, err);
reject(err);
});
})
);
});
return Promise.all(promises).then(() => {
console.log('All price updates completed successfully.');
}).catch(err => {
console.error('Error in processing item prices:', err);
});
}
});
That might be your case but does not solve the problem and complicates the data entry and maintenance for budgeting purposes. That is, unless you leave it editable after Submission.
Another approach would be to provide the customer for developing a planning price list for different scenarios out of current data within the running system.