mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-20 05:29:18 +00:00
Merge pull request #33601 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
|
|
||||||
- name: Setup dependencies
|
- name: Setup dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.set_target_exchange_rate(ref_doc)
|
self.set_target_exchange_rate(ref_doc)
|
||||||
|
|
||||||
def set_source_exchange_rate(self, ref_doc=None):
|
def set_source_exchange_rate(self, ref_doc=None):
|
||||||
if self.paid_from and not self.source_exchange_rate:
|
if self.paid_from:
|
||||||
if self.paid_from_account_currency == self.company_currency:
|
if self.paid_from_account_currency == self.company_currency:
|
||||||
self.source_exchange_rate = 1
|
self.source_exchange_rate = 1
|
||||||
else:
|
else:
|
||||||
@@ -622,7 +622,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.payment_type == "Receive"
|
self.payment_type == "Receive"
|
||||||
and self.base_total_allocated_amount < self.base_received_amount + total_deductions
|
and self.base_total_allocated_amount < self.base_received_amount + total_deductions
|
||||||
and self.total_allocated_amount
|
and self.total_allocated_amount
|
||||||
< self.paid_amount + (total_deductions / self.source_exchange_rate)
|
< flt(self.paid_amount) + (total_deductions / self.source_exchange_rate)
|
||||||
):
|
):
|
||||||
self.unallocated_amount = (
|
self.unallocated_amount = (
|
||||||
self.base_received_amount + total_deductions - self.base_total_allocated_amount
|
self.base_received_amount + total_deductions - self.base_total_allocated_amount
|
||||||
@@ -632,7 +632,7 @@ class PaymentEntry(AccountsController):
|
|||||||
self.payment_type == "Pay"
|
self.payment_type == "Pay"
|
||||||
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions)
|
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions)
|
||||||
and self.total_allocated_amount
|
and self.total_allocated_amount
|
||||||
< self.received_amount + (total_deductions / self.target_exchange_rate)
|
< flt(self.received_amount) + (total_deductions / self.target_exchange_rate)
|
||||||
):
|
):
|
||||||
self.unallocated_amount = (
|
self.unallocated_amount = (
|
||||||
self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)
|
self.base_paid_amount - (total_deductions + self.base_total_allocated_amount)
|
||||||
|
|||||||
@@ -810,7 +810,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.ple.party.isin(
|
self.ple.party.isin(
|
||||||
qb.from_(self.customer)
|
qb.from_(self.customer)
|
||||||
.select(self.customer.name)
|
.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 @@ class ReceivablePayableReport(object):
|
|||||||
def get_party_details(self, party):
|
def get_party_details(self, party):
|
||||||
if not party in self.party_details:
|
if not party in self.party_details:
|
||||||
if self.party_type == "Customer":
|
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(
|
self.party_details[party] = frappe.db.get_value(
|
||||||
"Customer",
|
"Customer",
|
||||||
party,
|
party,
|
||||||
["customer_name", "territory", "customer_group", "customer_primary_contact"],
|
fields,
|
||||||
as_dict=True,
|
as_dict=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -973,6 +978,9 @@ class ReceivablePayableReport(object):
|
|||||||
if self.filters.show_sales_person:
|
if self.filters.show_sales_person:
|
||||||
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
|
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":
|
if self.filters.party_type == "Supplier":
|
||||||
self.add_column(
|
self.add_column(
|
||||||
label=_("Supplier Group"),
|
label=_("Supplier Group"),
|
||||||
|
|||||||
@@ -121,6 +121,9 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
if row.sales_person:
|
if row.sales_person:
|
||||||
self.party_total[row.party].sales_person.append(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):
|
def get_columns(self):
|
||||||
self.columns = []
|
self.columns = []
|
||||||
self.add_column(
|
self.add_column(
|
||||||
@@ -160,6 +163,10 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
)
|
)
|
||||||
if self.filters.show_sales_person:
|
if self.filters.show_sales_person:
|
||||||
self.add_column(label=_("Sales Person"), fieldname="sales_person", fieldtype="Data")
|
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:
|
else:
|
||||||
self.add_column(
|
self.add_column(
|
||||||
label=_("Supplier Group"),
|
label=_("Supplier Group"),
|
||||||
|
|||||||
@@ -439,6 +439,18 @@ class GrossProfitGenerator(object):
|
|||||||
row.delivery_note, frappe._dict()
|
row.delivery_note, frappe._dict()
|
||||||
)
|
)
|
||||||
row.item_row = row.dn_detail
|
row.item_row = row.dn_detail
|
||||||
|
# Update warehouse and base_amount from 'Packed Item' List
|
||||||
|
if product_bundles and not row.parent:
|
||||||
|
# For Packed Items, row.parent_invoice will be the Bundle name
|
||||||
|
product_bundle = product_bundles.get(row.parent_invoice)
|
||||||
|
if product_bundle:
|
||||||
|
for packed_item in product_bundle:
|
||||||
|
if (
|
||||||
|
packed_item.get("item_code") == row.item_code
|
||||||
|
and packed_item.get("parent_detail_docname") == row.item_row
|
||||||
|
):
|
||||||
|
row.warehouse = packed_item.warehouse
|
||||||
|
row.base_amount = packed_item.base_amount
|
||||||
|
|
||||||
# get buying amount
|
# get buying amount
|
||||||
if row.item_code in product_bundles:
|
if row.item_code in product_bundles:
|
||||||
@@ -589,7 +601,9 @@ class GrossProfitGenerator(object):
|
|||||||
buying_amount = 0.0
|
buying_amount = 0.0
|
||||||
for packed_item in product_bundle:
|
for packed_item in product_bundle:
|
||||||
if packed_item.get("parent_detail_docname") == row.item_row:
|
if packed_item.get("parent_detail_docname") == row.item_row:
|
||||||
buying_amount += self.get_buying_amount(row, packed_item.item_code)
|
packed_item_row = row.copy()
|
||||||
|
packed_item_row.warehouse = packed_item.warehouse
|
||||||
|
buying_amount += self.get_buying_amount(packed_item_row, packed_item.item_code)
|
||||||
|
|
||||||
return flt(buying_amount, self.currency_precision)
|
return flt(buying_amount, self.currency_precision)
|
||||||
|
|
||||||
@@ -922,12 +936,25 @@ class GrossProfitGenerator(object):
|
|||||||
def load_product_bundle(self):
|
def load_product_bundle(self):
|
||||||
self.product_bundles = {}
|
self.product_bundles = {}
|
||||||
|
|
||||||
for d in frappe.db.sql(
|
pki = qb.DocType("Packed Item")
|
||||||
"""select parenttype, parent, parent_item,
|
|
||||||
item_code, warehouse, -1*qty as total_qty, parent_detail_docname
|
pki_query = (
|
||||||
from `tabPacked Item` where docstatus=1""",
|
frappe.qb.from_(pki)
|
||||||
as_dict=True,
|
.select(
|
||||||
):
|
pki.parenttype,
|
||||||
|
pki.parent,
|
||||||
|
pki.parent_item,
|
||||||
|
pki.item_code,
|
||||||
|
pki.warehouse,
|
||||||
|
(-1 * pki.qty).as_("total_qty"),
|
||||||
|
pki.rate,
|
||||||
|
(pki.rate * pki.qty).as_("base_amount"),
|
||||||
|
pki.parent_detail_docname,
|
||||||
|
)
|
||||||
|
.where(pki.docstatus == 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
for d in pki_query.run(as_dict=True):
|
||||||
self.product_bundles.setdefault(d.parenttype, frappe._dict()).setdefault(
|
self.product_bundles.setdefault(d.parenttype, frappe._dict()).setdefault(
|
||||||
d.parent, frappe._dict()
|
d.parent, frappe._dict()
|
||||||
).setdefault(d.parent_item, []).append(d)
|
).setdefault(d.parent_item, []).append(d)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
"supplier_and_price_defaults_section",
|
"supplier_and_price_defaults_section",
|
||||||
"supp_master_name",
|
"supp_master_name",
|
||||||
"supplier_group",
|
"supplier_group",
|
||||||
"column_break_4",
|
|
||||||
"buying_price_list",
|
"buying_price_list",
|
||||||
|
"column_break_4",
|
||||||
"maintain_same_rate_action",
|
"maintain_same_rate_action",
|
||||||
"role_to_override_stop_action",
|
"role_to_override_stop_action",
|
||||||
"transaction_settings_section",
|
"transaction_settings_section",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
|
"disable_last_purchase_rate",
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
@@ -71,7 +72,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "subcontract",
|
"fieldname": "subcontract",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Subcontracting Settings"
|
"label": "Subcontracting Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -118,8 +119,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "supplier_and_price_defaults_section",
|
"fieldname": "supplier_and_price_defaults_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Supplier and Price Defaults"
|
"label": "Naming Series and Price Defaults"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
@@ -127,12 +128,18 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "transaction_settings_section",
|
"fieldname": "transaction_settings_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Transaction Settings"
|
"label": "Transaction Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "disable_last_purchase_rate",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Disable Last Purchase Rate"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-cog",
|
"icon": "fa fa-cog",
|
||||||
@@ -140,7 +147,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-27 10:50:27.050252",
|
"modified": "2023-01-09 17:08:28.828173",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -743,9 +743,9 @@ class TestPurchaseOrder(FrappeTestCase):
|
|||||||
pe = get_payment_entry("Purchase Order", po_doc.name)
|
pe = get_payment_entry("Purchase Order", po_doc.name)
|
||||||
pe.mode_of_payment = "Cash"
|
pe.mode_of_payment = "Cash"
|
||||||
pe.paid_from = "Cash - _TC"
|
pe.paid_from = "Cash - _TC"
|
||||||
pe.source_exchange_rate = 80
|
pe.source_exchange_rate = 1
|
||||||
pe.target_exchange_rate = 1
|
pe.target_exchange_rate = 80
|
||||||
pe.paid_amount = po_doc.grand_total
|
pe.paid_amount = po_doc.base_grand_total
|
||||||
pe.save(ignore_permissions=True)
|
pe.save(ignore_permissions=True)
|
||||||
pe.submit()
|
pe.submit()
|
||||||
|
|
||||||
|
|||||||
@@ -216,6 +216,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
recipients=data.email_id,
|
recipients=data.email_id,
|
||||||
sender=sender,
|
sender=sender,
|
||||||
attachments=attachments,
|
attachments=attachments,
|
||||||
|
print_format=self.meta.default_print_format or "Standard",
|
||||||
send_email=True,
|
send_email=True,
|
||||||
doctype=self.doctype,
|
doctype=self.doctype,
|
||||||
name=self.name,
|
name=self.name,
|
||||||
@@ -224,9 +225,7 @@ class RequestforQuotation(BuyingController):
|
|||||||
frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier))
|
frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier))
|
||||||
|
|
||||||
def get_attachments(self):
|
def get_attachments(self):
|
||||||
attachments = [d.name for d in get_attachments(self.doctype, self.name)]
|
return [d.name for d in get_attachments(self.doctype, self.name)]
|
||||||
attachments.append(frappe.attach_print(self.doctype, self.name, doc=self))
|
|
||||||
return attachments
|
|
||||||
|
|
||||||
def update_rfq_supplier_status(self, sup_name=None):
|
def update_rfq_supplier_status(self, sup_name=None):
|
||||||
for supplier in self.suppliers:
|
for supplier in self.suppliers:
|
||||||
|
|||||||
@@ -278,6 +278,9 @@ class BuyingController(SubcontractingController):
|
|||||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not self.is_internal_transfer():
|
||||||
|
return
|
||||||
|
|
||||||
ref_doctype_map = {
|
ref_doctype_map = {
|
||||||
"Purchase Order": "Sales Order Item",
|
"Purchase Order": "Sales Order Item",
|
||||||
"Purchase Receipt": "Delivery Note Item",
|
"Purchase Receipt": "Delivery Note Item",
|
||||||
@@ -548,7 +551,9 @@ class BuyingController(SubcontractingController):
|
|||||||
self.process_fixed_asset()
|
self.process_fixed_asset()
|
||||||
self.update_fixed_asset(field)
|
self.update_fixed_asset(field)
|
||||||
|
|
||||||
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
|
if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "disable_last_purchase_rate"
|
||||||
|
):
|
||||||
update_last_purchase_rate(self, is_submit=1)
|
update_last_purchase_rate(self, is_submit=1)
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@@ -557,7 +562,9 @@ class BuyingController(SubcontractingController):
|
|||||||
if self.get("is_return"):
|
if self.get("is_return"):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
|
if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "disable_last_purchase_rate"
|
||||||
|
):
|
||||||
update_last_purchase_rate(self, is_submit=0)
|
update_last_purchase_rate(self, is_submit=0)
|
||||||
|
|
||||||
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
frappe.provide("erpnext.bom");
|
frappe.provide("erpnext.bom");
|
||||||
|
|
||||||
frappe.ui.form.on("BOM", {
|
frappe.ui.form.on("BOM", {
|
||||||
setup: function(frm) {
|
setup(frm) {
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Work Order': 'Work Order',
|
'Work Order': 'Work Order',
|
||||||
'Quality Inspection': 'Quality Inspection'
|
'Quality Inspection': 'Quality Inspection'
|
||||||
@@ -65,11 +65,11 @@ frappe.ui.form.on("BOM", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onload_post_render: function(frm) {
|
onload_post_render(frm) {
|
||||||
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
|
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh(frm) {
|
||||||
frm.toggle_enable("item", frm.doc.__islocal);
|
frm.toggle_enable("item", frm.doc.__islocal);
|
||||||
|
|
||||||
frm.set_indicator_formatter('item_code',
|
frm.set_indicator_formatter('item_code',
|
||||||
@@ -152,7 +152,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
make_work_order: function(frm) {
|
make_work_order(frm) {
|
||||||
frm.events.setup_variant_prompt(frm, "Work Order", (frm, item, data, variant_items) => {
|
frm.events.setup_variant_prompt(frm, "Work Order", (frm, item, data, variant_items) => {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
|
method: "erpnext.manufacturing.doctype.work_order.work_order.make_work_order",
|
||||||
@@ -164,7 +164,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
variant_items: variant_items
|
variant_items: variant_items
|
||||||
},
|
},
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r) {
|
callback(r) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
let doc = frappe.model.sync(r.message)[0];
|
let doc = frappe.model.sync(r.message)[0];
|
||||||
frappe.set_route("Form", doc.doctype, doc.name);
|
frappe.set_route("Form", doc.doctype, doc.name);
|
||||||
@@ -174,7 +174,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
make_variant_bom: function(frm) {
|
make_variant_bom(frm) {
|
||||||
frm.events.setup_variant_prompt(frm, "Variant BOM", (frm, item, data, variant_items) => {
|
frm.events.setup_variant_prompt(frm, "Variant BOM", (frm, item, data, variant_items) => {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.bom.bom.make_variant_bom",
|
method: "erpnext.manufacturing.doctype.bom.bom.make_variant_bom",
|
||||||
@@ -185,7 +185,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
variant_items: variant_items
|
variant_items: variant_items
|
||||||
},
|
},
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r) {
|
callback(r) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
let doc = frappe.model.sync(r.message)[0];
|
let doc = frappe.model.sync(r.message)[0];
|
||||||
frappe.set_route("Form", doc.doctype, doc.name);
|
frappe.set_route("Form", doc.doctype, doc.name);
|
||||||
@@ -195,7 +195,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
}, true);
|
}, true);
|
||||||
},
|
},
|
||||||
|
|
||||||
setup_variant_prompt: function(frm, title, callback, skip_qty_field) {
|
setup_variant_prompt(frm, title, callback, skip_qty_field) {
|
||||||
const fields = [];
|
const fields = [];
|
||||||
|
|
||||||
if (frm.doc.has_variants) {
|
if (frm.doc.has_variants) {
|
||||||
@@ -205,7 +205,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
fieldname: 'item',
|
fieldname: 'item',
|
||||||
options: "Item",
|
options: "Item",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
get_query: function() {
|
get_query() {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters: {
|
filters: {
|
||||||
@@ -273,7 +273,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
in_list_view: 1,
|
in_list_view: 1,
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
get_query: function(data) {
|
get_query(data) {
|
||||||
if (!data.item_code) {
|
if (!data.item_code) {
|
||||||
frappe.throw(__("Select template item"));
|
frappe.throw(__("Select template item"));
|
||||||
}
|
}
|
||||||
@@ -308,7 +308,7 @@ frappe.ui.form.on("BOM", {
|
|||||||
],
|
],
|
||||||
in_place_edit: true,
|
in_place_edit: true,
|
||||||
data: [],
|
data: [],
|
||||||
get_data: function () {
|
get_data () {
|
||||||
return [];
|
return [];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -343,14 +343,14 @@ frappe.ui.form.on("BOM", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
make_quality_inspection: function(frm) {
|
make_quality_inspection(frm) {
|
||||||
frappe.model.open_mapped_doc({
|
frappe.model.open_mapped_doc({
|
||||||
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
|
method: "erpnext.stock.doctype.quality_inspection.quality_inspection.make_quality_inspection",
|
||||||
frm: frm
|
frm: frm
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
update_cost: function(frm, save_doc=false) {
|
update_cost(frm, save_doc=false) {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: "update_cost",
|
method: "update_cost",
|
||||||
@@ -360,26 +360,26 @@ frappe.ui.form.on("BOM", {
|
|||||||
save: save_doc,
|
save: save_doc,
|
||||||
from_child_bom: false
|
from_child_bom: false
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback(r) {
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
if(!r.exc) frm.refresh_fields();
|
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)) {
|
if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
|
||||||
frm.set_value("plc_conversion_rate", 1.0);
|
frm.set_value("plc_conversion_rate", 1.0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
routing: function(frm) {
|
routing(frm) {
|
||||||
if (frm.doc.routing) {
|
if (frm.doc.routing) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: "get_routing",
|
method: "get_routing",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
callback: function(r) {
|
callback(r) {
|
||||||
if (!r.exc) {
|
if (!r.exc) {
|
||||||
frm.refresh_fields();
|
frm.refresh_fields();
|
||||||
erpnext.bom.calculate_op_cost(frm.doc);
|
erpnext.bom.calculate_op_cost(frm.doc);
|
||||||
@@ -388,6 +388,16 @@ frappe.ui.form.on("BOM", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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 @@ var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) {
|
|||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
d = locals[cdt][cdn];
|
d = locals[cdt][cdn];
|
||||||
if (d.is_process_loss) {
|
|
||||||
r.message.rate = 0;
|
|
||||||
r.message.base_rate = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$.extend(d, r.message);
|
$.extend(d, r.message);
|
||||||
refresh_field("items");
|
refresh_field("items");
|
||||||
@@ -717,10 +723,6 @@ frappe.tour['BOM'] = [
|
|||||||
frappe.ui.form.on("BOM Scrap Item", {
|
frappe.ui.form.on("BOM Scrap Item", {
|
||||||
item_code(frm, cdt, cdn) {
|
item_code(frm, cdt, cdn) {
|
||||||
const { item_code } = locals[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);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"document_type": "Setup",
|
"document_type": "Setup",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
|
"production_item_tab",
|
||||||
"item",
|
"item",
|
||||||
"company",
|
"company",
|
||||||
"item_name",
|
"item_name",
|
||||||
@@ -19,14 +20,15 @@
|
|||||||
"quantity",
|
"quantity",
|
||||||
"image",
|
"image",
|
||||||
"currency_detail",
|
"currency_detail",
|
||||||
"currency",
|
|
||||||
"conversion_rate",
|
|
||||||
"column_break_12",
|
|
||||||
"rm_cost_as_per",
|
"rm_cost_as_per",
|
||||||
"buying_price_list",
|
"buying_price_list",
|
||||||
"price_list_currency",
|
"price_list_currency",
|
||||||
"plc_conversion_rate",
|
"plc_conversion_rate",
|
||||||
|
"column_break_ivyw",
|
||||||
|
"currency",
|
||||||
|
"conversion_rate",
|
||||||
"section_break_21",
|
"section_break_21",
|
||||||
|
"operations_section_section",
|
||||||
"with_operations",
|
"with_operations",
|
||||||
"column_break_23",
|
"column_break_23",
|
||||||
"transfer_material_against",
|
"transfer_material_against",
|
||||||
@@ -34,13 +36,14 @@
|
|||||||
"operations_section",
|
"operations_section",
|
||||||
"operations",
|
"operations",
|
||||||
"materials_section",
|
"materials_section",
|
||||||
"inspection_required",
|
|
||||||
"quality_inspection_template",
|
|
||||||
"column_break_31",
|
|
||||||
"section_break_33",
|
|
||||||
"items",
|
"items",
|
||||||
"scrap_section",
|
"scrap_section",
|
||||||
|
"scrap_items_section",
|
||||||
"scrap_items",
|
"scrap_items",
|
||||||
|
"process_loss_section",
|
||||||
|
"process_loss_percentage",
|
||||||
|
"column_break_ssj2",
|
||||||
|
"process_loss_qty",
|
||||||
"costing",
|
"costing",
|
||||||
"operating_cost",
|
"operating_cost",
|
||||||
"raw_material_cost",
|
"raw_material_cost",
|
||||||
@@ -52,10 +55,14 @@
|
|||||||
"column_break_26",
|
"column_break_26",
|
||||||
"total_cost",
|
"total_cost",
|
||||||
"base_total_cost",
|
"base_total_cost",
|
||||||
"section_break_25",
|
"more_info_tab",
|
||||||
"description",
|
"description",
|
||||||
"column_break_27",
|
"column_break_27",
|
||||||
"has_variants",
|
"has_variants",
|
||||||
|
"quality_inspection_section_break",
|
||||||
|
"inspection_required",
|
||||||
|
"column_break_dxp7",
|
||||||
|
"quality_inspection_template",
|
||||||
"section_break0",
|
"section_break0",
|
||||||
"exploded_items",
|
"exploded_items",
|
||||||
"website_section",
|
"website_section",
|
||||||
@@ -68,7 +75,8 @@
|
|||||||
"show_items",
|
"show_items",
|
||||||
"show_operations",
|
"show_operations",
|
||||||
"web_long_description",
|
"web_long_description",
|
||||||
"amended_from"
|
"amended_from",
|
||||||
|
"connections_tab"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -183,7 +191,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "currency_detail",
|
"fieldname": "currency_detail",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Currency and Price List"
|
"label": "Cost Configuration"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "company",
|
"fieldname": "company",
|
||||||
@@ -208,10 +216,6 @@
|
|||||||
"precision": "9",
|
"precision": "9",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "column_break_12",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -261,7 +265,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "materials_section",
|
"fieldname": "materials_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Materials",
|
"label": "Raw Materials",
|
||||||
"oldfieldtype": "Section Break"
|
"oldfieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -276,18 +280,18 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "scrap_section",
|
"fieldname": "scrap_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Scrap"
|
"label": "Scrap & Process Loss"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "scrap_items",
|
"fieldname": "scrap_items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
"label": "Scrap Items",
|
"label": "Items",
|
||||||
"options": "BOM Scrap Item"
|
"options": "BOM Scrap Item"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "costing",
|
"fieldname": "costing",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Costing",
|
"label": "Costing",
|
||||||
"oldfieldtype": "Section Break"
|
"oldfieldtype": "Section Break"
|
||||||
},
|
},
|
||||||
@@ -379,10 +383,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "section_break_25",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fetch_from": "item.description",
|
"fetch_from": "item.description",
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
@@ -478,8 +478,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_21",
|
"fieldname": "section_break_21",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Operations"
|
"label": "Operations & Materials"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_23",
|
"fieldname": "column_break_23",
|
||||||
@@ -511,6 +511,7 @@
|
|||||||
"fetch_from": "item.has_variants",
|
"fetch_from": "item.has_variants",
|
||||||
"fieldname": "has_variants",
|
"fieldname": "has_variants",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 1,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Has Variants",
|
"label": "Has Variants",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
@@ -518,13 +519,63 @@
|
|||||||
"read_only": 1
|
"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"
|
"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",
|
"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",
|
"icon": "fa fa-sitemap",
|
||||||
@@ -532,7 +583,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-01-30 21:27:54.727298",
|
"modified": "2023-01-03 18:42:27.732107",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM",
|
"name": "BOM",
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ class BOM(WebsiteGenerator):
|
|||||||
self.update_exploded_items(save=False)
|
self.update_exploded_items(save=False)
|
||||||
self.update_stock_qty()
|
self.update_stock_qty()
|
||||||
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
|
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()
|
self.validate_scrap_items()
|
||||||
|
|
||||||
def get_context(self, context):
|
def get_context(self, context):
|
||||||
@@ -233,6 +234,7 @@ class BOM(WebsiteGenerator):
|
|||||||
"sequence_id",
|
"sequence_id",
|
||||||
"operation",
|
"operation",
|
||||||
"workstation",
|
"workstation",
|
||||||
|
"workstation_type",
|
||||||
"description",
|
"description",
|
||||||
"time_in_mins",
|
"time_in_mins",
|
||||||
"batch_size",
|
"batch_size",
|
||||||
@@ -876,36 +878,19 @@ class BOM(WebsiteGenerator):
|
|||||||
"""Get a complete tree representation preserving order of child items."""
|
"""Get a complete tree representation preserving order of child items."""
|
||||||
return BOMTree(self.name)
|
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):
|
def validate_scrap_items(self):
|
||||||
for item in self.scrap_items:
|
must_be_whole_number = frappe.get_value("UOM", self.uom, "must_be_whole_number")
|
||||||
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", item.stock_uom, "must_be_whole_number")
|
if self.process_loss_percentage and self.process_loss_percentage > 100:
|
||||||
if item.is_process_loss and must_be_whole_number:
|
frappe.throw(_("Process Loss Percentage cannot be greater than 100"))
|
||||||
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 item.is_process_loss and (item.stock_qty >= self.quantity):
|
if self.process_loss_qty and must_be_whole_number and self.process_loss_qty % 1 != 0:
|
||||||
msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.").format(
|
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.bold(item.item_code)
|
frappe.throw(msg, title=_("Invalid Process Loss Configuration"))
|
||||||
)
|
|
||||||
|
|
||||||
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"))
|
|
||||||
|
|
||||||
|
|
||||||
def get_bom_item_rate(args, bom_doc):
|
def get_bom_item_rate(args, bom_doc):
|
||||||
@@ -1053,7 +1038,7 @@ def get_bom_items_as_dict(
|
|||||||
query = query.format(
|
query = query.format(
|
||||||
table="BOM Scrap Item",
|
table="BOM Scrap Item",
|
||||||
where_conditions="",
|
where_conditions="",
|
||||||
select_columns=", item.description, is_process_loss",
|
select_columns=", item.description",
|
||||||
is_stock_item=is_stock_item,
|
is_stock_item=is_stock_item,
|
||||||
qty_field="stock_qty",
|
qty_field="stock_qty",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -384,36 +384,16 @@ class TestBOM(FrappeTestCase):
|
|||||||
def test_bom_with_process_loss_item(self):
|
def test_bom_with_process_loss_item(self):
|
||||||
fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
|
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(
|
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)
|
self.assertRaises(frappe.ValidationError, bom_doc.submit)
|
||||||
|
|
||||||
bom_doc = create_bom_with_process_loss_item(
|
bom_doc = create_bom_with_process_loss_item(fg_item_whole, bom_item, process_loss_percentage=20)
|
||||||
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
|
|
||||||
)
|
|
||||||
# Items with whole UOMs can't be PL Items
|
# Items with whole UOMs can't be PL Items
|
||||||
self.assertRaises(frappe.ValidationError, bom_doc.submit)
|
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):
|
def test_bom_item_query(self):
|
||||||
query = partial(
|
query = partial(
|
||||||
item_query,
|
item_query,
|
||||||
@@ -744,7 +724,7 @@ def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=Non
|
|||||||
|
|
||||||
|
|
||||||
def create_bom_with_process_loss_item(
|
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 = frappe.new_doc("BOM")
|
||||||
bom_doc.item = fg_item.item_code
|
bom_doc.item = fg_item.item_code
|
||||||
@@ -759,19 +739,22 @@ def create_bom_with_process_loss_item(
|
|||||||
"rate": 100.0,
|
"rate": 100.0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
bom_doc.append(
|
|
||||||
"scrap_items",
|
if scrap_qty:
|
||||||
{
|
bom_doc.append(
|
||||||
"item_code": fg_item.item_code,
|
"scrap_items",
|
||||||
"qty": scrap_qty,
|
{
|
||||||
"stock_qty": scrap_qty,
|
"item_code": fg_item.item_code,
|
||||||
"uom": fg_item.stock_uom,
|
"qty": scrap_qty,
|
||||||
"stock_uom": fg_item.stock_uom,
|
"stock_qty": scrap_qty,
|
||||||
"rate": scrap_rate,
|
"uom": fg_item.stock_uom,
|
||||||
"is_process_loss": is_process_loss,
|
"stock_uom": fg_item.stock_uom,
|
||||||
},
|
"rate": scrap_rate,
|
||||||
)
|
},
|
||||||
|
)
|
||||||
|
|
||||||
bom_doc.currency = "INR"
|
bom_doc.currency = "INR"
|
||||||
|
bom_doc.process_loss_percentage = process_loss_percentage
|
||||||
return bom_doc
|
return bom_doc
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"item_code",
|
"item_code",
|
||||||
"column_break_2",
|
"column_break_2",
|
||||||
"item_name",
|
"item_name",
|
||||||
"is_process_loss",
|
|
||||||
"quantity_and_rate",
|
"quantity_and_rate",
|
||||||
"stock_qty",
|
"stock_qty",
|
||||||
"rate",
|
"rate",
|
||||||
@@ -89,17 +88,11 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_2",
|
"fieldname": "column_break_2",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_process_loss",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Process Loss"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-06-22 16:46:12.153311",
|
"modified": "2023-01-03 14:19:28.460965",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "BOM Scrap Item",
|
"name": "BOM Scrap Item",
|
||||||
@@ -108,5 +101,6 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -846,20 +846,20 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
create_process_loss_bom_items,
|
create_process_loss_bom_items,
|
||||||
)
|
)
|
||||||
|
|
||||||
qty = 4
|
qty = 10
|
||||||
scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
|
scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
|
||||||
source_warehouse = "Stores - _TC"
|
source_warehouse = "Stores - _TC"
|
||||||
wip_warehouse = "_Test Warehouse - _TC"
|
wip_warehouse = "_Test Warehouse - _TC"
|
||||||
fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
|
fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
|
||||||
|
|
||||||
test_stock_entry.make_stock_entry(
|
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"
|
bom_no = f"BOM-{fg_item_non_whole.item_code}-001"
|
||||||
if not frappe.db.exists("BOM", bom_no):
|
if not frappe.db.exists("BOM", bom_no):
|
||||||
bom_doc = create_bom_with_process_loss_item(
|
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()
|
bom_doc.submit()
|
||||||
|
|
||||||
@@ -883,19 +883,15 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
|
|
||||||
# Testing stock entry values
|
# Testing stock entry values
|
||||||
items = se.get("items")
|
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
|
wo.load_from_db()
|
||||||
actual_fg_qty = qty - total_pl_qty
|
self.assertEqual(wo.status, "In Process")
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
@timeout(seconds=60)
|
@timeout(seconds=60)
|
||||||
def test_job_card_scrap_item(self):
|
def test_job_card_scrap_item(self):
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
"item_name",
|
"item_name",
|
||||||
"image",
|
"image",
|
||||||
"bom_no",
|
"bom_no",
|
||||||
|
"sales_order",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
"company",
|
"company",
|
||||||
"qty",
|
"qty",
|
||||||
"material_transferred_for_manufacturing",
|
"material_transferred_for_manufacturing",
|
||||||
"produced_qty",
|
"produced_qty",
|
||||||
"process_loss_qty",
|
"process_loss_qty",
|
||||||
"sales_order",
|
|
||||||
"project",
|
"project",
|
||||||
"serial_no_and_batch_for_finished_good_section",
|
"serial_no_and_batch_for_finished_good_section",
|
||||||
"has_serial_no",
|
"has_serial_no",
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"column_break_17",
|
"column_break_17",
|
||||||
"serial_no",
|
"serial_no",
|
||||||
"batch_size",
|
"batch_size",
|
||||||
|
"work_order_configuration",
|
||||||
"settings_section",
|
"settings_section",
|
||||||
"allow_alternative_item",
|
"allow_alternative_item",
|
||||||
"use_multi_level_bom",
|
"use_multi_level_bom",
|
||||||
@@ -42,7 +43,11 @@
|
|||||||
"fg_warehouse",
|
"fg_warehouse",
|
||||||
"scrap_warehouse",
|
"scrap_warehouse",
|
||||||
"required_items_section",
|
"required_items_section",
|
||||||
|
"materials_and_operations_tab",
|
||||||
"required_items",
|
"required_items",
|
||||||
|
"operations_section",
|
||||||
|
"operations",
|
||||||
|
"transfer_material_against",
|
||||||
"time",
|
"time",
|
||||||
"planned_start_date",
|
"planned_start_date",
|
||||||
"planned_end_date",
|
"planned_end_date",
|
||||||
@@ -51,9 +56,6 @@
|
|||||||
"actual_start_date",
|
"actual_start_date",
|
||||||
"actual_end_date",
|
"actual_end_date",
|
||||||
"lead_time",
|
"lead_time",
|
||||||
"operations_section",
|
|
||||||
"transfer_material_against",
|
|
||||||
"operations",
|
|
||||||
"section_break_22",
|
"section_break_22",
|
||||||
"planned_operating_cost",
|
"planned_operating_cost",
|
||||||
"actual_operating_cost",
|
"actual_operating_cost",
|
||||||
@@ -72,12 +74,14 @@
|
|||||||
"production_plan_item",
|
"production_plan_item",
|
||||||
"production_plan_sub_assembly_item",
|
"production_plan_sub_assembly_item",
|
||||||
"product_bundle_item",
|
"product_bundle_item",
|
||||||
"amended_from"
|
"amended_from",
|
||||||
|
"connections_tab"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "item",
|
"fieldname": "item",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
|
"label": "Production Item",
|
||||||
"options": "fa fa-gift"
|
"options": "fa fa-gift"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -236,7 +240,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "warehouses",
|
"fieldname": "warehouses",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Warehouses",
|
"label": "Warehouse",
|
||||||
"options": "fa fa-building"
|
"options": "fa fa-building"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -390,8 +394,8 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "more_info",
|
"fieldname": "more_info",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "More Information",
|
"label": "More Info",
|
||||||
"options": "fa fa-file-text"
|
"options": "fa fa-file-text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -474,8 +478,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "settings_section",
|
"fieldname": "settings_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break"
|
||||||
"label": "Settings"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_18",
|
"fieldname": "column_break_18",
|
||||||
@@ -568,6 +571,22 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"non_negative": 1,
|
"non_negative": 1,
|
||||||
"read_only": 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",
|
"icon": "fa fa-cogs",
|
||||||
@@ -575,7 +594,7 @@
|
|||||||
"image_field": "image",
|
"image_field": "image",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-01-24 21:18:12.160114",
|
"modified": "2023-01-03 14:16:35.427731",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Work Order",
|
"name": "Work Order",
|
||||||
|
|||||||
@@ -246,21 +246,11 @@ class WorkOrder(Document):
|
|||||||
status = "Draft"
|
status = "Draft"
|
||||||
elif self.docstatus == 1:
|
elif self.docstatus == 1:
|
||||||
if status != "Stopped":
|
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"
|
status = "Not Started"
|
||||||
if stock_entries:
|
if flt(self.material_transferred_for_manufacturing) > 0:
|
||||||
status = "In Process"
|
status = "In Process"
|
||||||
produced_qty = stock_entries.get("Manufacture")
|
if flt(self.produced_qty) >= flt(self.qty):
|
||||||
if flt(produced_qty) >= flt(self.qty):
|
status = "Completed"
|
||||||
status = "Completed"
|
|
||||||
else:
|
else:
|
||||||
status = "Cancelled"
|
status = "Cancelled"
|
||||||
|
|
||||||
@@ -285,14 +275,7 @@ class WorkOrder(Document):
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
qty = flt(
|
qty = self.get_transferred_or_manufactured_qty(purpose)
|
||||||
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]
|
|
||||||
)
|
|
||||||
|
|
||||||
completed_qty = self.qty + (allowance_percentage / 100 * self.qty)
|
completed_qty = self.qty + (allowance_percentage / 100 * self.qty)
|
||||||
if qty > completed_qty:
|
if qty > completed_qty:
|
||||||
@@ -314,26 +297,30 @@ class WorkOrder(Document):
|
|||||||
if self.production_plan:
|
if self.production_plan:
|
||||||
self.update_production_plan_status()
|
self.update_production_plan_status()
|
||||||
|
|
||||||
def set_process_loss_qty(self):
|
def get_transferred_or_manufactured_qty(self, purpose):
|
||||||
process_loss_qty = flt(
|
table = frappe.qb.DocType("Stock Entry")
|
||||||
frappe.db.sql(
|
query = frappe.qb.from_(table).where(
|
||||||
"""
|
(table.work_order == self.name) & (table.docstatus == 1) & (table.purpose == purpose)
|
||||||
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]
|
|
||||||
)
|
)
|
||||||
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):
|
def update_production_plan_status(self):
|
||||||
production_plan = frappe.get_doc("Production Plan", self.production_plan)
|
production_plan = frappe.get_doc("Production Plan", self.production_plan)
|
||||||
@@ -352,6 +339,7 @@ class WorkOrder(Document):
|
|||||||
|
|
||||||
produced_qty = total_qty[0][0] if total_qty else 0
|
produced_qty = total_qty[0][0] if total_qty else 0
|
||||||
|
|
||||||
|
self.update_status()
|
||||||
production_plan.run_method(
|
production_plan.run_method(
|
||||||
"update_produced_pending_qty", produced_qty, self.production_plan_item
|
"update_produced_pending_qty", produced_qty, self.production_plan_item
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -315,9 +315,11 @@ erpnext.patches.v14_0.fix_crm_no_of_employees
|
|||||||
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
||||||
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||||
|
erpnext.patches.v13_0.drop_unused_sle_index_parts
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
||||||
erpnext.patches.v13_0.update_schedule_type_in_loans
|
erpnext.patches.v13_0.update_schedule_type_in_loans
|
||||||
erpnext.patches.v14_0.update_partial_tds_fields
|
erpnext.patches.v14_0.update_partial_tds_fields
|
||||||
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
|
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
|
||||||
erpnext.patches.v14_0.setup_clear_repost_logs
|
erpnext.patches.v14_0.setup_clear_repost_logs
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
|
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
|
||||||
|
erpnext.patches.v14_0.update_entry_type_for_journal_entry
|
||||||
|
|||||||
14
erpnext/patches/v13_0/drop_unused_sle_index_parts.py
Normal file
14
erpnext/patches/v13_0/drop_unused_sle_index_parts.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import on_doctype_update
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
try:
|
||||||
|
frappe.db.sql_ddl("ALTER TABLE `tabStock Ledger Entry` DROP INDEX `posting_sort_index`")
|
||||||
|
except Exception:
|
||||||
|
frappe.log_error("Failed to drop index")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Recreate indexes
|
||||||
|
on_doctype_update()
|
||||||
18
erpnext/patches/v14_0/update_entry_type_for_journal_entry.py
Normal file
18
erpnext/patches/v14_0/update_entry_type_for_journal_entry.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import frappe
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
"""
|
||||||
|
Update Propery Setters for Journal Entry with new 'Entry Type'
|
||||||
|
"""
|
||||||
|
new_voucher_type = "Exchange Gain Or Loss"
|
||||||
|
prop_setter = frappe.db.get_list(
|
||||||
|
"Property Setter",
|
||||||
|
filters={"doc_type": "Journal Entry", "field_name": "voucher_type", "property": "options"},
|
||||||
|
)
|
||||||
|
if prop_setter:
|
||||||
|
property_setter_doc = frappe.get_doc("Property Setter", prop_setter[0].get("name"))
|
||||||
|
|
||||||
|
if new_voucher_type not in property_setter_doc.value.split("\n"):
|
||||||
|
property_setter_doc.value += "\n" + new_voucher_type
|
||||||
|
property_setter_doc.save()
|
||||||
@@ -13,6 +13,7 @@ import "./help_links";
|
|||||||
import "./agriculture/ternary_plot";
|
import "./agriculture/ternary_plot";
|
||||||
import "./templates/item_quick_entry.html";
|
import "./templates/item_quick_entry.html";
|
||||||
import "./utils/item_quick_entry";
|
import "./utils/item_quick_entry";
|
||||||
|
import "./utils/contact_address_quick_entry";
|
||||||
import "./utils/customer_quick_entry";
|
import "./utils/customer_quick_entry";
|
||||||
import "./utils/supplier_quick_entry";
|
import "./utils/supplier_quick_entry";
|
||||||
import "./call_popup/call_popup";
|
import "./call_popup/call_popup";
|
||||||
|
|||||||
100
erpnext/public/js/utils/contact_address_quick_entry.js
Normal file
100
erpnext/public/js/utils/contact_address_quick_entry.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
frappe.provide('frappe.ui.form');
|
||||||
|
|
||||||
|
frappe.ui.form.ContactAddressQuickEntryForm = class ContactAddressQuickEntryForm extends frappe.ui.form.QuickEntryForm {
|
||||||
|
constructor(doctype, after_insert, init_callback, doc, force) {
|
||||||
|
super(doctype, after_insert, init_callback, doc, force);
|
||||||
|
this.skip_redirect_on_error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_dialog() {
|
||||||
|
this.mandatory = this.mandatory.concat(this.get_variant_fields());
|
||||||
|
super.render_dialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
insert() {
|
||||||
|
/**
|
||||||
|
* Using alias fieldnames because the doctype definition define "email_id" and "mobile_no" as readonly fields.
|
||||||
|
* Therefor, resulting in the fields being "hidden".
|
||||||
|
*/
|
||||||
|
const map_field_names = {
|
||||||
|
"email_address": "email_id",
|
||||||
|
"mobile_number": "mobile_no",
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(map_field_names).forEach(([fieldname, new_fieldname]) => {
|
||||||
|
this.dialog.doc[new_fieldname] = this.dialog.doc[fieldname];
|
||||||
|
delete this.dialog.doc[fieldname];
|
||||||
|
});
|
||||||
|
|
||||||
|
return super.insert();
|
||||||
|
}
|
||||||
|
|
||||||
|
get_variant_fields() {
|
||||||
|
var variant_fields = [{
|
||||||
|
fieldtype: "Section Break",
|
||||||
|
label: __("Primary Contact Details"),
|
||||||
|
collapsible: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Email Id"),
|
||||||
|
fieldname: "email_address",
|
||||||
|
fieldtype: "Data",
|
||||||
|
options: "Email",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Mobile Number"),
|
||||||
|
fieldname: "mobile_number",
|
||||||
|
fieldtype: "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Section Break",
|
||||||
|
label: __("Primary Address Details"),
|
||||||
|
collapsible: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Address Line 1"),
|
||||||
|
fieldname: "address_line1",
|
||||||
|
fieldtype: "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Address Line 2"),
|
||||||
|
fieldname: "address_line2",
|
||||||
|
fieldtype: "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("ZIP Code"),
|
||||||
|
fieldname: "pincode",
|
||||||
|
fieldtype: "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldtype: "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("City"),
|
||||||
|
fieldname: "city",
|
||||||
|
fieldtype: "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("State"),
|
||||||
|
fieldname: "state",
|
||||||
|
fieldtype: "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Country"),
|
||||||
|
fieldname: "country",
|
||||||
|
fieldtype: "Link",
|
||||||
|
options: "Country"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: __("Customer POS Id"),
|
||||||
|
fieldname: "customer_pos_id",
|
||||||
|
fieldtype: "Data",
|
||||||
|
hidden: 1
|
||||||
|
}];
|
||||||
|
|
||||||
|
return variant_fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,81 +1,3 @@
|
|||||||
frappe.provide('frappe.ui.form');
|
frappe.provide('frappe.ui.form');
|
||||||
|
|
||||||
frappe.ui.form.CustomerQuickEntryForm = class CustomerQuickEntryForm extends frappe.ui.form.QuickEntryForm {
|
frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.ContactAddressQuickEntryForm;
|
||||||
constructor(doctype, after_insert, init_callback, doc, force) {
|
|
||||||
super(doctype, after_insert, init_callback, doc, force);
|
|
||||||
this.skip_redirect_on_error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_dialog() {
|
|
||||||
this.mandatory = this.mandatory.concat(this.get_variant_fields());
|
|
||||||
super.render_dialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
get_variant_fields() {
|
|
||||||
var variant_fields = [{
|
|
||||||
fieldtype: "Section Break",
|
|
||||||
label: __("Primary Contact Details"),
|
|
||||||
collapsible: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Email Id"),
|
|
||||||
fieldname: "email_id",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Mobile Number"),
|
|
||||||
fieldname: "mobile_no",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Section Break",
|
|
||||||
label: __("Primary Address Details"),
|
|
||||||
collapsible: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Address Line 1"),
|
|
||||||
fieldname: "address_line1",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Address Line 2"),
|
|
||||||
fieldname: "address_line2",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("ZIP Code"),
|
|
||||||
fieldname: "pincode",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("City"),
|
|
||||||
fieldname: "city",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("State"),
|
|
||||||
fieldname: "state",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Country"),
|
|
||||||
fieldname: "country",
|
|
||||||
fieldtype: "Link",
|
|
||||||
options: "Country"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Customer POS Id"),
|
|
||||||
fieldname: "customer_pos_id",
|
|
||||||
fieldtype: "Data",
|
|
||||||
hidden: 1
|
|
||||||
}];
|
|
||||||
|
|
||||||
return variant_fields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,77 +1,3 @@
|
|||||||
frappe.provide('frappe.ui.form');
|
frappe.provide('frappe.ui.form');
|
||||||
|
|
||||||
frappe.ui.form.SupplierQuickEntryForm = class SupplierQuickEntryForm extends frappe.ui.form.QuickEntryForm {
|
frappe.ui.form.SupplierQuickEntryForm = frappe.ui.form.ContactAddressQuickEntryForm;
|
||||||
constructor(doctype, after_insert, init_callback, doc, force) {
|
|
||||||
super(doctype, after_insert, init_callback, doc, force);
|
|
||||||
this.skip_redirect_on_error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_dialog() {
|
|
||||||
this.mandatory = this.mandatory.concat(this.get_variant_fields());
|
|
||||||
super.render_dialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
get_variant_fields() {
|
|
||||||
var variant_fields = [
|
|
||||||
{
|
|
||||||
fieldtype: "Section Break",
|
|
||||||
label: __("Primary Contact Details"),
|
|
||||||
collapsible: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Email Id"),
|
|
||||||
fieldname: "email_id",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Mobile Number"),
|
|
||||||
fieldname: "mobile_no",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Section Break",
|
|
||||||
label: __("Primary Address Details"),
|
|
||||||
collapsible: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Address Line 1"),
|
|
||||||
fieldname: "address_line1",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Address Line 2"),
|
|
||||||
fieldname: "address_line2",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("ZIP Code"),
|
|
||||||
fieldname: "pincode",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldtype: "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("City"),
|
|
||||||
fieldname: "city",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("State"),
|
|
||||||
fieldname: "state",
|
|
||||||
fieldtype: "Data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: __("Country"),
|
|
||||||
fieldname: "country",
|
|
||||||
fieldtype: "Link",
|
|
||||||
options: "Country"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return variant_fields;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
"fieldname": "route",
|
"fieldname": "route",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"label": "Route",
|
"label": "Route",
|
||||||
|
"no_copy": 1,
|
||||||
"unique": 1
|
"unique": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -232,11 +233,10 @@
|
|||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 3,
|
"max_attachments": 3,
|
||||||
"modified": "2022-03-09 12:27:11.055782",
|
"modified": "2023-01-05 12:21:30.458628",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Item Group",
|
"name": "Item Group",
|
||||||
"name_case": "Title Case",
|
|
||||||
"naming_rule": "By fieldname",
|
"naming_rule": "By fieldname",
|
||||||
"nsm_parent_field": "parent_item_group",
|
"nsm_parent_field": "parent_item_group",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@@ -164,10 +164,7 @@ class Item(Document):
|
|||||||
if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
|
if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.valuation_rate and self.standard_rate:
|
if not self.valuation_rate and not self.standard_rate and not self.is_customer_provided_item:
|
||||||
self.valuation_rate = self.standard_rate
|
|
||||||
|
|
||||||
if not self.valuation_rate and not self.is_customer_provided_item:
|
|
||||||
frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered"))
|
frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered"))
|
||||||
|
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
@@ -192,7 +189,7 @@ class Item(Document):
|
|||||||
item_code=self.name,
|
item_code=self.name,
|
||||||
target=default_warehouse,
|
target=default_warehouse,
|
||||||
qty=self.opening_stock,
|
qty=self.opening_stock,
|
||||||
rate=self.valuation_rate,
|
rate=self.valuation_rate or self.standard_rate,
|
||||||
company=default.company,
|
company=default.company,
|
||||||
posting_date=getdate(),
|
posting_date=getdate(),
|
||||||
posting_time=nowtime(),
|
posting_time=nowtime(),
|
||||||
@@ -279,7 +276,7 @@ class Item(Document):
|
|||||||
frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate"))
|
frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate"))
|
||||||
|
|
||||||
def update_template_tables(self):
|
def update_template_tables(self):
|
||||||
template = frappe.get_doc("Item", self.variant_of)
|
template = frappe.get_cached_doc("Item", self.variant_of)
|
||||||
|
|
||||||
# add item taxes from template
|
# add item taxes from template
|
||||||
for d in template.get("taxes"):
|
for d in template.get("taxes"):
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ def reset_packing_list(doc):
|
|||||||
# 1. items were deleted
|
# 1. items were deleted
|
||||||
# 2. if bundle item replaced by another item (same no. of items but different items)
|
# 2. if bundle item replaced by another item (same no. of items but different items)
|
||||||
# we maintain list to track recurring item rows as well
|
# we maintain list to track recurring item rows as well
|
||||||
items_before_save = [item.item_code for item in doc_before_save.get("items")]
|
items_before_save = [(item.name, item.item_code) for item in doc_before_save.get("items")]
|
||||||
items_after_save = [item.item_code for item in doc.get("items")]
|
items_after_save = [(item.name, item.item_code) for item in doc.get("items")]
|
||||||
reset_table = items_before_save != items_after_save
|
reset_table = items_before_save != items_after_save
|
||||||
else:
|
else:
|
||||||
# reset: if via Update Items OR
|
# reset: if via Update Items OR
|
||||||
|
|||||||
@@ -1501,6 +1501,49 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertTrue(return_pi.docstatus == 1)
|
self.assertTrue(return_pi.docstatus == 1)
|
||||||
|
|
||||||
|
def test_disable_last_purchase_rate(self):
|
||||||
|
from erpnext.stock.get_item_details import get_item_details
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"_Test Disable Last Purchase Rate",
|
||||||
|
{"is_purchase_item": 1, "is_stock_item": 1},
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "disable_last_purchase_rate", 1)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
item_code=item.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
args = pr.items[0].as_dict()
|
||||||
|
args.update(
|
||||||
|
{
|
||||||
|
"supplier": pr.supplier,
|
||||||
|
"doctype": pr.doctype,
|
||||||
|
"conversion_rate": pr.conversion_rate,
|
||||||
|
"currency": pr.currency,
|
||||||
|
"company": pr.company,
|
||||||
|
"posting_date": pr.posting_date,
|
||||||
|
"posting_time": pr.posting_time,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
res = get_item_details(args)
|
||||||
|
self.assertEqual(res.get("last_purchase_rate"), 0)
|
||||||
|
|
||||||
|
frappe.db.set_single_value("Buying Settings", "disable_last_purchase_rate", 0)
|
||||||
|
|
||||||
|
pr = make_purchase_receipt(
|
||||||
|
qty=1,
|
||||||
|
rate=100,
|
||||||
|
item_code=item.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
res = get_item_details(args)
|
||||||
|
self.assertEqual(res.get("last_purchase_rate"), 100)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"document_type": "Document",
|
"document_type": "Document",
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"items_section",
|
"stock_entry_details_tab",
|
||||||
"naming_series",
|
"naming_series",
|
||||||
"stock_entry_type",
|
"stock_entry_type",
|
||||||
"outgoing_stock_entry",
|
"outgoing_stock_entry",
|
||||||
@@ -26,15 +26,20 @@
|
|||||||
"posting_time",
|
"posting_time",
|
||||||
"set_posting_time",
|
"set_posting_time",
|
||||||
"inspection_required",
|
"inspection_required",
|
||||||
"from_bom",
|
|
||||||
"apply_putaway_rule",
|
"apply_putaway_rule",
|
||||||
"sb1",
|
"items_tab",
|
||||||
"bom_no",
|
"bom_info_section",
|
||||||
"fg_completed_qty",
|
"from_bom",
|
||||||
"cb1",
|
|
||||||
"use_multi_level_bom",
|
"use_multi_level_bom",
|
||||||
|
"bom_no",
|
||||||
|
"cb1",
|
||||||
|
"fg_completed_qty",
|
||||||
"get_items",
|
"get_items",
|
||||||
"section_break_12",
|
"section_break_7qsm",
|
||||||
|
"process_loss_percentage",
|
||||||
|
"column_break_e92r",
|
||||||
|
"process_loss_qty",
|
||||||
|
"section_break_jwgn",
|
||||||
"from_warehouse",
|
"from_warehouse",
|
||||||
"source_warehouse_address",
|
"source_warehouse_address",
|
||||||
"source_address_display",
|
"source_address_display",
|
||||||
@@ -44,6 +49,7 @@
|
|||||||
"target_address_display",
|
"target_address_display",
|
||||||
"sb0",
|
"sb0",
|
||||||
"scan_barcode",
|
"scan_barcode",
|
||||||
|
"items_section",
|
||||||
"items",
|
"items",
|
||||||
"get_stock_and_rate",
|
"get_stock_and_rate",
|
||||||
"section_break_19",
|
"section_break_19",
|
||||||
@@ -54,6 +60,7 @@
|
|||||||
"additional_costs_section",
|
"additional_costs_section",
|
||||||
"additional_costs",
|
"additional_costs",
|
||||||
"total_additional_costs",
|
"total_additional_costs",
|
||||||
|
"supplier_info_tab",
|
||||||
"contact_section",
|
"contact_section",
|
||||||
"supplier",
|
"supplier",
|
||||||
"supplier_name",
|
"supplier_name",
|
||||||
@@ -61,7 +68,7 @@
|
|||||||
"address_display",
|
"address_display",
|
||||||
"accounting_dimensions_section",
|
"accounting_dimensions_section",
|
||||||
"project",
|
"project",
|
||||||
"dimension_col_break",
|
"other_info_tab",
|
||||||
"printing_settings",
|
"printing_settings",
|
||||||
"select_print_heading",
|
"select_print_heading",
|
||||||
"print_settings_col_break",
|
"print_settings_col_break",
|
||||||
@@ -78,11 +85,6 @@
|
|||||||
"is_return"
|
"is_return"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
|
||||||
"fieldname": "items_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"oldfieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "naming_series",
|
"fieldname": "naming_series",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -236,17 +238,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"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",
|
"fieldname": "from_bom",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "From BOM",
|
"label": "From BOM",
|
||||||
"print_hide": 1
|
"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",
|
"depends_on": "from_bom",
|
||||||
"fieldname": "bom_no",
|
"fieldname": "bom_no",
|
||||||
@@ -285,10 +282,6 @@
|
|||||||
"oldfieldtype": "Button",
|
"oldfieldtype": "Button",
|
||||||
"print_hide": 1
|
"print_hide": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "section_break_12",
|
|
||||||
"fieldtype": "Section Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "Sets 'Source Warehouse' in each row of the items table.",
|
"description": "Sets 'Source Warehouse' in each row of the items table.",
|
||||||
"fieldname": "from_warehouse",
|
"fieldname": "from_warehouse",
|
||||||
@@ -411,7 +404,7 @@
|
|||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"collapsible_depends_on": "total_additional_costs",
|
"collapsible_depends_on": "total_additional_costs",
|
||||||
"fieldname": "additional_costs_section",
|
"fieldname": "additional_costs_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Additional Costs"
|
"label": "Additional Costs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -576,13 +569,9 @@
|
|||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
"fieldname": "accounting_dimensions_section",
|
"fieldname": "accounting_dimensions_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Tab Break",
|
||||||
"label": "Accounting Dimensions"
|
"label": "Accounting Dimensions"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "dimension_col_break",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "pick_list",
|
"fieldname": "pick_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -621,6 +610,66 @@
|
|||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 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",
|
"icon": "fa fa-file-text",
|
||||||
@@ -628,7 +677,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-10-07 14:39:51.943770",
|
"modified": "2023-01-03 16:02:50.741816",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry",
|
"name": "Stock Entry",
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ class StockEntry(StockController):
|
|||||||
self.validate_warehouse()
|
self.validate_warehouse()
|
||||||
self.validate_work_order()
|
self.validate_work_order()
|
||||||
self.validate_bom()
|
self.validate_bom()
|
||||||
|
self.set_process_loss_qty()
|
||||||
self.validate_purchase_order()
|
self.validate_purchase_order()
|
||||||
self.validate_subcontracting_order()
|
self.validate_subcontracting_order()
|
||||||
|
|
||||||
@@ -126,7 +127,7 @@ class StockEntry(StockController):
|
|||||||
self.validate_with_material_request()
|
self.validate_with_material_request()
|
||||||
self.validate_batch()
|
self.validate_batch()
|
||||||
self.validate_inspection()
|
self.validate_inspection()
|
||||||
# self.validate_fg_completed_qty()
|
self.validate_fg_completed_qty()
|
||||||
self.validate_difference_account()
|
self.validate_difference_account()
|
||||||
self.set_job_card_data()
|
self.set_job_card_data()
|
||||||
self.set_purpose_for_stock_entry()
|
self.set_purpose_for_stock_entry()
|
||||||
@@ -388,11 +389,20 @@ class StockEntry(StockController):
|
|||||||
item_wise_qty = {}
|
item_wise_qty = {}
|
||||||
if self.purpose == "Manufacture" and self.work_order:
|
if self.purpose == "Manufacture" and self.work_order:
|
||||||
for d in self.items:
|
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)
|
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():
|
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:
|
if self.fg_completed_qty != total:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format(
|
_("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format(
|
||||||
@@ -471,7 +481,7 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
if self.purpose == "Manufacture":
|
if self.purpose == "Manufacture":
|
||||||
if validate_for_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
|
d.s_warehouse = None
|
||||||
if not d.t_warehouse:
|
if not d.t_warehouse:
|
||||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||||
@@ -648,9 +658,7 @@ class StockEntry(StockController):
|
|||||||
outgoing_items_cost = self.set_rate_for_outgoing_items(
|
outgoing_items_cost = self.set_rate_for_outgoing_items(
|
||||||
reset_outgoing_rate, raise_error_if_no_rate
|
reset_outgoing_rate, raise_error_if_no_rate
|
||||||
)
|
)
|
||||||
finished_item_qty = sum(
|
finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
|
||||||
d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set basic rate for incoming items
|
# Set basic rate for incoming items
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
@@ -689,8 +697,6 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
# do not round off basic rate to avoid precision loss
|
# do not round off basic rate to avoid precision loss
|
||||||
d.basic_rate = flt(d.basic_rate)
|
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"))
|
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):
|
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
|
||||||
@@ -992,7 +998,9 @@ class StockEntry(StockController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def mark_finished_and_scrap_items(self):
|
def mark_finished_and_scrap_items(self):
|
||||||
if any([d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]):
|
if self.purpose != "Repack" and any(
|
||||||
|
[d.item_code for d in self.items if (d.is_finished_item and d.t_warehouse)]
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
finished_item = self.get_finished_item()
|
finished_item = self.get_finished_item()
|
||||||
@@ -1241,7 +1249,6 @@ class StockEntry(StockController):
|
|||||||
if self.work_order:
|
if self.work_order:
|
||||||
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
pro_doc = frappe.get_doc("Work Order", self.work_order)
|
||||||
_validate_work_order(pro_doc)
|
_validate_work_order(pro_doc)
|
||||||
pro_doc.run_method("update_status")
|
|
||||||
|
|
||||||
if self.fg_completed_qty:
|
if self.fg_completed_qty:
|
||||||
pro_doc.run_method("update_work_order_qty")
|
pro_doc.run_method("update_work_order_qty")
|
||||||
@@ -1249,6 +1256,7 @@ class StockEntry(StockController):
|
|||||||
pro_doc.run_method("update_planned_qty")
|
pro_doc.run_method("update_planned_qty")
|
||||||
pro_doc.update_batch_produced_qty(self)
|
pro_doc.update_batch_produced_qty(self)
|
||||||
|
|
||||||
|
pro_doc.run_method("update_status")
|
||||||
if not pro_doc.operations:
|
if not pro_doc.operations:
|
||||||
pro_doc.set_actual_dates()
|
pro_doc.set_actual_dates()
|
||||||
|
|
||||||
@@ -1469,11 +1477,11 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
# add finished goods item
|
# add finished goods item
|
||||||
if self.purpose in ("Manufacture", "Repack"):
|
if self.purpose in ("Manufacture", "Repack"):
|
||||||
|
self.set_process_loss_qty()
|
||||||
self.load_items_from_bom()
|
self.load_items_from_bom()
|
||||||
|
|
||||||
self.set_scrap_items()
|
self.set_scrap_items()
|
||||||
self.set_actual_qty()
|
self.set_actual_qty()
|
||||||
self.update_items_for_process_loss()
|
|
||||||
self.validate_customer_provided_item()
|
self.validate_customer_provided_item()
|
||||||
self.calculate_rate_and_amount(raise_error_if_no_rate=False)
|
self.calculate_rate_and_amount(raise_error_if_no_rate=False)
|
||||||
|
|
||||||
@@ -1486,6 +1494,21 @@ class StockEntry(StockController):
|
|||||||
|
|
||||||
self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
|
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):
|
def set_work_order_details(self):
|
||||||
if not getattr(self, "pro_doc", None):
|
if not getattr(self, "pro_doc", None):
|
||||||
self.pro_doc = frappe._dict()
|
self.pro_doc = frappe._dict()
|
||||||
@@ -1518,7 +1541,7 @@ class StockEntry(StockController):
|
|||||||
args = {
|
args = {
|
||||||
"to_warehouse": to_warehouse,
|
"to_warehouse": to_warehouse,
|
||||||
"from_warehouse": "",
|
"from_warehouse": "",
|
||||||
"qty": self.fg_completed_qty,
|
"qty": flt(self.fg_completed_qty) - flt(self.process_loss_qty),
|
||||||
"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,
|
||||||
@@ -1966,7 +1989,6 @@ class StockEntry(StockController):
|
|||||||
)
|
)
|
||||||
se_child.is_finished_item = item_row.get("is_finished_item", 0)
|
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_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.po_detail = item_row.get("po_detail")
|
||||||
se_child.sco_rm_detail = item_row.get("sco_rm_detail")
|
se_child.sco_rm_detail = item_row.get("sco_rm_detail")
|
||||||
|
|
||||||
@@ -2213,31 +2235,6 @@ class StockEntry(StockController):
|
|||||||
material_requests.append(material_request)
|
material_requests.append(material_request)
|
||||||
frappe.db.set_value("Material Request", material_request, "transfer_status", status)
|
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):
|
def set_serial_no_batch_for_finished_good(self):
|
||||||
serial_nos = []
|
serial_nos = []
|
||||||
if self.pro_doc.serial_no:
|
if self.pro_doc.serial_no:
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"is_finished_item",
|
"is_finished_item",
|
||||||
"is_scrap_item",
|
"is_scrap_item",
|
||||||
"quality_inspection",
|
"quality_inspection",
|
||||||
"is_process_loss",
|
|
||||||
"subcontracted_item",
|
"subcontracted_item",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"description",
|
"description",
|
||||||
@@ -559,12 +558,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"default": "0",
|
|
||||||
"fieldname": "is_process_loss",
|
|
||||||
"fieldtype": "Check",
|
|
||||||
"label": "Is Process Loss"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"depends_on": "barcode",
|
"depends_on": "barcode",
|
||||||
@@ -578,7 +571,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-02 13:00:34.258828",
|
"modified": "2023-01-03 14:51:16.575515",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Entry Detail",
|
"name": "Stock Entry Detail",
|
||||||
|
|||||||
@@ -221,14 +221,9 @@ class StockLedgerEntry(Document):
|
|||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
if not frappe.db.has_index("tabStock Ledger Entry", "posting_sort_index"):
|
frappe.db.add_index(
|
||||||
frappe.db.commit()
|
"Stock Ledger Entry", fields=["posting_date", "posting_time"], index_name="posting_sort_index"
|
||||||
frappe.db.add_index(
|
)
|
||||||
"Stock Ledger Entry",
|
|
||||||
fields=["posting_date", "posting_time", "name"],
|
|
||||||
index_name="posting_sort_index",
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
|
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
|
||||||
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
|
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
|
||||||
frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
|
frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
|
||||||
|
|||||||
@@ -411,7 +411,9 @@ def get_basic_details(args, item, overwrite_warehouse=True):
|
|||||||
args.stock_qty = out.stock_qty
|
args.stock_qty = out.stock_qty
|
||||||
|
|
||||||
# calculate last purchase rate
|
# calculate last purchase rate
|
||||||
if args.get("doctype") in purchase_doctypes:
|
if args.get("doctype") in purchase_doctypes and not frappe.db.get_single_value(
|
||||||
|
"Buying Settings", "disable_last_purchase_rate"
|
||||||
|
):
|
||||||
from erpnext.buying.doctype.purchase_order.purchase_order import item_last_purchase_rate
|
from erpnext.buying.doctype.purchase_order.purchase_order import item_last_purchase_rate
|
||||||
|
|
||||||
out.last_purchase_rate = item_last_purchase_rate(
|
out.last_purchase_rate = item_last_purchase_rate(
|
||||||
@@ -813,6 +815,9 @@ def get_price_list_rate(args, item_doc, out=None):
|
|||||||
flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)
|
flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
|
||||||
|
return out
|
||||||
|
|
||||||
if not out.price_list_rate and args.transaction_type == "buying":
|
if not out.price_list_rate and args.transaction_type == "buying":
|
||||||
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
from erpnext.stock.doctype.item.item import get_last_purchase_details
|
||||||
|
|
||||||
|
|||||||
@@ -1270,20 +1270,6 @@ def get_valuation_rate(
|
|||||||
(item_code, warehouse, voucher_no, voucher_type),
|
(item_code, warehouse, voucher_no, voucher_type),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not last_valuation_rate:
|
|
||||||
# Get valuation rate from last sle for the item against any warehouse
|
|
||||||
last_valuation_rate = frappe.db.sql(
|
|
||||||
"""select valuation_rate
|
|
||||||
from `tabStock Ledger Entry` force index (item_code)
|
|
||||||
where
|
|
||||||
item_code = %s
|
|
||||||
AND valuation_rate > 0
|
|
||||||
AND is_cancelled = 0
|
|
||||||
AND NOT(voucher_no = %s AND voucher_type = %s)
|
|
||||||
order by posting_date desc, posting_time desc, name desc limit 1""",
|
|
||||||
(item_code, voucher_no, voucher_type),
|
|
||||||
)
|
|
||||||
|
|
||||||
if last_valuation_rate:
|
if last_valuation_rate:
|
||||||
return flt(last_valuation_rate[0][0])
|
return flt(last_valuation_rate[0][0])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user