mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-26 18:18:30 +00:00
Merge pull request #38589 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -192,7 +192,7 @@ class ExchangeRateRevaluation(Document):
|
||||
# round off balance based on currency precision
|
||||
# and consider debit-credit difference allowance
|
||||
currency_precision = get_currency_precision()
|
||||
rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
|
||||
rounding_loss_allowance = float(rounding_loss_allowance)
|
||||
for acc in account_details:
|
||||
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
|
||||
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
|
||||
|
||||
@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
|
||||
frappe.ui.form.on("Journal Entry", {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("bank_account", "account", "account");
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger', 'Asset', 'Asset Movement', 'Repost Accounting Ledger'];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
@@ -98,6 +98,8 @@ class JournalEntry(AccountsController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
)
|
||||
self.make_gl_entries(1)
|
||||
self.update_advance_paid()
|
||||
|
||||
@@ -9,6 +9,8 @@ import frappe
|
||||
from frappe import ValidationError, _, qb, scrub, throw
|
||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||
from frappe.utils.data import comma_and, fmt_money
|
||||
from pypika import Case
|
||||
from pypika.functions import Coalesce, Sum
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.bank_account.bank_account import (
|
||||
@@ -1566,12 +1568,13 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list
|
||||
if not split_rows:
|
||||
continue
|
||||
|
||||
frappe.msgprint(
|
||||
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
if len(split_rows) > 1:
|
||||
frappe.msgprint(
|
||||
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
outstanding_invoices_after_split += split_rows
|
||||
continue
|
||||
|
||||
@@ -1853,18 +1856,24 @@ def get_company_defaults(company):
|
||||
|
||||
|
||||
def get_outstanding_on_journal_entry(name):
|
||||
res = frappe.db.sql(
|
||||
"SELECT "
|
||||
'CASE WHEN party_type IN ("Customer") '
|
||||
"THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) "
|
||||
"ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) "
|
||||
"END as outstanding_amount "
|
||||
"FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) "
|
||||
"AND party_type IS NOT NULL "
|
||||
'AND party_type != ""',
|
||||
(name, name),
|
||||
as_dict=1,
|
||||
)
|
||||
gl = frappe.qb.DocType("GL Entry")
|
||||
res = (
|
||||
frappe.qb.from_(gl)
|
||||
.select(
|
||||
Case()
|
||||
.when(
|
||||
gl.party_type == "Customer",
|
||||
Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
|
||||
)
|
||||
.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
|
||||
.as_("outstanding_amount")
|
||||
)
|
||||
.where(
|
||||
(Coalesce(gl.party_type, "") != "")
|
||||
& (gl.is_cancelled == 0)
|
||||
& ((gl.voucher_no == name) | (gl.against_voucher == name))
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
|
||||
|
||||
|
||||
@@ -203,9 +203,10 @@
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "icon-resize-horizontal",
|
||||
"is_virtual": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-15 05:35:50.109290",
|
||||
"modified": "2023-11-17 17:33:55.701726",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation",
|
||||
@@ -230,6 +231,5 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
"states": []
|
||||
}
|
||||
|
||||
@@ -29,6 +29,58 @@ class PaymentReconciliation(Document):
|
||||
self.accounting_dimension_filter_conditions = []
|
||||
self.ple_posting_date_filter = []
|
||||
|
||||
def load_from_db(self):
|
||||
# 'modified' attribute is required for `run_doc_method` to work properly.
|
||||
doc_dict = frappe._dict(
|
||||
{
|
||||
"modified": None,
|
||||
"company": None,
|
||||
"party": None,
|
||||
"party_type": None,
|
||||
"receivable_payable_account": None,
|
||||
"default_advance_account": None,
|
||||
"from_invoice_date": None,
|
||||
"to_invoice_date": None,
|
||||
"invoice_limit": 50,
|
||||
"from_payment_date": None,
|
||||
"to_payment_date": None,
|
||||
"payment_limit": 50,
|
||||
"minimum_invoice_amount": None,
|
||||
"minimum_payment_amount": None,
|
||||
"maximum_invoice_amount": None,
|
||||
"maximum_payment_amount": None,
|
||||
"bank_cash_account": None,
|
||||
"cost_center": None,
|
||||
"payment_name": None,
|
||||
"invoice_name": None,
|
||||
}
|
||||
)
|
||||
super(Document, self).__init__(doc_dict)
|
||||
|
||||
def save(self):
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_count(args):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_stats(args):
|
||||
pass
|
||||
|
||||
def db_insert(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def db_update(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unreconciled_entries(self):
|
||||
self.get_nonreconciled_payment_entries()
|
||||
|
||||
@@ -159,9 +159,10 @@
|
||||
"label": "Difference Posting Date"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-23 10:44:56.066303",
|
||||
"modified": "2023-11-17 17:33:38.612615",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -71,9 +71,10 @@
|
||||
"label": "Exchange Rate"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-08 18:18:02.502149",
|
||||
"modified": "2023-11-17 17:33:45.455166",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Invoice",
|
||||
|
||||
@@ -107,9 +107,10 @@
|
||||
"options": "Cost Center"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-03 07:43:29.965353",
|
||||
"modified": "2023-11-17 17:33:34.818530",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Payment",
|
||||
|
||||
@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
super.onload();
|
||||
|
||||
// Ignore linked advances
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
|
||||
if(!this.frm.doc.__islocal) {
|
||||
// show credit_to in print format
|
||||
|
||||
@@ -1290,6 +1290,8 @@ class PurchaseInvoice(BuyingController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
"Tax Withheld Vouchers",
|
||||
)
|
||||
@@ -1703,6 +1705,4 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
target_doc,
|
||||
)
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
"oldfieldname": "import_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_receipt && doc.pr_detail)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -893,7 +894,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:33:48.547297",
|
||||
"modified": "2023-11-30 16:26:05.629780",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -1934,7 +1934,6 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ def set_address_details(
|
||||
company_address=None,
|
||||
shipping_address=None,
|
||||
*,
|
||||
ignore_permissions=False
|
||||
ignore_permissions=False,
|
||||
):
|
||||
billing_address_field = (
|
||||
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
||||
@@ -239,7 +239,7 @@ def set_address_details(
|
||||
shipping_address_display=render_address(
|
||||
shipping_address, check_permissions=not ignore_permissions
|
||||
),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address),
|
||||
)
|
||||
|
||||
if party_details.company_address:
|
||||
@@ -250,7 +250,7 @@ def set_address_details(
|
||||
party_details.company_address_display
|
||||
or render_address(party_details.company_address, check_permissions=False)
|
||||
),
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address),
|
||||
)
|
||||
|
||||
# shipping address - if not already set
|
||||
@@ -258,7 +258,7 @@ def set_address_details(
|
||||
party_details.update(
|
||||
shipping_address=party_details.billing_address,
|
||||
shipping_address_display=party_details.billing_address_display,
|
||||
**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
|
||||
**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
|
||||
)
|
||||
|
||||
party_address, shipping_address = (
|
||||
@@ -956,6 +956,9 @@ def get_partywise_advanced_payment_amount(
|
||||
if party:
|
||||
query = query.where(ple.party == party)
|
||||
|
||||
if invoice_doctypes := frappe.get_hooks("invoice_doctypes"):
|
||||
query = query.where(ple.voucher_type.notin(invoice_doctypes))
|
||||
|
||||
data = query.run()
|
||||
if data:
|
||||
return frappe._dict(data)
|
||||
|
||||
@@ -1085,7 +1085,7 @@ class ReceivablePayableReport(object):
|
||||
)
|
||||
|
||||
if self.filters.show_remarks:
|
||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
|
||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200)
|
||||
|
||||
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
|
||||
if not fieldname:
|
||||
|
||||
@@ -282,7 +282,8 @@ def get_conditions(filters):
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if not dimension.disabled:
|
||||
# Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section
|
||||
if not dimension.disabled and dimension.document_type != "Finance Book":
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
|
||||
@@ -1809,6 +1809,8 @@ class QueryPaymentLedger(object):
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(filter_on_against_voucher_no))
|
||||
.where(Criterion.all(self.common_filter))
|
||||
.where(Criterion.all(self.dimensions_filter))
|
||||
.where(Criterion.all(self.voucher_posting_date))
|
||||
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
|
||||
.orderby(ple.posting_date, ple.voucher_no)
|
||||
.having(qb.Field("amount_in_account_currency") > 0)
|
||||
|
||||
@@ -322,16 +322,16 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
make_schedules_editable: function(frm) {
|
||||
if (frm.doc.finance_books) {
|
||||
if (frm.doc.finance_books.length) {
|
||||
var is_manual_hence_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
||||
? true : false;
|
||||
var is_shift_hence_editable = frm.doc.finance_books.filter(d => d.shift_based).length > 0
|
||||
? true : false;
|
||||
|
||||
frm.toggle_enable("depreciation_schedule", is_manual_hence_editable || is_shift_hence_editable);
|
||||
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", is_manual_hence_editable);
|
||||
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", is_manual_hence_editable);
|
||||
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", is_shift_hence_editable);
|
||||
frm.toggle_enable("schedules", is_manual_hence_editable || is_shift_hence_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_manual_hence_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_manual_hence_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("shift", is_shift_hence_editable);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
|
||||
class TestAssetValueAdjustment(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_asset_data()
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC"
|
||||
)
|
||||
|
||||
def test_current_asset_value(self):
|
||||
pr = make_purchase_receipt(
|
||||
|
||||
@@ -86,6 +86,10 @@ class PurchaseOrder(BuyingController):
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
mri_compare_fields = [["project", "="], ["item_code", "="]]
|
||||
if self.is_subcontracted:
|
||||
mri_compare_fields = [["project", "="]]
|
||||
|
||||
super(PurchaseOrder, self).validate_with_previous_doc(
|
||||
{
|
||||
"Supplier Quotation": {
|
||||
@@ -108,7 +112,7 @@ class PurchaseOrder(BuyingController):
|
||||
},
|
||||
"Material Request Item": {
|
||||
"ref_dn_field": "material_request_item",
|
||||
"compare_fields": [["project", "="], ["item_code", "="]],
|
||||
"compare_fields": mri_compare_fields,
|
||||
"is_child_table": True,
|
||||
},
|
||||
}
|
||||
@@ -282,23 +286,6 @@ class PurchaseOrder(BuyingController):
|
||||
check_list.append(d.material_request)
|
||||
check_on_hold_or_closed_status("Material Request", d.material_request)
|
||||
|
||||
def update_requested_qty(self):
|
||||
material_request_map = {}
|
||||
for d in self.get("items"):
|
||||
if d.material_request_item:
|
||||
material_request_map.setdefault(d.material_request, []).append(d.material_request_item)
|
||||
|
||||
for mr, mr_item_rows in material_request_map.items():
|
||||
if mr and mr_item_rows:
|
||||
mr_obj = frappe.get_doc("Material Request", mr)
|
||||
|
||||
if mr_obj.status in ["Stopped", "Cancelled"]:
|
||||
frappe.throw(
|
||||
_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError
|
||||
)
|
||||
|
||||
mr_obj.update_requested_qty(mr_item_rows)
|
||||
|
||||
def update_ordered_qty(self, po_item_rows=None):
|
||||
"""update requested qty (before ordered_qty is updated)"""
|
||||
item_wh_list = []
|
||||
@@ -340,7 +327,9 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_status_updater()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
self.update_requested_qty()
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
self.update_requested_qty()
|
||||
|
||||
self.update_ordered_qty()
|
||||
self.validate_budget()
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
@@ -372,7 +361,9 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
# Must be called after updating ordered qty in Material Request
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
self.update_requested_qty()
|
||||
if not self.is_subcontracted or self.is_old_subcontracting_flow:
|
||||
self.update_requested_qty()
|
||||
|
||||
self.update_ordered_qty()
|
||||
|
||||
self.update_blanket_order()
|
||||
@@ -450,6 +441,20 @@ class PurchaseOrder(BuyingController):
|
||||
else:
|
||||
self.db_set("per_received", 0, update_modified=False)
|
||||
|
||||
def update_ordered_qty_in_so_for_removed_items(self, removed_items):
|
||||
"""
|
||||
Updates ordered_qty in linked SO when item rows are removed using Update Items
|
||||
"""
|
||||
if not self.is_against_so():
|
||||
return
|
||||
for item in removed_items:
|
||||
prev_ordered_qty = frappe.get_cached_value(
|
||||
"Sales Order Item", item.get("sales_order_item"), "ordered_qty"
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
|
||||
)
|
||||
|
||||
|
||||
def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
|
||||
"""get last purchase rate for an item"""
|
||||
@@ -536,8 +541,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
@@ -617,7 +620,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
postprocess,
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -679,7 +681,10 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
|
||||
},
|
||||
"Purchase Order Item": {
|
||||
"doctype": "Subcontracting Order Service Item",
|
||||
"field_map": {},
|
||||
"field_map": {
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item",
|
||||
},
|
||||
"field_no_map": [],
|
||||
},
|
||||
},
|
||||
@@ -705,8 +710,8 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_subcontracting_order_created(po_name) -> bool:
|
||||
count = frappe.db.count(
|
||||
"Subcontracting Order", {"purchase_order": po_name, "status": ["not in", ["Draft", "Cancelled"]]}
|
||||
return (
|
||||
True
|
||||
if frappe.db.exists("Subcontracting Order", {"purchase_order": po_name, "docstatus": ["=", 1]})
|
||||
else False
|
||||
)
|
||||
|
||||
return True if count else False
|
||||
|
||||
@@ -168,7 +168,6 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -2874,6 +2874,9 @@ def validate_and_delete_children(parent, data) -> bool:
|
||||
d.cancel()
|
||||
d.delete()
|
||||
|
||||
if parent.doctype == "Purchase Order":
|
||||
parent.update_ordered_qty_in_so_for_removed_items(deleted_children)
|
||||
|
||||
# need to update ordered qty in Material Request first
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
parent.update_prevdoc_status()
|
||||
@@ -3129,7 +3132,10 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
|
||||
if parent_doctype == "Purchase Order":
|
||||
update_last_purchase_rate(parent, is_submit=1)
|
||||
parent.update_prevdoc_status()
|
||||
|
||||
if any_qty_changed or items_added_or_removed or any_conversion_factor_changed:
|
||||
parent.update_prevdoc_status()
|
||||
|
||||
parent.update_requested_qty()
|
||||
parent.update_ordered_qty()
|
||||
parent.update_ordered_and_reserved_qty()
|
||||
|
||||
@@ -530,8 +530,6 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None):
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -789,6 +789,23 @@ class SubcontractingController(StockController):
|
||||
|
||||
return self._sub_contracted_items
|
||||
|
||||
def update_requested_qty(self):
|
||||
material_request_map = {}
|
||||
for d in self.get("items"):
|
||||
if d.material_request_item:
|
||||
material_request_map.setdefault(d.material_request, []).append(d.material_request_item)
|
||||
|
||||
for mr, mr_item_rows in material_request_map.items():
|
||||
if mr and mr_item_rows:
|
||||
mr_obj = frappe.get_doc("Material Request", mr)
|
||||
|
||||
if mr_obj.status in ["Stopped", "Cancelled"]:
|
||||
frappe.throw(
|
||||
_("Material Request {0} is cancelled or stopped").format(mr), frappe.InvalidStatusError
|
||||
)
|
||||
|
||||
mr_obj.update_requested_qty(mr_item_rows)
|
||||
|
||||
|
||||
def get_item_details(items):
|
||||
item = frappe.qb.DocType("Item")
|
||||
|
||||
@@ -938,6 +938,7 @@ def make_subcontracted_items():
|
||||
"Subcontracted Item SA5": {},
|
||||
"Subcontracted Item SA6": {},
|
||||
"Subcontracted Item SA7": {},
|
||||
"Subcontracted Item SA8": {},
|
||||
}
|
||||
|
||||
for item, properties in sub_contracted_items.items():
|
||||
@@ -957,6 +958,7 @@ def make_raw_materials():
|
||||
},
|
||||
"Subcontracted SRM Item 4": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
|
||||
"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
|
||||
"Subcontracted SRM Item 8": {},
|
||||
}
|
||||
|
||||
for item, properties in raw_materials.items():
|
||||
@@ -980,6 +982,7 @@ def make_service_items():
|
||||
"Subcontracted Service Item 5": {},
|
||||
"Subcontracted Service Item 6": {},
|
||||
"Subcontracted Service Item 7": {},
|
||||
"Subcontracted Service Item 8": {},
|
||||
}
|
||||
|
||||
for item, properties in service_items.items():
|
||||
@@ -1003,6 +1006,7 @@ def make_bom_for_subcontracted_items():
|
||||
"Subcontracted Item SA5": ["Subcontracted SRM Item 5"],
|
||||
"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
|
||||
"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
|
||||
"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
|
||||
}
|
||||
|
||||
for item_code, raw_materials in boms.items():
|
||||
|
||||
@@ -40,7 +40,7 @@ class Lead(SellingController, CRMNote):
|
||||
if self.source == "Existing Customer" and self.customer:
|
||||
contact = frappe.db.get_value(
|
||||
"Dynamic Link",
|
||||
{"link_doctype": "Customer", "link_name": self.customer},
|
||||
{"link_doctype": "Customer", "parenttype": "Contact", "link_name": self.customer},
|
||||
"parent",
|
||||
)
|
||||
if contact:
|
||||
|
||||
@@ -1306,7 +1306,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
order_by = "idx desc, name, item_name"
|
||||
|
||||
fields = ["name", "item_group", "item_name", "description"]
|
||||
fields = ["name", "item_name", "item_group", "description"]
|
||||
fields.extend(
|
||||
[field for field in searchfields if not field in ["name", "item_group", "description"]]
|
||||
)
|
||||
|
||||
@@ -213,29 +213,27 @@ class JobCard(Document):
|
||||
production_capacity = 1
|
||||
query = query.where(jctl.employee == args.get("employee"))
|
||||
|
||||
existing = query.run(as_dict=True)
|
||||
existing_time_logs = query.run(as_dict=True)
|
||||
|
||||
overlap_count = self.get_overlap_count(existing)
|
||||
if existing and production_capacity > overlap_count:
|
||||
return
|
||||
if not self.has_overlap(production_capacity, existing_time_logs):
|
||||
return {}
|
||||
|
||||
if self.workstation_type:
|
||||
if workstation := self.get_workstation_based_on_available_slot(existing):
|
||||
if workstation := self.get_workstation_based_on_available_slot(existing_time_logs):
|
||||
self.workstation = workstation
|
||||
return None
|
||||
|
||||
return existing[0] if existing else None
|
||||
return existing_time_logs[0] if existing_time_logs else None
|
||||
|
||||
@staticmethod
|
||||
def get_overlap_count(time_logs):
|
||||
count = 1
|
||||
def has_overlap(self, production_capacity, time_logs):
|
||||
overlap = False
|
||||
if production_capacity == 1 and len(time_logs) > 0:
|
||||
return True
|
||||
|
||||
# Check overlap exists or not between the overlapping time logs with the current Job Card
|
||||
for idx, row in enumerate(time_logs):
|
||||
next_idx = idx
|
||||
if idx + 1 < len(time_logs):
|
||||
next_idx = idx + 1
|
||||
next_row = time_logs[next_idx]
|
||||
for row in time_logs:
|
||||
count = 1
|
||||
for next_row in time_logs:
|
||||
if row.name == next_row.name:
|
||||
continue
|
||||
|
||||
@@ -255,7 +253,10 @@ class JobCard(Document):
|
||||
):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
if count > production_capacity:
|
||||
return True
|
||||
|
||||
return overlap
|
||||
|
||||
def get_workstation_based_on_available_slot(self, existing) -> Optional[str]:
|
||||
workstations = get_workstations(self.workstation_type)
|
||||
|
||||
@@ -1521,19 +1521,23 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
|
||||
)
|
||||
|
||||
locations = get_available_item_locations(
|
||||
item.get("item_code"), warehouses, item.get("quantity"), company, ignore_validation=True
|
||||
item.get("item_code"),
|
||||
warehouses,
|
||||
item.get("quantity") * item.get("conversion_factor"),
|
||||
company,
|
||||
ignore_validation=True,
|
||||
)
|
||||
|
||||
required_qty = item.get("quantity")
|
||||
if item.get("conversion_factor") and item.get("purchase_uom") != item.get("stock_uom"):
|
||||
# Convert qty to stock UOM
|
||||
required_qty = required_qty * item.get("conversion_factor")
|
||||
|
||||
# get available material by transferring to production warehouse
|
||||
for d in locations:
|
||||
if required_qty <= 0:
|
||||
return
|
||||
|
||||
conversion_factor = 1.0
|
||||
if purchase_uom != stock_uom and purchase_uom == item["uom"]:
|
||||
conversion_factor = get_uom_conversion_factor(item["item_code"], item["uom"])
|
||||
|
||||
new_dict = copy.deepcopy(item)
|
||||
quantity = required_qty if d.get("qty") > required_qty else d.get("qty")
|
||||
|
||||
@@ -1543,10 +1547,11 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
|
||||
"material_request_type": "Material Transfer",
|
||||
"uom": new_dict.get("stock_uom"), # internal transfer should be in stock UOM
|
||||
"from_warehouse": d.get("warehouse"),
|
||||
"conversion_factor": 1.0,
|
||||
}
|
||||
)
|
||||
|
||||
required_qty -= quantity / conversion_factor
|
||||
required_qty -= quantity
|
||||
new_mr_items.append(new_dict)
|
||||
|
||||
# raise purchase request for remaining qty
|
||||
@@ -1558,7 +1563,7 @@ def get_materials_from_other_locations(item, warehouses, new_mr_items, company):
|
||||
if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
|
||||
required_qty = ceil(required_qty)
|
||||
|
||||
item["quantity"] = required_qty
|
||||
item["quantity"] = required_qty / item.get("conversion_factor")
|
||||
|
||||
new_mr_items.append(item)
|
||||
|
||||
|
||||
@@ -1272,12 +1272,14 @@ class TestProductionPlan(FrappeTestCase):
|
||||
for row in items:
|
||||
row = frappe._dict(row)
|
||||
if row.material_request_type == "Material Transfer":
|
||||
self.assertTrue(row.uom == row.stock_uom)
|
||||
self.assertTrue(row.from_warehouse in [wh1, wh2])
|
||||
self.assertEqual(row.quantity, 2)
|
||||
|
||||
if row.material_request_type == "Purchase":
|
||||
self.assertTrue(row.uom != row.stock_uom)
|
||||
self.assertTrue(row.warehouse == mrp_warhouse)
|
||||
self.assertEqual(row.quantity, 12)
|
||||
self.assertEqual(row.quantity, 12.0)
|
||||
|
||||
def test_mr_qty_for_same_rm_with_different_sub_assemblies(self):
|
||||
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||
@@ -1393,6 +1395,58 @@ class TestProductionPlan(FrappeTestCase):
|
||||
|
||||
self.assertEqual(after_qty, before_qty)
|
||||
|
||||
def test_material_request_qty_purchase_and_material_transfer(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
|
||||
bom_item = make_item(
|
||||
properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1", "purchase_uom": "Nos"}
|
||||
).name
|
||||
|
||||
store_warehouse = create_warehouse("Store Warehouse", company="_Test Company")
|
||||
rm_warehouse = create_warehouse("RM Warehouse", company="_Test Company")
|
||||
|
||||
make_stock_entry(
|
||||
item_code=bom_item,
|
||||
qty=60,
|
||||
target=store_warehouse,
|
||||
rate=99,
|
||||
)
|
||||
|
||||
if not frappe.db.exists("UOM Conversion Detail", {"parent": bom_item, "uom": "Nos"}):
|
||||
doc = frappe.get_doc("Item", bom_item)
|
||||
doc.append("uoms", {"uom": "Nos", "conversion_factor": 10})
|
||||
doc.save()
|
||||
|
||||
make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
|
||||
|
||||
pln = create_production_plan(
|
||||
item_code=fg_item, planned_qty=10, stock_uom="_Test UOM 1", do_not_submit=1
|
||||
)
|
||||
|
||||
pln.for_warehouse = rm_warehouse
|
||||
items = get_items_for_material_requests(
|
||||
pln.as_dict(), warehouses=[{"warehouse": store_warehouse}]
|
||||
)
|
||||
|
||||
for row in items:
|
||||
self.assertEqual(row.get("quantity"), 10.0)
|
||||
self.assertEqual(row.get("material_request_type"), "Material Transfer")
|
||||
self.assertEqual(row.get("uom"), "_Test UOM 1")
|
||||
self.assertEqual(row.get("from_warehouse"), store_warehouse)
|
||||
self.assertEqual(row.get("conversion_factor"), 1.0)
|
||||
|
||||
items = get_items_for_material_requests(
|
||||
pln.as_dict(), warehouses=[{"warehouse": pln.for_warehouse}]
|
||||
)
|
||||
|
||||
for row in items:
|
||||
self.assertEqual(row.get("quantity"), 1.0)
|
||||
self.assertEqual(row.get("material_request_type"), "Purchase")
|
||||
self.assertEqual(row.get("uom"), "Nos")
|
||||
self.assertEqual(row.get("conversion_factor"), 10.0)
|
||||
|
||||
|
||||
def create_production_plan(**args):
|
||||
"""
|
||||
|
||||
@@ -270,6 +270,7 @@ erpnext.patches.v13_0.reset_corrupt_defaults
|
||||
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
|
||||
erpnext.patches.v14_0.update_reference_due_date_in_journal_entry
|
||||
erpnext.patches.v14_0.france_depreciation_warning
|
||||
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
||||
|
||||
[post_model_sync]
|
||||
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
|
||||
@@ -344,13 +345,12 @@ erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_sales_order_item
|
||||
erpnext.patches.v14_0.rename_over_order_allowance_field
|
||||
erpnext.patches.v14_0.migrate_delivery_stop_lock_field
|
||||
execute:frappe.db.set_single_value("Payment Reconciliation", "invoice_limit", 50)
|
||||
execute:frappe.db.set_single_value("Payment Reconciliation", "payment_limit", 50)
|
||||
erpnext.patches.v14_0.rename_daily_depreciation_to_depreciation_amount_based_on_num_days_in_month
|
||||
erpnext.patches.v14_0.rename_depreciation_amount_based_on_num_days_in_month_to_daily_prorata_based
|
||||
erpnext.patches.v14_0.add_default_for_repost_settings
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
|
||||
erpnext.patches.v14_0.update_zero_asset_quantity_field
|
||||
execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
|
||||
erpnext.patches.v14_0.clear_reconciliation_values_from_singles
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from frappe import qb
|
||||
|
||||
|
||||
def execute():
|
||||
"""
|
||||
Clear `tabSingles` and Payment Reconciliation tables of values
|
||||
"""
|
||||
singles = qb.DocType("Singles")
|
||||
qb.from_(singles).delete().where(singles.doctype == "Payment Reconciliation").run()
|
||||
doctypes = [
|
||||
"Payment Reconciliation Invoice",
|
||||
"Payment Reconciliation Payment",
|
||||
"Payment Reconciliation Allocation",
|
||||
]
|
||||
for x in doctypes:
|
||||
dt = qb.DocType(x)
|
||||
qb.from_(dt).delete().run()
|
||||
@@ -13,7 +13,7 @@ frappe.ui.form.on("Communication", {
|
||||
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
|
||||
frm.trigger('make_issue_from_communication');
|
||||
})
|
||||
}, "Create");
|
||||
}, __("Create"));
|
||||
}
|
||||
|
||||
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
|
||||
|
||||
@@ -328,7 +328,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
onload_post_render() {
|
||||
if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
|
||||
&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
|
||||
&& !this.frm.doc.__onload?.load_after_mapping) {
|
||||
frappe.after_ajax(() => this.apply_default_taxes());
|
||||
} else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
|
||||
&& !this.frm.doc.is_pos) {
|
||||
@@ -918,9 +918,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
let me = this;
|
||||
this.set_dynamic_labels();
|
||||
let company_currency = this.get_company_currency();
|
||||
// Added `ignore_price_list` to determine if document is loading after mapping from another doc
|
||||
// Added `load_after_mapping` to determine if document is loading after mapping from another doc
|
||||
if(this.frm.doc.currency && this.frm.doc.currency !== company_currency
|
||||
&& !(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) {
|
||||
&& !this.frm.doc.__onload?.load_after_mapping) {
|
||||
|
||||
this.get_exchange_rate(transaction_date, this.frm.doc.currency, company_currency,
|
||||
function(exchange_rate) {
|
||||
@@ -952,7 +952,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if(flt(this.frm.doc.conversion_rate)>0.0) {
|
||||
if(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
|
||||
if(this.frm.doc.__onload?.load_after_mapping) {
|
||||
this.calculate_taxes_and_totals();
|
||||
} else if (!this.in_apply_price_list){
|
||||
this.apply_price_list();
|
||||
@@ -1039,9 +1039,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.set_dynamic_labels();
|
||||
|
||||
var company_currency = this.get_company_currency();
|
||||
// Added `ignore_price_list` to determine if document is loading after mapping from another doc
|
||||
// Added `load_after_mapping` to determine if document is loading after mapping from another doc
|
||||
if(this.frm.doc.price_list_currency !== company_currency &&
|
||||
!(this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list)) {
|
||||
!this.frm.doc.__onload?.load_after_mapping) {
|
||||
this.get_exchange_rate(this.frm.doc.posting_date, this.frm.doc.price_list_currency, company_currency,
|
||||
function(exchange_rate) {
|
||||
me.frm.set_value("plc_conversion_rate", exchange_rate);
|
||||
@@ -1420,7 +1420,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
// Target doc created from a mapped doc
|
||||
if (this.frm.doc.__onload && this.frm.doc.__onload.ignore_price_list) {
|
||||
if (this.frm.doc.__onload?.load_after_mapping) {
|
||||
// Calculate totals even though pricing rule is not applied.
|
||||
// `apply_pricing_rule` is triggered due to change in data which most likely contributes to Total.
|
||||
if (calculate_taxes_and_totals) me.calculate_taxes_and_totals();
|
||||
|
||||
431
erpnext/public/js/utils/sales_common.js
Normal file
431
erpnext/public/js/utils/sales_common.js
Normal file
@@ -0,0 +1,431 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.provide("erpnext.selling");
|
||||
|
||||
erpnext.sales_common = {
|
||||
setup_selling_controller:function() {
|
||||
erpnext.selling.SellingController = class SellingController extends erpnext.TransactionController {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.toggle_enable_for_stock_uom("allow_to_edit_stock_uom_qty_for_sales");
|
||||
this.frm.email_field = "contact_email";
|
||||
}
|
||||
|
||||
onload() {
|
||||
super.onload();
|
||||
this.setup_queries();
|
||||
this.frm.set_query('shipping_rule', function() {
|
||||
return {
|
||||
filters: {
|
||||
"shipping_rule_type": "Selling"
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
setup_queries() {
|
||||
var me = this;
|
||||
|
||||
$.each([["customer", "customer"],
|
||||
["lead", "lead"]],
|
||||
function(i, opts) {
|
||||
if(me.frm.fields_dict[opts[0]])
|
||||
me.frm.set_query(opts[0], erpnext.queries[opts[1]]);
|
||||
});
|
||||
|
||||
me.frm.set_query('contact_person', erpnext.queries.contact_query);
|
||||
me.frm.set_query('customer_address', erpnext.queries.address_query);
|
||||
me.frm.set_query('shipping_address_name', erpnext.queries.address_query);
|
||||
me.frm.set_query('dispatch_address_name', erpnext.queries.dispatch_address_query);
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
|
||||
|
||||
if(this.frm.fields_dict.selling_price_list) {
|
||||
this.frm.set_query("selling_price_list", function() {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict.tc_name) {
|
||||
this.frm.set_query("tc_name", function() {
|
||||
return { filters: { selling: 1 } };
|
||||
});
|
||||
}
|
||||
|
||||
if(!this.frm.fields_dict["items"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
|
||||
this.frm.set_query("item_code", "items", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: {'is_sales_item': 1, 'customer': me.frm.doc.customer, 'has_variants': 0}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["packed_items"] &&
|
||||
this.frm.fields_dict["packed_items"].grid.get_field('batch_no')) {
|
||||
this.frm.set_query("batch_no", "packed_items", function(doc, cdt, cdn) {
|
||||
return me.set_query_for_batch(doc, cdt, cdn)
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
|
||||
this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
|
||||
return me.set_query_for_item_tax_template(doc, cdt, cdn)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
refresh() {
|
||||
super.refresh();
|
||||
|
||||
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
|
||||
|
||||
this.frm.toggle_display("customer_name",
|
||||
(this.frm.doc.customer_name && this.frm.doc.customer_name!==this.frm.doc.customer));
|
||||
|
||||
this.toggle_editable_price_list_rate();
|
||||
}
|
||||
|
||||
customer() {
|
||||
var me = this;
|
||||
erpnext.utils.get_party_details(this.frm, null, null, function() {
|
||||
me.apply_price_list();
|
||||
});
|
||||
}
|
||||
|
||||
customer_address() {
|
||||
erpnext.utils.get_address_display(this.frm, "customer_address");
|
||||
erpnext.utils.set_taxes_from_address(this.frm, "customer_address", "customer_address", "shipping_address_name");
|
||||
}
|
||||
|
||||
shipping_address_name() {
|
||||
erpnext.utils.get_address_display(this.frm, "shipping_address_name", "shipping_address");
|
||||
erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name");
|
||||
}
|
||||
|
||||
dispatch_address_name() {
|
||||
erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address");
|
||||
}
|
||||
|
||||
sales_partner() {
|
||||
this.apply_pricing_rule();
|
||||
}
|
||||
|
||||
campaign() {
|
||||
this.apply_pricing_rule();
|
||||
}
|
||||
|
||||
selling_price_list() {
|
||||
this.apply_price_list();
|
||||
this.set_dynamic_labels();
|
||||
}
|
||||
|
||||
discount_percentage(doc, cdt, cdn) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
item.discount_amount = 0.0;
|
||||
this.apply_discount_on_item(doc, cdt, cdn, 'discount_percentage');
|
||||
}
|
||||
|
||||
discount_amount(doc, cdt, cdn) {
|
||||
|
||||
if(doc.name === cdn) {
|
||||
return;
|
||||
}
|
||||
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
item.discount_percentage = 0.0;
|
||||
this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount');
|
||||
}
|
||||
|
||||
commission_rate() {
|
||||
this.calculate_commission();
|
||||
}
|
||||
|
||||
total_commission() {
|
||||
frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]);
|
||||
|
||||
const { amount_eligible_for_commission } = this.frm.doc;
|
||||
if(!amount_eligible_for_commission) return;
|
||||
|
||||
this.frm.set_value(
|
||||
"commission_rate", flt(
|
||||
this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
allocated_percentage(doc, cdt, cdn) {
|
||||
var sales_person = frappe.get_doc(cdt, cdn);
|
||||
if(sales_person.allocated_percentage) {
|
||||
|
||||
sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
|
||||
precision("allocated_percentage", sales_person));
|
||||
|
||||
sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission *
|
||||
sales_person.allocated_percentage / 100.0,
|
||||
precision("allocated_amount", sales_person));
|
||||
refresh_field(["allocated_amount"], sales_person);
|
||||
|
||||
this.calculate_incentive(sales_person);
|
||||
refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name,
|
||||
sales_person.parentfield);
|
||||
}
|
||||
}
|
||||
|
||||
sales_person(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.calculate_incentive(row);
|
||||
refresh_field("incentives",row.name,row.parentfield);
|
||||
}
|
||||
|
||||
toggle_editable_price_list_rate() {
|
||||
var df = frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "price_list_rate", this.frm.doc.name);
|
||||
var editable_price_list_rate = cint(frappe.defaults.get_default("editable_price_list_rate"));
|
||||
|
||||
if(df && editable_price_list_rate) {
|
||||
const parent_field = frappe.meta.get_parentfield(this.frm.doc.doctype, this.frm.doc.doctype + " Item");
|
||||
if (!this.frm.fields_dict[parent_field]) return;
|
||||
|
||||
this.frm.fields_dict[parent_field].grid.update_docfield_property(
|
||||
'price_list_rate', 'read_only', 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
calculate_commission() {
|
||||
if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return;
|
||||
|
||||
if(this.frm.doc.commission_rate > 100) {
|
||||
this.frm.set_value("commission_rate", 100);
|
||||
frappe.throw(`${__(frappe.meta.get_label(
|
||||
this.frm.doc.doctype, "commission_rate", this.frm.doc.name
|
||||
))} ${__("cannot be greater than 100")}`);
|
||||
}
|
||||
|
||||
this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce(
|
||||
(sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0
|
||||
)
|
||||
|
||||
this.frm.doc.total_commission = flt(
|
||||
this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0,
|
||||
precision("total_commission")
|
||||
);
|
||||
|
||||
refresh_field(["amount_eligible_for_commission", "total_commission"]);
|
||||
}
|
||||
|
||||
calculate_contribution() {
|
||||
var me = this;
|
||||
$.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
|
||||
frappe.model.round_floats_in(sales_person);
|
||||
if (!sales_person.allocated_percentage) return;
|
||||
|
||||
sales_person.allocated_amount = flt(
|
||||
me.frm.doc.amount_eligible_for_commission
|
||||
* sales_person.allocated_percentage
|
||||
/ 100.0,
|
||||
precision("allocated_amount", sales_person)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
calculate_incentive(row) {
|
||||
if(row.allocated_amount)
|
||||
{
|
||||
row.incentives = flt(
|
||||
row.allocated_amount * row.commission_rate / 100.0,
|
||||
precision("incentives", row));
|
||||
}
|
||||
}
|
||||
|
||||
set_dynamic_labels() {
|
||||
super.set_dynamic_labels();
|
||||
this.set_product_bundle_help(this.frm.doc);
|
||||
}
|
||||
|
||||
set_product_bundle_help(doc) {
|
||||
if(!this.frm.fields_dict.packing_list) return;
|
||||
if ((doc.packed_items || []).length) {
|
||||
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(true);
|
||||
|
||||
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
|
||||
var help_msg = "<div class='alert alert-warning'>" +
|
||||
__("For 'Product Bundle' items, Warehouse, Serial No and Batch No will be considered from the 'Packing List' table. If Warehouse and Batch No are same for all packing items for any 'Product Bundle' item, those values can be entered in the main Item table, values will be copied to 'Packing List' table.")+
|
||||
"</div>";
|
||||
frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = help_msg;
|
||||
}
|
||||
} else {
|
||||
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(false);
|
||||
if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
|
||||
frappe.meta.get_docfield(doc.doctype, 'product_bundle_help', doc.name).options = '';
|
||||
}
|
||||
}
|
||||
refresh_field('product_bundle_help');
|
||||
}
|
||||
|
||||
company_address() {
|
||||
var me = this;
|
||||
if(this.frm.doc.company_address) {
|
||||
frappe.call({
|
||||
method: "frappe.contacts.doctype.address.address.get_address_display",
|
||||
args: {"address_dict": this.frm.doc.company_address },
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
me.frm.set_value("company_address_display", r.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.frm.set_value("company_address_display", "");
|
||||
}
|
||||
}
|
||||
|
||||
conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate) {
|
||||
super.conversion_factor(doc, cdt, cdn, dont_fetch_price_list_rate);
|
||||
}
|
||||
|
||||
qty(doc, cdt, cdn) {
|
||||
super.qty(doc, cdt, cdn);
|
||||
}
|
||||
|
||||
pick_serial_and_batch(doc, cdt, cdn) {
|
||||
let item = locals[cdt][cdn];
|
||||
let me = this;
|
||||
let path = "assets/erpnext/js/utils/serial_no_batch_selector.js";
|
||||
|
||||
frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||
.then((r) => {
|
||||
if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
item.has_serial_no = r.message.has_serial_no;
|
||||
item.has_batch_no = r.message.has_batch_no;
|
||||
item.type_of_transaction = item.qty > 0 ? "Outward":"Inward";
|
||||
|
||||
item.title = item.has_serial_no ?
|
||||
__("Select Serial No") : __("Select Batch No");
|
||||
|
||||
if (item.has_serial_no && item.has_batch_no) {
|
||||
item.title = __("Select Serial and Batch");
|
||||
}
|
||||
|
||||
frappe.require(path, function() {
|
||||
new erpnext.SerialBatchPackageSelector(
|
||||
me.frm, item, (r) => {
|
||||
if (r) {
|
||||
frappe.model.set_value(item.doctype, item.name, {
|
||||
"serial_and_batch_bundle": r.name,
|
||||
"qty": Math.abs(r.total_qty)
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
update_auto_repeat_reference(doc) {
|
||||
if (doc.auto_repeat) {
|
||||
frappe.call({
|
||||
method:"frappe.automation.doctype.auto_repeat.auto_repeat.update_reference",
|
||||
args:{
|
||||
docname: doc.auto_repeat,
|
||||
reference:doc.name
|
||||
},
|
||||
callback: function(r){
|
||||
if (r.message=="success") {
|
||||
frappe.show_alert({message:__("Auto repeat document updated"), indicator:'green'});
|
||||
} else {
|
||||
frappe.show_alert({message:__("An error occurred during the update process"), indicator:'red'});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
project() {
|
||||
let me = this;
|
||||
if(in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) {
|
||||
if(this.frm.doc.project) {
|
||||
frappe.call({
|
||||
method:'erpnext.projects.doctype.project.project.get_cost_center_name' ,
|
||||
args: {project: this.frm.doc.project},
|
||||
callback: function(r, rt) {
|
||||
if(!r.exc) {
|
||||
$.each(me.frm.doc["items"] || [], function(i, row) {
|
||||
if(r.message) {
|
||||
frappe.model.set_value(row.doctype, row.name, "cost_center", r.message);
|
||||
frappe.msgprint(__("Cost Center For Item with Item Code {0} has been Changed to {1}", [row.item_name, r.message]));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coupon_code() {
|
||||
this.frm.set_value("discount_amount", 0);
|
||||
this.frm.set_value("additional_discount_percentage", 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.pre_sales = {
|
||||
set_as_lost: function(doctype) {
|
||||
frappe.ui.form.on(doctype, {
|
||||
set_as_lost_dialog: function(frm) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Set as Lost"),
|
||||
fields: [
|
||||
{
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": __("Lost Reasons"),
|
||||
"fieldname": "lost_reason",
|
||||
"options": frm.doctype === 'Opportunity' ? 'Opportunity Lost Reason Detail': 'Quotation Lost Reason Detail',
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": __("Competitors"),
|
||||
"fieldname": "competitors",
|
||||
"options": "Competitor Detail"
|
||||
},
|
||||
{
|
||||
"fieldtype": "Small Text",
|
||||
"label": __("Detailed Reason"),
|
||||
"fieldname": "detailed_reason"
|
||||
},
|
||||
],
|
||||
primary_action: function() {
|
||||
let values = dialog.get_values();
|
||||
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: 'declare_enquiry_lost',
|
||||
args: {
|
||||
'lost_reasons_list': values.lost_reason,
|
||||
'competitors': values.competitors ? values.competitors : [],
|
||||
'detailed_reason': values.detailed_reason
|
||||
},
|
||||
callback: function(r) {
|
||||
dialog.hide();
|
||||
frm.reload_doc();
|
||||
},
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Declare Lost')
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -651,7 +651,7 @@
|
||||
"search_fields": "customer_name,customer_group,territory, mobile_no,primary_address",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1
|
||||
|
||||
@@ -582,7 +582,8 @@ def get_customer_outstanding(
|
||||
"""
|
||||
select sum(debit) - sum(credit)
|
||||
from `tabGL Entry` where party_type = 'Customer'
|
||||
and party = %s and company=%s {0}""".format(
|
||||
and is_cancelled = 0 and party = %s
|
||||
and company=%s {0}""".format(
|
||||
cond
|
||||
),
|
||||
(customer, company),
|
||||
|
||||
@@ -352,9 +352,6 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
|
||||
# postprocess: fetch shipping address, set missing values
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@@ -423,8 +420,6 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
ignore_permissions=ignore_permissions,
|
||||
)
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -716,8 +716,6 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
||||
|
||||
target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
|
||||
|
||||
target_doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return target_doc
|
||||
|
||||
|
||||
@@ -806,8 +804,6 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
if automatically_fetch_payment_terms:
|
||||
doclist.set_payment_schedule()
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe import _, msgprint, qb
|
||||
from frappe.query_builder import Criterion
|
||||
|
||||
from erpnext import get_company_currency
|
||||
|
||||
@@ -214,24 +215,33 @@ def get_conditions(filters, date_field):
|
||||
if items:
|
||||
conditions.append("dt_item.item_code in (%s)" % ", ".join(["%s"] * len(items)))
|
||||
values += items
|
||||
else:
|
||||
# return empty result, if no items are fetched after filtering on 'item group' and 'brand'
|
||||
conditions.append("dt_item.item_code = Null")
|
||||
|
||||
return " and ".join(conditions), values
|
||||
|
||||
|
||||
def get_items(filters):
|
||||
item = qb.DocType("Item")
|
||||
|
||||
item_query_conditions = []
|
||||
if filters.get("item_group"):
|
||||
key = "item_group"
|
||||
elif filters.get("brand"):
|
||||
key = "brand"
|
||||
else:
|
||||
key = ""
|
||||
|
||||
items = []
|
||||
if key:
|
||||
items = frappe.db.sql_list(
|
||||
"""select name from tabItem where %s = %s""" % (key, "%s"), (filters[key])
|
||||
# Handle 'Parent' nodes as well.
|
||||
item_group = qb.DocType("Item Group")
|
||||
lft, rgt = frappe.db.get_all(
|
||||
"Item Group", filters={"name": filters.get("item_group")}, fields=["lft", "rgt"], as_list=True
|
||||
)[0]
|
||||
item_group_query = (
|
||||
qb.from_(item_group)
|
||||
.select(item_group.name)
|
||||
.where((item_group.lft >= lft) & (item_group.rgt <= rgt))
|
||||
)
|
||||
item_query_conditions.append(item.item_group.isin(item_group_query))
|
||||
if filters.get("brand"):
|
||||
item_query_conditions.append(item.brand == filters.get("brand"))
|
||||
|
||||
items = qb.from_(item).select(item.name).where(Criterion.all(item_query_conditions)).run()
|
||||
return items
|
||||
|
||||
|
||||
|
||||
@@ -665,8 +665,6 @@ def make_sales_invoice(source_name, target_doc=None):
|
||||
if automatically_fetch_payment_terms:
|
||||
doc.set_payment_schedule()
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
|
||||
@@ -1057,7 +1057,6 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
return doclist
|
||||
|
||||
|
||||
|
||||
@@ -352,6 +352,7 @@
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"print_width": "100px",
|
||||
"read_only_depends_on": "eval: (!parent.is_return && doc.purchase_order && doc.purchase_order_item)",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
@@ -1054,7 +1055,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:38:15.251994",
|
||||
"modified": "2023-11-30 16:12:02.364608",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -241,7 +241,7 @@ frappe.ui.form.on('Stock Entry', {
|
||||
}
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus===0) {
|
||||
if (frm.doc.docstatus === 0) {
|
||||
frm.add_custom_button(__('Purchase Invoice'), function() {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_stock_entry",
|
||||
@@ -294,7 +294,8 @@ frappe.ui.form.on('Stock Entry', {
|
||||
})
|
||||
}, __("Get Items From"));
|
||||
}
|
||||
if (frm.doc.docstatus===0 && frm.doc.purpose == "Material Issue") {
|
||||
|
||||
if (frm.doc.docstatus === 0 && frm.doc.purpose == "Material Issue") {
|
||||
frm.add_custom_button(__('Expired Batches'), function() {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.stock_entry.stock_entry.get_expired_batch_items",
|
||||
@@ -379,6 +380,10 @@ frappe.ui.form.on('Stock Entry', {
|
||||
frm.remove_custom_button('Bill of Materials', "Get Items From");
|
||||
frm.events.show_bom_custom_button(frm);
|
||||
frm.trigger('add_to_transit');
|
||||
|
||||
frm.fields_dict.items.grid.update_docfield_property(
|
||||
'basic_rate', 'read_only', frm.doc.purpose == "Material Receipt" ? 0 : 1
|
||||
);
|
||||
},
|
||||
|
||||
purpose: function(frm) {
|
||||
|
||||
@@ -17,6 +17,7 @@ frappe.query_reports["Stock Analytics"] = {
|
||||
fieldtype: "Link",
|
||||
options:"Item",
|
||||
default: "",
|
||||
get_query: () => ({filters: { 'is_stock_item': 1 }}),
|
||||
},
|
||||
{
|
||||
fieldname: "value_quantity",
|
||||
|
||||
@@ -270,7 +270,7 @@ def get_items(filters):
|
||||
if item_code := filters.get("item_code"):
|
||||
return [item_code]
|
||||
else:
|
||||
item_filters = {}
|
||||
item_filters = {"is_stock_item": 1}
|
||||
if item_group := filters.get("item_group"):
|
||||
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
|
||||
item_filters["item_group"] = ("in", children + [item_group])
|
||||
|
||||
@@ -13,10 +13,18 @@ const DIFFERENCE_FIELD_NAMES = [
|
||||
|
||||
frappe.query_reports["Stock Ledger Variance"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"default": frappe.defaults.get_user_default("Company")
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item",
|
||||
"label": __("Item"),
|
||||
"options": "Item",
|
||||
get_query: function() {
|
||||
return {
|
||||
@@ -27,7 +35,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
|
||||
{
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
"label": __("Warehouse"),
|
||||
"options": "Warehouse",
|
||||
get_query: function() {
|
||||
return {
|
||||
@@ -38,7 +46,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
|
||||
{
|
||||
"fieldname": "difference_in",
|
||||
"fieldtype": "Select",
|
||||
"label": "Difference In",
|
||||
"label": __("Difference In"),
|
||||
"options": [
|
||||
"",
|
||||
"Qty",
|
||||
@@ -49,7 +57,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
|
||||
{
|
||||
"fieldname": "include_disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Disabled",
|
||||
"label": __("Include Disabled"),
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -55,6 +55,11 @@ def get_columns():
|
||||
"label": _("Warehouse"),
|
||||
"options": "Warehouse",
|
||||
},
|
||||
{
|
||||
"fieldname": "valuation_method",
|
||||
"fieldtype": "Data",
|
||||
"label": _("Valuation Method"),
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Link",
|
||||
@@ -194,6 +199,7 @@ def get_columns():
|
||||
def get_data(filters=None):
|
||||
filters = frappe._dict(filters or {})
|
||||
item_warehouse_map = get_item_warehouse_combinations(filters)
|
||||
valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
|
||||
|
||||
data = []
|
||||
if item_warehouse_map:
|
||||
@@ -206,8 +212,17 @@ def get_data(filters=None):
|
||||
continue
|
||||
|
||||
for row in report_data:
|
||||
if has_difference(row, precision, filters.difference_in):
|
||||
data.append(add_item_warehouse_details(row, item_warehouse))
|
||||
if has_difference(
|
||||
row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method
|
||||
):
|
||||
row.update(
|
||||
{
|
||||
"item_code": item_warehouse.item_code,
|
||||
"warehouse": item_warehouse.warehouse,
|
||||
"valuation_method": item_warehouse.valuation_method or valuation_method,
|
||||
}
|
||||
)
|
||||
data.append(row)
|
||||
break
|
||||
|
||||
return data
|
||||
@@ -229,8 +244,14 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict:
|
||||
.select(
|
||||
bin.item_code,
|
||||
bin.warehouse,
|
||||
item.valuation_method,
|
||||
)
|
||||
.where(
|
||||
(item.is_stock_item == 1)
|
||||
& (item.has_serial_no == 0)
|
||||
& (warehouse.is_group == 0)
|
||||
& (warehouse.company == filters.company)
|
||||
)
|
||||
.where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
|
||||
)
|
||||
|
||||
if filters.item_code:
|
||||
@@ -243,37 +264,27 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict:
|
||||
return query.run(as_dict=1)
|
||||
|
||||
|
||||
def has_difference(row, precision, difference_in):
|
||||
has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
|
||||
has_value_difference = (
|
||||
flt(row.diff_value_diff, precision)
|
||||
or flt(row.fifo_value_diff, precision)
|
||||
or flt(row.fifo_difference_diff, precision)
|
||||
)
|
||||
has_valuation_difference = flt(row.valuation_diff, precision) or flt(
|
||||
row.fifo_valuation_diff, precision
|
||||
)
|
||||
def has_difference(row, precision, difference_in, valuation_method):
|
||||
if valuation_method == "Moving Average":
|
||||
qty_diff = flt(row.difference_in_qty, precision)
|
||||
value_diff = flt(row.diff_value_diff, precision)
|
||||
valuation_diff = flt(row.valuation_diff, precision)
|
||||
else:
|
||||
qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
|
||||
value_diff = (
|
||||
flt(row.diff_value_diff, precision)
|
||||
or flt(row.fifo_value_diff, precision)
|
||||
or flt(row.fifo_difference_diff, precision)
|
||||
)
|
||||
valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
|
||||
|
||||
if difference_in == "Qty" and has_qty_difference:
|
||||
if difference_in == "Qty" and qty_diff:
|
||||
return True
|
||||
elif difference_in == "Value" and has_value_difference:
|
||||
elif difference_in == "Value" and value_diff:
|
||||
return True
|
||||
elif difference_in == "Valuation" and has_valuation_difference:
|
||||
elif difference_in == "Valuation" and valuation_diff:
|
||||
return True
|
||||
elif difference_in not in ["Qty", "Value", "Valuation"] and (
|
||||
has_qty_difference or has_value_difference or has_valuation_difference
|
||||
qty_diff or value_diff or valuation_diff
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def add_item_warehouse_details(row, item_warehouse):
|
||||
row.update(
|
||||
{
|
||||
"item_code": item_warehouse.item_code,
|
||||
"warehouse": item_warehouse.warehouse,
|
||||
}
|
||||
)
|
||||
|
||||
return row
|
||||
|
||||
@@ -8,11 +8,28 @@ from frappe.utils import flt
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
|
||||
from erpnext.controllers.subcontracting_controller import SubcontractingController
|
||||
from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
|
||||
from erpnext.stock.stock_balance import update_bin_qty
|
||||
from erpnext.stock.utils import get_bin
|
||||
|
||||
|
||||
class SubcontractingOrder(SubcontractingController):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SubcontractingOrder, self).__init__(*args, **kwargs)
|
||||
|
||||
self.status_updater = [
|
||||
{
|
||||
"source_dt": "Subcontracting Order Item",
|
||||
"target_dt": "Material Request Item",
|
||||
"join_field": "material_request_item",
|
||||
"target_field": "ordered_qty",
|
||||
"target_parent_dt": "Material Request",
|
||||
"target_parent_field": "per_ordered",
|
||||
"target_ref_field": "stock_qty",
|
||||
"source_field": "qty",
|
||||
"percent_join_field": "material_request",
|
||||
}
|
||||
]
|
||||
|
||||
def before_validate(self):
|
||||
super(SubcontractingOrder, self).before_validate()
|
||||
|
||||
@@ -26,11 +43,15 @@ class SubcontractingOrder(SubcontractingController):
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
|
||||
def on_submit(self):
|
||||
self.update_prevdoc_status()
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty_for_subcontracting()
|
||||
self.update_reserved_qty_for_subcontracting()
|
||||
self.update_status()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_prevdoc_status()
|
||||
self.update_requested_qty()
|
||||
self.update_ordered_qty_for_subcontracting()
|
||||
self.update_reserved_qty_for_subcontracting()
|
||||
self.update_status()
|
||||
@@ -114,7 +135,32 @@ class SubcontractingOrder(SubcontractingController):
|
||||
):
|
||||
item_wh_list.append([item.item_code, item.warehouse])
|
||||
for item_code, warehouse in item_wh_list:
|
||||
update_bin_qty(item_code, warehouse, {"ordered_qty": get_ordered_qty(item_code, warehouse)})
|
||||
update_bin_qty(
|
||||
item_code, warehouse, {"ordered_qty": self.get_ordered_qty(item_code, warehouse)}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_ordered_qty(item_code, warehouse):
|
||||
table = frappe.qb.DocType("Subcontracting Order")
|
||||
child = frappe.qb.DocType("Subcontracting Order Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.inner_join(child)
|
||||
.on(table.name == child.parent)
|
||||
.select((child.qty - child.received_qty) * child.conversion_factor)
|
||||
.where(
|
||||
(table.docstatus == 1)
|
||||
& (child.item_code == item_code)
|
||||
& (child.warehouse == warehouse)
|
||||
& (child.qty > child.received_qty)
|
||||
& (table.status != "Completed")
|
||||
)
|
||||
)
|
||||
|
||||
query = query.run()
|
||||
|
||||
return flt(query[0][0]) if query else 0
|
||||
|
||||
def update_reserved_qty_for_subcontracting(self):
|
||||
for item in self.supplied_items:
|
||||
@@ -139,7 +185,9 @@ class SubcontractingOrder(SubcontractingController):
|
||||
"qty": si.fg_item_qty,
|
||||
"stock_uom": item.stock_uom,
|
||||
"bom": bom,
|
||||
},
|
||||
"material_request": si.material_request,
|
||||
"material_request_item": si.material_request_item,
|
||||
}
|
||||
)
|
||||
else:
|
||||
frappe.throw(
|
||||
|
||||
@@ -6,6 +6,7 @@ from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_subcontracting_order
|
||||
from erpnext.controllers.subcontracting_controller import (
|
||||
@@ -566,6 +567,123 @@ class TestSubcontractingOrder(FrappeTestCase):
|
||||
self.assertEqual(sco.status, "Closed")
|
||||
self.assertEqual(sco.supplied_items[0].returned_qty, 5)
|
||||
|
||||
def test_ordered_qty_for_subcontracting_order(self):
|
||||
service_items = [
|
||||
{
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"item_code": "Subcontracted Service Item 8",
|
||||
"qty": 10,
|
||||
"rate": 100,
|
||||
"fg_item": "Subcontracted Item SA8",
|
||||
"fg_item_qty": 10,
|
||||
},
|
||||
]
|
||||
|
||||
ordered_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="ordered_qty",
|
||||
)
|
||||
ordered_qty = flt(ordered_qty)
|
||||
|
||||
sco = get_subcontracting_order(service_items=service_items)
|
||||
sco.reload()
|
||||
|
||||
new_ordered_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="ordered_qty",
|
||||
)
|
||||
new_ordered_qty = flt(new_ordered_qty)
|
||||
|
||||
self.assertEqual(ordered_qty + 10, new_ordered_qty)
|
||||
|
||||
for row in sco.supplied_items:
|
||||
make_stock_entry(
|
||||
target="_Test Warehouse 1 - _TC",
|
||||
item_code=row.rm_item_code,
|
||||
qty=row.required_qty,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
scr = make_subcontracting_receipt(sco.name)
|
||||
scr.submit()
|
||||
|
||||
new_ordered_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="ordered_qty",
|
||||
)
|
||||
|
||||
self.assertEqual(ordered_qty, new_ordered_qty)
|
||||
|
||||
scr.reload()
|
||||
scr.cancel()
|
||||
|
||||
new_ordered_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="ordered_qty",
|
||||
)
|
||||
|
||||
self.assertEqual(ordered_qty + 10, new_ordered_qty)
|
||||
|
||||
def test_requested_qty_for_subcontracting_order(self):
|
||||
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
|
||||
from erpnext.stock.doctype.material_request.test_material_request import make_material_request
|
||||
|
||||
requested_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="indented_qty",
|
||||
)
|
||||
requested_qty = flt(requested_qty)
|
||||
|
||||
mr = make_material_request(
|
||||
item_code="Subcontracted Item SA8",
|
||||
material_request_type="Purchase",
|
||||
qty=10,
|
||||
)
|
||||
|
||||
self.assertTrue(mr.docstatus == 1)
|
||||
|
||||
new_requested_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="indented_qty",
|
||||
)
|
||||
new_requested_qty = flt(new_requested_qty)
|
||||
|
||||
self.assertEqual(requested_qty + 10, new_requested_qty)
|
||||
|
||||
po = make_purchase_order(mr.name)
|
||||
po.is_subcontracted = 1
|
||||
po.supplier = "_Test Supplier"
|
||||
po.items[0].fg_item = "Subcontracted Item SA8"
|
||||
po.items[0].fg_item_qty = 10
|
||||
po.items[0].item_code = "Subcontracted Service Item 8"
|
||||
po.items[0].item_name = "Subcontracted Service Item 8"
|
||||
po.items[0].qty = 10
|
||||
po.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
self.assertTrue(po.items[0].material_request)
|
||||
self.assertTrue(po.items[0].material_request_item)
|
||||
|
||||
sco = create_subcontracting_order(po_name=po.name)
|
||||
self.assertTrue(sco.items[0].material_request)
|
||||
self.assertTrue(sco.items[0].material_request_item)
|
||||
|
||||
new_requested_qty = frappe.db.get_value(
|
||||
"Bin",
|
||||
filters={"warehouse": "_Test Warehouse - _TC", "item_code": "Subcontracted Item SA8"},
|
||||
fieldname="indented_qty",
|
||||
)
|
||||
new_requested_qty = flt(new_requested_qty)
|
||||
|
||||
self.assertEqual(requested_qty, new_requested_qty)
|
||||
|
||||
|
||||
def create_subcontracting_order(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
"manufacture_section",
|
||||
"manufacturer",
|
||||
"manufacturer_part_no",
|
||||
"column_break_impp",
|
||||
"reference_section",
|
||||
"material_request",
|
||||
"column_break_fpyl",
|
||||
"material_request_item",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -332,13 +337,44 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_impp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"no_copy": 1,
|
||||
"options": "Material Request",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Material Request Item",
|
||||
"no_copy": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "reference_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fpyl",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:38:37.640677",
|
||||
"modified": "2023-11-30 15:29:43.744618",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Order Item",
|
||||
@@ -351,4 +387,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +1,160 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2022-04-01 19:23:05.728354",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"column_break_2",
|
||||
"item_name",
|
||||
"section_break_4",
|
||||
"qty",
|
||||
"column_break_6",
|
||||
"rate",
|
||||
"column_break_8",
|
||||
"amount",
|
||||
"section_break_10",
|
||||
"fg_item",
|
||||
"column_break_12",
|
||||
"fg_item_qty"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 2,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Name",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 1,
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity",
|
||||
"print_width": "60px",
|
||||
"reqd": 1,
|
||||
"width": "60px"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 2,
|
||||
"fetch_from": "item_code.standard_rate",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finished Good Item",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "fg_item_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Finished Good Item Quantity",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-07 11:43:43.094867",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Order Service Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "item_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2022-04-01 19:23:05.728354",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"column_break_2",
|
||||
"item_name",
|
||||
"section_break_4",
|
||||
"qty",
|
||||
"column_break_6",
|
||||
"rate",
|
||||
"column_break_8",
|
||||
"amount",
|
||||
"section_break_10",
|
||||
"fg_item",
|
||||
"column_break_12",
|
||||
"fg_item_qty",
|
||||
"section_break_kphn",
|
||||
"material_request",
|
||||
"column_break_piqi",
|
||||
"material_request_item"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 2,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Item Name",
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 1,
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Quantity",
|
||||
"print_width": "60px",
|
||||
"reqd": 1,
|
||||
"width": "60px"
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 2,
|
||||
"fetch_from": "item_code.standard_rate",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fg_item",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finished Good Item",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "fg_item_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Finished Good Item Quantity",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_kphn",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request",
|
||||
"fieldtype": "Link",
|
||||
"label": "Material Request",
|
||||
"no_copy": 1,
|
||||
"options": "Material Request",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_piqi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "material_request_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Material Request Item",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-30 13:29:31.017440",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Order Service Item",
|
||||
"naming_rule": "Random",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "item_name",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
|
||||
@@ -111,12 +111,12 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries_on_cancel()
|
||||
self.repost_future_sle_and_gle()
|
||||
self.delete_auto_created_batches()
|
||||
self.set_consumed_qty_in_subcontract_order()
|
||||
self.set_subcontracting_order_status()
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries_on_cancel()
|
||||
self.repost_future_sle_and_gle()
|
||||
self.update_status()
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"subcontracting_receipt_item",
|
||||
"section_break_45",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"serial_no",
|
||||
"col_break5",
|
||||
"batch_no",
|
||||
@@ -465,12 +466,19 @@
|
||||
"fieldname": "accounting_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "include_exploded_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Exploded Items",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-14 18:38:26.459669",
|
||||
"modified": "2023-11-30 12:05:51.920705",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
||||
@@ -4,93 +4,67 @@
|
||||
frappe.provide("erpnext.support");
|
||||
|
||||
frappe.ui.form.on("Warranty Claim", {
|
||||
setup: function(frm) {
|
||||
frm.set_query('contact_person', erpnext.queries.contact_query);
|
||||
frm.set_query('customer_address', erpnext.queries.address_query);
|
||||
frm.set_query('customer', erpnext.queries.customer);
|
||||
setup: (frm) => {
|
||||
frm.set_query("contact_person", erpnext.queries.contact_query);
|
||||
frm.set_query("customer_address", erpnext.queries.address_query);
|
||||
frm.set_query("customer", erpnext.queries.customer);
|
||||
|
||||
frm.add_fetch('serial_no', 'item_code', 'item_code');
|
||||
frm.add_fetch('serial_no', 'item_name', 'item_name');
|
||||
frm.add_fetch('serial_no', 'description', 'description');
|
||||
frm.add_fetch('serial_no', 'maintenance_status', 'warranty_amc_status');
|
||||
frm.add_fetch('serial_no', 'warranty_expiry_date', 'warranty_expiry_date');
|
||||
frm.add_fetch('serial_no', 'amc_expiry_date', 'amc_expiry_date');
|
||||
frm.add_fetch('serial_no', 'customer', 'customer');
|
||||
frm.add_fetch('serial_no', 'customer_name', 'customer_name');
|
||||
frm.add_fetch('item_code', 'item_name', 'item_name');
|
||||
frm.add_fetch('item_code', 'description', 'description');
|
||||
frm.set_query("serial_no", () => {
|
||||
let filters = {
|
||||
company: frm.doc.company,
|
||||
};
|
||||
|
||||
if (frm.doc.item_code) {
|
||||
filters["item_code"] = frm.doc.item_code;
|
||||
}
|
||||
|
||||
return { filters: filters };
|
||||
});
|
||||
|
||||
frm.set_query("item_code", () => {
|
||||
return {
|
||||
filters: {
|
||||
disabled: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
onload: function(frm) {
|
||||
if(!frm.doc.status) {
|
||||
frm.set_value('status', 'Open');
|
||||
|
||||
onload: (frm) => {
|
||||
if (!frm.doc.status) {
|
||||
frm.set_value("status", "Open");
|
||||
}
|
||||
},
|
||||
customer: function(frm) {
|
||||
|
||||
refresh: (frm) => {
|
||||
frappe.dynamic_link = {
|
||||
doc: frm.doc,
|
||||
fieldname: "customer",
|
||||
doctype: "Customer",
|
||||
};
|
||||
|
||||
if (
|
||||
!frm.doc.__islocal &&
|
||||
["Open", "Work In Progress"].includes(frm.doc.status)
|
||||
) {
|
||||
frm.add_custom_button(__("Maintenance Visit"), () => {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
|
||||
frm: frm,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
customer: (frm) => {
|
||||
erpnext.utils.get_party_details(frm);
|
||||
},
|
||||
customer_address: function(frm) {
|
||||
|
||||
customer_address: (frm) => {
|
||||
erpnext.utils.get_address_display(frm);
|
||||
},
|
||||
contact_person: function(frm) {
|
||||
|
||||
contact_person: (frm) => {
|
||||
erpnext.utils.get_contact_details(frm);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
erpnext.support.WarrantyClaim = class WarrantyClaim extends frappe.ui.form.Controller {
|
||||
refresh() {
|
||||
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
|
||||
|
||||
if(!cur_frm.doc.__islocal &&
|
||||
(cur_frm.doc.status=='Open' || cur_frm.doc.status == 'Work In Progress')) {
|
||||
cur_frm.add_custom_button(__('Maintenance Visit'),
|
||||
this.make_maintenance_visit);
|
||||
}
|
||||
}
|
||||
|
||||
make_maintenance_visit() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.support.doctype.warranty_claim.warranty_claim.make_maintenance_visit",
|
||||
frm: cur_frm
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
extend_cscript(cur_frm.cscript, new erpnext.support.WarrantyClaim({frm: cur_frm}));
|
||||
|
||||
cur_frm.fields_dict['serial_no'].get_query = function(doc, cdt, cdn) {
|
||||
var cond = [];
|
||||
var filter = [
|
||||
['Serial No', 'docstatus', '!=', 2]
|
||||
];
|
||||
if(doc.item_code) {
|
||||
cond = ['Serial No', 'item_code', '=', doc.item_code];
|
||||
filter.push(cond);
|
||||
}
|
||||
if(doc.customer) {
|
||||
cond = ['Serial No', 'customer', '=', doc.customer];
|
||||
filter.push(cond);
|
||||
}
|
||||
return{
|
||||
filters:filter
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) {
|
||||
if(doc.serial_no) {
|
||||
return{
|
||||
doctype: "Serial No",
|
||||
fields: "item_code",
|
||||
filters:{
|
||||
name: doc.serial_no
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
return{
|
||||
filters:[
|
||||
['Item', 'docstatus', '!=', 2],
|
||||
['Item', 'disabled', '=', 0]
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial No",
|
||||
"options": "Serial No"
|
||||
"options": "Serial No",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
@@ -126,6 +127,8 @@
|
||||
"options": "fa fa-ticket"
|
||||
},
|
||||
{
|
||||
"fetch_from": "serial_no.item_code",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@@ -138,6 +141,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.item_code",
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
@@ -147,6 +151,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.item_code",
|
||||
"fetch_from": "item_code.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description",
|
||||
@@ -162,17 +167,24 @@
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fetch_from": "serial_no.maintenance_status",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "warranty_amc_status",
|
||||
"fieldtype": "Select",
|
||||
"label": "Warranty / AMC Status",
|
||||
"options": "\nUnder Warranty\nOut of Warranty\nUnder AMC\nOut of AMC"
|
||||
"options": "\nUnder Warranty\nOut of Warranty\nUnder AMC\nOut of AMC",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "serial_no.warranty_expiry_date",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "warranty_expiry_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Warranty Expiry Date"
|
||||
},
|
||||
{
|
||||
"fetch_from": "serial_no.amc_expiry_date",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "amc_expiry_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "AMC Expiry Date"
|
||||
@@ -223,6 +235,7 @@
|
||||
{
|
||||
"bold": 1,
|
||||
"depends_on": "customer",
|
||||
"fetch_from": "customer.customer_name",
|
||||
"fieldname": "customer_name",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
@@ -362,7 +375,8 @@
|
||||
],
|
||||
"icon": "fa fa-bug",
|
||||
"idx": 1,
|
||||
"modified": "2021-11-09 17:26:09.703215",
|
||||
"links": [],
|
||||
"modified": "2023-11-28 17:30:35.676410",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Warranty Claim",
|
||||
|
||||
Reference in New Issue
Block a user