Merge pull request #32933 from AnandBaburajan/asset_depreciation_schedule
feat: separating depreciation schedule from assets into a new doc
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 2b3d8cb..0c71b41 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -41,12 +41,17 @@
install_whktml() {
- wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf
+ if [ "$(lsb_release -rs)" = "22.04" ]; then
+ wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
+ sudo apt install /tmp/wkhtmltox.deb
+ else
+ echo "Please update this script to support wkhtmltopdf for $(lsb_release -ds)"
+ exit 1
+ fi
}
install_whktml &
+wkpid=$!
+
cd ~/frappe-bench || exit
@@ -60,6 +65,8 @@
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
+wait $wkpid
+
bench start &> bench_run_logs.txt &
CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5a46002..37bb37e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -13,10 +13,10 @@
with:
fetch-depth: 0
persist-credentials: false
- - name: Setup Node.js v14
+ - name: Setup Node.js
uses: actions/setup-node@v2
with:
- node-version: 14
+ node-version: 18
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
@@ -28,4 +28,4 @@
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
- run: npx semantic-release
\ No newline at end of file
+ run: npx semantic-release
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index bbb8a7e..c70c76f 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -16,12 +16,12 @@
workflow_dispatch:
inputs:
user:
- description: 'user'
+ description: 'Frappe Framework repository user (add your username for forks)'
required: true
default: 'frappe'
type: string
branch:
- description: 'Branch name'
+ description: 'Frappe Framework branch'
default: 'develop'
required: false
type: string
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index fb2e444..94a1510 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -810,7 +810,7 @@
self.ple.party.isin(
qb.from_(self.customer)
.select(self.customer.name)
- .where(self.customer.default_sales_partner == self.filters.get("payment_terms_template"))
+ .where(self.customer.default_sales_partner == self.filters.get("sales_partner"))
)
)
@@ -869,10 +869,15 @@
def get_party_details(self, party):
if not party in self.party_details:
if self.party_type == "Customer":
+ fields = ["customer_name", "territory", "customer_group", "customer_primary_contact"]
+
+ if self.filters.get("sales_partner"):
+ fields.append("default_sales_partner")
+
self.party_details[party] = frappe.db.get_value(
"Customer",
party,
- ["customer_name", "territory", "customer_group", "customer_primary_contact"],
+ fields,
as_dict=True,
)
else:
@@ -973,6 +978,9 @@
if self.filters.show_sales_person:
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
+ if self.filters.sales_partner:
+ self.add_column(label=_("Sales Partner"), fieldname="default_sales_partner", fieldtype="Data")
+
if self.filters.party_type == "Supplier":
self.add_column(
label=_("Supplier Group"),
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 889f5a2..29217b0 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -121,6 +121,9 @@
if row.sales_person:
self.party_total[row.party].sales_person.append(row.sales_person)
+ if self.filters.sales_partner:
+ self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner")
+
def get_columns(self):
self.columns = []
self.add_column(
@@ -160,6 +163,10 @@
)
if self.filters.show_sales_person:
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
+
+ if self.filters.sales_partner:
+ self.add_column(label=_("Sales Partner"), fieldname="default_sales_partner", fieldtype="Data")
+
else:
self.add_column(
label=_("Supplier Group"),
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index ecad41f..4dd8205 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -4,7 +4,7 @@
frappe.provide("erpnext.bom");
frappe.ui.form.on("BOM", {
- setup: function(frm) {
+ setup(frm) {
frm.custom_make_buttons = {
'Work Order': 'Work Order',
'Quality Inspection': 'Quality Inspection'
@@ -65,11 +65,11 @@
});
},
- onload_post_render: function(frm) {
+ onload_post_render(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
- refresh: function(frm) {
+ refresh(frm) {
frm.toggle_enable("item", frm.doc.__islocal);
frm.set_indicator_formatter('item_code',
@@ -152,7 +152,7 @@
}
},
- make_work_order: function(frm) {
+ make_work_order(frm) {
frm.events.setup_variant_prompt(frm, "Work Order", (frm, item, data, variant_items) => {
frappe.call({
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
@@ -164,7 +164,7 @@
variant_items: variant_items
},
freeze: true,
- callback: function(r) {
+ callback(r) {
if(r.message) {
let doc = frappe.model.sync(r.message)[0];
frappe.set_route("Form", doc.doctype, doc.name);
@@ -174,7 +174,7 @@
});
},
- make_variant_bom: function(frm) {
+ make_variant_bom(frm) {
frm.events.setup_variant_prompt(frm, "Variant BOM", (frm, item, data, variant_items) => {
frappe.call({
method: "erpnext.manufacturing.doctype.bom.bom.make_variant_bom",
@@ -185,7 +185,7 @@
variant_items: variant_items
},
freeze: true,
- callback: function(r) {
+ callback(r) {
if(r.message) {
let doc = frappe.model.sync(r.message)[0];
frappe.set_route("Form", doc.doctype, doc.name);
@@ -195,7 +195,7 @@
}, true);
},
- setup_variant_prompt: function(frm, title, callback, skip_qty_field) {
+ setup_variant_prompt(frm, title, callback, skip_qty_field) {
const fields = [];
if (frm.doc.has_variants) {
@@ -205,7 +205,7 @@
fieldname: 'item',
options: "Item",
reqd: 1,
- get_query: function() {
+ get_query() {
return {
query: "erpnext.controllers.queries.item_query",
filters: {
@@ -273,7 +273,7 @@
fieldtype: "Link",
in_list_view: 1,
reqd: 1,
- get_query: function(data) {
+ get_query(data) {
if (!data.item_code) {
frappe.throw(__("Select template item"));
}
@@ -308,7 +308,7 @@
],
in_place_edit: true,
data: [],
- get_data: function () {
+ get_data () {
return [];
},
});
@@ -343,14 +343,14 @@
}
},
- make_quality_inspection: function(frm) {
+ make_quality_inspection(frm) {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
frm: frm
})
},
- update_cost: function(frm, save_doc=false) {
+ update_cost(frm, save_doc=false) {
return frappe.call({
doc: frm.doc,
method: "update_cost",
@@ -360,26 +360,26 @@
save: save_doc,
from_child_bom: false
},
- callback: function(r) {
+ callback(r) {
refresh_field("items");
if(!r.exc) frm.refresh_fields();
}
});
},
- rm_cost_as_per: function(frm) {
+ rm_cost_as_per(frm) {
if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
frm.set_value("plc_conversion_rate", 1.0);
}
},
- routing: function(frm) {
+ routing(frm) {
if (frm.doc.routing) {
frappe.call({
doc: frm.doc,
method: "get_routing",
freeze: true,
- callback: function(r) {
+ callback(r) {
if (!r.exc) {
frm.refresh_fields();
erpnext.bom.calculate_op_cost(frm.doc);
@@ -388,6 +388,16 @@
}
});
}
+ },
+
+ process_loss_percentage(frm) {
+ let qty = 0.0
+ if (frm.doc.process_loss_percentage) {
+ qty = (frm.doc.quantity * frm.doc.process_loss_percentage) / 100;
+ }
+
+ frm.set_value("process_loss_qty", qty);
+ frm.set_value("add_process_loss_cost_in_fg", qty ? 1: 0);
}
});
@@ -479,10 +489,6 @@
},
callback: function(r) {
d = locals[cdt][cdn];
- if (d.is_process_loss) {
- r.message.rate = 0;
- r.message.base_rate = 0;
- }
$.extend(d, r.message);
refresh_field("items");
@@ -717,10 +723,6 @@
frappe.ui.form.on("BOM Scrap Item", {
item_code(frm, cdt, cdn) {
const { item_code } = locals[cdt][cdn];
- if (item_code === frm.doc.item) {
- locals[cdt][cdn].is_process_loss = 1;
- trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code);
- }
},
});
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 0b44196..c31b69f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -6,6 +6,7 @@
"document_type": "Setup",
"engine": "InnoDB",
"field_order": [
+ "production_item_tab",
"item",
"company",
"item_name",
@@ -19,14 +20,15 @@
"quantity",
"image",
"currency_detail",
- "currency",
- "conversion_rate",
- "column_break_12",
"rm_cost_as_per",
"buying_price_list",
"price_list_currency",
"plc_conversion_rate",
+ "column_break_ivyw",
+ "currency",
+ "conversion_rate",
"section_break_21",
+ "operations_section_section",
"with_operations",
"column_break_23",
"transfer_material_against",
@@ -34,13 +36,14 @@
"operations_section",
"operations",
"materials_section",
- "inspection_required",
- "quality_inspection_template",
- "column_break_31",
- "section_break_33",
"items",
"scrap_section",
+ "scrap_items_section",
"scrap_items",
+ "process_loss_section",
+ "process_loss_percentage",
+ "column_break_ssj2",
+ "process_loss_qty",
"costing",
"operating_cost",
"raw_material_cost",
@@ -52,10 +55,14 @@
"column_break_26",
"total_cost",
"base_total_cost",
- "section_break_25",
+ "more_info_tab",
"description",
"column_break_27",
"has_variants",
+ "quality_inspection_section_break",
+ "inspection_required",
+ "column_break_dxp7",
+ "quality_inspection_template",
"section_break0",
"exploded_items",
"website_section",
@@ -68,7 +75,8 @@
"show_items",
"show_operations",
"web_long_description",
- "amended_from"
+ "amended_from",
+ "connections_tab"
],
"fields": [
{
@@ -183,7 +191,7 @@
{
"fieldname": "currency_detail",
"fieldtype": "Section Break",
- "label": "Currency and Price List"
+ "label": "Cost Configuration"
},
{
"fieldname": "company",
@@ -209,10 +217,6 @@
"reqd": 1
},
{
- "fieldname": "column_break_12",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
@@ -261,7 +265,7 @@
{
"fieldname": "materials_section",
"fieldtype": "Section Break",
- "label": "Materials",
+ "label": "Raw Materials",
"oldfieldtype": "Section Break"
},
{
@@ -276,18 +280,18 @@
{
"collapsible": 1,
"fieldname": "scrap_section",
- "fieldtype": "Section Break",
- "label": "Scrap"
+ "fieldtype": "Tab Break",
+ "label": "Scrap & Process Loss"
},
{
"fieldname": "scrap_items",
"fieldtype": "Table",
- "label": "Scrap Items",
+ "label": "Items",
"options": "BOM Scrap Item"
},
{
"fieldname": "costing",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Costing",
"oldfieldtype": "Section Break"
},
@@ -380,10 +384,6 @@
"read_only": 1
},
{
- "fieldname": "section_break_25",
- "fieldtype": "Section Break"
- },
- {
"fetch_from": "item.description",
"fieldname": "description",
"fieldtype": "Small Text",
@@ -478,8 +478,8 @@
},
{
"fieldname": "section_break_21",
- "fieldtype": "Section Break",
- "label": "Operations"
+ "fieldtype": "Tab Break",
+ "label": "Operations & Materials"
},
{
"fieldname": "column_break_23",
@@ -511,6 +511,7 @@
"fetch_from": "item.has_variants",
"fieldname": "has_variants",
"fieldtype": "Check",
+ "hidden": 1,
"in_list_view": 1,
"label": "Has Variants",
"no_copy": 1,
@@ -518,13 +519,63 @@
"read_only": 1
},
{
- "fieldname": "column_break_31",
+ "fieldname": "connections_tab",
+ "fieldtype": "Tab Break",
+ "label": "Connections",
+ "show_dashboard": 1
+ },
+ {
+ "fieldname": "operations_section_section",
+ "fieldtype": "Section Break",
+ "label": "Operations"
+ },
+ {
+ "fieldname": "process_loss_section",
+ "fieldtype": "Section Break",
+ "label": "Process Loss"
+ },
+ {
+ "fieldname": "process_loss_percentage",
+ "fieldtype": "Percent",
+ "label": "% Process Loss"
+ },
+ {
+ "fieldname": "process_loss_qty",
+ "fieldtype": "Float",
+ "label": "Process Loss Qty",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_ssj2",
"fieldtype": "Column Break"
},
{
- "fieldname": "section_break_33",
+ "fieldname": "more_info_tab",
+ "fieldtype": "Tab Break",
+ "label": "More Info"
+ },
+ {
+ "fieldname": "column_break_dxp7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "quality_inspection_section_break",
"fieldtype": "Section Break",
- "hide_border": 1
+ "label": "Quality Inspection"
+ },
+ {
+ "fieldname": "production_item_tab",
+ "fieldtype": "Tab Break",
+ "label": "Production Item"
+ },
+ {
+ "fieldname": "column_break_ivyw",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "scrap_items_section",
+ "fieldtype": "Section Break",
+ "label": "Scrap Items"
}
],
"icon": "fa fa-sitemap",
@@ -532,7 +583,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2022-01-30 21:27:54.727298",
+ "modified": "2023-01-03 18:42:27.732107",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index ca4f63d..53af28d 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -193,6 +193,7 @@
self.update_exploded_items(save=False)
self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
+ self.set_process_loss_qty()
self.validate_scrap_items()
def get_context(self, context):
@@ -233,6 +234,7 @@
"sequence_id",
"operation",
"workstation",
+ "workstation_type",
"description",
"time_in_mins",
"batch_size",
@@ -876,36 +878,19 @@
"""Get a complete tree representation preserving order of child items."""
return BOMTree(self.name)
+ def set_process_loss_qty(self):
+ if self.process_loss_percentage:
+ self.process_loss_qty = flt(self.quantity) * flt(self.process_loss_percentage) / 100
+
def validate_scrap_items(self):
- for item in self.scrap_items:
- msg = ""
- if item.item_code == self.item and not item.is_process_loss:
- msg = _(
- "Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked."
- ).format(frappe.bold(item.item_code))
- elif item.item_code != self.item and item.is_process_loss:
- msg = _(
- "Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from the item to be manufactured or repacked"
- ).format(frappe.bold(item.item_code))
+ must_be_whole_number = frappe.get_value("UOM", self.uom, "must_be_whole_number")
- must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number")
- if item.is_process_loss and must_be_whole_number:
- msg = _(
- "Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM."
- ).format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
+ if self.process_loss_percentage and self.process_loss_percentage > 100:
+ frappe.throw(_("Process Loss Percentage cannot be greater than 100"))
- if item.is_process_loss and (item.stock_qty >= self.quantity):
- msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.").format(
- frappe.bold(item.item_code)
- )
-
- if item.is_process_loss and (item.rate > 0):
- msg = _(
- "Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked."
- ).format(frappe.bold(item.item_code))
-
- if msg:
- frappe.throw(msg, title=_("Note"))
+ if self.process_loss_qty and must_be_whole_number and self.process_loss_qty % 1 != 0:
+ msg = f"Item: {frappe.bold(self.item)} with Stock UOM: {frappe.bold(self.uom)} can't have fractional process loss qty as UOM {frappe.bold(self.uom)} is a whole Number."
+ frappe.throw(msg, title=_("Invalid Process Loss Configuration"))
def get_bom_item_rate(args, bom_doc):
@@ -1053,7 +1038,7 @@
query = query.format(
table="BOM Scrap Item",
where_conditions="",
- select_columns=", item.description, is_process_loss",
+ select_columns=", item.description",
is_stock_item=is_stock_item,
qty_field="stock_qty",
)
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index e34ac12..16f5c79 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -384,36 +384,16 @@
def test_bom_with_process_loss_item(self):
fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
- if not frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001"):
- bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, fg_qty=1
- )
- bom_doc.submit()
-
bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=2, scrap_rate=0
+ fg_item_non_whole, bom_item, scrap_qty=2, scrap_rate=0, process_loss_percentage=110
)
- # PL Item qty can't be >= FG Item qty
+ # PL can't be > 100
self.assertRaises(frappe.ValidationError, bom_doc.submit)
- bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=1, scrap_rate=100
- )
- # PL Item rate has to be 0
- self.assertRaises(frappe.ValidationError, bom_doc.submit)
-
- bom_doc = create_bom_with_process_loss_item(
- fg_item_whole, bom_item, scrap_qty=0.25, scrap_rate=0
- )
+ bom_doc = create_bom_with_process_loss_item(fg_item_whole, bom_item, process_loss_percentage=20)
# Items with whole UOMs can't be PL Items
self.assertRaises(frappe.ValidationError, bom_doc.submit)
- bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, is_process_loss=0
- )
- # FG Items in Scrap/Loss Table should have Is Process Loss set
- self.assertRaises(frappe.ValidationError, bom_doc.submit)
-
def test_bom_item_query(self):
query = partial(
item_query,
@@ -744,7 +724,7 @@
def create_bom_with_process_loss_item(
- fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1
+ fg_item, bom_item, scrap_qty=0, scrap_rate=0, fg_qty=2, process_loss_percentage=0
):
bom_doc = frappe.new_doc("BOM")
bom_doc.item = fg_item.item_code
@@ -759,19 +739,22 @@
"rate": 100.0,
},
)
- bom_doc.append(
- "scrap_items",
- {
- "item_code": fg_item.item_code,
- "qty": scrap_qty,
- "stock_qty": scrap_qty,
- "uom": fg_item.stock_uom,
- "stock_uom": fg_item.stock_uom,
- "rate": scrap_rate,
- "is_process_loss": is_process_loss,
- },
- )
+
+ if scrap_qty:
+ bom_doc.append(
+ "scrap_items",
+ {
+ "item_code": fg_item.item_code,
+ "qty": scrap_qty,
+ "stock_qty": scrap_qty,
+ "uom": fg_item.stock_uom,
+ "stock_uom": fg_item.stock_uom,
+ "rate": scrap_rate,
+ },
+ )
+
bom_doc.currency = "INR"
+ bom_doc.process_loss_percentage = process_loss_percentage
return bom_doc
diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
index 7018082..b2ef19b 100644
--- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
+++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
@@ -8,7 +8,6 @@
"item_code",
"column_break_2",
"item_name",
- "is_process_loss",
"quantity_and_rate",
"stock_qty",
"rate",
@@ -89,17 +88,11 @@
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
- },
- {
- "default": "0",
- "fieldname": "is_process_loss",
- "fieldtype": "Check",
- "label": "Is Process Loss"
}
],
"istable": 1,
"links": [],
- "modified": "2021-06-22 16:46:12.153311",
+ "modified": "2023-01-03 14:19:28.460965",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Scrap Item",
@@ -108,5 +101,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index f568264..729ed42 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -846,20 +846,20 @@
create_process_loss_bom_items,
)
- qty = 4
+ qty = 10
scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
source_warehouse = "Stores - _TC"
wip_warehouse = "_Test Warehouse - _TC"
fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
test_stock_entry.make_stock_entry(
- item_code=bom_item.item_code, target=source_warehouse, qty=4, basic_rate=100
+ item_code=bom_item.item_code, target=source_warehouse, qty=qty, basic_rate=100
)
bom_no = f"BOM-{fg_item_non_whole.item_code}-001"
if not frappe.db.exists("BOM", bom_no):
bom_doc = create_bom_with_process_loss_item(
- fg_item_non_whole, bom_item, scrap_qty=scrap_qty, scrap_rate=0, fg_qty=1, is_process_loss=1
+ fg_item_non_whole, bom_item, fg_qty=1, process_loss_percentage=10
)
bom_doc.submit()
@@ -883,19 +883,15 @@
# Testing stock entry values
items = se.get("items")
- self.assertEqual(len(items), 3, "There should be 3 items including process loss.")
+ self.assertEqual(len(items), 2, "There should be 3 items including process loss.")
+ fg_item = items[1]
- source_item, fg_item, pl_item = items
+ self.assertEqual(fg_item.qty, qty - 1)
+ self.assertEqual(se.process_loss_percentage, 10)
+ self.assertEqual(se.process_loss_qty, 1)
- total_pl_qty = qty * scrap_qty
- actual_fg_qty = qty - total_pl_qty
-
- self.assertEqual(pl_item.qty, total_pl_qty)
- self.assertEqual(fg_item.qty, actual_fg_qty)
-
- # Testing Work Order values
- self.assertEqual(frappe.db.get_value("Work Order", wo.name, "produced_qty"), qty)
- self.assertEqual(frappe.db.get_value("Work Order", wo.name, "process_loss_qty"), total_pl_qty)
+ wo.load_from_db()
+ self.assertEqual(wo.status, "In Process")
@timeout(seconds=60)
def test_job_card_scrap_item(self):
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 9452a63..25e16d6 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -14,13 +14,13 @@
"item_name",
"image",
"bom_no",
+ "sales_order",
"column_break1",
"company",
"qty",
"material_transferred_for_manufacturing",
"produced_qty",
"process_loss_qty",
- "sales_order",
"project",
"serial_no_and_batch_for_finished_good_section",
"has_serial_no",
@@ -28,6 +28,7 @@
"column_break_17",
"serial_no",
"batch_size",
+ "work_order_configuration",
"settings_section",
"allow_alternative_item",
"use_multi_level_bom",
@@ -42,7 +43,11 @@
"fg_warehouse",
"scrap_warehouse",
"required_items_section",
+ "materials_and_operations_tab",
"required_items",
+ "operations_section",
+ "operations",
+ "transfer_material_against",
"time",
"planned_start_date",
"planned_end_date",
@@ -51,9 +56,6 @@
"actual_start_date",
"actual_end_date",
"lead_time",
- "operations_section",
- "transfer_material_against",
- "operations",
"section_break_22",
"planned_operating_cost",
"actual_operating_cost",
@@ -72,12 +74,14 @@
"production_plan_item",
"production_plan_sub_assembly_item",
"product_bundle_item",
- "amended_from"
+ "amended_from",
+ "connections_tab"
],
"fields": [
{
"fieldname": "item",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
+ "label": "Production Item",
"options": "fa fa-gift"
},
{
@@ -236,7 +240,7 @@
{
"fieldname": "warehouses",
"fieldtype": "Section Break",
- "label": "Warehouses",
+ "label": "Warehouse",
"options": "fa fa-building"
},
{
@@ -390,8 +394,8 @@
{
"collapsible": 1,
"fieldname": "more_info",
- "fieldtype": "Section Break",
- "label": "More Information",
+ "fieldtype": "Tab Break",
+ "label": "More Info",
"options": "fa fa-file-text"
},
{
@@ -474,8 +478,7 @@
},
{
"fieldname": "settings_section",
- "fieldtype": "Section Break",
- "label": "Settings"
+ "fieldtype": "Section Break"
},
{
"fieldname": "column_break_18",
@@ -568,6 +571,22 @@
"no_copy": 1,
"non_negative": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "connections_tab",
+ "fieldtype": "Tab Break",
+ "label": "Connections",
+ "show_dashboard": 1
+ },
+ {
+ "fieldname": "work_order_configuration",
+ "fieldtype": "Tab Break",
+ "label": "Configuration"
+ },
+ {
+ "fieldname": "materials_and_operations_tab",
+ "fieldtype": "Tab Break",
+ "label": "Materials & Operations"
}
],
"icon": "fa fa-cogs",
@@ -575,7 +594,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2022-01-24 21:18:12.160114",
+ "modified": "2023-01-03 14:16:35.427731",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 52753a0..ae9e9c6 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -246,21 +246,11 @@
status = "Draft"
elif self.docstatus == 1:
if status != "Stopped":
- stock_entries = frappe._dict(
- frappe.db.sql(
- """select purpose, sum(fg_completed_qty)
- from `tabStock Entry` where work_order=%s and docstatus=1
- group by purpose""",
- self.name,
- )
- )
-
status = "Not Started"
- if stock_entries:
+ if flt(self.material_transferred_for_manufacturing) > 0:
status = "In Process"
- produced_qty = stock_entries.get("Manufacture")
- if flt(produced_qty) >= flt(self.qty):
- status = "Completed"
+ if flt(self.produced_qty) >= flt(self.qty):
+ status = "Completed"
else:
status = "Cancelled"
@@ -285,14 +275,7 @@
):
continue
- qty = flt(
- frappe.db.sql(
- """select sum(fg_completed_qty)
- from `tabStock Entry` where work_order=%s and docstatus=1
- and purpose=%s""",
- (self.name, purpose),
- )[0][0]
- )
+ qty = self.get_transferred_or_manufactured_qty(purpose)
completed_qty = self.qty + (allowance_percentage / 100 * self.qty)
if qty > completed_qty:
@@ -314,26 +297,30 @@
if self.production_plan:
self.update_production_plan_status()
- def set_process_loss_qty(self):
- process_loss_qty = flt(
- frappe.db.sql(
- """
- SELECT sum(qty) FROM `tabStock Entry Detail`
- WHERE
- is_process_loss=1
- AND parent IN (
- SELECT name FROM `tabStock Entry`
- WHERE
- work_order=%s
- AND purpose='Manufacture'
- AND docstatus=1
- )
- """,
- (self.name,),
- )[0][0]
+ def get_transferred_or_manufactured_qty(self, purpose):
+ table = frappe.qb.DocType("Stock Entry")
+ query = frappe.qb.from_(table).where(
+ (table.work_order == self.name) & (table.docstatus == 1) & (table.purpose == purpose)
)
- if process_loss_qty is not None:
- self.db_set("process_loss_qty", process_loss_qty)
+
+ if purpose == "Manufacture":
+ query = query.select(Sum(table.fg_completed_qty) - Sum(table.process_loss_qty))
+ else:
+ query = query.select(Sum(table.fg_completed_qty))
+
+ return flt(query.run()[0][0])
+
+ def set_process_loss_qty(self):
+ table = frappe.qb.DocType("Stock Entry")
+ process_loss_qty = (
+ frappe.qb.from_(table)
+ .select(Sum(table.process_loss_qty))
+ .where(
+ (table.work_order == self.name) & (table.purpose == "Manufacture") & (table.docstatus == 1)
+ )
+ ).run()[0][0]
+
+ self.db_set("process_loss_qty", flt(process_loss_qty))
def update_production_plan_status(self):
production_plan = frappe.get_doc("Production Plan", self.production_plan)
@@ -352,6 +339,7 @@
produced_qty = total_qty[0][0] if total_qty else 0
+ self.update_status()
production_plan.run_method(
"update_produced_pending_qty", produced_qty, self.production_plan_item
)
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 4735f24..7d80ac1 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -7,6 +7,8 @@
from frappe import _
from frappe.desk.reportview import get_match_cond
from frappe.model.document import Document
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Count, CurDate, Date, UnixTimestamp
from frappe.utils import add_days, flt, get_datetime, get_time, get_url, nowtime, today
from erpnext import get_default_company
@@ -297,17 +299,19 @@
user.welcome_email_sent = 1
-def get_timeline_data(doctype, name):
+def get_timeline_data(doctype: str, name: str) -> dict[int, int]:
"""Return timeline for attendance"""
+
+ timesheet_detail = frappe.qb.DocType("Timesheet Detail")
+
return dict(
- frappe.db.sql(
- """select unix_timestamp(from_time), count(*)
- from `tabTimesheet Detail` where project=%s
- and from_time > date_sub(curdate(), interval 1 year)
- and docstatus < 2
- group by date(from_time)""",
- name,
- )
+ frappe.qb.from_(timesheet_detail)
+ .select(UnixTimestamp(timesheet_detail.from_time), Count("*"))
+ .where(timesheet_detail.project == name)
+ .where(timesheet_detail.from_time > CurDate() - Interval(years=1))
+ .where(timesheet_detail.docstatus < 2)
+ .groupby(Date(timesheet_detail.from_time))
+ .run()
)
diff --git a/erpnext/setup/doctype/item_group/item_group.json b/erpnext/setup/doctype/item_group/item_group.json
index 50f923d..2986087 100644
--- a/erpnext/setup/doctype/item_group/item_group.json
+++ b/erpnext/setup/doctype/item_group/item_group.json
@@ -123,6 +123,7 @@
"fieldname": "route",
"fieldtype": "Data",
"label": "Route",
+ "no_copy": 1,
"unique": 1
},
{
@@ -232,11 +233,10 @@
"is_tree": 1,
"links": [],
"max_attachments": 3,
- "modified": "2022-03-09 12:27:11.055782",
+ "modified": "2023-01-05 12:21:30.458628",
"modified_by": "Administrator",
"module": "Setup",
"name": "Item Group",
- "name_case": "Title Case",
"naming_rule": "By fieldname",
"nsm_parent_field": "parent_item_group",
"owner": "Administrator",
diff --git a/erpnext/setup/doctype/sales_person/sales_person.py b/erpnext/setup/doctype/sales_person/sales_person.py
index 0082c70..beff7f5 100644
--- a/erpnext/setup/doctype/sales_person/sales_person.py
+++ b/erpnext/setup/doctype/sales_person/sales_person.py
@@ -2,8 +2,13 @@
# License: GNU General Public License v3. See license.txt
+from collections import defaultdict
+from itertools import chain
+
import frappe
from frappe import _
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Count, CurDate, UnixTimestamp
from frappe.utils import flt
from frappe.utils.nestedset import NestedSet, get_root_of
@@ -77,61 +82,31 @@
frappe.db.add_index("Sales Person", ["lft", "rgt"])
-def get_timeline_data(doctype, name):
+def get_timeline_data(doctype: str, name: str) -> dict[int, int]:
+ def _fetch_activity(doctype: str, date_field: str):
+ sales_team = frappe.qb.DocType("Sales Team")
+ transaction = frappe.qb.DocType(doctype)
- out = {}
-
- out.update(
- dict(
- frappe.db.sql(
- """select
- unix_timestamp(dt.transaction_date), count(st.parenttype)
- from
- `tabSales Order` dt, `tabSales Team` st
- where
- st.sales_person = %s and st.parent = dt.name and dt.transaction_date > date_sub(curdate(), interval 1 year)
- group by dt.transaction_date """,
- name,
- )
+ return dict(
+ frappe.qb.from_(transaction)
+ .join(sales_team)
+ .on(transaction.name == sales_team.parent)
+ .select(UnixTimestamp(transaction[date_field]), Count("*"))
+ .where(sales_team.sales_person == name)
+ .where(transaction[date_field] > CurDate() - Interval(years=1))
+ .groupby(transaction[date_field])
+ .run()
)
- )
- sales_invoice = dict(
- frappe.db.sql(
- """select
- unix_timestamp(dt.posting_date), count(st.parenttype)
- from
- `tabSales Invoice` dt, `tabSales Team` st
- where
- st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
- group by dt.posting_date """,
- name,
- )
- )
+ sales_order_activity = _fetch_activity("Sales Order", "transaction_date")
+ sales_invoice_activity = _fetch_activity("Sales Invoice", "posting_date")
+ delivery_note_activity = _fetch_activity("Delivery Note", "posting_date")
- for key in sales_invoice:
- if out.get(key):
- out[key] += sales_invoice[key]
- else:
- out[key] = sales_invoice[key]
+ merged_activities = defaultdict(int)
- delivery_note = dict(
- frappe.db.sql(
- """select
- unix_timestamp(dt.posting_date), count(st.parenttype)
- from
- `tabDelivery Note` dt, `tabSales Team` st
- where
- st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
- group by dt.posting_date """,
- name,
- )
- )
+ for ts, count in chain(
+ sales_order_activity.items(), sales_invoice_activity.items(), delivery_note_activity.items()
+ ):
+ merged_activities[ts] += count
- for key in delivery_note:
- if out.get(key):
- out[key] += delivery_note[key]
- else:
- out[key] = delivery_note[key]
-
- return out
+ return merged_activities
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index d1d228d..629e50e 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -22,7 +22,6 @@
"allow_alternative_item",
"is_stock_item",
"has_variants",
- "include_item_in_manufacturing",
"opening_stock",
"valuation_rate",
"standard_rate",
@@ -112,6 +111,7 @@
"quality_inspection_template",
"inspection_required_before_delivery",
"manufacturing",
+ "include_item_in_manufacturing",
"is_sub_contracted_item",
"default_bom",
"column_break_74",
@@ -911,7 +911,7 @@
"index_web_pages_for_search": 1,
"links": [],
"make_attachments_public": 1,
- "modified": "2022-09-13 04:08:17.431731",
+ "modified": "2023-01-07 22:45:00.341745",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 20bc9d9..cf12380 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -8,6 +8,8 @@
import frappe
from frappe import _
from frappe.model.document import Document
+from frappe.query_builder import Interval
+from frappe.query_builder.functions import Count, CurDate, UnixTimestamp
from frappe.utils import (
cint,
cstr,
@@ -997,18 +999,19 @@
).insert()
-def get_timeline_data(doctype, name):
+def get_timeline_data(doctype: str, name: str) -> dict[int, int]:
"""get timeline data based on Stock Ledger Entry. This is displayed as heatmap on the item page."""
- items = frappe.db.sql(
- """select unix_timestamp(posting_date), count(*)
- from `tabStock Ledger Entry`
- where item_code=%s and posting_date > date_sub(curdate(), interval 1 year)
- group by posting_date""",
- name,
- )
+ sle = frappe.qb.DocType("Stock Ledger Entry")
- return dict(items)
+ return dict(
+ frappe.qb.from_(sle)
+ .select(UnixTimestamp(sle.posting_date), Count("*"))
+ .where(sle.item_code == name)
+ .where(sle.posting_date > CurDate() - Interval(years=1))
+ .groupby(sle.posting_date)
+ .run()
+ )
def validate_end_of_life(item_code, end_of_life=None, disabled=None):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 7e9420d..9c0f1fc 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -7,7 +7,7 @@
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
- "items_section",
+ "stock_entry_details_tab",
"naming_series",
"stock_entry_type",
"outgoing_stock_entry",
@@ -26,15 +26,20 @@
"posting_time",
"set_posting_time",
"inspection_required",
- "from_bom",
"apply_putaway_rule",
- "sb1",
- "bom_no",
- "fg_completed_qty",
- "cb1",
+ "items_tab",
+ "bom_info_section",
+ "from_bom",
"use_multi_level_bom",
+ "bom_no",
+ "cb1",
+ "fg_completed_qty",
"get_items",
- "section_break_12",
+ "section_break_7qsm",
+ "process_loss_percentage",
+ "column_break_e92r",
+ "process_loss_qty",
+ "section_break_jwgn",
"from_warehouse",
"source_warehouse_address",
"source_address_display",
@@ -44,6 +49,7 @@
"target_address_display",
"sb0",
"scan_barcode",
+ "items_section",
"items",
"get_stock_and_rate",
"section_break_19",
@@ -54,6 +60,7 @@
"additional_costs_section",
"additional_costs",
"total_additional_costs",
+ "supplier_info_tab",
"contact_section",
"supplier",
"supplier_name",
@@ -61,7 +68,7 @@
"address_display",
"accounting_dimensions_section",
"project",
- "dimension_col_break",
+ "other_info_tab",
"printing_settings",
"select_print_heading",
"print_settings_col_break",
@@ -79,11 +86,6 @@
],
"fields": [
{
- "fieldname": "items_section",
- "fieldtype": "Section Break",
- "oldfieldtype": "Section Break"
- },
- {
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
@@ -236,18 +238,13 @@
},
{
"default": "0",
- "depends_on": "eval:in_list([\"Material Issue\", \"Material Transfer\", \"Manufacture\", \"Repack\", \t\t\t\t\t\"Send to Subcontractor\", \"Material Transfer for Manufacture\", \"Material Consumption for Manufacture\"], doc.purpose)",
+ "depends_on": "eval:in_list([\"Material Issue\", \"Material Transfer\", \"Manufacture\", \"Repack\", \"Send to Subcontractor\", \"Material Transfer for Manufacture\", \"Material Consumption for Manufacture\"], doc.purpose)",
"fieldname": "from_bom",
"fieldtype": "Check",
"label": "From BOM",
"print_hide": 1
},
{
- "depends_on": "eval: doc.from_bom && (doc.purpose!==\"Sales Return\" && doc.purpose!==\"Purchase Return\")",
- "fieldname": "sb1",
- "fieldtype": "Section Break"
- },
- {
"depends_on": "from_bom",
"fieldname": "bom_no",
"fieldtype": "Link",
@@ -286,10 +283,6 @@
"print_hide": 1
},
{
- "fieldname": "section_break_12",
- "fieldtype": "Section Break"
- },
- {
"description": "Sets 'Source Warehouse' in each row of the items table.",
"fieldname": "from_warehouse",
"fieldtype": "Link",
@@ -411,7 +404,7 @@
"collapsible": 1,
"collapsible_depends_on": "total_additional_costs",
"fieldname": "additional_costs_section",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Additional Costs"
},
{
@@ -576,14 +569,10 @@
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
- "fieldtype": "Section Break",
+ "fieldtype": "Tab Break",
"label": "Accounting Dimensions"
},
{
- "fieldname": "dimension_col_break",
- "fieldtype": "Column Break"
- },
- {
"fieldname": "pick_list",
"fieldtype": "Link",
"label": "Pick List",
@@ -621,6 +610,66 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "items_tab",
+ "fieldtype": "Tab Break",
+ "label": "Items"
+ },
+ {
+ "fieldname": "bom_info_section",
+ "fieldtype": "Section Break",
+ "label": "BOM Info"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_jwgn",
+ "fieldtype": "Section Break",
+ "label": "Default Warehouse"
+ },
+ {
+ "fieldname": "other_info_tab",
+ "fieldtype": "Tab Break",
+ "label": "Other Info"
+ },
+ {
+ "fieldname": "supplier_info_tab",
+ "fieldtype": "Tab Break",
+ "label": "Supplier Info"
+ },
+ {
+ "fieldname": "stock_entry_details_tab",
+ "fieldtype": "Tab Break",
+ "label": "Details",
+ "oldfieldtype": "Section Break"
+ },
+ {
+ "fieldname": "section_break_7qsm",
+ "fieldtype": "Section Break"
+ },
+ {
+ "depends_on": "process_loss_percentage",
+ "fieldname": "process_loss_qty",
+ "fieldtype": "Float",
+ "label": "Process Loss Qty",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_e92r",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval:doc.from_bom && doc.fg_completed_qty",
+ "fetch_from": "bom_no.process_loss_percentage",
+ "fetch_if_empty": 1,
+ "fieldname": "process_loss_percentage",
+ "fieldtype": "Percent",
+ "label": "% Process Loss"
+ },
+ {
+ "fieldname": "items_section",
+ "fieldtype": "Section Break",
+ "label": "Items"
}
],
"icon": "fa fa-file-text",
@@ -628,7 +677,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-10-07 14:39:51.943770",
+ "modified": "2023-01-03 16:02:50.741816",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d401f81..352ef57 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -113,6 +113,7 @@
self.validate_warehouse()
self.validate_work_order()
self.validate_bom()
+ self.set_process_loss_qty()
self.validate_purchase_order()
self.validate_subcontracting_order()
@@ -123,7 +124,7 @@
self.validate_with_material_request()
self.validate_batch()
self.validate_inspection()
- # self.validate_fg_completed_qty()
+ self.validate_fg_completed_qty()
self.validate_difference_account()
self.set_job_card_data()
self.set_purpose_for_stock_entry()
@@ -385,11 +386,20 @@
item_wise_qty = {}
if self.purpose == "Manufacture" and self.work_order:
for d in self.items:
- if d.is_finished_item or d.is_process_loss:
+ if d.is_finished_item:
item_wise_qty.setdefault(d.item_code, []).append(d.qty)
+ precision = frappe.get_precision("Stock Entry Detail", "qty")
for item_code, qty_list in item_wise_qty.items():
- total = flt(sum(qty_list), frappe.get_precision("Stock Entry Detail", "qty"))
+ total = flt(sum(qty_list), precision)
+
+ if (self.fg_completed_qty - total) > 0:
+ self.process_loss_qty = flt(self.fg_completed_qty - total, precision)
+ self.process_loss_percentage = flt(self.process_loss_qty * 100 / self.fg_completed_qty)
+
+ if self.process_loss_qty:
+ total += flt(self.process_loss_qty, precision)
+
if self.fg_completed_qty != total:
frappe.throw(
_("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format(
@@ -468,7 +478,7 @@
if self.purpose == "Manufacture":
if validate_for_manufacture:
- if d.is_finished_item or d.is_scrap_item or d.is_process_loss:
+ if d.is_finished_item or d.is_scrap_item:
d.s_warehouse = None
if not d.t_warehouse:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -645,9 +655,7 @@
outgoing_items_cost = self.set_rate_for_outgoing_items(
reset_outgoing_rate, raise_error_if_no_rate
)
- finished_item_qty = sum(
- d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss
- )
+ finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
# Set basic rate for incoming items
for d in self.get("items"):
@@ -686,8 +694,6 @@
# do not round off basic rate to avoid precision loss
d.basic_rate = flt(d.basic_rate)
- if d.is_process_loss:
- d.basic_rate = flt(0.0)
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
@@ -1238,7 +1244,6 @@
if self.work_order:
pro_doc = frappe.get_doc("Work Order", self.work_order)
_validate_work_order(pro_doc)
- pro_doc.run_method("update_status")
if self.fg_completed_qty:
pro_doc.run_method("update_work_order_qty")
@@ -1246,6 +1251,7 @@
pro_doc.run_method("update_planned_qty")
pro_doc.update_batch_produced_qty(self)
+ pro_doc.run_method("update_status")
if not pro_doc.operations:
pro_doc.set_actual_dates()
@@ -1466,11 +1472,11 @@
# add finished goods item
if self.purpose in ("Manufacture", "Repack"):
+ self.set_process_loss_qty()
self.load_items_from_bom()
self.set_scrap_items()
self.set_actual_qty()
- self.update_items_for_process_loss()
self.validate_customer_provided_item()
self.calculate_rate_and_amount(raise_error_if_no_rate=False)
@@ -1483,6 +1489,21 @@
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
+ def set_process_loss_qty(self):
+ if self.purpose not in ("Manufacture", "Repack"):
+ return
+
+ self.process_loss_qty = 0.0
+ if not self.process_loss_percentage:
+ self.process_loss_percentage = frappe.get_cached_value(
+ "BOM", self.bom_no, "process_loss_percentage"
+ )
+
+ if self.process_loss_percentage:
+ self.process_loss_qty = flt(
+ (flt(self.fg_completed_qty) * flt(self.process_loss_percentage)) / 100
+ )
+
def set_work_order_details(self):
if not getattr(self, "pro_doc", None):
self.pro_doc = frappe._dict()
@@ -1515,7 +1536,7 @@
args = {
"to_warehouse": to_warehouse,
"from_warehouse": "",
- "qty": self.fg_completed_qty,
+ "qty": flt(self.fg_completed_qty) - flt(self.process_loss_qty),
"item_name": item.item_name,
"description": item.description,
"stock_uom": item.stock_uom,
@@ -1963,7 +1984,6 @@
)
se_child.is_finished_item = item_row.get("is_finished_item", 0)
se_child.is_scrap_item = item_row.get("is_scrap_item", 0)
- se_child.is_process_loss = item_row.get("is_process_loss", 0)
se_child.po_detail = item_row.get("po_detail")
se_child.sco_rm_detail = item_row.get("sco_rm_detail")
@@ -2210,31 +2230,6 @@
material_requests.append(material_request)
frappe.db.set_value("Material Request", material_request, "transfer_status", status)
- def update_items_for_process_loss(self):
- process_loss_dict = {}
- for d in self.get("items"):
- if not d.is_process_loss:
- continue
-
- scrap_warehouse = frappe.db.get_single_value(
- "Manufacturing Settings", "default_scrap_warehouse"
- )
- if scrap_warehouse is not None:
- d.t_warehouse = scrap_warehouse
- d.is_scrap_item = 0
-
- if d.item_code not in process_loss_dict:
- process_loss_dict[d.item_code] = [flt(0), flt(0)]
- process_loss_dict[d.item_code][0] += flt(d.transfer_qty)
- process_loss_dict[d.item_code][1] += flt(d.qty)
-
- for d in self.get("items"):
- if not d.is_finished_item or d.item_code not in process_loss_dict:
- continue
- # Assumption: 1 finished item has 1 row.
- d.transfer_qty -= process_loss_dict[d.item_code][0]
- d.qty -= process_loss_dict[d.item_code][1]
-
def set_serial_no_batch_for_finished_good(self):
serial_nos = []
if self.pro_doc.serial_no:
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 95f4f5f..fe81a87 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -20,7 +20,6 @@
"is_finished_item",
"is_scrap_item",
"quality_inspection",
- "is_process_loss",
"subcontracted_item",
"section_break_8",
"description",
@@ -561,12 +560,6 @@
},
{
"default": "0",
- "fieldname": "is_process_loss",
- "fieldtype": "Check",
- "label": "Is Process Loss"
- },
- {
- "default": "0",
"depends_on": "barcode",
"fieldname": "has_item_scanned",
"fieldtype": "Check",
@@ -578,7 +571,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-11-02 13:00:34.258828",
+ "modified": "2023-01-03 14:51:16.575515",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",