diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 325a356c3ce..220b74727b9 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -485,6 +485,10 @@ def set_default_accounts(company): "default_payable_account": frappe.db.get_value( "Account", {"company": company.name, "account_type": "Payable", "is_group": 0} ), + "default_provisional_account": frappe.db.get_value( + "Account", + {"company": company.name, "account_type": "Service Received But Not Billed", "is_group": 0}, + ), } ) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 1714fffc167..b63d57c900e 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -34,9 +34,6 @@ class JournalEntry(AccountsController): def __init__(self, *args, **kwargs): super(JournalEntry, self).__init__(*args, **kwargs) - def get_feed(self): - return self.voucher_type - def validate(self): if self.voucher_type == "Opening Entry": self.is_opening = "Yes" diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 3fc1adff2d3..4a7a57b6275 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -305,6 +305,7 @@ "fieldname": "source_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", + "precision": "9", "print_hide": 1, "reqd": 1 }, @@ -334,6 +335,7 @@ "fieldname": "target_exchange_rate", "fieldtype": "Float", "label": "Exchange Rate", + "precision": "9", "print_hide": 1, "reqd": 1 }, @@ -731,7 +733,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-02-23 20:08:39.559814", + "modified": "2022-12-08 16:25:43.824051", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 052b1df6f51..79fab6480c1 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -684,35 +684,34 @@ class PaymentEntry(AccountsController): ) def validate_payment_against_negative_invoice(self): - if (self.payment_type == "Pay" and self.party_type == "Customer") or ( - self.payment_type == "Receive" and self.party_type == "Supplier" + if (self.payment_type != "Pay" or self.party_type != "Customer") and ( + self.payment_type != "Receive" or self.party_type != "Supplier" ): + return - total_negative_outstanding = sum( - abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 + total_negative_outstanding = sum( + abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0 + ) + + paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount + additional_charges = sum(flt(d.amount) for d in self.deductions) + + if not total_negative_outstanding: + if self.party_type == "Customer": + msg = _("Cannot pay to Customer without any negative outstanding invoice") + else: + msg = _("Cannot receive from Supplier without any negative outstanding invoice") + + frappe.throw(msg, InvalidPaymentEntry) + + elif paid_amount - additional_charges > total_negative_outstanding: + frappe.throw( + _("Paid Amount cannot be greater than total negative outstanding amount {0}").format( + total_negative_outstanding + ), + InvalidPaymentEntry, ) - paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount - additional_charges = sum([flt(d.amount) for d in self.deductions]) - - if not total_negative_outstanding: - frappe.throw( - _("Cannot {0} {1} {2} without any negative outstanding invoice").format( - _(self.payment_type), - (_("to") if self.party_type == "Customer" else _("from")), - self.party_type, - ), - InvalidPaymentEntry, - ) - - elif paid_amount - additional_charges > total_negative_outstanding: - frappe.throw( - _("Paid Amount cannot be greater than total negative outstanding amount {0}").format( - total_negative_outstanding - ), - InvalidPaymentEntry, - ) - def set_title(self): if frappe.flags.in_import and self.title: # do not set title dynamically if title exists during data import. @@ -1188,6 +1187,7 @@ def get_outstanding_reference_documents(args): ple = qb.DocType("Payment Ledger Entry") common_filter = [] + accounting_dimensions_filter = [] posting_and_due_date = [] # confirm that Supplier is not blocked @@ -1217,7 +1217,7 @@ def get_outstanding_reference_documents(args): # Add cost center condition if args.get("cost_center"): condition += " and cost_center='%s'" % args.get("cost_center") - common_filter.append(ple.cost_center == args.get("cost_center")) + accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center")) date_fields_dict = { "posting_date": ["from_posting_date", "to_posting_date"], @@ -1243,6 +1243,7 @@ def get_outstanding_reference_documents(args): posting_date=posting_and_due_date, min_outstanding=args.get("outstanding_amt_greater_than"), max_outstanding=args.get("outstanding_amt_less_than"), + accounting_dimensions=accounting_dimensions_filter, ) outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices) diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json index 8961167f018..3003c68196e 100644 --- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json +++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json @@ -25,7 +25,8 @@ "in_list_view": 1, "label": "Type", "options": "DocType", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "columns": 2, @@ -35,7 +36,8 @@ "in_list_view": 1, "label": "Name", "options": "reference_doctype", - "reqd": 1 + "reqd": 1, + "search_index": 1 }, { "fieldname": "due_date", @@ -104,7 +106,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-09-26 17:06:55.597389", + "modified": "2022-12-12 12:31:44.919895", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Reference", @@ -113,5 +115,6 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index 52efd33fefa..ff212f2a35f 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -23,6 +23,7 @@ class PaymentReconciliation(Document): def __init__(self, *args, **kwargs): super(PaymentReconciliation, self).__init__(*args, **kwargs) self.common_filter_conditions = [] + self.accounting_dimension_filter_conditions = [] self.ple_posting_date_filter = [] @frappe.whitelist() @@ -193,6 +194,7 @@ class PaymentReconciliation(Document): posting_date=self.ple_posting_date_filter, 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, + accounting_dimensions=self.accounting_dimension_filter_conditions, ) if self.invoice_limit: @@ -381,7 +383,7 @@ class PaymentReconciliation(Document): self.common_filter_conditions.append(ple.company == self.company) if self.get("cost_center") and (get_invoices or get_return_invoices): - self.common_filter_conditions.append(ple.cost_center == self.cost_center) + self.accounting_dimension_filter_conditions.append(ple.cost_center == self.cost_center) if get_invoices: if self.from_invoice_date: diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py index dae029b4084..6030134fff2 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py @@ -8,6 +8,8 @@ from frappe import qb from frappe.tests.utils import FrappeTestCase from frappe.utils import add_days, nowdate +from erpnext import get_default_cost_center +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.party import get_party_account @@ -20,6 +22,7 @@ class TestPaymentReconciliation(FrappeTestCase): self.create_item() self.create_customer() self.create_account() + self.create_cost_center() self.clear_old_entries() def tearDown(self): @@ -216,6 +219,22 @@ class TestPaymentReconciliation(FrappeTestCase): ) return je + def create_cost_center(self): + # Setup cost center + cc_name = "Sub" + + self.main_cc = frappe.get_doc("Cost Center", get_default_cost_center(self.company)) + + cc_exists = frappe.db.get_list("Cost Center", filters={"cost_center_name": cc_name}) + if cc_exists: + self.sub_cc = frappe.get_doc("Cost Center", cc_exists[0].name) + else: + sub_cc = frappe.new_doc("Cost Center") + sub_cc.cost_center_name = "Sub" + sub_cc.parent_cost_center = self.main_cc.parent_cost_center + sub_cc.company = self.main_cc.company + self.sub_cc = sub_cc.save() + def test_filter_min_max(self): # check filter condition minimum and maximum amount self.create_sales_invoice(qty=1, rate=300) @@ -578,3 +597,24 @@ class TestPaymentReconciliation(FrappeTestCase): self.assertEqual(len(pr.payments), 1) self.assertEqual(pr.payments[0].amount, amount) self.assertEqual(pr.payments[0].currency, "EUR") + + def test_differing_cost_center_on_invoice_and_payment(self): + """ + Cost Center filter should not affect outstanding amount calculation + """ + + si = self.create_sales_invoice(qty=1, rate=100, do_not_submit=True) + si.cost_center = self.main_cc.name + si.submit() + pr = get_payment_entry(si.doctype, si.name) + pr.cost_center = self.sub_cc.name + pr = pr.save().submit() + + pr = self.create_payment_reconciliation() + pr.cost_center = self.main_cc.name + + pr.get_unreconciled_entries() + + # check PR tool output + self.assertEqual(len(pr.get("invoices")), 0) + self.assertEqual(len(pr.get("payments")), 0) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index b666e0d7c69..2943500cf44 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -255,7 +255,7 @@ def apply_pricing_rule(args, doc=None): for item in item_list: args_copy = copy.deepcopy(args) args_copy.update(item) - data = get_pricing_rule_for_item(args_copy, item.get("price_list_rate"), doc=doc) + data = get_pricing_rule_for_item(args_copy, doc=doc) out.append(data) if ( @@ -292,7 +292,7 @@ def update_pricing_rule_uom(pricing_rule, args): pricing_rule.uom = row.uom -def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False): +def get_pricing_rule_for_item(args, doc=None, for_validate=False): from erpnext.accounts.doctype.pricing_rule.utils import ( get_applied_pricing_rules, get_pricing_rule_items, diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index d27f65eba04..5bb366a770f 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -1123,7 +1123,7 @@ def make_pricing_rule(**args): "apply_on": args.apply_on or "Item Code", "applicable_for": args.applicable_for, "selling": args.selling or 0, - "currency": "USD", + "currency": "INR", "apply_discount_on_rate": args.apply_discount_on_rate or 0, "buying": args.buying or 0, "min_qty": args.min_qty or 0.0, diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index bb54b23e263..3989f8a8ac2 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -250,6 +250,17 @@ def get_other_conditions(conditions, values, args): and ifnull(`tabPricing Rule`.valid_upto, '2500-12-31')""" values["transaction_date"] = args.get("transaction_date") + if args.get("doctype") in [ + "Quotation", + "Sales Order", + "Delivery Note", + "Sales Invoice", + "POS Invoice", + ]: + conditions += """ and ifnull(`tabPricing Rule`.selling, 0) = 1""" + else: + conditions += """ and ifnull(`tabPricing Rule`.buying, 0) = 1""" + return conditions @@ -669,7 +680,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): item_details.free_item_data.append(free_item_data_args) -def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): +def apply_pricing_rule_for_free_items(doc, pricing_rule_args): if pricing_rule_args: items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index a03157ebd4b..6281400fbcc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -64,12 +64,13 @@ "tax_withholding_net_total", "base_tax_withholding_net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_58", - "tax_category", - "column_break_49", "shipping_rule", + "column_break_49", "incoterm", + "named_place", "section_break_51", "taxes", "totals", @@ -1541,13 +1542,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-11-25 12:44:29.935567", + "modified": "2022-12-12 18:37:38.142688", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index b38bce72168..4729d9c3db9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -61,12 +61,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_38", "shipping_rule", - "incoterm", "column_break_55", - "tax_category", + "incoterm", + "named_place", "section_break_40", "taxes", "section_break_43", @@ -2122,6 +2123,12 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", @@ -2134,7 +2141,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2022-12-05 16:18:14.532114", + "modified": "2022-12-12 18:34:33.409895", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py index 9dab4e91fba..8708342b118 100644 --- a/erpnext/accounts/doctype/subscription/subscription.py +++ b/erpnext/accounts/doctype/subscription/subscription.py @@ -280,7 +280,8 @@ class Subscription(Document): self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval()) self.validate_end_date() self.validate_to_follow_calendar_months() - self.cost_center = erpnext.get_default_cost_center(self.get("company")) + if not self.cost_center: + self.cost_center = erpnext.get_default_cost_center(self.get("company")) def validate_trial_period(self): """ diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 30ed91b9744..b834d1404d0 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -121,12 +121,24 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None): else: tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) + cost_center = get_cost_center(inv) + tax_row.update({"cost_center": cost_center}) + if inv.doctype == "Purchase Invoice": return tax_row, tax_deducted_on_advances, voucher_wise_amount else: return tax_row +def get_cost_center(inv): + cost_center = frappe.get_cached_value("Company", inv.company, "cost_center") + + if len(inv.get("taxes", [])) > 0: + cost_center = inv.get("taxes")[0].cost_center + + return cost_center + + def get_tax_withholding_details(tax_withholding_category, posting_date, company): tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index a195c575866..9d9684340c7 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -99,6 +99,9 @@ class ReceivablePayableReport(object): # Get return entries self.get_return_entries() + # Get Exchange Rate Revaluations + self.get_exchange_rate_revaluations() + self.data = [] for ple in self.ple_entries: @@ -251,7 +254,8 @@ class ReceivablePayableReport(object): row.invoice_grand_total = row.invoiced if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and ( - abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision + (abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision) + or (row.voucher_no in self.err_journals) ): # non-zero oustanding, we must consider this row @@ -1028,3 +1032,17 @@ class ReceivablePayableReport(object): "data": {"labels": self.ageing_column_labels, "datasets": rows}, "type": "percentage", } + + def get_exchange_rate_revaluations(self): + je = qb.DocType("Journal Entry") + results = ( + qb.from_(je) + .select(je.name) + .where( + (je.company == self.filters.company) + & (je.posting_date.lte(self.filters.report_date)) + & (je.voucher_type == "Exchange Rate Revaluation") + ) + .run() + ) + self.err_journals = [x[0] for x in results] if results else [] diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index bac8beed2e2..97a9c15fc76 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -1,9 +1,10 @@ import unittest import frappe -from frappe.tests.utils import FrappeTestCase -from frappe.utils import add_days, getdate, today +from frappe.tests.utils import FrappeTestCase, change_settings +from frappe.utils import add_days, flt, getdate, today +from erpnext import get_default_cost_center from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute @@ -17,10 +18,37 @@ class TestAccountsReceivable(FrappeTestCase): frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'") frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabJournal Entry` where company='_Test Company 2'") + frappe.db.sql("delete from `tabExchange Rate Revaluation` where company='_Test Company 2'") + + self.create_usd_account() def tearDown(self): frappe.db.rollback() + def create_usd_account(self): + name = "Debtors USD" + exists = frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors USD"} + ) + if exists: + self.debtors_usd = exists[0].name + else: + debtors = frappe.get_doc( + "Account", + frappe.db.get_list( + "Account", filters={"company": "_Test Company 2", "account_name": "Debtors"} + )[0].name, + ) + + debtors_usd = frappe.new_doc("Account") + debtors_usd.company = debtors.company + debtors_usd.account_name = "Debtors USD" + debtors_usd.account_currency = "USD" + debtors_usd.parent_account = debtors.parent_account + debtors_usd.account_type = debtors.account_type + self.debtors_usd = debtors_usd.save().name + def test_accounts_receivable(self): filters = { "company": "_Test Company 2", @@ -33,7 +61,7 @@ class TestAccountsReceivable(FrappeTestCase): } # check invoice grand total and invoiced column's value for 3 payment terms - name = make_sales_invoice() + name = make_sales_invoice().name report = execute(filters) expected_data = [[100, 30], [100, 50], [100, 20]] @@ -118,8 +146,74 @@ class TestAccountsReceivable(FrappeTestCase): ], ) + @change_settings( + "Accounts Settings", {"allow_multi_currency_invoices_against_single_party_account": 1} + ) + def test_exchange_revaluation_for_party(self): + """ + Exchange Revaluation for party on Receivable/Payable shoule be included + """ -def make_sales_invoice(): + company = "_Test Company 2" + customer = "_Test Customer 2" + + # Using Exchange Gain/Loss account for unrealized as well. + company_doc = frappe.get_doc("Company", company) + company_doc.unrealized_exchange_gain_loss_account = company_doc.exchange_gain_loss_account + company_doc.save() + + si = make_sales_invoice(no_payment_schedule=True, do_not_submit=True) + si.currency = "USD" + si.conversion_rate = 0.90 + si.debit_to = self.debtors_usd + si = si.save().submit() + + # Exchange Revaluation + err = frappe.new_doc("Exchange Rate Revaluation") + err.company = company + err.posting_date = today() + accounts = err.get_accounts_data() + err.extend("accounts", accounts) + err.accounts[0].new_exchange_rate = 0.95 + row = err.accounts[0] + row.new_balance_in_base_currency = flt( + row.new_exchange_rate * flt(row.balance_in_account_currency) + ) + row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) + err.set_total_gain_loss() + err = err.save().submit() + + # Submit JV for ERR + jv = frappe.get_doc(err.make_jv_entry()) + jv = jv.save() + for x in jv.accounts: + x.cost_center = get_default_cost_center(jv.company) + jv.submit() + + filters = { + "company": company, + "report_date": today(), + "range1": 30, + "range2": 60, + "range3": 90, + "range4": 120, + } + report = execute(filters) + + expected_data_for_err = [0, -5, 0, 5] + row = [x for x in report[1] if x.voucher_type == jv.doctype and x.voucher_no == jv.name][0] + self.assertEqual( + expected_data_for_err, + [ + row.invoiced, + row.paid, + row.credit_note, + row.outstanding, + ], + ) + + +def make_sales_invoice(no_payment_schedule=False, do_not_submit=False): frappe.set_user("Administrator") si = create_sales_invoice( @@ -134,22 +228,26 @@ def make_sales_invoice(): do_not_save=1, ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), - ) - si.append( - "payment_schedule", - dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), - ) + if not no_payment_schedule: + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 30)), invoice_portion=30.00, payment_amount=30), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 60)), invoice_portion=50.00, payment_amount=50), + ) + si.append( + "payment_schedule", + dict(due_date=getdate(add_days(today(), 90)), invoice_portion=20.00, payment_amount=20), + ) - si.submit() + si = si.save() - return si.name + if not do_not_submit: + si = si.submit() + + return si def make_payment(docname): diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e93fb6138a8..d269e1f7945 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -533,12 +533,13 @@ def get_accounts(root_type, companies): ], filters={"company": company, "root_type": root_type}, ): - if account.account_name not in added_accounts: + if account.account_number: + account_key = account.account_number + "-" + account.account_name + else: + account_key = account.account_name + + if account_key not in added_accounts: accounts.append(account) - if account.account_number: - account_key = account.account_number + "-" + account.account_name - else: - account_key = account.account_name added_accounts.append(account_key) return accounts diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index dacc809da0d..99e86ae78e9 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -503,7 +503,7 @@ class GrossProfitGenerator(object): invoice_portion = 100 elif row.invoice_portion: invoice_portion = row.invoice_portion - else: + elif row.payment_amount: invoice_portion = row.payment_amount * 100 / row.base_net_amount if i == 0: diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py index ba8d3072283..ba733c2d185 100644 --- a/erpnext/accounts/report/tax_detail/tax_detail.py +++ b/erpnext/accounts/report/tax_detail/tax_detail.py @@ -234,8 +234,11 @@ def modify_report_columns(doctype, field, column): if field in ["item_tax_rate", "base_net_amount"]: return None - if doctype == "GL Entry" and field in ["debit", "credit"]: - column.update({"label": _("Amount"), "fieldname": "amount"}) + if doctype == "GL Entry": + if field in ["debit", "credit"]: + column.update({"label": _("Amount"), "fieldname": "amount"}) + elif field == "voucher_type": + column.update({"fieldtype": "Data", "options": ""}) if field == "taxes_and_charges": column.update({"label": _("Taxes and Charges Template")}) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 41702d65b49..1e573b01bad 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -836,6 +836,7 @@ def get_outstanding_invoices( posting_date=None, min_outstanding=None, max_outstanding=None, + accounting_dimensions=None, ): ple = qb.DocType("Payment Ledger Entry") @@ -866,6 +867,7 @@ def get_outstanding_invoices( min_outstanding=min_outstanding, max_outstanding=max_outstanding, get_invoices=True, + accounting_dimensions=accounting_dimensions or [], ) for d in invoice_list: @@ -1615,6 +1617,7 @@ class QueryPaymentLedger(object): .where(ple.delinked == 0) .where(Criterion.all(filter_on_voucher_no)) .where(Criterion.all(self.common_filter)) + .where(Criterion.all(self.dimensions_filter)) .where(Criterion.all(self.voucher_posting_date)) .groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party) ) @@ -1702,6 +1705,7 @@ class QueryPaymentLedger(object): max_outstanding=None, get_payments=False, get_invoices=False, + accounting_dimensions=None, ): """ Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE @@ -1717,6 +1721,7 @@ class QueryPaymentLedger(object): self.reset() self.vouchers = vouchers self.common_filter = common_filter or [] + self.dimensions_filter = accounting_dimensions or [] self.voucher_posting_date = posting_date or [] self.min_outstanding = min_outstanding self.max_outstanding = max_outstanding diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 93496261aa0..ce7de874c56 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -62,12 +62,13 @@ "set_reserve_warehouse", "supplied_items", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_53", - "tax_category", - "column_break_50", "shipping_rule", + "column_break_50", "incoterm", + "named_place", "section_break_52", "taxes", "totals", @@ -1256,13 +1257,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:28:07.729943", + "modified": "2022-12-12 18:36:37.455134", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index 98c7dc9bd32..a9f5afb2e98 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -57,44 +57,96 @@ frappe.ui.form.on("Request for Quotation",{ }); }, __("Tools")); - frm.add_custom_button(__('Download PDF'), () => { - var suppliers = []; - const fields = [{ - fieldtype: 'Link', - label: __('Select a Supplier'), - fieldname: 'supplier', - options: 'Supplier', - reqd: 1, - get_query: () => { - return { - filters: [ - ["Supplier", "name", "in", frm.doc.suppliers.map((row) => {return row.supplier;})] - ] - } - } - }]; - - frappe.prompt(fields, data => { - var child = locals[cdt][cdn] - - var w = window.open( - frappe.urllib.get_full_url("/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" - +"doctype="+encodeURIComponent(frm.doc.doctype) - +"&name="+encodeURIComponent(frm.doc.name) - +"&supplier="+encodeURIComponent(data.supplier) - +"&no_letterhead=0")); - if(!w) { - frappe.msgprint(__("Please enable pop-ups")); return; - } + frm.add_custom_button( + __("Download PDF"), + () => { + frappe.prompt( + [ + { + fieldtype: "Link", + label: "Select a Supplier", + fieldname: "supplier", + options: "Supplier", + reqd: 1, + default: frm.doc.suppliers?.length == 1 ? frm.doc.suppliers[0].supplier : "", + get_query: () => { + return { + filters: [ + [ + "Supplier", + "name", + "in", + frm.doc.suppliers.map((row) => { + return row.supplier; + }), + ], + ], + }; + }, + }, + { + fieldtype: "Section Break", + label: "Print Settings", + fieldname: "print_settings", + collapsible: 1, + }, + { + fieldtype: "Link", + label: "Print Format", + fieldname: "print_format", + options: "Print Format", + placeholder: "Standard", + get_query: () => { + return { + filters: { + doc_type: "Request for Quotation", + }, + }; + }, + }, + { + fieldtype: "Link", + label: "Language", + fieldname: "language", + options: "Language", + default: frappe.boot.lang, + }, + { + fieldtype: "Link", + label: "Letter Head", + fieldname: "letter_head", + options: "Letter Head", + default: frm.doc.letter_head, + }, + ], + (data) => { + var w = window.open( + frappe.urllib.get_full_url( + "/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" + + new URLSearchParams({ + doctype: frm.doc.doctype, + name: frm.doc.name, + supplier: data.supplier, + print_format: data.print_format || "Standard", + language: data.language || frappe.boot.lang, + letter_head: data.letter_head || frm.doc.letter_head || "", + }).toString() + ) + ); + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); + return; + } + }, + "Download PDF for Supplier", + "Download" + ); }, - 'Download PDF for Supplier', - 'Download'); - }, - __("Tools")); + __("Tools") + ); - frm.page.set_inner_btn_group_as_primary(__('Create')); + frm.page.set_inner_btn_group_as_primary(__("Create")); } - }, make_supplier_quotation: function(frm) { diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index bdbc9ce0b73..dbc36449570 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -389,10 +389,17 @@ def create_rfq_items(sq_doc, supplier, data): @frappe.whitelist() -def get_pdf(doctype, name, supplier): - doc = get_rfq_doc(doctype, name, supplier) - if doc: - download_pdf(doctype, name, doc=doc) +def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None): + # permissions get checked in `download_pdf` + if doc := get_rfq_doc(doctype, name, supplier): + download_pdf( + doctype, + name, + print_format, + doc=doc, + language=language, + letter_head=letter_head or None, + ) def get_rfq_doc(doctype, name, supplier): diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index bebff1c3acb..120b2f8bbe1 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -20,9 +20,6 @@ from erpnext.utilities.transaction_base import TransactionBase class Supplier(TransactionBase): - def get_feed(self): - return self.supplier_name - def onload(self): """Load address and contacts in `__onload`""" load_address_and_contact(self) diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index 7776ab8ec84..c5b369bedd5 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -40,12 +40,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_34", - "tax_category", - "column_break_36", "shipping_rule", + "column_break_36", "incoterm", + "named_place", "section_break_38", "taxes", "totals", @@ -830,6 +831,12 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-shopping-cart", @@ -837,7 +844,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:27:32.179686", + "modified": "2022-12-12 18:35:39.740974", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 5a051e3bafc..334a2d806d6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -197,7 +197,7 @@ class AccountsController(TransactionBase): validate_einvoice_fields(self) - if self.doctype != "Material Request": + if self.doctype != "Material Request" and not self.ignore_pricing_rule: apply_pricing_rule_on_transaction(self) def before_cancel(self): diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 2efa5457368..7989a40a3de 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -25,10 +25,6 @@ class BuyingController(SubcontractingController): def __setup__(self): self.flags.ignore_permlevel_for_fields = ["buying_price_list", "price_list_currency"] - def get_feed(self): - if self.get("supplier_name"): - return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total) - def validate(self): super(BuyingController, self).validate() if getattr(self, "supplier", None) and not self.supplier_name: diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 0ebc8d4b4de..8b073a43202 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -19,9 +19,6 @@ class SellingController(StockController): def __setup__(self): self.flags.ignore_permlevel_for_fields = ["selling_price_list", "price_list_currency"] - def get_feed(self): - return _("To {0} | {1} {2}").format(self.customer_name, self.currency, self.grand_total) - def onload(self): super(SellingController, self).onload() if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index bf077282bf8..d4972973d0b 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -347,16 +347,21 @@ class StatusUpdater(Document): ) def warn_about_bypassing_with_role(self, item, qty_or_amount, role): - action = _("Over Receipt/Delivery") if qty_or_amount == "qty" else _("Overbilling") + if qty_or_amount == "qty": + msg = _("Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.") + else: + msg = _("Overbilling of {0} {1} ignored for item {2} because you have {3} role.") - msg = _("{0} of {1} {2} ignored for item {3} because you have {4} role.").format( - action, - _(item["target_ref_field"].title()), - frappe.bold(item["reduce_by"]), - frappe.bold(item.get("item_code")), - role, + frappe.msgprint( + msg.format( + _(item["target_ref_field"].title()), + frappe.bold(item["reduce_by"]), + frappe.bold(item.get("item_code")), + role, + ), + indicator="orange", + alert=True, ) - frappe.msgprint(msg, indicator="orange", alert=True) def update_qty(self, update_modified=True): """Updates qty or amount at row level diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 361e13cd1f9..b0ff5d4c3b8 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -14,9 +14,6 @@ from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_ class Lead(SellingController, CRMNote): - def get_feed(self): - return "{0}: {1}".format(_(self.status), self.lead_name) - def onload(self): customer = frappe.db.get_value("Customer", {"lead_name": self.name}) self.get("__onload").is_customer = customer diff --git a/erpnext/hooks.py b/erpnext/hooks.py index fd19d2585cc..7d72c76b819 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -420,6 +420,7 @@ scheduler_events = { "erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall", "erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans", "erpnext.crm.utils.open_leads_opportunities_based_on_todays_event", + "erpnext.stock.doctype.stock_entry.stock_entry.audit_incorrect_valuation_entries", ], "monthly_long": [ "erpnext.accounts.deferred_revenue.process_deferred_accounting", diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py index 0d319bf7424..b900b216e6c 100644 --- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py +++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py @@ -10,9 +10,6 @@ from erpnext.utilities.transaction_base import TransactionBase class MaintenanceVisit(TransactionBase): - def get_feed(self): - return _("To {0}").format(self.customer_name) - def validate_serial_no(self): for d in self.get("purposes"): if d.serial_no and not frappe.db.exists("Serial No", d.serial_no): diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 36466ff6d7d..f568264c908 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1154,6 +1154,36 @@ class TestWorkOrder(FrappeTestCase): except frappe.MandatoryError: self.fail("Batch generation causing failing in Work Order") + @change_settings("Manufacturing Settings", {"make_serial_no_batch_from_work_order": 1}) + def test_auto_serial_no_creation(self): + from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom + + fg_item = frappe.generate_hash(length=20) + child_item = frappe.generate_hash(length=20) + + bom_tree = {fg_item: {child_item: {}}} + + create_nested_bom(bom_tree, prefix="") + + item = frappe.get_doc("Item", fg_item) + item.has_serial_no = 1 + item.serial_no_series = f"{item.name}.#####" + item.save() + + try: + wo_order = make_wo_order_test_record(item=fg_item, qty=2, skip_transfer=True) + serial_nos = wo_order.serial_no + stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 10)) + stock_entry.set_work_order_details() + stock_entry.set_serial_no_batch_for_finished_good() + for row in stock_entry.items: + if row.item_code == fg_item: + self.assertTrue(row.serial_no) + self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos))) + + except frappe.MandatoryError: + self.fail("Batch generation causing failing in Work Order") + @change_settings( "Manufacturing Settings", {"backflush_raw_materials_based_on": "Material Transferred for Manufacture"}, diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py index 1e1b4356008..cdf1541f888 100644 --- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py +++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py @@ -4,7 +4,7 @@ import frappe from frappe import _ -from frappe.query_builder.functions import Floor, Sum +from frappe.query_builder.functions import Sum from pypika.terms import ExistsCriterion @@ -58,9 +58,9 @@ def get_bom_stock(filters): bom_item.description, bom_item.stock_qty, bom_item.stock_uom, - bom_item.stock_qty * qty_to_produce / bom.quantity, - Sum(bin.actual_qty).as_("actual_qty"), - Sum(Floor(bin.actual_qty / (bom_item.stock_qty * qty_to_produce / bom.quantity))), + (bom_item.stock_qty / bom.quantity) * qty_to_produce, + Sum(bin.actual_qty), + Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity), ) .where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM")) .groupby(bom_item.item_code) diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py index 7be8c5df180..c8b03e678bc 100644 --- a/erpnext/portal/utils.py +++ b/erpnext/portal/utils.py @@ -102,7 +102,7 @@ def create_party_contact(doctype, fullname, user, party_name): contact = frappe.new_doc("Contact") contact.update({"first_name": fullname, "email_id": user}) contact.append("links", dict(link_doctype=doctype, link_name=party_name)) - contact.append("email_ids", dict(email_id=user)) + contact.append("email_ids", dict(email_id=user, is_primary=True)) contact.flags.ignore_mandatory = True contact.insert(ignore_permissions=True) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index cbf2493d9a7..4735f24e571 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -15,9 +15,6 @@ from erpnext.setup.doctype.holiday_list.holiday_list import is_holiday class Project(Document): - def get_feed(self): - return "{0}: {1}".format(_(self.status), frappe.safe_decode(self.project_name)) - def onload(self): self.set_onload( "activity_summary", diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 79f1b3adb4e..2dde542d2d4 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -20,9 +20,6 @@ class CircularReferenceError(frappe.ValidationError): class Task(NestedSet): nsm_parent_field = "parent_task" - def get_feed(self): - return "{0}: {1}".format(_(self.status), self.subject) - def get_customer_details(self): cust = frappe.db.sql("select customer_name from `tabCustomer` where name=%s", self.customer) if cust: diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 74810005ed9..1f8a5e39f25 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -58,7 +58,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if ( in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) - && this.frm.doc.s_pos + && this.frm.doc.is_pos && this.frm.doc.is_return ) { this.set_total_amount_to_default_mop(); diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index cf05d06a690..aa57bc2168e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1130,10 +1130,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe qty(doc, cdt, cdn) { let item = frappe.get_doc(cdt, cdn); - item.pricing_rules = '' - this.conversion_factor(doc, cdt, cdn, true); - this.calculate_stock_uom_rate(doc, cdt, cdn); - this.apply_pricing_rule(item, true); + // item.pricing_rules = '' + frappe.run_serially([ + () => this.remove_pricing_rule(item), + () => this.conversion_factor(doc, cdt, cdn, true), + () => this.calculate_stock_uom_rate(doc, cdt, cdn), + () => this.apply_pricing_rule(item, true) + ]); } calculate_stock_uom_rate(doc, cdt, cdn) { @@ -1357,16 +1360,21 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe var item_list = []; $.each(this.frm.doc["items"] || [], function(i, d) { - if (d.item_code && !d.is_free_item) { - item_list.push({ - "doctype": d.doctype, - "name": d.name, - "item_code": d.item_code, - "pricing_rules": d.pricing_rules, - "parenttype": d.parenttype, - "parent": d.parent, - "price_list_rate": d.price_list_rate - }) + if (d.item_code) { + if (d.is_free_item) { + // Simply remove free items + me.frm.get_field("items").grid.grid_rows[i].remove(); + } else { + item_list.push({ + "doctype": d.doctype, + "name": d.name, + "item_code": d.item_code, + "pricing_rules": d.pricing_rules, + "parenttype": d.parenttype, + "parent": d.parent, + "price_list_rate": d.price_list_rate + }) + } } }); return this.frm.call({ diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 60c33567bef..12ecb0112a4 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -27,9 +27,6 @@ from erpnext.utilities.transaction_base import TransactionBase class Customer(TransactionBase): - def get_feed(self): - return self.customer_name - def onload(self): """Load address and contacts in `__onload`""" load_address_and_contact(self) diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index 08918f4d61a..eb2c0a48ac5 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -43,12 +43,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_36", - "tax_category", - "column_break_34", "shipping_rule", + "column_break_34", "incoterm", + "named_place", "section_break_36", "taxes", "section_break_39", @@ -1059,13 +1060,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:20:54.984348", + "modified": "2022-12-12 18:32:28.671332", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 6f0b381fc16..b151dd5e79c 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -30,6 +30,24 @@ class TestQuotation(FrappeTestCase): self.assertTrue(sales_order.get("payment_schedule")) + def test_maintain_rate_in_sales_cycle_is_enforced(self): + from erpnext.selling.doctype.quotation.quotation import make_sales_order + + maintain_rate = frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate") + frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", 1) + + quotation = frappe.copy_doc(test_records[0]) + quotation.transaction_date = nowdate() + quotation.valid_till = add_months(quotation.transaction_date, 1) + quotation.insert() + quotation.submit() + + sales_order = make_sales_order(quotation.name) + sales_order.items[0].rate = 1 + self.assertRaises(frappe.ValidationError, sales_order.save) + + frappe.db.set_single_value("Selling Settings", "maintain_same_sales_rate", maintain_rate) + def test_make_sales_order_with_different_currency(self): from erpnext.selling.doctype.quotation.quotation import make_sales_order diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 9ec32cbfc67..ccea8407ab8 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -58,12 +58,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_38", - "tax_category", - "column_break_49", "shipping_rule", + "column_break_49", "incoterm", + "named_place", "section_break_40", "taxes", "section_break_43", @@ -1630,13 +1631,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-file-text", "idx": 105, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:22:00.413878", + "modified": "2022-12-12 18:34:00.681780", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 78e2370878f..0013c95032f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -194,7 +194,7 @@ class SalesOrder(SellingController): ) if cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate")): - self.validate_rate_with_reference_doc([["Quotation", "prev_docname", "quotation_item"]]) + self.validate_rate_with_reference_doc([["Quotation", "prevdoc_docname", "quotation_item"]]) def update_enquiry_status(self, prevdoc, flag): enq = frappe.db.sql( diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py index ace2e29c2b4..5c4b57813d3 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py +++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py @@ -12,7 +12,10 @@ def get_data(): "Auto Repeat": "reference_document", "Maintenance Visit": "prevdoc_docname", }, - "internal_links": {"Quotation": ["items", "prevdoc_docname"]}, + "internal_links": { + "Quotation": ["items", "prevdoc_docname"], + "Material Request": ["items", "material_request"], + }, "transactions": [ { "label": _("Fulfillment"), diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index 91748bc7be2..f3f931edfdc 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -44,6 +44,12 @@ frappe.query_reports["Sales Order Analysis"] = { } } }, + { + "fieldname": "warehouse", + "label": __("Warehouse"), + "fieldtype": "Link", + "options": "Warehouse" + }, { "fieldname": "status", "label": __("Status"), diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py index 720aa41982a..63d339a839d 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py @@ -53,6 +53,9 @@ def get_conditions(filters): if filters.get("status"): conditions += " and so.status in %(status)s" + if filters.get("warehouse"): + conditions += " and soi.warehouse = %(warehouse)s" + return conditions diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index d6f23780942..07ee2890c46 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -70,9 +70,6 @@ class Company(NestedSet): self.abbr = self.abbr.strip() - # if self.get('__islocal') and len(self.abbr) > 5: - # frappe.throw(_("Abbreviation cannot have more than 5 characters")) - if not self.abbr.strip(): frappe.throw(_("Abbreviation is mandatory")) diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index c18a4b2214b..4256a7d8312 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -204,7 +204,7 @@ class TransactionDeletionRecord(Document): @frappe.whitelist() def get_doctypes_to_be_ignored(): - doctypes_to_be_ignored_list = [ + doctypes_to_be_ignored = [ "Account", "Cost Center", "Warehouse", @@ -223,4 +223,7 @@ def get_doctypes_to_be_ignored(): "Customer", "Supplier", ] - return doctypes_to_be_ignored_list + + doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or []) + + return doctypes_to_be_ignored diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 80e4bcb640f..165a56b7839 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -57,12 +57,13 @@ "total", "net_total", "taxes_section", + "tax_category", "taxes_and_charges", "column_break_43", - "tax_category", - "column_break_39", "shipping_rule", + "column_break_39", "incoterm", + "named_place", "section_break_41", "taxes", "section_break_44", @@ -1388,13 +1389,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:22:42.860790", + "modified": "2022-12-12 18:38:53.067799", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index afad7511be6..94f63a599b5 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -22,9 +22,6 @@ form_grid_templates = {"items": "templates/form_grid/material_request_grid.html" class MaterialRequest(BuyingController): - def get_feed(self): - return - def check_if_already_pulled(self): pass diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 5de7fedc4e0..aff5e0539c7 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -192,13 +192,13 @@ class PickList(Document): if item_map.get(key): item_map[key].qty += item.qty - item_map[key].stock_qty += item.stock_qty + item_map[key].stock_qty += flt(item.stock_qty, item.precision("stock_qty")) else: item_map[key] = item # maintain count of each item (useful to limit get query) self.item_count_map.setdefault(item_code, 0) - self.item_count_map[item_code] += item.stock_qty + self.item_count_map[item_code] += flt(item.stock_qty, item.precision("stock_qty")) return item_map.values() diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index ab91d7c8c94..8f043585b89 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -58,12 +58,13 @@ "total", "net_total", "taxes_charges_section", + "tax_category", "taxes_and_charges", "shipping_col", - "tax_category", - "column_break_53", "shipping_rule", + "column_break_53", "incoterm", + "named_place", "taxes_section", "taxes", "totals", @@ -1225,13 +1226,19 @@ "fieldtype": "Link", "label": "Incoterm", "options": "Incoterm" + }, + { + "depends_on": "incoterm", + "fieldname": "named_place", + "fieldtype": "Data", + "label": "Named Place" } ], "icon": "fa fa-truck", "idx": 261, "is_submittable": 1, "links": [], - "modified": "2022-11-17 17:29:30.067536", + "modified": "2022-12-12 18:40:32.447752", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index a2748d0d09a..541d4d17e18 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -766,13 +766,13 @@ def get_delivery_note_serial_no(item_code, qty, delivery_note): @frappe.whitelist() def auto_fetch_serial_number( - qty: float, + qty: int, item_code: str, warehouse: str, posting_date: Optional[str] = None, batch_nos: Optional[Union[str, List[str]]] = None, for_doctype: Optional[str] = None, - exclude_sr_nos: Optional[List[str]] = None, + exclude_sr_nos=None, ) -> List[str]: filters = frappe._dict({"item_code": item_code, "warehouse": warehouse}) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index f6c53f7bca0..a047a9b8142 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -4,12 +4,24 @@ import json from collections import defaultdict +from typing import Dict import frappe from frappe import _ from frappe.model.mapper import get_mapped_doc from frappe.query_builder.functions import Sum -from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate +from frappe.utils import ( + add_days, + cint, + comma_or, + cstr, + flt, + format_time, + formatdate, + getdate, + nowdate, + today, +) import erpnext from erpnext.accounts.general_ledger import process_gl_map @@ -83,9 +95,6 @@ class StockEntry(StockController): } ) - def get_feed(self): - return self.stock_entry_type - def onload(self): for item in self.get("items"): item.update(get_bin_details(item.item_code, item.s_warehouse)) @@ -2239,16 +2248,16 @@ class StockEntry(StockController): d.qty -= process_loss_dict[d.item_code][1] def set_serial_no_batch_for_finished_good(self): - serial_nos = "" + serial_nos = [] if self.pro_doc.serial_no: - serial_nos = self.get_serial_nos_for_fg() + serial_nos = self.get_serial_nos_for_fg() or [] for row in self.items: if row.is_finished_item and row.item_code == self.pro_doc.production_item: if serial_nos: row.serial_no = "\n".join(serial_nos[0 : cint(row.qty)]) - def get_serial_nos_for_fg(self, args): + def get_serial_nos_for_fg(self): fields = [ "`tabStock Entry`.`name`", "`tabStock Entry Detail`.`qty`", @@ -2264,9 +2273,7 @@ class StockEntry(StockController): ] stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters) - - if self.pro_doc.serial_no: - return self.get_available_serial_nos(stock_entries) + return self.get_available_serial_nos(stock_entries) def get_available_serial_nos(self, stock_entries): used_serial_nos = [] @@ -2705,3 +2712,62 @@ def get_stock_entry_data(work_order): ) .orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx) ).run(as_dict=1) + + +def audit_incorrect_valuation_entries(): + # Audit of stock transfer entries having incorrect valuation + from erpnext.controllers.stock_controller import create_repost_item_valuation_entry + + stock_entries = get_incorrect_stock_entries() + + for stock_entry, values in stock_entries.items(): + reposting_data = frappe._dict( + { + "posting_date": values.posting_date, + "posting_time": values.posting_time, + "voucher_type": "Stock Entry", + "voucher_no": stock_entry, + "company": values.company, + } + ) + + create_repost_item_valuation_entry(reposting_data) + + +def get_incorrect_stock_entries() -> Dict: + stock_entry = frappe.qb.DocType("Stock Entry") + stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") + transfer_purposes = [ + "Material Transfer", + "Material Transfer for Manufacture", + "Send to Subcontractor", + ] + + query = ( + frappe.qb.from_(stock_entry) + .inner_join(stock_ledger_entry) + .on(stock_entry.name == stock_ledger_entry.voucher_no) + .select( + stock_entry.name, + stock_entry.company, + stock_entry.posting_date, + stock_entry.posting_time, + Sum(stock_ledger_entry.stock_value_difference).as_("stock_value"), + ) + .where( + (stock_entry.docstatus == 1) + & (stock_entry.purpose.isin(transfer_purposes)) + & (stock_ledger_entry.modified > add_days(today(), -2)) + ) + .groupby(stock_ledger_entry.voucher_detail_no) + .having(Sum(stock_ledger_entry.stock_value_difference) != 0) + ) + + data = query.run(as_dict=True) + stock_entries = {} + + for row in data: + if abs(row.stock_value) > 0.1 and row.name not in stock_entries: + stock_entries.setdefault(row.name, row) + + return stock_entries diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index b574b718fe1..680d209735e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -5,7 +5,7 @@ import frappe from frappe.permissions import add_user_permission, remove_user_permission from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, flt, nowdate, nowtime, today +from frappe.utils import add_days, flt, now, nowdate, nowtime, today from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.stock.doctype.item.test_item import ( @@ -17,6 +17,8 @@ from erpnext.stock.doctype.item.test_item import ( from erpnext.stock.doctype.serial_no.serial_no import * # noqa from erpnext.stock.doctype.stock_entry.stock_entry import ( FinishedGoodError, + audit_incorrect_valuation_entries, + get_incorrect_stock_entries, move_sample_to_retention_warehouse, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -1614,6 +1616,44 @@ class TestStockEntry(FrappeTestCase): self.assertRaises(BatchExpiredError, se.save) + def test_audit_incorrect_stock_entries(self): + item_code = "Test Incorrect Valuation Rate Item - 001" + create_item(item_code=item_code, is_stock_item=1) + + make_stock_entry( + item_code=item_code, + purpose="Material Receipt", + posting_date=add_days(nowdate(), -10), + qty=2, + rate=500, + to_warehouse="_Test Warehouse - _TC", + ) + + transfer_entry = make_stock_entry( + item_code=item_code, + purpose="Material Transfer", + qty=2, + rate=500, + from_warehouse="_Test Warehouse - _TC", + to_warehouse="_Test Warehouse 1 - _TC", + ) + + sle_name = frappe.db.get_value( + "Stock Ledger Entry", {"voucher_no": transfer_entry.name, "actual_qty": (">", 0)}, "name" + ) + + frappe.db.set_value( + "Stock Ledger Entry", sle_name, {"modified": add_days(now(), -1), "stock_value_difference": 10} + ) + + stock_entries = get_incorrect_stock_entries() + self.assertTrue(transfer_entry.name in stock_entries) + + audit_incorrect_valuation_entries() + + stock_entries = get_incorrect_stock_entries() + self.assertFalse(transfer_entry.name in stock_entries) + def make_serialized_item(**args): args = frappe._dict(args) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 3a0b38a0fcd..398b3c98e38 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -715,8 +715,8 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): def get_stock_balance_for( item_code: str, warehouse: str, - posting_date: str, - posting_time: str, + posting_date, + posting_time, batch_no: Optional[str] = None, with_valuation_rate: bool = True, ): diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 108611c09bf..1741d654601 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -113,7 +113,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru if args.get(key) is None: args[key] = value - data = get_pricing_rule_for_item(args, out.price_list_rate, doc, for_validate=for_validate) + data = get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate) out.update(data) @@ -828,9 +828,9 @@ def insert_item_price(args): ): if frappe.has_permission("Item Price", "write"): price_list_rate = ( - (args.rate + args.discount_amount) / args.get("conversion_factor") + (flt(args.rate) + flt(args.discount_amount)) / args.get("conversion_factor") if args.get("conversion_factor") - else (args.rate + args.discount_amount) + else (flt(args.rate) + flt(args.discount_amount)) ) item_price = frappe.db.get_value( @@ -1305,7 +1305,7 @@ def apply_price_list_on_item(args): item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1) item_details = get_price_list_rate(args, item_doc) - item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate)) + item_details.update(get_pricing_rule_for_item(args)) return item_details diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index a6fc049cbde..c4358b809fc 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -82,7 +82,7 @@ def get_item_info(filters): item.safety_stock, item.lead_time_days, ) - .where(item.is_stock_item == 1) + .where((item.is_stock_item == 1) & (item.disabled == 0)) ) if brand := filters.get("brand"): diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 7f3e0cf4c21..f23419e98e8 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -18,9 +18,6 @@ from frappe.utils.user import is_website_user class Issue(Document): - def get_feed(self): - return "{0}: {1}".format(_(self.status), self.subject) - def validate(self): if self.is_new() and self.via_customer_portal: self.flags.create_communication = True diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.py b/erpnext/support/doctype/warranty_claim/warranty_claim.py index c86356f2a2c..ff63b77f9ed 100644 --- a/erpnext/support/doctype/warranty_claim/warranty_claim.py +++ b/erpnext/support/doctype/warranty_claim/warranty_claim.py @@ -10,9 +10,6 @@ from erpnext.utilities.transaction_base import TransactionBase class WarrantyClaim(TransactionBase): - def get_feed(self): - return _("{0}: From {1}").format(self.status, self.customer_name) - def validate(self): if session["user"] != "Guest" and not self.customer: frappe.throw(_("Customer is required")) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index f1d830245f4..1014e27d6cf 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -1849,6 +1849,8 @@ Outstanding Amt,Offener Betrag, Outstanding Cheques and Deposits to clear,Ausstehende Schecks und Anzahlungen zum verbuchen, Outstanding for {0} cannot be less than zero ({1}),Ausstände für {0} können nicht kleiner als Null sein ({1}), Outward taxable supplies(zero rated),Steuerpflichtige Lieferungen aus dem Ausland (null bewertet), +Over Receipt/Delivery of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Annahme bzw. Lieferung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben." +Overbilling of {0} {1} ignored for item {2} because you have {3} role.,"Überhöhte Abrechnung von Artikel {2} mit {0} {1} wurde ignoriert, weil Sie die Rolle {3} haben." Overdue,Überfällig, Overlap in scoring between {0} and {1},Überlappung beim Scoring zwischen {0} und {1}, Overlapping conditions found between:,Überlagernde Bedingungen gefunden zwischen:, @@ -9914,4 +9916,3 @@ Cost and Freight,Kosten und Fracht, Delivered at Place,Geliefert benannter Ort, Delivered at Place Unloaded,Geliefert benannter Ort entladen, Delivered Duty Paid,Geliefert verzollt, -{0} of {1} {2} ignored for item {3} because you have {4} role,"{0} von Artikel {3} mit {1} {2} wurde ignoriert, weil Sie die Rolle {4} haben." diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py index 04ee0b3b1eb..afe9654e8ea 100644 --- a/erpnext/utilities/product.py +++ b/erpnext/utilities/product.py @@ -110,6 +110,7 @@ def get_price(item_code, price_list, customer_group, company, qty=1): "conversion_rate": 1, "for_shopping_cart": True, "currency": frappe.db.get_value("Price List", price_list, "currency"), + "doctype": "Quotation", } )