Merge pull request #31910 from deepeshgarg007/cash_and_non_trade_discount_fix
fix: Cash and non trade discount calculation
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 48edda9..4618d08 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -181,7 +181,11 @@
frappe.throw(_("Party is mandatory"))
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
- self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
+
+ if frappe.db.has_column(self.party_type, _party_name):
+ self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
+ else:
+ self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
if self.party:
if not self.party_balance:
@@ -295,6 +299,9 @@
def validate_reference_documents(self):
valid_reference_doctypes = self.get_valid_reference_doctypes()
+ if not valid_reference_doctypes:
+ return
+
for d in self.get("references"):
if not d.allocated_amount:
continue
@@ -362,7 +369,7 @@
if not d.allocated_amount:
continue
- if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
+ if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount, is_return = frappe.get_cached_value(
d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
)
@@ -1201,7 +1208,7 @@
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
- # Get positive outstanding sales /purchase invoices/ Fees
+ # Get positive outstanding sales /purchase invoices
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={0} and voucher_no={1}".format(
@@ -1597,10 +1604,11 @@
elif reference_doctype != "Journal Entry":
if not total_amount:
if party_account_currency == company_currency:
- total_amount = ref_doc.base_grand_total
+ # for handling cases that don't have multi-currency (base field)
+ total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
exchange_rate = 1
else:
- total_amount = ref_doc.grand_total
+ total_amount = ref_doc.get("grand_total")
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc.
@@ -1611,7 +1619,7 @@
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
else:
- outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
+ outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
else:
# Get the exchange rate based on the posting date of the ref doc.
@@ -1629,16 +1637,23 @@
@frappe.whitelist()
-def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
+def get_payment_entry(
+ dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
+):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
- party_type = set_party_type(dt)
+ if not party_type:
+ party_type = set_party_type(dt)
+
party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)
- payment_type = set_payment_type(dt, doc)
+
+ if not payment_type:
+ payment_type = set_payment_type(dt, doc)
+
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
party_amount, dt, party_account_currency, doc
)
@@ -1788,8 +1803,6 @@
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice":
party_account = doc.credit_to
- elif dt == "Fees":
- party_account = doc.receivable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@@ -1805,8 +1818,7 @@
def set_payment_type(dt, doc):
if (
- dt == "Sales Order"
- or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
+ dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
@@ -1824,18 +1836,15 @@
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
- elif dt == "Fees":
- grand_total = doc.grand_total
- outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
else:
if party_account_currency == doc.company_currency:
- grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
+ grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total"))
else:
- grand_total = flt(doc.get("rounded_total") or doc.grand_total)
- outstanding_amount = grand_total - flt(doc.advance_paid)
+ grand_total = flt(doc.get("rounded_total") or doc.get("grand_total"))
+ outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid))
return grand_total, outstanding_amount
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
index 98f3420..1d596c1 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js
@@ -36,6 +36,15 @@
});
set_html_data(frm);
+
+ if (frm.doc.docstatus == 1) {
+ if (!frm.doc.posting_date) {
+ frm.set_value("posting_date", frappe.datetime.nowdate());
+ }
+ if (!frm.doc.posting_time) {
+ frm.set_value("posting_time", frappe.datetime.now_time());
+ }
+ }
},
refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
index d6e35c6..9d15e6c 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.json
@@ -11,6 +11,7 @@
"period_end_date",
"column_break_3",
"posting_date",
+ "posting_time",
"pos_opening_entry",
"status",
"section_break_5",
@@ -51,7 +52,6 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Period End Date",
- "read_only": 1,
"reqd": 1
},
{
@@ -219,6 +219,13 @@
"fieldtype": "Small Text",
"label": "Error",
"read_only": 1
+ },
+ {
+ "fieldname": "posting_time",
+ "fieldtype": "Time",
+ "label": "Posting Time",
+ "no_copy": 1,
+ "reqd": 1
}
],
"is_submittable": 1,
@@ -228,10 +235,11 @@
"link_fieldname": "pos_closing_entry"
}
],
- "modified": "2021-10-20 16:19:25.340565",
+ "modified": "2022-08-01 11:37:14.991228",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
+ "naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -278,5 +286,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index 49aab0d..655c4ec 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -15,6 +15,9 @@
class POSClosingEntry(StatusUpdater):
def validate(self):
+ self.posting_date = self.posting_date or frappe.utils.nowdate()
+ self.posting_time = self.posting_time or frappe.utils.nowtime()
+
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
index d762087..a059455 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.json
@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"posting_date",
+ "posting_time",
"merge_invoices_based_on",
"column_break_3",
"pos_closing_entry",
@@ -105,12 +106,19 @@
"label": "Customer Group",
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"options": "Customer Group"
+ },
+ {
+ "fieldname": "posting_time",
+ "fieldtype": "Time",
+ "label": "Posting Time",
+ "no_copy": 1,
+ "reqd": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-09-14 11:17:19.001142",
+ "modified": "2022-08-01 11:36:42.456429",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice Merge Log",
@@ -173,5 +181,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 5003a1d..81a234a 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -9,7 +9,7 @@
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc
-from frappe.utils import cint, flt, getdate, nowdate
+from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
from frappe.utils.background_jobs import enqueue
from frappe.utils.scheduler import is_scheduler_inactive
@@ -99,6 +99,7 @@
sales_invoice.is_consolidated = 1
sales_invoice.set_posting_time = 1
sales_invoice.posting_date = getdate(self.posting_date)
+ sales_invoice.posting_time = get_time(self.posting_time)
sales_invoice.save()
sales_invoice.submit()
@@ -115,6 +116,7 @@
credit_note.is_consolidated = 1
credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date)
+ credit_note.posting_time = get_time(self.posting_time)
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
@@ -402,6 +404,9 @@
merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
)
+ merge_log.posting_time = (
+ get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
+ )
merge_log.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index de3927e..e6ff128 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -1791,4 +1791,6 @@
target_doc,
)
+ doc.set_onload("ignore_price_list", True)
+
return doc
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index a519d8b..6004e2b 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -318,7 +318,6 @@
"is_cancelled": 0,
"party_type": party_type,
"party": ["in", parties],
- "against_voucher": ["is", "not set"],
}
if company:
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 526ea9d..54af225 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -616,7 +616,7 @@
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
if previous_stock_value:
- return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
+ return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
else:
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 4e29ee5..31a4837 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -15,9 +15,12 @@
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {
let d = locals[cdt][cdn];
return {
- query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts",
- filters: {'supplier': d.supplier}
- }
+ query: "frappe.contacts.doctype.contact.contact.contact_query",
+ filters: {
+ link_doctype: "Supplier",
+ link_name: d.supplier || ""
+ }
+ };
}
},
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 3ef57bb..ee28eb6 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -287,18 +287,6 @@
@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql(
- """select `tabContact`.name from `tabContact`, `tabDynamic Link`
- where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
- and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
- limit %(page_len)s offset %(start)s""",
- {"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
- )
-
-
-@frappe.whitelist()
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
def postprocess(source, target_doc):
if for_supplier:
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
index c772c1a..d13d970 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
@@ -4,6 +4,8 @@
# Decompiled by https://python-decompiler.com
+import copy
+
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -11,10 +13,12 @@
execute,
)
from erpnext.controllers.tests.test_subcontracting_controller import (
+ get_rm_items,
get_subcontracting_order,
make_service_item,
+ make_stock_in_entry,
+ make_stock_transfer_entry,
)
-from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_subcontracting_receipt,
)
@@ -36,15 +40,18 @@
sco = get_subcontracting_order(
service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC"
)
- make_stock_entry(
- item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100
+ rm_items = get_rm_items(sco.supplied_items)
+ itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+ for item in rm_items:
+ item["sco_rm_detail"] = sco.items[0].name
+
+ make_stock_transfer_entry(
+ sco_no=sco.name,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
)
- make_stock_entry(
- item_code="_Test Item Home Desktop 100",
- target="_Test Warehouse 1 - _TC",
- qty=100,
- basic_rate=100,
- )
+
make_subcontracting_receipt_against_sco(sco.name)
sco.reload()
col, data = execute(
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index e27718a..36bed36 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -36,6 +36,10 @@
pass
+class BatchExpiredError(frappe.ValidationError):
+ pass
+
+
class StockController(AccountsController):
def validate(self):
super(StockController, self).validate()
@@ -77,6 +81,10 @@
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+ is_material_issue = False
+ if self.doctype == "Stock Entry" and self.purpose == "Material Issue":
+ is_material_issue = True
+
for d in self.get("items"):
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
serial_nos = frappe.get_all(
@@ -93,6 +101,9 @@
)
)
+ if is_material_issue:
+ continue
+
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
@@ -100,7 +111,8 @@
frappe.throw(
_("Row #{0}: The batch {1} has already expired.").format(
d.idx, get_link_to_form("Batch", d.get("batch_no"))
- )
+ ),
+ BatchExpiredError,
)
def clean_serial_nos(self):
@@ -310,7 +322,13 @@
)
if (
self.doctype
- not in ("Purchase Receipt", "Purchase Invoice", "Stock Reconciliation", "Stock Entry")
+ not in (
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Stock Reconciliation",
+ "Stock Entry",
+ "Subcontracting Receipt",
+ )
and not is_expense_account
):
frappe.throw(
@@ -374,9 +392,24 @@
def update_inventory_dimensions(self, row, sl_dict) -> None:
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
for dimension in dimensions:
- if dimension and row.get(dimension.source_fieldname):
+ if not dimension:
+ continue
+
+ if row.get(dimension.source_fieldname):
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
+ if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
+ sl_dict[dimension.target_fieldname] = self.get(dimension.fetch_from_parent)
+
+ # Get value based on doctype name
+ if not sl_dict.get(dimension.target_fieldname):
+ fieldname = frappe.get_cached_value(
+ "DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname"
+ )
+
+ if fieldname and self.get(fieldname):
+ sl_dict[dimension.target_fieldname] = self.get(fieldname)
+
def make_sl_entries(self, sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 2a2f8f5..1372c89 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -490,7 +490,7 @@
row.item_code,
row.get(self.subcontract_data.order_field),
) and transfer_item.qty > 0:
- qty = self.__get_qty_based_on_material_transfer(row, transfer_item) or 0
+ qty = flt(self.__get_qty_based_on_material_transfer(row, transfer_item))
transfer_item.qty -= qty
self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
@@ -720,6 +720,25 @@
sco_doc = frappe.get_doc("Subcontracting Order", sco)
sco_doc.update_status()
+ def set_missing_values_in_additional_costs(self):
+ self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
+
+ if self.total_additional_costs:
+ if self.distribute_additional_costs_based_on == "Amount":
+ total_amt = sum(flt(item.amount) for item in self.get("items"))
+ for item in self.items:
+ item.additional_cost_per_qty = (
+ (item.amount * self.total_additional_costs) / total_amt
+ ) / item.qty
+ else:
+ total_qty = sum(flt(item.qty) for item in self.get("items"))
+ additional_cost_per_qty = self.total_additional_costs / total_qty
+ for item in self.items:
+ item.additional_cost_per_qty = additional_cost_per_qty
+ else:
+ for item in self.items:
+ item.additional_cost_per_qty = 0
+
@frappe.whitelist()
def get_current_stock(self):
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
@@ -730,7 +749,7 @@
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
"actual_qty",
)
- item.current_stock = flt(actual_qty) or 0
+ item.current_stock = flt(actual_qty)
@property
def sub_contracted_items(self):
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 4fab805..bc503f5 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -36,6 +36,36 @@
sco.remove_empty_rows()
self.assertEqual((len_before - 1), len(sco.service_items))
+ def test_set_missing_values_in_additional_costs(self):
+ sco = get_subcontracting_order(do_not_submit=1)
+
+ rate_without_additional_cost = sco.items[0].rate
+ amount_without_additional_cost = sco.items[0].amount
+
+ additional_amount = 120
+ sco.append(
+ "additional_costs",
+ {
+ "expense_account": "Cost of Goods Sold - _TC",
+ "description": "Test",
+ "amount": additional_amount,
+ },
+ )
+ sco.save()
+
+ additional_cost_per_qty = additional_amount / sco.items[0].qty
+
+ self.assertEqual(sco.items[0].additional_cost_per_qty, additional_cost_per_qty)
+ self.assertEqual(rate_without_additional_cost + additional_cost_per_qty, sco.items[0].rate)
+ self.assertEqual(amount_without_additional_cost + additional_amount, sco.items[0].amount)
+
+ sco.additional_costs = []
+ sco.save()
+
+ self.assertEqual(sco.items[0].additional_cost_per_qty, 0)
+ self.assertEqual(rate_without_additional_cost, sco.items[0].rate)
+ self.assertEqual(amount_without_additional_cost, sco.items[0].amount)
+
def test_create_raw_materials_supplied(self):
sco = get_subcontracting_order()
sco.supplied_items = None
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 7d676e4..cd4aaee 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -12,7 +12,9 @@
import frappe
from bs4 import BeautifulSoup as bs
from frappe import _
-from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+from frappe.custom.doctype.custom_field.custom_field import (
+ create_custom_fields as _create_custom_fields,
+)
from frappe.model.document import Document
from frappe.utils.data import format_datetime
@@ -577,22 +579,25 @@
new_year.save()
oldest_year = new_year
- def create_custom_fields(doctypes):
- tally_guid_df = {
- "fieldtype": "Data",
- "fieldname": "tally_guid",
- "read_only": 1,
- "label": "Tally GUID",
- }
- tally_voucher_no_df = {
- "fieldtype": "Data",
- "fieldname": "tally_voucher_no",
- "read_only": 1,
- "label": "Tally Voucher Number",
- }
- for df in [tally_guid_df, tally_voucher_no_df]:
- for doctype in doctypes:
- create_custom_field(doctype, df)
+ def create_custom_fields():
+ _create_custom_fields(
+ {
+ ("Journal Entry", "Purchase Invoice", "Sales Invoice"): [
+ {
+ "fieldtype": "Data",
+ "fieldname": "tally_guid",
+ "read_only": 1,
+ "label": "Tally GUID",
+ },
+ {
+ "fieldtype": "Data",
+ "fieldname": "tally_voucher_no",
+ "read_only": 1,
+ "label": "Tally Voucher Number",
+ },
+ ]
+ }
+ )
def create_price_list():
frappe.get_doc(
@@ -628,7 +633,7 @@
create_fiscal_years(vouchers)
create_price_list()
- create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
+ create_custom_fields()
total = len(vouchers)
is_last = False
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
index 2e18776..4aa98aa 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
@@ -6,7 +6,7 @@
import frappe
from frappe import _
-from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.model.document import Document
from frappe.utils.nestedset import get_root_of
@@ -19,27 +19,24 @@
def create_delete_custom_fields(self):
if self.enable_sync:
- custom_fields = {}
- # create
- for doctype in ["Customer", "Sales Order", "Item", "Address"]:
- df = dict(
- fieldname="woocommerce_id",
- label="Woocommerce ID",
- fieldtype="Data",
- read_only=1,
- print_hide=1,
- )
- create_custom_field(doctype, df)
-
- for doctype in ["Customer", "Address"]:
- df = dict(
- fieldname="woocommerce_email",
- label="Woocommerce Email",
- fieldtype="Data",
- read_only=1,
- print_hide=1,
- )
- create_custom_field(doctype, df)
+ create_custom_fields(
+ {
+ ("Customer", "Sales Order", "Item", "Address"): dict(
+ fieldname="woocommerce_id",
+ label="Woocommerce ID",
+ fieldtype="Data",
+ read_only=1,
+ print_hide=1,
+ ),
+ ("Customer", "Address"): dict(
+ fieldname="woocommerce_email",
+ label="Woocommerce Email",
+ fieldtype="Data",
+ read_only=1,
+ print_hide=1,
+ ),
+ }
+ )
if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}):
item_group = frappe.new_doc("Item Group")
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index c4f0c59..a08feb4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -520,6 +520,10 @@
"Purchase Order",
"Purchase Receipt",
"Sales Order",
+ "Subcontracting Order",
+ "Subcontracting Order Item",
+ "Subcontracting Receipt",
+ "Subcontracting Receipt Item",
]
# get matching queries for Bank Reconciliation
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index b29f671..70637d3 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -189,8 +189,8 @@
self.validate_transfer_against()
self.set_routing_operations()
self.validate_operations()
- self.update_exploded_items(save=False)
self.calculate_cost()
+ self.update_exploded_items(save=False)
self.update_stock_qty()
self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate=False, save=False)
self.validate_scrap_items()
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index a190cc7..27f3cc9 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -611,6 +611,34 @@
bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
+ def test_exploded_items_rate(self):
+ rm_item = make_item(
+ properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
+ ).name
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+
+ from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+
+ bom = make_bom(item=fg_item, raw_materials=[rm_item], do_not_save=True)
+
+ bom.rm_cost_as_per = "Last Purchase Rate"
+ bom.save()
+ self.assertEqual(bom.items[0].base_rate, 89)
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
+ bom.rm_cost_as_per = "Price List"
+ bom.save()
+ self.assertEqual(bom.items[0].base_rate, 0.0)
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
+ bom.rm_cost_as_per = "Valuation Rate"
+ bom.save()
+ self.assertEqual(bom.items[0].base_rate, 99)
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
+ bom.submit()
+ self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
+
def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 0a8ae7b..c526611 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -184,6 +184,7 @@
"in_list_view": 1,
"label": "Rate",
"options": "currency",
+ "read_only": 1,
"reqd": 1
},
{
@@ -288,7 +289,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-05-19 02:32:43.785470",
+ "modified": "2022-07-28 10:20:51.559010",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 70ccb78..2cdf8d3 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -482,7 +482,6 @@
"bom_no",
"stock_uom",
"bom_level",
- "production_plan_item",
"schedule_date",
]:
if row.get(field):
@@ -639,6 +638,9 @@
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
+ if not row.item_code:
+ frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
+
bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 040e791..e2415ad 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -11,8 +11,9 @@
get_warehouse_list,
)
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
+from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry as make_se_from_wo
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
-from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
@@ -583,9 +584,6 @@
Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel)
"""
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
- from erpnext.manufacturing.doctype.work_order.work_order import (
- make_stock_entry as make_se_from_wo,
- )
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -629,9 +627,6 @@
def test_production_plan_pending_qty_independent_items(self):
"Test Prod Plan impact if items are added independently (no from SO or MR)."
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
- from erpnext.manufacturing.doctype.work_order.work_order import (
- make_stock_entry as make_se_from_wo,
- )
make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -728,6 +723,57 @@
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item)
+ def test_produced_qty_for_multi_level_bom_item(self):
+ # Create Items and BOMs
+ rm_item = make_item(properties={"is_stock_item": 1}).name
+ sub_assembly_item = make_item(properties={"is_stock_item": 1}).name
+ fg_item = make_item(properties={"is_stock_item": 1}).name
+
+ make_stock_entry(
+ item_code=rm_item,
+ qty=60,
+ to_warehouse="Work In Progress - _TC",
+ rate=99,
+ purpose="Material Receipt",
+ )
+
+ make_bom(item=sub_assembly_item, raw_materials=[rm_item], rm_qty=3)
+ make_bom(item=fg_item, raw_materials=[sub_assembly_item], rm_qty=4)
+
+ # Step - 1: Create Production Plan
+ pln = create_production_plan(item_code=fg_item, planned_qty=5, skip_getting_mr_items=1)
+ pln.get_sub_assembly_items()
+
+ # Step - 2: Create Work Orders
+ pln.make_work_order()
+ work_orders = frappe.get_all("Work Order", filters={"production_plan": pln.name}, pluck="name")
+ sa_wo = fg_wo = None
+ for work_order in work_orders:
+ wo_doc = frappe.get_doc("Work Order", work_order)
+ if wo_doc.production_plan_item:
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+ )
+ fg_wo = wo_doc.name
+ else:
+ wo_doc.update(
+ {"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Work In Progress - _TC"}
+ )
+ sa_wo = wo_doc.name
+ wo_doc.submit()
+
+ # Step - 3: Complete Work Orders
+ se = frappe.get_doc(make_se_from_wo(sa_wo, "Manufacture"))
+ se.submit()
+
+ se = frappe.get_doc(make_se_from_wo(fg_wo, "Manufacture"))
+ se.submit()
+
+ # Step - 4: Check Production Plan Item Produced Qty
+ pln.load_from_db()
+ self.assertEqual(pln.status, "Completed")
+ self.assertEqual(pln.po_items[0].produced_qty, 5)
+
def create_production_plan(**args):
"""
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index e5beacd..d92353a 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -310,4 +310,5 @@
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation
erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022
-erpnext.patches.v14_0.fix_crm_no_of_employees
\ No newline at end of file
+erpnext.patches.v14_0.fix_crm_no_of_employees
+erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py b/erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py
new file mode 100644
index 0000000..b349c07
--- /dev/null
+++ b/erpnext/patches/v14_0/create_accounting_dimensions_in_subcontracting_doctypes.py
@@ -0,0 +1,47 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+
+
+def execute():
+ accounting_dimensions = frappe.db.get_all(
+ "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
+ )
+
+ if not accounting_dimensions:
+ return
+
+ count = 1
+ for d in accounting_dimensions:
+
+ if count % 2 == 0:
+ insert_after_field = "dimension_col_break"
+ else:
+ insert_after_field = "accounting_dimensions_section"
+
+ for doctype in [
+ "Subcontracting Order",
+ "Subcontracting Order Item",
+ "Subcontracting Receipt",
+ "Subcontracting Receipt Item",
+ ]:
+
+ field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
+
+ if field:
+ continue
+
+ df = {
+ "fieldname": d.fieldname,
+ "label": d.label,
+ "fieldtype": "Link",
+ "options": d.document_type,
+ "insert_after": insert_after_field,
+ }
+
+ try:
+ create_custom_field(doctype, df, ignore_validate=True)
+ frappe.clear_cache(doctype=doctype)
+ except Exception:
+ pass
+
+ count += 1
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index e78e4b6..a2be936 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -379,7 +379,7 @@
{fcond} {mcond}
order by
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
- (case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end)
+ (case when locate(%(_txt)s, full_name) > 0 then locate(%(_txt)s, full_name) else 99999 end),
idx desc,
name, full_name
limit %(page_len)s offset %(start)s""".format(
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index eacf480..e7dd211 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -524,7 +524,8 @@
const currency = this.events.get_frm().doc.currency;
const taxes_html = taxes.map(t => {
if (t.tax_amount_after_discount_amount == 0.0) return;
- const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
+ // if tax rate is 0, don't print it.
+ const description = /[0-9]+/.test(t.description) ? t.description : ((t.rate != 0) ? `${t.description} @ ${t.rate}%`: t.description);
return `<div class="tax-row">
<div class="tax-label">${description}</div>
<div class="tax-value">${format_currency(t.tax_amount_after_discount_amount, currency)}</div>
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index eeb8523..40165c3 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -130,7 +130,8 @@
if (!doc.taxes.length) return '';
let taxes_html = doc.taxes.map(t => {
- const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`;
+ // if tax rate is 0, don't print it.
+ const description = /[0-9]+/.test(t.description) ? t.description : ((t.rate != 0) ? `${t.description} @ ${t.rate}%`: t.description);
return `
<div class="tax-row">
<div class="tax-label">${description}</div>
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 7d7e6b5..2076dde 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -4,7 +4,7 @@
import frappe
from frappe import _
-from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.utils import cint
@@ -83,35 +83,32 @@
def create_print_setting_custom_fields():
- create_custom_field(
- "Print Settings",
+ create_custom_fields(
{
- "label": _("Compact Item Print"),
- "fieldname": "compact_item_print",
- "fieldtype": "Check",
- "default": 1,
- "insert_after": "with_letterhead",
- },
- )
- create_custom_field(
- "Print Settings",
- {
- "label": _("Print UOM after Quantity"),
- "fieldname": "print_uom_after_quantity",
- "fieldtype": "Check",
- "default": 0,
- "insert_after": "compact_item_print",
- },
- )
- create_custom_field(
- "Print Settings",
- {
- "label": _("Print taxes with zero amount"),
- "fieldname": "print_taxes_with_zero_amount",
- "fieldtype": "Check",
- "default": 0,
- "insert_after": "allow_print_for_cancelled",
- },
+ "Print Settings": [
+ {
+ "label": _("Compact Item Print"),
+ "fieldname": "compact_item_print",
+ "fieldtype": "Check",
+ "default": "1",
+ "insert_after": "with_letterhead",
+ },
+ {
+ "label": _("Print UOM after Quantity"),
+ "fieldname": "print_uom_after_quantity",
+ "fieldtype": "Check",
+ "default": "0",
+ "insert_after": "compact_item_print",
+ },
+ {
+ "label": _("Print taxes with zero amount"),
+ "fieldname": "print_taxes_with_zero_amount",
+ "fieldtype": "Check",
+ "default": "0",
+ "insert_after": "allow_print_for_cancelled",
+ },
+ ]
+ }
)
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 3e470d4..271e2e0 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -473,7 +473,13 @@
"doctype": "Batch",
"batch_id": args.batch_id,
"item": args.item_code,
+ "expiry_date": args.expiry_date,
}
- ).insert()
+ )
+
+ if args.expiry_date:
+ batch.expiry_date = args.expiry_date
+
+ batch.insert()
return batch
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
index 91a21f4..07cb73b 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.js
@@ -35,14 +35,39 @@
refresh(frm) {
if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
&& frm.doc.__onload.has_stock_ledger.length) {
- let msg = __('Stock transactions exists against this dimension, user can not update document.');
- frm.dashboard.add_comment(msg, 'blue', true);
+ let allow_to_edit_fields = ['disabled', 'fetch_from_parent',
+ 'type_of_transaction', 'condition'];
frm.fields.forEach((field) => {
- if (field.df.fieldname !== 'disabled') {
+ if (!in_list(allow_to_edit_fields, field.df.fieldname)) {
frm.set_df_property(field.df.fieldname, "read_only", "1");
}
});
}
+
+ if (!frm.is_new()) {
+ frm.add_custom_button(__('Delete Dimension'), () => {
+ frm.trigger('delete_dimension');
+ });
+ }
+ },
+
+ delete_dimension(frm) {
+ let msg = (`
+ Custom fields related to this dimension will be deleted on deletion of dimension.
+ <br> Do you want to delete {0} dimension?
+ `);
+
+ frappe.confirm(__(msg, [frm.doc.name.bold()]), () => {
+ frappe.call({
+ method: 'erpnext.stock.doctype.inventory_dimension.inventory_dimension.delete_dimension',
+ args: {
+ dimension: frm.doc.name
+ },
+ callback: function() {
+ frappe.set_route('List', 'Inventory Dimension');
+ }
+ });
+ });
}
});
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
index 8b334d1..03e7fda 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
@@ -1,6 +1,5 @@
{
"actions": [],
- "allow_rename": 1,
"autoname": "field:dimension_name",
"creation": "2022-06-17 13:04:16.554051",
"doctype": "DocType",
@@ -22,6 +21,7 @@
"document_type",
"istable",
"type_of_transaction",
+ "fetch_from_parent",
"column_break_16",
"condition",
"applicable_condition_example_section",
@@ -101,12 +101,14 @@
"fieldname": "target_fieldname",
"fieldtype": "Data",
"label": "Target Fieldname (Stock Ledger Entry)",
+ "no_copy": 1,
"read_only": 1
},
{
"fieldname": "source_fieldname",
"fieldtype": "Data",
"label": "Source Fieldname",
+ "no_copy": 1,
"read_only": 1
},
{
@@ -123,7 +125,7 @@
"fieldname": "type_of_transaction",
"fieldtype": "Select",
"label": "Type of Transaction",
- "options": "\nInward\nOutward"
+ "options": "\nInward\nOutward\nBoth"
},
{
"fieldname": "html_19",
@@ -140,11 +142,18 @@
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "istable",
+ "description": "Set fieldname or DocType name like Supplier, Customer etc.",
+ "fieldname": "fetch_from_parent",
+ "fieldtype": "Data",
+ "label": "Fetch Value From Parent Form"
}
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-07-19 21:06:11.824976",
+ "modified": "2022-08-17 11:43:24.722441",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",
diff --git a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
index 5a9541f..4ff8f33 100644
--- a/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/inventory_dimension.py
@@ -43,13 +43,37 @@
return
old_doc = self._doc_before_save
+ allow_to_edit_fields = [
+ "disabled",
+ "fetch_from_parent",
+ "type_of_transaction",
+ "condition",
+ ]
+
for field in frappe.get_meta("Inventory Dimension").fields:
- if field.fieldname != "disabled" and old_doc.get(field.fieldname) != self.get(field.fieldname):
+ if field.fieldname not in allow_to_edit_fields and old_doc.get(field.fieldname) != self.get(
+ field.fieldname
+ ):
msg = f"""The user can not change value of the field {bold(field.label)} because
stock transactions exists against the dimension {bold(self.name)}."""
frappe.throw(_(msg), DoNotChangeError)
+ def on_trash(self):
+ self.delete_custom_fields()
+
+ def delete_custom_fields(self):
+ filters = {"fieldname": self.source_fieldname}
+
+ if self.document_type:
+ filters["dt"] = self.document_type
+
+ for field in frappe.get_all("Custom Field", filters=filters):
+ frappe.delete_doc("Custom Field", field.name)
+
+ msg = f"Deleted custom fields related to the dimension {self.name}"
+ frappe.msgprint(_(msg))
+
def reset_value(self):
if self.apply_to_all_doctypes:
self.istable = 0
@@ -76,30 +100,35 @@
self.add_custom_fields()
def add_custom_fields(self):
- dimension_field = dict(
- fieldname=self.source_fieldname,
- fieldtype="Link",
- insert_after="warehouse",
- options=self.reference_document,
- label=self.dimension_name,
- )
+ dimension_fields = [
+ dict(
+ fieldname="inventory_dimension",
+ fieldtype="Section Break",
+ insert_after="warehouse",
+ label="Inventory Dimension",
+ collapsible=1,
+ ),
+ dict(
+ fieldname=self.source_fieldname,
+ fieldtype="Link",
+ insert_after="inventory_dimension",
+ options=self.reference_document,
+ label=self.dimension_name,
+ ),
+ ]
custom_fields = {}
if self.apply_to_all_doctypes:
for doctype in get_inventory_documents():
- if not frappe.db.get_value(
- "Custom Field", {"dt": doctype[0], "fieldname": self.source_fieldname}
- ):
- custom_fields.setdefault(doctype[0], dimension_field)
- elif not frappe.db.get_value(
- "Custom Field", {"dt": self.document_type, "fieldname": self.source_fieldname}
- ):
- custom_fields.setdefault(self.document_type, dimension_field)
+ custom_fields.setdefault(doctype[0], dimension_fields)
+ else:
+ custom_fields.setdefault(self.document_type, dimension_fields)
if not frappe.db.get_value(
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
):
+ dimension_field = dimension_fields[1]
dimension_field["fieldname"] = self.target_fieldname
custom_fields["Stock Ledger Entry"] = dimension_field
@@ -143,7 +172,7 @@
elif (
row.type_of_transaction == "Outward"
if doc.docstatus == 1
- else row.type_of_transaction != "Inward"
+ else row.type_of_transaction != "Outward"
) and sl_dict.actual_qty > 0:
continue
@@ -166,7 +195,14 @@
if not frappe.local.document_wise_inventory_dimensions.get(doctype):
dimensions = frappe.get_all(
"Inventory Dimension",
- fields=["name", "source_fieldname", "condition", "target_fieldname", "type_of_transaction"],
+ fields=[
+ "name",
+ "source_fieldname",
+ "condition",
+ "target_fieldname",
+ "type_of_transaction",
+ "fetch_from_parent",
+ ],
filters={"disabled": 0},
or_filters={"document_type": doctype, "apply_to_all_doctypes": 1},
)
@@ -194,3 +230,9 @@
frappe.local.inventory_dimensions = dimensions
return frappe.local.inventory_dimensions
+
+
+@frappe.whitelist()
+def delete_dimension(dimension):
+ doc = frappe.get_doc("Inventory Dimension", dimension)
+ doc.delete()
diff --git a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
index 998a0e9..cc90b74 100644
--- a/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
+++ b/erpnext/stock/doctype/inventory_dimension/test_inventory_dimension.py
@@ -8,6 +8,7 @@
CanNotBeChildDoc,
CanNotBeDefaultDimension,
DoNotChangeError,
+ delete_dimension,
)
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -42,6 +43,32 @@
self.assertRaises(CanNotBeDefaultDimension, inv_dim1.insert)
+ def test_delete_inventory_dimension(self):
+ inv_dim1 = create_inventory_dimension(
+ reference_document="Shelf",
+ type_of_transaction="Outward",
+ dimension_name="From Shelf",
+ apply_to_all_doctypes=0,
+ document_type="Stock Entry Detail",
+ condition="parent.purpose == 'Material Issue'",
+ )
+
+ inv_dim1.save()
+
+ custom_field = frappe.db.get_value(
+ "Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name"
+ )
+
+ self.assertTrue(custom_field)
+
+ delete_dimension(inv_dim1.name)
+
+ custom_field = frappe.db.get_value(
+ "Custom Field", {"fieldname": "from_shelf", "dt": "Stock Entry Detail"}, "name"
+ )
+
+ self.assertFalse(custom_field)
+
def test_inventory_dimension(self):
warehouse = "Shelf Warehouse - _TC"
item_code = "_Test Item"
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index c97dbee..39833b5 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -792,10 +792,8 @@
{
"fieldname": "expense_account",
"fieldtype": "Link",
- "hidden": 1,
"label": "Expense Account",
- "options": "Account",
- "read_only": 1
+ "options": "Account"
},
{
"fieldname": "accounting_dimensions_section",
@@ -1001,7 +999,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-06-17 05:32:16.483178",
+ "modified": "2022-07-28 19:27:54.880781",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
index 4cd40bf..d6e00ea 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
@@ -15,7 +15,7 @@
return {
filters: {
name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note',
- 'Sales Invoice', 'Stock Entry', 'Stock Reconciliation']]
+ 'Sales Invoice', 'Stock Entry', 'Stock Reconciliation', 'Subcontracting Receipt']]
}
};
});
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 1c514a9..e3a8438 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -583,18 +583,23 @@
},
add_to_transit: function(frm) {
- if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') {
- frm.set_value('to_warehouse', '');
+ if(frm.doc.purpose=='Material Transfer') {
+ var filters = {
+ 'is_group': 0,
+ 'company': frm.doc.company
+ }
+
+ if(frm.doc.add_to_transit){
+ filters['warehouse_type'] = 'Transit';
+ frm.set_value('to_warehouse', '');
+ frm.trigger('set_transit_warehouse');
+ }
+
frm.fields_dict.to_warehouse.get_query = function() {
return {
- filters:{
- 'warehouse_type' : 'Transit',
- 'is_group': 0,
- 'company': frm.doc.company
- }
+ filters:filters
};
};
- frm.trigger('set_transit_warehouse');
}
},
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index a2f9978..b574b71 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -5,7 +5,7 @@
import frappe
from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings
-from frappe.utils import add_days, flt, nowdate, nowtime
+from frappe.utils import add_days, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import (
@@ -1589,6 +1589,31 @@
self.assertEqual(obj.items[index].basic_rate, 200)
self.assertEqual(obj.items[index].basic_amount, 2000)
+ def test_batch_expiry(self):
+ from erpnext.controllers.stock_controller import BatchExpiredError
+ from erpnext.stock.doctype.batch.test_batch import make_new_batch
+
+ item_code = "Test Batch Expiry Test Item - 001"
+ item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
+
+ item_doc.has_batch_no = 1
+ item_doc.save()
+
+ batch = make_new_batch(
+ batch_id=frappe.generate_hash("", 5), item_code=item_doc.name, expiry_date=add_days(today(), -1)
+ )
+
+ se = make_stock_entry(
+ item_code=item_code,
+ purpose="Material Receipt",
+ qty=4,
+ to_warehouse="_Test Warehouse - _TC",
+ batch_no=batch.name,
+ do_not_save=True,
+ )
+
+ self.assertRaises(BatchExpiredError, se.save)
+
def make_serialized_item(**args):
args = frappe._dict(args)
diff --git a/erpnext/stock/landed_taxes_and_charges_common.js b/erpnext/stock/landed_taxes_and_charges_common.js
index ff8a69f..1d76a3d 100644
--- a/erpnext/stock/landed_taxes_and_charges_common.js
+++ b/erpnext/stock/landed_taxes_and_charges_common.js
@@ -1,4 +1,4 @@
-let document_list = ['Landed Cost Voucher', 'Stock Entry'];
+let document_list = ['Landed Cost Voucher', 'Stock Entry', 'Subcontracting Order', 'Subcontracting Receipt'];
document_list.forEach((doctype) => {
frappe.ui.form.on(doctype, {
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
index dbd337a..c20f8ab 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js
@@ -3,6 +3,8 @@
frappe.provide('erpnext.buying');
+{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
+
frappe.ui.form.on('Subcontracting Order', {
setup: (frm) => {
frm.get_field("items").grid.cannot_add_rows = true;
@@ -136,6 +138,16 @@
}
});
+frappe.ui.form.on('Landed Cost Taxes and Charges', {
+ amount: function (frm, cdt, cdn) {
+ frm.events.set_base_amount(frm, cdt, cdn);
+ },
+
+ expense_account: function (frm, cdt, cdn) {
+ frm.events.set_account_currency(frm, cdt, cdn);
+ }
+});
+
erpnext.buying.SubcontractingOrderController = class SubcontractingOrderController {
setup() {
this.frm.custom_make_buttons = {
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
index c6e76c7..f98f559 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json
@@ -19,6 +19,10 @@
"transaction_date",
"schedule_date",
"amended_from",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "project",
"address_and_contact_section",
"supplier_address",
"address_display",
@@ -422,12 +426,34 @@
"fieldtype": "Select",
"label": "Distribute Additional Costs Based On ",
"options": "Qty\nAmount"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2022-04-11 21:02:44.097841",
+ "modified": "2022-08-15 14:08:49.204218",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order",
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 71cdc94..156f027 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -82,25 +82,6 @@
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
- def set_missing_values_in_additional_costs(self):
- if self.get("additional_costs"):
- self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
-
- if self.total_additional_costs:
- if self.distribute_additional_costs_based_on == "Amount":
- total_amt = sum(flt(item.amount) for item in self.get("items"))
- for item in self.items:
- item.additional_cost_per_qty = (
- (item.amount * self.total_additional_costs) / total_amt
- ) / item.qty
- else:
- total_qty = sum(flt(item.qty) for item in self.get("items"))
- additional_cost_per_qty = self.total_additional_costs / total_qty
- for item in self.items:
- item.additional_cost_per_qty = additional_cost_per_qty
- else:
- self.total_additional_costs = 0
-
def set_missing_values_in_service_items(self):
for idx, item in enumerate(self.get("service_items")):
self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty
@@ -114,9 +95,7 @@
def set_missing_values_in_items(self):
total_qty = total = 0
for item in self.items:
- item.rate = (
- item.rm_cost_per_qty + item.service_cost_per_qty + (item.additional_cost_per_qty or 0)
- )
+ item.rate = item.rm_cost_per_qty + item.service_cost_per_qty + flt(item.additional_cost_per_qty)
item.amount = item.qty * item.rate
total_qty += flt(item.qty)
total += flt(item.amount)
@@ -187,7 +166,7 @@
total_required_qty = total_supplied_qty = 0
for item in self.supplied_items:
total_required_qty += item.required_qty
- total_supplied_qty += item.supplied_qty or 0
+ total_supplied_qty += flt(item.supplied_qty)
if total_supplied_qty:
status = "Partial Material Transferred"
if total_supplied_qty >= total_required_qty:
diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
index 291f47a..3675a4e 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json
@@ -40,6 +40,10 @@
"manufacture_section",
"manufacturer",
"manufacturer_part_no",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "project",
"section_break_34",
"page_break"
],
@@ -304,13 +308,35 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions"
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-11 21:28:06.585338",
+ "modified": "2022-08-15 14:25:45.177703",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Order Item",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index b2506cd..aff76eb 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -3,6 +3,8 @@
frappe.provide('erpnext.buying');
+{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
+
frappe.ui.form.on('Subcontracting Receipt', {
setup: (frm) => {
frm.get_field('supplied_items').grid.cannot_add_rows = true;
@@ -48,6 +50,13 @@
is_group: 0
}
}));
+
+ frm.set_query("expense_account", "items", function () {
+ return {
+ query: "erpnext.controllers.queries.get_expense_account",
+ filters: { 'company': frm.doc.company }
+ };
+ });
},
refresh: (frm) => {
@@ -121,6 +130,16 @@
},
});
+frappe.ui.form.on('Landed Cost Taxes and Charges', {
+ amount: function (frm, cdt, cdn) {
+ frm.events.set_base_amount(frm, cdt, cdn);
+ },
+
+ expense_account: function (frm, cdt, cdn) {
+ frm.events.set_account_currency(frm, cdt, cdn);
+ }
+});
+
frappe.ui.form.on('Subcontracting Receipt Item', {
item_code(frm) {
set_missing_values(frm);
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
index e963814..84e9554 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.json
@@ -15,8 +15,13 @@
"company",
"posting_date",
"posting_time",
+ "set_posting_time",
"is_return",
"return_against",
+ "accounting_dimensions_section",
+ "cost_center",
+ "dimension_col_break",
+ "project",
"section_addresses",
"supplier_address",
"contact_person",
@@ -43,12 +48,14 @@
"raw_material_details",
"get_current_stock",
"supplied_items",
+ "additional_costs_section",
+ "distribute_additional_costs_based_on",
+ "additional_costs",
+ "total_additional_costs",
"section_break_46",
"in_words",
"bill_no",
"bill_date",
- "accounting_details_section",
- "provisional_expense_account",
"more_info",
"status",
"column_break_39",
@@ -132,6 +139,7 @@
"label": "Date",
"no_copy": 1,
"print_width": "100px",
+ "read_only_depends_on": "eval: !doc.set_posting_time",
"reqd": 1,
"search_index": 1,
"width": "100px"
@@ -144,6 +152,7 @@
"no_copy": 1,
"print_hide": 1,
"print_width": "100px",
+ "read_only_depends_on": "eval: !doc.set_posting_time",
"reqd": 1,
"width": "100px"
},
@@ -521,19 +530,6 @@
"read_only": 1
},
{
- "collapsible": 1,
- "fieldname": "accounting_details_section",
- "fieldtype": "Section Break",
- "label": "Accounting Details"
- },
- {
- "fieldname": "provisional_expense_account",
- "fieldtype": "Link",
- "hidden": 1,
- "label": "Provisional Expense Account",
- "options": "Account"
- },
- {
"default": "0",
"fieldname": "is_return",
"fieldtype": "Check",
@@ -569,11 +565,70 @@
{
"fieldname": "section_break_47",
"fieldtype": "Section Break"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "accounting_dimensions_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Dimensions "
+ },
+ {
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "label": "Cost Center",
+ "options": "Cost Center"
+ },
+ {
+ "fieldname": "dimension_col_break",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "label": "Project",
+ "options": "Project"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "total_additional_costs",
+ "depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)",
+ "fieldname": "additional_costs_section",
+ "fieldtype": "Section Break",
+ "label": "Additional Costs"
+ },
+ {
+ "default": "Qty",
+ "fieldname": "distribute_additional_costs_based_on",
+ "fieldtype": "Select",
+ "label": "Distribute Additional Costs Based On ",
+ "options": "Qty\nAmount"
+ },
+ {
+ "fieldname": "additional_costs",
+ "fieldtype": "Table",
+ "label": "Additional Costs",
+ "options": "Landed Cost Taxes and Charges"
+ },
+ {
+ "fieldname": "total_additional_costs",
+ "fieldtype": "Currency",
+ "label": "Total Additional Costs",
+ "print_hide_if_no_value": 1,
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "depends_on": "eval:doc.docstatus==0",
+ "fieldname": "set_posting_time",
+ "fieldtype": "Check",
+ "label": "Edit Posting Date and Time",
+ "print_hide": 1
}
],
+ "in_create": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-04-18 13:15:12.011682",
+ "modified": "2022-08-22 17:30:40.827517",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 0c4ec6f..021d9aa 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -3,7 +3,7 @@
import frappe
from frappe import _
-from frappe.utils import cint, getdate, nowdate
+from frappe.utils import cint, flt, getdate, nowdate
from erpnext.controllers.subcontracting_controller import SubcontractingController
@@ -103,6 +103,7 @@
@frappe.whitelist()
def set_missing_values(self):
+ self.set_missing_values_in_additional_costs()
self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items()
@@ -125,12 +126,12 @@
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
rm_supp_cost.pop(item.name)
- if self.is_new() and item.rm_supp_cost > 0:
+ if item.recalculate_rate:
item.rate = (
- item.rm_cost_per_qty + (item.service_cost_per_qty or 0) + item.additional_cost_per_qty
+ flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
)
- item.received_qty = item.qty + (item.rejected_qty or 0)
+ item.received_qty = item.qty + flt(item.rejected_qty)
item.amount = item.qty * item.rate
total_qty += item.qty
total_amount += item.amount
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
index e2785ce..fd86895 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
@@ -29,6 +29,7 @@
"rate_and_amount",
"rate",
"amount",
+ "recalculate_rate",
"column_break_19",
"rm_cost_per_qty",
"service_cost_per_qty",
@@ -49,15 +50,16 @@
"col_break5",
"batch_no",
"rejected_serial_no",
- "expense_account",
"manufacture_details",
"manufacturer",
"column_break_16",
"manufacturer_part_no",
+ "accounting_details_section",
+ "expense_account",
"accounting_dimensions_section",
- "project",
- "dimension_col_break",
"cost_center",
+ "dimension_col_break",
+ "project",
"section_break_80",
"page_break"
],
@@ -192,6 +194,8 @@
"label": "Rate",
"options": "currency",
"print_width": "100px",
+ "read_only": 1,
+ "read_only_depends_on": "eval: doc.recalculate_rate",
"width": "100px"
},
{
@@ -363,10 +367,8 @@
{
"fieldname": "expense_account",
"fieldtype": "Link",
- "hidden": 1,
"label": "Expense Account",
- "options": "Account",
- "read_only": 1
+ "options": "Account"
},
{
"collapsible": 1,
@@ -456,12 +458,23 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "fieldname": "accounting_details_section",
+ "fieldtype": "Section Break",
+ "label": "Accounting Details"
+ },
+ {
+ "default": "1",
+ "fieldname": "recalculate_rate",
+ "fieldtype": "Check",
+ "label": "Recalculate Rate"
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-04-21 12:07:55.899701",
+ "modified": "2022-08-20 17:16:48.269164",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",