Merge pull request #39591 from ruthra-kumar/conversion_on_future_payments
refactor: Do proper currency conversion on Future Payments column in AR/AP report
diff --git a/.github/workflows/patch_faux.yml b/.github/workflows/patch_faux.yml
new file mode 100644
index 0000000..93d88bd
--- /dev/null
+++ b/.github/workflows/patch_faux.yml
@@ -0,0 +1,22 @@
+# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
+
+name: Skipped Patch Test
+
+on:
+ pull_request:
+ paths:
+ - "**.js"
+ - "**.css"
+ - "**.md"
+ - "**.html"
+ - "**.csv"
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ name: Patch Test
+
+ steps:
+ - name: Pass skipped tests unconditionally
+ run: "echo Skipped"
diff --git a/.github/workflows/server-tests-mariadb-faux.yml b/.github/workflows/server-tests-mariadb-faux.yml
new file mode 100644
index 0000000..8334661
--- /dev/null
+++ b/.github/workflows/server-tests-mariadb-faux.yml
@@ -0,0 +1,24 @@
+# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
+
+name: Skipped Tests
+
+on:
+ pull_request:
+ paths:
+ - "**.js"
+ - "**.css"
+ - "**.md"
+ - "**.html"
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ container: [1, 2, 3, 4]
+
+ name: Python Unit Tests
+
+ steps:
+ - name: Pass skipped tests unconditionally
+ run: "echo Skipped"
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 7579da8..f722c90 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -187,8 +187,8 @@
def update_advance_paid(self):
advance_paid = frappe._dict()
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in advance_payment_doctypes:
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 3a1e1ea..b1b8d5e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -87,12 +87,14 @@
"status",
"custom_remarks",
"remarks",
+ "base_in_words",
"column_break_16",
"letter_head",
"print_heading",
"bank",
"bank_account_no",
"payment_order",
+ "in_words",
"subscription_section",
"auto_repeat",
"amended_from",
@@ -747,6 +749,20 @@
"hidden": 1,
"label": "Book Advance Payments in Separate Party Account",
"read_only": 1
+ },
+ {
+ "fieldname": "base_in_words",
+ "fieldtype": "Small Text",
+ "label": "In Words (Company Currency)",
+ "print_hide": 1,
+ "read_only": 1
+ },
+ {
+ "fieldname": "in_words",
+ "fieldtype": "Small Text",
+ "label": "In Words",
+ "print_hide": 1,
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cfe0d9c..7e88b6b 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -178,6 +178,7 @@
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked()
self.set_status()
+ self.set_total_in_words()
def on_submit(self):
if self.difference_amount:
@@ -786,6 +787,21 @@
self.db_set("status", self.status, update_modified=True)
+ def set_total_in_words(self):
+ from frappe.utils import money_in_words
+
+ if self.payment_type in ("Pay", "Internal Transfer"):
+ base_amount = abs(self.base_paid_amount)
+ amount = abs(self.paid_amount)
+ currency = self.paid_from_account_currency
+ elif self.payment_type == "Receive":
+ base_amount = abs(self.base_received_amount)
+ amount = abs(self.received_amount)
+ currency = self.paid_to_account_currency
+
+ self.base_in_words = money_in_words(base_amount, self.company_currency)
+ self.in_words = money_in_words(amount, currency)
+
def set_tax_withholding(self):
if self.party_type != "Supplier":
return
@@ -927,8 +943,8 @@
def calculate_base_allocated_amount_for_reference(self, d) -> float:
base_allocated_amount = 0
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
if d.reference_doctype in advance_payment_doctypes:
# When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
# This is so there are no Exchange Gain/Loss generated for such doctypes
@@ -1428,8 +1444,8 @@
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
frappe.get_doc(
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index 666926f..ff2aa6d 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -41,6 +41,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
+ "ignore_user_permissions": 1,
"label": "Company",
"options": "Company",
"reqd": 1
@@ -229,7 +230,7 @@
"is_virtual": 1,
"issingle": 1,
"links": [],
- "modified": "2023-12-14 13:38:16.264013",
+ "modified": "2024-01-18 11:56:20.234667",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Reconciliation",
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 839348a..a18104e 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -170,8 +170,8 @@
self.request_phone_payment()
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
if self.reference_doctype in advance_payment_doctypes:
# set advance payment status
ref_doc.set_total_advance_paid()
@@ -216,8 +216,8 @@
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
if self.reference_doctype in advance_payment_doctypes:
# set advance payment status
ref_doc.set_total_advance_paid()
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index ca031f0..1a7eef6 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -11,7 +11,6 @@
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
SalesInvoice,
- get_bank_cash_account,
get_mode_of_payment_info,
update_multi_mode_option,
)
@@ -208,7 +207,6 @@
self.validate_stock_availablility()
self.validate_return_items_qty()
self.set_status()
- self.set_account_for_mode_of_payment()
self.validate_pos()
self.validate_payment_amount()
self.validate_loyalty_transaction()
@@ -643,11 +641,6 @@
update_multi_mode_option(self, pos_profile)
self.paid_amount = 0
- def set_account_for_mode_of_payment(self):
- for pay in self.payments:
- if not pay.account:
- pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
-
@frappe.whitelist()
def create_payment_request(self):
for pay in self.payments:
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index f85fc87..a48f5ea 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -1253,6 +1253,7 @@
"fieldtype": "Select",
"in_standard_filter": 1,
"label": "Status",
+ "no_copy": 1,
"options": "\nDraft\nReturn\nDebit Note Issued\nSubmitted\nPaid\nPartly Paid\nUnpaid\nOverdue\nCancelled\nInternal Transfer",
"print_hide": 1
},
@@ -1612,7 +1613,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2023-11-29 15:35:44.697496",
+ "modified": "2024-01-26 10:46:00.469053",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index cc19650..c381b8a 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -421,7 +421,8 @@
self.calculate_taxes_and_totals()
def before_save(self):
- set_account_for_mode_of_payment(self)
+ self.set_account_for_mode_of_payment()
+ self.set_paid_amount()
def on_submit(self):
self.validate_pos_paid_amount()
@@ -712,9 +713,6 @@
):
data.sales_invoice = sales_invoice
- def on_update(self):
- self.set_paid_amount()
-
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
fields_to_check = [
@@ -745,6 +743,11 @@
self.paid_amount = paid_amount
self.base_paid_amount = base_paid_amount
+ def set_account_for_mode_of_payment(self):
+ for payment in self.payments:
+ if not payment.account:
+ payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
+
def validate_time_sheets_are_submitted(self):
for data in self.timesheets:
if data.time_sheet:
@@ -2113,12 +2116,6 @@
return make_return_doc("Sales Invoice", source_name, target_doc)
-def set_account_for_mode_of_payment(self):
- for data in self.payments:
- if not data.account:
- data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
-
-
def get_inter_company_details(doc, doctype):
if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"]:
parties = frappe.db.get_all(
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 65b3aba..30700d0 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -240,7 +240,6 @@
cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
if account:
-
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
acc.check_permission("read")
@@ -286,18 +285,22 @@
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
if account or (party_type and party) or account_type:
-
+ precision = get_currency_precision()
if in_account_currency:
- select_field = "sum(debit_in_account_currency) - sum(credit_in_account_currency)"
+ select_field = (
+ "sum(round(debit_in_account_currency, %s)) - sum(round(credit_in_account_currency, %s))"
+ )
else:
- select_field = "sum(debit) - sum(credit)"
+ select_field = "sum(round(debit, %s)) - sum(round(credit, %s))"
+
bal = frappe.db.sql(
"""
SELECT {0}
FROM `tabGL Entry` gle
WHERE {1}""".format(
select_field, " and ".join(cond)
- )
+ ),
+ (precision, precision),
)[0][0]
# if bal is None, return 0
return flt(bal)
@@ -619,8 +622,8 @@
# Update Advance Paid in SO/PO since they might be getting unlinked
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
if jv_detail.get("reference_type") in advance_payment_doctypes:
frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
@@ -696,8 +699,8 @@
# Update Advance Paid in SO/PO since they are getting unlinked
advance_payment_doctypes = frappe.get_hooks(
- "advance_payment_customer_doctypes"
- ) + frappe.get_hooks("advance_payment_supplier_doctypes")
+ "advance_payment_receivable_doctypes"
+ ) + frappe.get_hooks("advance_payment_payable_doctypes")
if existing_row.get("reference_doctype") in advance_payment_doctypes:
frappe.get_doc(
existing_row.reference_doctype, existing_row.reference_name
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 02e7a9b..673fe54 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -571,16 +571,16 @@
indicator: 'red'
});
}
- var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset');
- var asset_quantity = is_grouped_asset ? item.qty : 1;
- var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
+ frappe.db.get_value('Item', item.item_code, 'is_grouped_asset', (r) => {
+ var asset_quantity = r.is_grouped_asset ? item.qty : 1;
+ var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
- frm.set_value('gross_purchase_amount', purchase_amount);
- frm.set_value('purchase_receipt_amount', purchase_amount);
- frm.set_value('asset_quantity', asset_quantity);
- frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
- if(item.asset_location) { frm.set_value('location', item.asset_location); }
-
+ frm.set_value('gross_purchase_amount', purchase_amount);
+ frm.set_value('purchase_receipt_amount', purchase_amount);
+ frm.set_value('asset_quantity', asset_quantity);
+ frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
+ if(item.asset_location) { frm.set_value('location', item.asset_location); }
+ });
},
set_depreciation_rate: function(frm, row) {
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index cc23d9d..166e8c4 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -519,14 +519,11 @@
movement.cancel()
def cancel_capitalization(self):
- asset_capitalization = frappe.db.get_value(
- "Asset Capitalization",
- {"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"},
- )
-
- if asset_capitalization:
- asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization)
- asset_capitalization.cancel()
+ if self.capitalized_in:
+ self.db_set("capitalized_in", None)
+ asset_capitalization = frappe.get_doc("Asset Capitalization", self.capitalized_in)
+ if asset_capitalization.docstatus == 1:
+ asset_capitalization.cancel()
def delete_depreciation_entries(self):
if self.calculate_depreciation:
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index a93af94..df4593b 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -561,6 +561,8 @@
def reverse_depreciation_entry_made_after_disposal(asset, date):
for row in asset.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
+ if not asset_depr_schedule_doc:
+ continue
for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if schedule.schedule_date == date:
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index cad74df..5e251a5 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -146,6 +146,7 @@
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
+ frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
if asset_doc.docstatus == 1:
asset_doc.cancel()
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index afbea61..1ed719d 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -22,6 +22,7 @@
get_link_to_form,
getdate,
nowdate,
+ parse_json,
today,
)
@@ -833,6 +834,37 @@
self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
+ def append_taxes_from_item_tax_template(self):
+ if not frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template"):
+ return
+
+ for row in self.items:
+ item_tax_rate = row.get("item_tax_rate")
+ if not item_tax_rate:
+ continue
+
+ if isinstance(item_tax_rate, str):
+ item_tax_rate = parse_json(item_tax_rate)
+
+ for account_head, rate in item_tax_rate.items():
+ row = self.get_tax_row(account_head)
+
+ if not row:
+ self.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": account_head,
+ "rate": 0,
+ "description": account_head,
+ },
+ )
+
+ def get_tax_row(self, account_head):
+ for row in self.taxes:
+ if row.account_head == account_head:
+ return row
+
def set_other_charges(self):
self.set("taxes", [])
self.set_taxes()
@@ -1761,9 +1793,9 @@
def set_total_advance_paid(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
- if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"):
+ if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
party = self.customer
- if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"):
+ if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
party = self.supplier
advance = (
frappe.qb.from_(ple)
@@ -1829,9 +1861,9 @@
"docstatus": 1,
},
)
- if self.doctype in frappe.get_hooks("advance_payment_customer_doctypes"):
+ if self.doctype in frappe.get_hooks("advance_payment_receivable_doctypes"):
new_status = "Requested" if prs else "Not Requested"
- if self.doctype in frappe.get_hooks("advance_payment_supplier_doctypes"):
+ if self.doctype in frappe.get_hooks("advance_payment_payable_doctypes"):
new_status = "Initiated" if prs else "Not Initiated"
if new_status == self.advance_payment_status:
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 9555902..65d0872 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -881,7 +881,9 @@
"posting_time": self.posting_time,
"qty": -1 * item.consumed_qty,
"voucher_detail_no": item.name,
- "serial_and_batch_bundle": item.serial_and_batch_bundle,
+ "serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
+ "serial_no": item.get("serial_no"),
+ "batch_no": item.get("batch_no"),
}
)
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index f33fff0..14b7656 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -481,8 +481,8 @@
communication_doctypes = ["Customer", "Supplier"]
-advance_payment_customer_doctypes = ["Sales Order"]
-advance_payment_supplier_doctypes = ["Purchase Order"]
+advance_payment_receivable_doctypes = ["Sales Order"]
+advance_payment_payable_doctypes = ["Purchase Order"]
invoice_doctypes = ["Sales Invoice", "Purchase Invoice"]
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 6406735..f831a88 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -790,24 +790,25 @@
if (me.frm.doc.price_list_currency == company_currency) {
me.frm.set_value('plc_conversion_rate', 1.0);
}
- if (company_doc && company_doc.default_letter_head) {
- if(me.frm.fields_dict.letter_head) {
- me.frm.set_value("letter_head", company_doc.default_letter_head);
+ if (company_doc){
+ if (company_doc.default_letter_head) {
+ if(me.frm.fields_dict.letter_head) {
+ me.frm.set_value("letter_head", company_doc.default_letter_head);
+ }
+ }
+ let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
+ if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
+ selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
+ me.frm.set_value("tc_name", company_doc.default_selling_terms);
+ }
+ let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
+ "Material Request", "Purchase Receipt"];
+ // Purchase Invoice is excluded as per issue #3345
+ if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
+ buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
+ me.frm.set_value("tc_name", company_doc.default_buying_terms);
}
}
- let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
- if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
- selling_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
- me.frm.set_value("tc_name", company_doc.default_selling_terms);
- }
- let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
- "Material Request", "Purchase Receipt"];
- // Purchase Invoice is excluded as per issue #3345
- if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
- buying_doctypes_for_tc.indexOf(me.frm.doc.doctype) != -1) {
- me.frm.set_value("tc_name", company_doc.default_buying_terms);
- }
-
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
() => me.update_item_tax_map(),
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 2a4855e..86c7a72 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -609,6 +609,61 @@
quotation.items[0].conversion_factor = 2.23
self.assertRaises(frappe.ValidationError, quotation.save)
+ def test_item_tax_template_for_quotation(self):
+ from erpnext.stock.doctype.item.test_item import make_item
+
+ if not frappe.db.exists("Account", {"account_name": "_Test Vat", "company": "_Test Company"}):
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": "_Test Vat",
+ "company": "_Test Company",
+ "account_type": "Tax",
+ "root_type": "Asset",
+ "is_group": 0,
+ "parent_account": "Tax Assets - _TC",
+ "tax_rate": 10,
+ }
+ ).insert()
+
+ if not frappe.db.exists("Item Tax Template", "Vat Template - _TC"):
+ doc = frappe.get_doc(
+ {
+ "doctype": "Item Tax Template",
+ "name": "Vat Template",
+ "title": "Vat Template",
+ "company": "_Test Company",
+ "taxes": [
+ {
+ "tax_type": "_Test Vat - _TC",
+ "tax_rate": 5,
+ }
+ ],
+ }
+ ).insert()
+
+ item_doc = make_item("_Test Item Tax Template QTN", {"is_stock_item": 1})
+ if not frappe.db.exists(
+ "Item Tax", {"parent": item_doc.name, "item_tax_template": "Vat Template - _TC"}
+ ):
+ item_doc.append("taxes", {"item_tax_template": "Vat Template - _TC"})
+ item_doc.save()
+
+ quotation = make_quotation(
+ item_code="_Test Item Tax Template QTN", qty=1, rate=100, do_not_submit=1
+ )
+ self.assertFalse(quotation.taxes)
+
+ quotation.append_taxes_from_item_tax_template()
+ quotation.save()
+ self.assertTrue(quotation.taxes)
+ for row in quotation.taxes:
+ self.assertEqual(row.account_head, "_Test Vat - _TC")
+ self.assertAlmostEqual(row.base_tax_amount, quotation.total * 5 / 100)
+
+ item_doc.taxes = []
+ item_doc.save()
+
test_records = frappe.get_test_records("Quotation")
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 87aeeac..9599980 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -118,6 +118,7 @@
"oldfieldtype": "Link",
"options": "Item",
"print_width": "150px",
+ "reqd": 1,
"width": "150px"
},
{
@@ -908,7 +909,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-24 13:24:55.756320",
+ "modified": "2024-01-25 14:24:00.330219",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.py b/erpnext/selling/doctype/sales_order_item/sales_order_item.py
index 25f5b4b..fa7b9b9 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.py
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.py
@@ -43,7 +43,8 @@
gross_profit: DF.Currency
image: DF.Attach | None
is_free_item: DF.Check
- item_code: DF.Link | None
+ is_stock_item: DF.Check
+ item_code: DF.Link
item_group: DF.Link | None
item_name: DF.Data
item_tax_rate: DF.Code | None
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 3fdda2c..0f12f38 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1597,8 +1597,8 @@
{
"item_code": args.item or args.item_code or "_Test Item",
"warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty if args.get("qty") is not None else 1,
- "rate": args.rate if args.get("rate") is not None else 100,
+ "qty": args.get("qty", 1),
+ "rate": args.get("rate", 100),
"conversion_factor": 1.0,
"serial_and_batch_bundle": bundle_id,
"allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1,
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index d90b71a..03fe20b 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -514,6 +514,13 @@
schedule_date() {
set_schedule_date(this.frm);
}
+
+ qty(doc, cdt, cdn) {
+ var row = frappe.get_doc(cdt, cdn);
+ row.amount = flt(row.qty) * flt(row.rate);
+ frappe.model.set_value(cdt, cdn, "amount", row.amount);
+ refresh_field("amount", row.name, row.parentfield);
+ }
};
// for backward compatibility: combine new and previous states
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 57ba5bb..6f72684 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -21,9 +21,7 @@
get_serial_nos_from_bundle,
make_serial_batch_bundle,
)
-from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
-from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
class TestPurchaseReceipt(FrappeTestCase):
@@ -735,7 +733,6 @@
po.cancel()
def test_serial_no_against_purchase_receipt(self):
- from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
item_code = "Test Manual Created Serial No"
if not frappe.db.exists("Item", item_code):
@@ -1020,6 +1017,11 @@
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+ create_stock_reconciliation,
+ )
+ from erpnext.stock.get_item_details import get_valuation_rate
+ from erpnext.stock.utils import get_stock_balance
prepare_data_for_internal_transfer()
@@ -1034,6 +1036,22 @@
company="_Test Company with perpetual inventory",
)
+ if (
+ get_valuation_rate(
+ pr1.items[0].item_code, "_Test Company with perpetual inventory", warehouse="Stores - TCP1"
+ )
+ != 50
+ ):
+ balance = get_stock_balance(item_code=pr1.items[0].item_code, warehouse="Stores - TCP1")
+ create_stock_reconciliation(
+ item_code=pr1.items[0].item_code,
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ qty=balance,
+ rate=50,
+ do_not_save=True,
+ )
+
customer = "_Test Internal Customer 2"
company = "_Test Company with perpetual inventory"
@@ -1071,7 +1089,8 @@
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
expected_gle = [
- ["Stock In Hand - TCP1", 272.5, 0.0],
+ ["Stock In Hand - TCP1", 250.0, 0.0],
+ ["Cost of Goods Sold - TCP1", 22.5, 0.0],
["_Test Account Stock In Hand - TCP1", 0.0, 250.0],
["_Test Account Shipping Charges - TCP1", 0.0, 22.5],
]
diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js
index 6de5f00..fe6e83e 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.js
+++ b/erpnext/stock/report/stock_balance/stock_balance.js
@@ -99,7 +99,7 @@
"fieldname": 'ignore_closing_balance',
"label": __('Ignore Closing Balance'),
"fieldtype": 'Check',
- "default": 1
+ "default": 0
},
],
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index f29e7ea..76af5d7 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -289,6 +289,21 @@
in_rate = batch_obj.get_incoming_rate()
+ elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
+ in_rate = get_avg_purchase_rate(args.get("serial_no"))
+ elif (
+ args.get("batch_no")
+ and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
+ and not args.get("serial_and_batch_bundle")
+ ):
+ in_rate = get_batch_incoming_rate(
+ item_code=args.get("item_code"),
+ warehouse=args.get("warehouse"),
+ batch_no=args.get("batch_no"),
+ posting_date=args.get("posting_date"),
+ posting_time=args.get("posting_time"),
+ )
+
else:
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
@@ -319,6 +334,38 @@
return flt(in_rate)
+def get_batch_incoming_rate(
+ item_code, warehouse, batch_no, posting_date, posting_time, creation=None
+):
+
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+
+ timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
+ posting_date, posting_time
+ )
+ if creation:
+ timestamp_condition |= (
+ CombineDatetime(sle.posting_date, sle.posting_time)
+ == CombineDatetime(posting_date, posting_time)
+ ) & (sle.creation < creation)
+
+ batch_details = (
+ frappe.qb.from_(sle)
+ .select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
+ .where(
+ (sle.item_code == item_code)
+ & (sle.warehouse == warehouse)
+ & (sle.batch_no == batch_no)
+ & (sle.serial_and_batch_bundle.isnull())
+ & (sle.is_cancelled == 0)
+ )
+ .where(timestamp_condition)
+ ).run(as_dict=True)
+
+ if batch_details and batch_details[0].batch_qty:
+ return batch_details[0].batch_value / batch_details[0].batch_qty
+
+
def get_avg_purchase_rate(serial_nos):
"""get average value of serial numbers"""