Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-31816

This commit is contained in:
Deepesh Garg
2022-08-22 09:09:16 +05:30
committed by GitHub
45 changed files with 774 additions and 212 deletions

View File

@@ -1184,6 +1184,7 @@ def get_outstanding_reference_documents(args):
ple = qb.DocType("Payment Ledger Entry") ple = qb.DocType("Payment Ledger Entry")
common_filter = [] common_filter = []
posting_and_due_date = []
# confirm that Supplier is not blocked # confirm that Supplier is not blocked
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":
@@ -1224,7 +1225,7 @@ def get_outstanding_reference_documents(args):
condition += " and {0} between '{1}' and '{2}'".format( condition += " and {0} between '{1}' and '{2}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1]) fieldname, args.get(date_fields[0]), args.get(date_fields[1])
) )
common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])]) posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
if args.get("company"): if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company"))) condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
@@ -1235,6 +1236,7 @@ def get_outstanding_reference_documents(args):
args.get("party"), args.get("party"),
args.get("party_account"), args.get("party_account"),
common_filter=common_filter, common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"), min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"), max_outstanding=args.get("outstanding_amt_less_than"),
) )

View File

@@ -22,6 +22,7 @@ class PaymentReconciliation(Document):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs) super(PaymentReconciliation, self).__init__(*args, **kwargs)
self.common_filter_conditions = [] self.common_filter_conditions = []
self.ple_posting_date_filter = []
@frappe.whitelist() @frappe.whitelist()
def get_unreconciled_entries(self): def get_unreconciled_entries(self):
@@ -150,6 +151,7 @@ class PaymentReconciliation(Document):
return_outstanding = ple_query.get_voucher_outstandings( return_outstanding = ple_query.get_voucher_outstandings(
vouchers=return_invoices, vouchers=return_invoices,
common_filter=self.common_filter_conditions, common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None, min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None, max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True, get_payments=True,
@@ -187,6 +189,7 @@ class PaymentReconciliation(Document):
self.party, self.party,
self.receivable_payable_account, self.receivable_payable_account,
common_filter=self.common_filter_conditions, common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None, min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None, max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
) )
@@ -350,6 +353,7 @@ class PaymentReconciliation(Document):
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False): def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear() self.common_filter_conditions.clear()
self.ple_posting_date_filter.clear()
ple = qb.DocType("Payment Ledger Entry") ple = qb.DocType("Payment Ledger Entry")
self.common_filter_conditions.append(ple.company == self.company) self.common_filter_conditions.append(ple.company == self.company)
@@ -359,15 +363,15 @@ class PaymentReconciliation(Document):
if get_invoices: if get_invoices:
if self.from_invoice_date: if self.from_invoice_date:
self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date)) self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date))
if self.to_invoice_date: if self.to_invoice_date:
self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date)) self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date))
elif get_return_invoices: elif get_return_invoices:
if self.from_payment_date: if self.from_payment_date:
self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date)) self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date))
if self.to_payment_date: if self.to_payment_date:
self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date)) self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
def get_conditions(self, get_payments=False): def get_conditions(self, get_payments=False):
condition = " and company = '{0}' ".format(self.company) condition = " and company = '{0}' ".format(self.company)

View File

@@ -283,6 +283,41 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 2) self.assertEqual(len(pr.get("invoices")), 2)
self.assertEqual(len(pr.get("payments")), 2) self.assertEqual(len(pr.get("payments")), 2)
def test_filter_posting_date_case2(self):
"""
Posting date should not affect outstanding amount calculation
"""
from_date = add_days(nowdate(), -30)
to_date = nowdate()
self.create_payment_entry(amount=25, posting_date=from_date).submit()
self.create_sales_invoice(rate=25, qty=1, posting_date=to_date)
pr = self.create_payment_reconciliation()
pr.from_invoice_date = pr.from_payment_date = from_date
pr.to_invoice_date = pr.to_payment_date = to_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
pr.from_invoice_date = pr.from_payment_date = to_date
pr.to_invoice_date = pr.to_payment_date = to_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
def test_filter_invoice_limit(self): def test_filter_invoice_limit(self):
# check filter condition - invoice limit # check filter condition - invoice limit
transaction_date = nowdate() transaction_date = nowdate()

View File

@@ -36,6 +36,15 @@ frappe.ui.form.on('POS Closing Entry', {
}); });
set_html_data(frm); 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) { refresh: function(frm) {

View File

@@ -11,6 +11,7 @@
"period_end_date", "period_end_date",
"column_break_3", "column_break_3",
"posting_date", "posting_date",
"posting_time",
"pos_opening_entry", "pos_opening_entry",
"status", "status",
"section_break_5", "section_break_5",
@@ -51,7 +52,6 @@
"fieldtype": "Datetime", "fieldtype": "Datetime",
"in_list_view": 1, "in_list_view": 1,
"label": "Period End Date", "label": "Period End Date",
"read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -219,6 +219,13 @@
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Error", "label": "Error",
"read_only": 1 "read_only": 1
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
@@ -228,10 +235,11 @@
"link_fieldname": "pos_closing_entry" "link_fieldname": "pos_closing_entry"
} }
], ],
"modified": "2021-10-20 16:19:25.340565", "modified": "2022-08-01 11:37:14.991228",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Closing Entry", "name": "POS Closing Entry",
"naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -278,5 +286,6 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -15,6 +15,9 @@ from erpnext.controllers.status_updater import StatusUpdater
class POSClosingEntry(StatusUpdater): class POSClosingEntry(StatusUpdater):
def validate(self): 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": 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")) frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"posting_date", "posting_date",
"posting_time",
"merge_invoices_based_on", "merge_invoices_based_on",
"column_break_3", "column_break_3",
"pos_closing_entry", "pos_closing_entry",
@@ -105,12 +106,19 @@
"label": "Customer Group", "label": "Customer Group",
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'", "mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
"options": "Customer Group" "options": "Customer Group"
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-09-14 11:17:19.001142", "modified": "2022-08-01 11:36:42.456429",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice Merge Log", "name": "POS Invoice Merge Log",
@@ -173,5 +181,6 @@
], ],
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -9,7 +9,7 @@ from frappe import _
from frappe.core.page.background_jobs.background_jobs import get_info from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.mapper import map_child_doc, map_doc 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.background_jobs import enqueue
from frappe.utils.scheduler import is_scheduler_inactive from frappe.utils.scheduler import is_scheduler_inactive
@@ -99,6 +99,7 @@ class POSInvoiceMergeLog(Document):
sales_invoice.is_consolidated = 1 sales_invoice.is_consolidated = 1
sales_invoice.set_posting_time = 1 sales_invoice.set_posting_time = 1
sales_invoice.posting_date = getdate(self.posting_date) sales_invoice.posting_date = getdate(self.posting_date)
sales_invoice.posting_time = get_time(self.posting_time)
sales_invoice.save() sales_invoice.save()
sales_invoice.submit() sales_invoice.submit()
@@ -115,6 +116,7 @@ class POSInvoiceMergeLog(Document):
credit_note.is_consolidated = 1 credit_note.is_consolidated = 1
credit_note.set_posting_time = 1 credit_note.set_posting_time = 1
credit_note.posting_date = getdate(self.posting_date) 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? # TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice # credit_note.return_against = self.consolidated_invoice
credit_note.save() credit_note.save()
@@ -402,6 +404,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
merge_log.posting_date = ( merge_log.posting_date = (
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate() 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.customer = customer
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None

View File

@@ -823,7 +823,13 @@ def get_held_invoices(party_type, party):
def get_outstanding_invoices( def get_outstanding_invoices(
party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None party_type,
party,
account,
common_filter=None,
posting_date=None,
min_outstanding=None,
max_outstanding=None,
): ):
ple = qb.DocType("Payment Ledger Entry") ple = qb.DocType("Payment Ledger Entry")
@@ -850,6 +856,7 @@ def get_outstanding_invoices(
ple_query = QueryPaymentLedger() ple_query = QueryPaymentLedger()
invoice_list = ple_query.get_voucher_outstandings( invoice_list = ple_query.get_voucher_outstandings(
common_filter=common_filter, common_filter=common_filter,
posting_date=posting_date,
min_outstanding=min_outstanding, min_outstanding=min_outstanding,
max_outstanding=max_outstanding, max_outstanding=max_outstanding,
get_invoices=True, get_invoices=True,
@@ -1501,6 +1508,7 @@ class QueryPaymentLedger(object):
# query filters # query filters
self.vouchers = [] self.vouchers = []
self.common_filter = [] self.common_filter = []
self.voucher_posting_date = []
self.min_outstanding = None self.min_outstanding = None
self.max_outstanding = None self.max_outstanding = None
@@ -1571,6 +1579,7 @@ class QueryPaymentLedger(object):
.where(ple.delinked == 0) .where(ple.delinked == 0)
.where(Criterion.all(filter_on_voucher_no)) .where(Criterion.all(filter_on_voucher_no))
.where(Criterion.all(self.common_filter)) .where(Criterion.all(self.common_filter))
.where(Criterion.all(self.voucher_posting_date))
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party) .groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
) )
@@ -1652,6 +1661,7 @@ class QueryPaymentLedger(object):
self, self,
vouchers=None, vouchers=None,
common_filter=None, common_filter=None,
posting_date=None,
min_outstanding=None, min_outstanding=None,
max_outstanding=None, max_outstanding=None,
get_payments=False, get_payments=False,
@@ -1671,6 +1681,7 @@ class QueryPaymentLedger(object):
self.reset() self.reset()
self.vouchers = vouchers self.vouchers = vouchers
self.common_filter = common_filter or [] self.common_filter = common_filter or []
self.voucher_posting_date = posting_date or []
self.min_outstanding = min_outstanding self.min_outstanding = min_outstanding
self.max_outstanding = max_outstanding self.max_outstanding = max_outstanding
self.get_payments = get_payments self.get_payments = get_payments

View File

@@ -15,9 +15,12 @@ frappe.ui.form.on("Request for Quotation",{
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) { frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {
let d = locals[cdt][cdn]; let d = locals[cdt][cdn];
return { return {
query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts", query: "frappe.contacts.doctype.contact.contact.contact_query",
filters: {'supplier': d.supplier} filters: {
} link_doctype: "Supplier",
link_name: d.supplier || ""
}
};
} }
}, },

View File

@@ -286,18 +286,6 @@ def get_list_context(context=None):
return list_context return list_context
@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() @frappe.whitelist()
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None): def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
def postprocess(source, target_doc): def postprocess(source, target_doc):

View File

@@ -4,6 +4,8 @@
# Decompiled by https://python-decompiler.com # Decompiled by https://python-decompiler.com
import copy
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
@@ -11,10 +13,12 @@ from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_
execute, execute,
) )
from erpnext.controllers.tests.test_subcontracting_controller import ( from erpnext.controllers.tests.test_subcontracting_controller import (
get_rm_items,
get_subcontracting_order, get_subcontracting_order,
make_service_item, 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 ( from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
make_subcontracting_receipt, make_subcontracting_receipt,
) )
@@ -36,15 +40,18 @@ class TestSubcontractedItemToBeReceived(FrappeTestCase):
sco = get_subcontracting_order( sco = get_subcontracting_order(
service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC" service_items=service_items, supplier_warehouse="_Test Warehouse 1 - _TC"
) )
make_stock_entry( rm_items = get_rm_items(sco.supplied_items)
item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100 itemwise_details = make_stock_in_entry(rm_items=rm_items)
)
make_stock_entry( for item in rm_items:
item_code="_Test Item Home Desktop 100", item["sco_rm_detail"] = sco.items[0].name
target="_Test Warehouse 1 - _TC",
qty=100, make_stock_transfer_entry(
basic_rate=100, sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
) )
make_subcontracting_receipt_against_sco(sco.name) make_subcontracting_receipt_against_sco(sco.name)
sco.reload() sco.reload()
col, data = execute( col, data = execute(

View File

@@ -36,6 +36,10 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError):
pass pass
class BatchExpiredError(frappe.ValidationError):
pass
class StockController(AccountsController): class StockController(AccountsController):
def validate(self): def validate(self):
super(StockController, self).validate() super(StockController, self).validate()
@@ -77,6 +81,10 @@ class StockController(AccountsController):
def validate_serialized_batch(self): def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos 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"): for d in self.get("items"):
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no: if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
serial_nos = frappe.get_all( serial_nos = frappe.get_all(
@@ -93,6 +101,9 @@ class StockController(AccountsController):
) )
) )
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: 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") expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
@@ -100,7 +111,8 @@ class StockController(AccountsController):
frappe.throw( frappe.throw(
_("Row #{0}: The batch {1} has already expired.").format( _("Row #{0}: The batch {1} has already expired.").format(
d.idx, get_link_to_form("Batch", d.get("batch_no")) d.idx, get_link_to_form("Batch", d.get("batch_no"))
) ),
BatchExpiredError,
) )
def clean_serial_nos(self): def clean_serial_nos(self):
@@ -310,7 +322,13 @@ class StockController(AccountsController):
) )
if ( if (
self.doctype 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 and not is_expense_account
): ):
frappe.throw( frappe.throw(
@@ -374,9 +392,24 @@ class StockController(AccountsController):
def update_inventory_dimensions(self, row, sl_dict) -> None: def update_inventory_dimensions(self, row, sl_dict) -> None:
dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self) dimensions = get_evaluated_inventory_dimension(row, sl_dict, parent_doc=self)
for dimension in dimensions: 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) 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): 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 from erpnext.stock.stock_ledger import make_sl_entries

View File

@@ -490,7 +490,7 @@ class SubcontractingController(StockController):
row.item_code, row.item_code,
row.get(self.subcontract_data.order_field), row.get(self.subcontract_data.order_field),
) and transfer_item.qty > 0: ) 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 transfer_item.qty -= qty
self.__add_supplied_item(row, transfer_item.get("item_details"), qty) self.__add_supplied_item(row, transfer_item.get("item_details"), qty)
@@ -720,6 +720,25 @@ class SubcontractingController(StockController):
sco_doc = frappe.get_doc("Subcontracting Order", sco) sco_doc = frappe.get_doc("Subcontracting Order", sco)
sco_doc.update_status() 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() @frappe.whitelist()
def get_current_stock(self): def get_current_stock(self):
if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]: if self.doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
@@ -730,7 +749,7 @@ class SubcontractingController(StockController):
{"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse}, {"item_code": item.rm_item_code, "warehouse": self.supplier_warehouse},
"actual_qty", "actual_qty",
) )
item.current_stock = flt(actual_qty) or 0 item.current_stock = flt(actual_qty)
@property @property
def sub_contracted_items(self): def sub_contracted_items(self):

View File

@@ -36,6 +36,36 @@ class TestSubcontractingController(FrappeTestCase):
sco.remove_empty_rows() sco.remove_empty_rows()
self.assertEqual((len_before - 1), len(sco.service_items)) 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): def test_create_raw_materials_supplied(self):
sco = get_subcontracting_order() sco = get_subcontracting_order()
sco.supplied_items = None sco.supplied_items = None

View File

@@ -12,7 +12,9 @@ from decimal import Decimal
import frappe import frappe
from bs4 import BeautifulSoup as bs from bs4 import BeautifulSoup as bs
from frappe import _ 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.model.document import Document
from frappe.utils.data import format_datetime from frappe.utils.data import format_datetime
@@ -577,22 +579,25 @@ class TallyMigration(Document):
new_year.save() new_year.save()
oldest_year = new_year oldest_year = new_year
def create_custom_fields(doctypes): def create_custom_fields():
tally_guid_df = { _create_custom_fields(
"fieldtype": "Data", {
"fieldname": "tally_guid", ("Journal Entry", "Purchase Invoice", "Sales Invoice"): [
"read_only": 1, {
"label": "Tally GUID", "fieldtype": "Data",
} "fieldname": "tally_guid",
tally_voucher_no_df = { "read_only": 1,
"fieldtype": "Data", "label": "Tally GUID",
"fieldname": "tally_voucher_no", },
"read_only": 1, {
"label": "Tally Voucher Number", "fieldtype": "Data",
} "fieldname": "tally_voucher_no",
for df in [tally_guid_df, tally_voucher_no_df]: "read_only": 1,
for doctype in doctypes: "label": "Tally Voucher Number",
create_custom_field(doctype, df) },
]
}
)
def create_price_list(): def create_price_list():
frappe.get_doc( frappe.get_doc(
@@ -628,7 +633,7 @@ class TallyMigration(Document):
create_fiscal_years(vouchers) create_fiscal_years(vouchers)
create_price_list() create_price_list()
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"]) create_custom_fields()
total = len(vouchers) total = len(vouchers)
is_last = False is_last = False

View File

@@ -6,7 +6,7 @@ from urllib.parse import urlparse
import frappe import frappe
from frappe import _ 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.model.document import Document
from frappe.utils.nestedset import get_root_of from frappe.utils.nestedset import get_root_of
@@ -19,27 +19,24 @@ class WoocommerceSettings(Document):
def create_delete_custom_fields(self): def create_delete_custom_fields(self):
if self.enable_sync: if self.enable_sync:
custom_fields = {} create_custom_fields(
# create {
for doctype in ["Customer", "Sales Order", "Item", "Address"]: ("Customer", "Sales Order", "Item", "Address"): dict(
df = dict( fieldname="woocommerce_id",
fieldname="woocommerce_id", label="Woocommerce ID",
label="Woocommerce ID", fieldtype="Data",
fieldtype="Data", read_only=1,
read_only=1, print_hide=1,
print_hide=1, ),
) ("Customer", "Address"): dict(
create_custom_field(doctype, df) fieldname="woocommerce_email",
label="Woocommerce Email",
for doctype in ["Customer", "Address"]: fieldtype="Data",
df = dict( read_only=1,
fieldname="woocommerce_email", print_hide=1,
label="Woocommerce Email", ),
fieldtype="Data", }
read_only=1, )
print_hide=1,
)
create_custom_field(doctype, df)
if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}): if not frappe.get_value("Item Group", {"name": _("WooCommerce Products")}):
item_group = frappe.new_doc("Item Group") item_group = frappe.new_doc("Item Group")

View File

@@ -520,6 +520,10 @@ accounting_dimension_doctypes = [
"Purchase Order", "Purchase Order",
"Purchase Receipt", "Purchase Receipt",
"Sales Order", "Sales Order",
"Subcontracting Order",
"Subcontracting Order Item",
"Subcontracting Receipt",
"Subcontracting Receipt Item",
] ]
# get matching queries for Bank Reconciliation # get matching queries for Bank Reconciliation

View File

@@ -189,8 +189,8 @@ class BOM(WebsiteGenerator):
self.validate_transfer_against() self.validate_transfer_against()
self.set_routing_operations() self.set_routing_operations()
self.validate_operations() self.validate_operations()
self.update_exploded_items(save=False)
self.calculate_cost() self.calculate_cost()
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.validate_scrap_items() self.validate_scrap_items()

View File

@@ -611,6 +611,34 @@ class TestBOM(FrappeTestCase):
bom.reload() bom.reload()
self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name) 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"): 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}) return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})

View File

@@ -184,6 +184,7 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Rate", "label": "Rate",
"options": "currency", "options": "currency",
"read_only": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -288,7 +289,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-05-19 02:32:43.785470", "modified": "2022-07-28 10:20:51.559010",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM Item", "name": "BOM Item",

View File

@@ -482,7 +482,6 @@ class ProductionPlan(Document):
"bom_no", "bom_no",
"stock_uom", "stock_uom",
"bom_level", "bom_level",
"production_plan_item",
"schedule_date", "schedule_date",
]: ]:
if row.get(field): if row.get(field):
@@ -639,6 +638,9 @@ class ProductionPlan(Document):
sub_assembly_items_store = [] # temporary store to process all subassembly items sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_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 = [] bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) 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) self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)

View File

@@ -11,8 +11,9 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_warehouse_list, get_warehouse_list,
) )
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError 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.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_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation, create_stock_reconciliation,
@@ -583,9 +584,6 @@ class TestProductionPlan(FrappeTestCase):
Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel) 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.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( make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -629,9 +627,6 @@ class TestProductionPlan(FrappeTestCase):
def test_production_plan_pending_qty_independent_items(self): def test_production_plan_pending_qty_independent_items(self):
"Test Prod Plan impact if items are added independently (no from SO or MR)." "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.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( make_stock_entry(
item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100 item_code="Raw Material Item 1", target="Work In Progress - _TC", qty=2, basic_rate=100
@@ -728,6 +723,57 @@ class TestProductionPlan(FrappeTestCase):
for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items): for po_item, subassy_item in zip(pp.po_items, pp.sub_assembly_items):
self.assertEqual(po_item.name, subassy_item.production_plan_item) 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): def create_production_plan(**args):
""" """

View File

@@ -310,4 +310,5 @@ erpnext.patches.v14_0.crm_ux_cleanup
erpnext.patches.v14_0.remove_india_localisation # 14-07-2022 erpnext.patches.v14_0.remove_india_localisation # 14-07-2022
erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation 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.remove_hr_and_payroll_modules # 20-07-2022
erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.fix_crm_no_of_employees
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes

View File

@@ -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

View File

@@ -379,7 +379,7 @@ def get_users_for_project(doctype, txt, searchfield, start, page_len, filters):
{fcond} {mcond} {fcond} {mcond}
order by order by
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end), (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, idx desc,
name, full_name name, full_name
limit %(page_len)s offset %(start)s""".format( limit %(page_len)s offset %(start)s""".format(

View File

@@ -4,7 +4,7 @@
import frappe import frappe
from frappe import _ 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.desk.page.setup_wizard.setup_wizard import add_all_roles_to
from frappe.utils import cint from frappe.utils import cint
@@ -83,35 +83,32 @@ def setup_currency_exchange():
def create_print_setting_custom_fields(): def create_print_setting_custom_fields():
create_custom_field( create_custom_fields(
"Print Settings",
{ {
"label": _("Compact Item Print"), "Print Settings": [
"fieldname": "compact_item_print", {
"fieldtype": "Check", "label": _("Compact Item Print"),
"default": 1, "fieldname": "compact_item_print",
"insert_after": "with_letterhead", "fieldtype": "Check",
}, "default": "1",
) "insert_after": "with_letterhead",
create_custom_field( },
"Print Settings", {
{ "label": _("Print UOM after Quantity"),
"label": _("Print UOM after Quantity"), "fieldname": "print_uom_after_quantity",
"fieldname": "print_uom_after_quantity", "fieldtype": "Check",
"fieldtype": "Check", "default": "0",
"default": 0, "insert_after": "compact_item_print",
"insert_after": "compact_item_print", },
}, {
) "label": _("Print taxes with zero amount"),
create_custom_field( "fieldname": "print_taxes_with_zero_amount",
"Print Settings", "fieldtype": "Check",
{ "default": "0",
"label": _("Print taxes with zero amount"), "insert_after": "allow_print_for_cancelled",
"fieldname": "print_taxes_with_zero_amount", },
"fieldtype": "Check", ]
"default": 0, }
"insert_after": "allow_print_for_cancelled",
},
) )

View File

@@ -473,7 +473,13 @@ def make_new_batch(**args):
"doctype": "Batch", "doctype": "Batch",
"batch_id": args.batch_id, "batch_id": args.batch_id,
"item": args.item_code, "item": args.item_code,
"expiry_date": args.expiry_date,
} }
).insert() )
if args.expiry_date:
batch.expiry_date = args.expiry_date
batch.insert()
return batch return batch

View File

@@ -35,14 +35,39 @@ frappe.ui.form.on('Inventory Dimension', {
refresh(frm) { refresh(frm) {
if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger if (frm.doc.__onload && frm.doc.__onload.has_stock_ledger
&& frm.doc.__onload.has_stock_ledger.length) { && frm.doc.__onload.has_stock_ledger.length) {
let msg = __('Stock transactions exists against this dimension, user can not update document.'); let allow_to_edit_fields = ['disabled', 'fetch_from_parent',
frm.dashboard.add_comment(msg, 'blue', true); 'type_of_transaction', 'condition'];
frm.fields.forEach((field) => { 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"); 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');
}
});
});
} }
}); });

View File

@@ -1,6 +1,5 @@
{ {
"actions": [], "actions": [],
"allow_rename": 1,
"autoname": "field:dimension_name", "autoname": "field:dimension_name",
"creation": "2022-06-17 13:04:16.554051", "creation": "2022-06-17 13:04:16.554051",
"doctype": "DocType", "doctype": "DocType",
@@ -22,6 +21,7 @@
"document_type", "document_type",
"istable", "istable",
"type_of_transaction", "type_of_transaction",
"fetch_from_parent",
"column_break_16", "column_break_16",
"condition", "condition",
"applicable_condition_example_section", "applicable_condition_example_section",
@@ -101,12 +101,14 @@
"fieldname": "target_fieldname", "fieldname": "target_fieldname",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Target Fieldname (Stock Ledger Entry)", "label": "Target Fieldname (Stock Ledger Entry)",
"no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "source_fieldname", "fieldname": "source_fieldname",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Source Fieldname", "label": "Source Fieldname",
"no_copy": 1,
"read_only": 1 "read_only": 1
}, },
{ {
@@ -123,7 +125,7 @@
"fieldname": "type_of_transaction", "fieldname": "type_of_transaction",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Type of Transaction", "label": "Type of Transaction",
"options": "\nInward\nOutward" "options": "\nInward\nOutward\nBoth"
}, },
{ {
"fieldname": "html_19", "fieldname": "html_19",
@@ -140,11 +142,18 @@
{ {
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break" "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, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2022-07-19 21:06:11.824976", "modified": "2022-08-17 11:43:24.722441",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Inventory Dimension", "name": "Inventory Dimension",

View File

@@ -43,13 +43,37 @@ class InventoryDimension(Document):
return return
old_doc = self._doc_before_save 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: 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 msg = f"""The user can not change value of the field {bold(field.label)} because
stock transactions exists against the dimension {bold(self.name)}.""" stock transactions exists against the dimension {bold(self.name)}."""
frappe.throw(_(msg), DoNotChangeError) 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): def reset_value(self):
if self.apply_to_all_doctypes: if self.apply_to_all_doctypes:
self.istable = 0 self.istable = 0
@@ -76,30 +100,35 @@ class InventoryDimension(Document):
self.add_custom_fields() self.add_custom_fields()
def add_custom_fields(self): def add_custom_fields(self):
dimension_field = dict( dimension_fields = [
fieldname=self.source_fieldname, dict(
fieldtype="Link", fieldname="inventory_dimension",
insert_after="warehouse", fieldtype="Section Break",
options=self.reference_document, insert_after="warehouse",
label=self.dimension_name, 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 = {} custom_fields = {}
if self.apply_to_all_doctypes: if self.apply_to_all_doctypes:
for doctype in get_inventory_documents(): for doctype in get_inventory_documents():
if not frappe.db.get_value( custom_fields.setdefault(doctype[0], dimension_fields)
"Custom Field", {"dt": doctype[0], "fieldname": self.source_fieldname} else:
): custom_fields.setdefault(self.document_type, dimension_fields)
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)
if not frappe.db.get_value( if not frappe.db.get_value(
"Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname} "Custom Field", {"dt": "Stock Ledger Entry", "fieldname": self.target_fieldname}
): ):
dimension_field = dimension_fields[1]
dimension_field["fieldname"] = self.target_fieldname dimension_field["fieldname"] = self.target_fieldname
custom_fields["Stock Ledger Entry"] = dimension_field custom_fields["Stock Ledger Entry"] = dimension_field
@@ -143,7 +172,7 @@ def get_evaluated_inventory_dimension(doc, sl_dict, parent_doc=None):
elif ( elif (
row.type_of_transaction == "Outward" row.type_of_transaction == "Outward"
if doc.docstatus == 1 if doc.docstatus == 1
else row.type_of_transaction != "Inward" else row.type_of_transaction != "Outward"
) and sl_dict.actual_qty > 0: ) and sl_dict.actual_qty > 0:
continue continue
@@ -166,7 +195,14 @@ def get_document_wise_inventory_dimensions(doctype) -> dict:
if not frappe.local.document_wise_inventory_dimensions.get(doctype): if not frappe.local.document_wise_inventory_dimensions.get(doctype):
dimensions = frappe.get_all( dimensions = frappe.get_all(
"Inventory Dimension", "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}, filters={"disabled": 0},
or_filters={"document_type": doctype, "apply_to_all_doctypes": 1}, or_filters={"document_type": doctype, "apply_to_all_doctypes": 1},
) )
@@ -194,3 +230,9 @@ def get_inventory_dimensions():
frappe.local.inventory_dimensions = dimensions frappe.local.inventory_dimensions = dimensions
return frappe.local.inventory_dimensions return frappe.local.inventory_dimensions
@frappe.whitelist()
def delete_dimension(dimension):
doc = frappe.get_doc("Inventory Dimension", dimension)
doc.delete()

View File

@@ -8,6 +8,7 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import (
CanNotBeChildDoc, CanNotBeChildDoc,
CanNotBeDefaultDimension, CanNotBeDefaultDimension,
DoNotChangeError, DoNotChangeError,
delete_dimension,
) )
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
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -42,6 +43,32 @@ class TestInventoryDimension(FrappeTestCase):
self.assertRaises(CanNotBeDefaultDimension, inv_dim1.insert) 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): def test_inventory_dimension(self):
warehouse = "Shelf Warehouse - _TC" warehouse = "Shelf Warehouse - _TC"
item_code = "_Test Item" item_code = "_Test Item"

View File

@@ -792,10 +792,8 @@
{ {
"fieldname": "expense_account", "fieldname": "expense_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1,
"label": "Expense Account", "label": "Expense Account",
"options": "Account", "options": "Account"
"read_only": 1
}, },
{ {
"fieldname": "accounting_dimensions_section", "fieldname": "accounting_dimensions_section",
@@ -1001,7 +999,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-06-17 05:32:16.483178", "modified": "2022-07-28 19:27:54.880781",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Purchase Receipt Item", "name": "Purchase Receipt Item",

View File

@@ -15,7 +15,7 @@ frappe.ui.form.on('Repost Item Valuation', {
return { return {
filters: { filters: {
name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note', name: ['in', ['Purchase Receipt', 'Purchase Invoice', 'Delivery Note',
'Sales Invoice', 'Stock Entry', 'Stock Reconciliation']] 'Sales Invoice', 'Stock Entry', 'Stock Reconciliation', 'Subcontracting Receipt']]
} }
}; };
}); });

View File

@@ -583,18 +583,23 @@ frappe.ui.form.on('Stock Entry', {
}, },
add_to_transit: function(frm) { add_to_transit: function(frm) {
if(frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer') { if(frm.doc.purpose=='Material Transfer') {
frm.set_value('to_warehouse', ''); 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() { frm.fields_dict.to_warehouse.get_query = function() {
return { return {
filters:{ filters:filters
'warehouse_type' : 'Transit',
'is_group': 0,
'company': frm.doc.company
}
}; };
}; };
frm.trigger('set_transit_warehouse');
} }
}, },

View File

@@ -5,7 +5,7 @@
import frappe import frappe
from frappe.permissions import add_user_permission, remove_user_permission from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings 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.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import ( from erpnext.stock.doctype.item.test_item import (
@@ -1589,6 +1589,31 @@ class TestStockEntry(FrappeTestCase):
self.assertEqual(obj.items[index].basic_rate, 200) self.assertEqual(obj.items[index].basic_rate, 200)
self.assertEqual(obj.items[index].basic_amount, 2000) 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): def make_serialized_item(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -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) => { document_list.forEach((doctype) => {
frappe.ui.form.on(doctype, { frappe.ui.form.on(doctype, {

View File

@@ -3,6 +3,8 @@
frappe.provide('erpnext.buying'); frappe.provide('erpnext.buying');
{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
frappe.ui.form.on('Subcontracting Order', { frappe.ui.form.on('Subcontracting Order', {
setup: (frm) => { setup: (frm) => {
frm.get_field("items").grid.cannot_add_rows = true; frm.get_field("items").grid.cannot_add_rows = true;
@@ -136,6 +138,16 @@ frappe.ui.form.on('Subcontracting Order', {
} }
}); });
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 { erpnext.buying.SubcontractingOrderController = class SubcontractingOrderController {
setup() { setup() {
this.frm.custom_make_buttons = { this.frm.custom_make_buttons = {

View File

@@ -19,6 +19,10 @@
"transaction_date", "transaction_date",
"schedule_date", "schedule_date",
"amended_from", "amended_from",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"address_and_contact_section", "address_and_contact_section",
"supplier_address", "supplier_address",
"address_display", "address_display",
@@ -422,12 +426,34 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Distribute Additional Costs Based On ", "label": "Distribute Additional Costs Based On ",
"options": "Qty\nAmount" "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", "icon": "fa fa-file-text",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-04-11 21:02:44.097841", "modified": "2022-08-15 14:08:49.204218",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Order", "name": "Subcontracting Order",

View File

@@ -82,25 +82,6 @@ class SubcontractingOrder(SubcontractingController):
self.set_missing_values_in_supplied_items() self.set_missing_values_in_supplied_items()
self.set_missing_values_in_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): def set_missing_values_in_service_items(self):
for idx, item in enumerate(self.get("service_items")): for idx, item in enumerate(self.get("service_items")):
self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty self.items[idx].service_cost_per_qty = item.amount / self.items[idx].qty
@@ -114,9 +95,7 @@ class SubcontractingOrder(SubcontractingController):
def set_missing_values_in_items(self): def set_missing_values_in_items(self):
total_qty = total = 0 total_qty = total = 0
for item in self.items: for item in self.items:
item.rate = ( item.rate = item.rm_cost_per_qty + item.service_cost_per_qty + flt(item.additional_cost_per_qty)
item.rm_cost_per_qty + item.service_cost_per_qty + (item.additional_cost_per_qty or 0)
)
item.amount = item.qty * item.rate item.amount = item.qty * item.rate
total_qty += flt(item.qty) total_qty += flt(item.qty)
total += flt(item.amount) total += flt(item.amount)
@@ -187,7 +166,7 @@ class SubcontractingOrder(SubcontractingController):
total_required_qty = total_supplied_qty = 0 total_required_qty = total_supplied_qty = 0
for item in self.supplied_items: for item in self.supplied_items:
total_required_qty += item.required_qty 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: if total_supplied_qty:
status = "Partial Material Transferred" status = "Partial Material Transferred"
if total_supplied_qty >= total_required_qty: if total_supplied_qty >= total_required_qty:

View File

@@ -40,6 +40,10 @@
"manufacture_section", "manufacture_section",
"manufacturer", "manufacturer",
"manufacturer_part_no", "manufacturer_part_no",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_34", "section_break_34",
"page_break" "page_break"
], ],
@@ -304,13 +308,35 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 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, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-04-11 21:28:06.585338", "modified": "2022-08-15 14:25:45.177703",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Order Item", "name": "Subcontracting Order Item",

View File

@@ -3,6 +3,8 @@
frappe.provide('erpnext.buying'); frappe.provide('erpnext.buying');
{% include 'erpnext/stock/landed_taxes_and_charges_common.js' %};
frappe.ui.form.on('Subcontracting Receipt', { frappe.ui.form.on('Subcontracting Receipt', {
setup: (frm) => { setup: (frm) => {
frm.get_field('supplied_items').grid.cannot_add_rows = true; frm.get_field('supplied_items').grid.cannot_add_rows = true;
@@ -48,6 +50,13 @@ frappe.ui.form.on('Subcontracting Receipt', {
is_group: 0 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) => { refresh: (frm) => {
@@ -121,6 +130,16 @@ frappe.ui.form.on('Subcontracting Receipt', {
}, },
}); });
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', { frappe.ui.form.on('Subcontracting Receipt Item', {
item_code(frm) { item_code(frm) {
set_missing_values(frm); set_missing_values(frm);

View File

@@ -15,8 +15,13 @@
"company", "company",
"posting_date", "posting_date",
"posting_time", "posting_time",
"set_posting_time",
"is_return", "is_return",
"return_against", "return_against",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_addresses", "section_addresses",
"supplier_address", "supplier_address",
"contact_person", "contact_person",
@@ -43,12 +48,14 @@
"raw_material_details", "raw_material_details",
"get_current_stock", "get_current_stock",
"supplied_items", "supplied_items",
"additional_costs_section",
"distribute_additional_costs_based_on",
"additional_costs",
"total_additional_costs",
"section_break_46", "section_break_46",
"in_words", "in_words",
"bill_no", "bill_no",
"bill_date", "bill_date",
"accounting_details_section",
"provisional_expense_account",
"more_info", "more_info",
"status", "status",
"column_break_39", "column_break_39",
@@ -132,6 +139,7 @@
"label": "Date", "label": "Date",
"no_copy": 1, "no_copy": 1,
"print_width": "100px", "print_width": "100px",
"read_only_depends_on": "eval: !doc.set_posting_time",
"reqd": 1, "reqd": 1,
"search_index": 1, "search_index": 1,
"width": "100px" "width": "100px"
@@ -144,6 +152,7 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"print_width": "100px", "print_width": "100px",
"read_only_depends_on": "eval: !doc.set_posting_time",
"reqd": 1, "reqd": 1,
"width": "100px" "width": "100px"
}, },
@@ -520,19 +529,6 @@
"options": "Company", "options": "Company",
"read_only": 1 "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", "default": "0",
"fieldname": "is_return", "fieldname": "is_return",
@@ -569,11 +565,69 @@
{ {
"fieldname": "section_break_47", "fieldname": "section_break_47",
"fieldtype": "Section Break" "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
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-04-18 13:15:12.011682", "modified": "2022-08-19 19:50:16.935124",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Receipt", "name": "Subcontracting Receipt",

View File

@@ -3,7 +3,7 @@
import frappe import frappe
from frappe import _ 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 from erpnext.controllers.subcontracting_controller import SubcontractingController
@@ -103,6 +103,7 @@ class SubcontractingReceipt(SubcontractingController):
@frappe.whitelist() @frappe.whitelist()
def set_missing_values(self): def set_missing_values(self):
self.set_missing_values_in_additional_costs()
self.set_missing_values_in_supplied_items() self.set_missing_values_in_supplied_items()
self.set_missing_values_in_items() self.set_missing_values_in_items()
@@ -125,12 +126,12 @@ class SubcontractingReceipt(SubcontractingController):
item.rm_cost_per_qty = item.rm_supp_cost / item.qty item.rm_cost_per_qty = item.rm_supp_cost / item.qty
rm_supp_cost.pop(item.name) rm_supp_cost.pop(item.name)
if self.is_new() and item.rm_supp_cost > 0: if item.recalculate_rate:
item.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 item.amount = item.qty * item.rate
total_qty += item.qty total_qty += item.qty
total_amount += item.amount total_amount += item.amount

View File

@@ -29,6 +29,7 @@
"rate_and_amount", "rate_and_amount",
"rate", "rate",
"amount", "amount",
"recalculate_rate",
"column_break_19", "column_break_19",
"rm_cost_per_qty", "rm_cost_per_qty",
"service_cost_per_qty", "service_cost_per_qty",
@@ -49,15 +50,16 @@
"col_break5", "col_break5",
"batch_no", "batch_no",
"rejected_serial_no", "rejected_serial_no",
"expense_account",
"manufacture_details", "manufacture_details",
"manufacturer", "manufacturer",
"column_break_16", "column_break_16",
"manufacturer_part_no", "manufacturer_part_no",
"accounting_details_section",
"expense_account",
"accounting_dimensions_section", "accounting_dimensions_section",
"project",
"dimension_col_break",
"cost_center", "cost_center",
"dimension_col_break",
"project",
"section_break_80", "section_break_80",
"page_break" "page_break"
], ],
@@ -192,6 +194,8 @@
"label": "Rate", "label": "Rate",
"options": "currency", "options": "currency",
"print_width": "100px", "print_width": "100px",
"read_only": 1,
"read_only_depends_on": "eval: doc.recalculate_rate",
"width": "100px" "width": "100px"
}, },
{ {
@@ -363,10 +367,8 @@
{ {
"fieldname": "expense_account", "fieldname": "expense_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1,
"label": "Expense Account", "label": "Expense Account",
"options": "Account", "options": "Account"
"read_only": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
@@ -456,12 +458,23 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 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, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-04-21 12:07:55.899701", "modified": "2022-08-20 17:16:48.269164",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Subcontracting", "module": "Subcontracting",
"name": "Subcontracting Receipt Item", "name": "Subcontracting Receipt Item",