Merge pull request #32412 from deepeshgarg007/tcs_deduction_issue
fix: Incorrect TCS amount deducted in Sales Invoice
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index b126d57..6f8b382 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1553,7 +1553,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2022-03-22 13:00:24.166684",
+ "modified": "2022-09-27 13:00:24.166684",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 6e3a076..fbe0ef3 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -239,14 +239,14 @@
frappe.bold(d.warehouse),
frappe.bold(d.qty),
)
- if flt(available_stock) <= 0:
+ if is_stock_item and flt(available_stock) <= 0:
frappe.throw(
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
d.idx, item_code, warehouse
),
title=_("Item Unavailable"),
)
- elif flt(available_stock) < flt(d.qty):
+ elif is_stock_item and flt(available_stock) < flt(d.qty):
frappe.throw(
_(
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -632,11 +632,12 @@
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
return bin_qty - pos_sales_qty, is_stock_item
else:
- is_stock_item = False
+ is_stock_item = True
if frappe.db.exists("Product Bundle", item_code):
return get_bundle_availability(item_code, warehouse), is_stock_item
else:
- # Is a service item
+ is_stock_item = False
+ # Is a service item or non_stock item
return 0, is_stock_item
@@ -650,7 +651,9 @@
available_qty = item_bin_qty - item_pos_reserved_qty
max_available_bundles = available_qty / item.qty
- if bundle_bin_qty > max_available_bundles:
+ if bundle_bin_qty > max_available_bundles and frappe.get_value(
+ "Item", item.item_code, "is_stock_item"
+ ):
bundle_bin_qty = max_available_bundles
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
@@ -740,3 +743,7 @@
]:
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
append_payment(payment_mode[0])
+
+
+def on_doctype_update():
+ frappe.db.add_index("POS Invoice", ["return_against"])
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 986fc03..3020e6d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -98,7 +98,6 @@
"section_break_44",
"apply_discount_on",
"base_discount_amount",
- "additional_discount_account",
"column_break_46",
"additional_discount_percentage",
"discount_amount",
@@ -1388,12 +1387,6 @@
"read_only": 1
},
{
- "fieldname": "additional_discount_account",
- "fieldtype": "Link",
- "label": "Additional Discount Account",
- "options": "Account"
- },
- {
"default": "0",
"fieldname": "ignore_default_payment_terms_template",
"fieldtype": "Check",
@@ -1445,7 +1438,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2022-09-13 23:39:54.525037",
+ "modified": "2022-09-27 11:07:55.766844",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index d185300..2b633cb 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -669,9 +669,6 @@
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
- enable_discount_accounting = cint(
- frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
- )
provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
@@ -1159,9 +1156,6 @@
def make_tax_gl_entries(self, gl_entries):
# tax table gl entries
valuation_tax = {}
- enable_discount_accounting = cint(
- frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
- )
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, None)
@@ -1249,15 +1243,6 @@
)
)
- @property
- def enable_discount_accounting(self):
- if not hasattr(self, "_enable_discount_accounting"):
- self._enable_discount_accounting = cint(
- frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
- )
-
- return self._enable_discount_accounting
-
def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account)
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 0a4f25b..f901257 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1543,6 +1543,37 @@
pi.save()
self.assertEqual(pi.items[0].conversion_factor, 1000)
+ def test_batch_expiry_for_purchase_invoice(self):
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
+ item = self.make_item(
+ "_Test Batch Item For Return Check",
+ {
+ "is_purchase_item": 1,
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "TBIRC.#####",
+ },
+ )
+
+ pi = make_purchase_invoice(
+ qty=1,
+ item_code=item.name,
+ update_stock=True,
+ )
+
+ pi.load_from_db()
+ batch_no = pi.items[0].batch_no
+ self.assertTrue(batch_no)
+
+ frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
+
+ return_pi = make_return_doc(pi.doctype, pi.name)
+ return_pi.save().submit()
+
+ self.assertTrue(return_pi.docstatus == 1)
+
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 7fa2fe2..fca7e3a 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -74,7 +74,6 @@
"manufacturer_part_no",
"accounting",
"expense_account",
- "discount_account",
"col_break5",
"is_fixed_asset",
"asset_location",
@@ -861,12 +860,6 @@
"read_only": 1
},
{
- "fieldname": "discount_account",
- "fieldtype": "Link",
- "label": "Discount Account",
- "options": "Account"
- },
- {
"fieldname": "product_bundle",
"fieldtype": "Link",
"label": "Product Bundle",
@@ -877,7 +870,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2022-06-17 05:31:10.520171",
+ "modified": "2022-09-27 10:54:23.980713",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e51938b..afd5a59 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -22,9 +22,12 @@
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import get_account_currency
from erpnext.assets.doctype.asset.depreciation import (
+ depreciate_asset,
get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain,
+ reset_depreciation_schedule,
+ reverse_depreciation_entry_made_after_disposal,
)
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController
@@ -1081,23 +1084,25 @@
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
- asset, item.base_net_amount, item.finance_book
+ asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
)
asset.db_set("disposal_date", None)
if asset.calculate_depreciation:
- self.reverse_depreciation_entry_made_after_disposal(asset)
- self.reset_depreciation_schedule(asset)
+ posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
+ reverse_depreciation_entry_made_after_disposal(asset, posting_date)
+ reset_depreciation_schedule(asset, self.posting_date)
else:
+ if asset.calculate_depreciation:
+ depreciate_asset(asset, self.posting_date)
+ asset.reload()
+
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
- asset, item.base_net_amount, item.finance_book
+ asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
)
asset.db_set("disposal_date", self.posting_date)
- if asset.calculate_depreciation:
- self.depreciate_asset(asset)
-
for gle in fixed_asset_gl_entries:
gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item))
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index a43a16c..5512d41 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -230,7 +230,7 @@
datasets: [{
color: 'green',
values: asset_values,
- formatted: asset_values.map(d => d.toFixed(2))
+ formatted: asset_values.map(d => d?.toFixed(2))
}]
},
type: 'line'
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 7438638..9794170 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -4,11 +4,12 @@
import frappe
from frappe import _
-from frappe.utils import cint, flt, getdate, today
+from frappe.utils import add_months, cint, flt, getdate, nowdate, today
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
+from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
def post_depreciation_entries(date=None, commit=True):
@@ -196,6 +197,11 @@
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
)
+ date = today()
+
+ depreciate_asset(asset, date)
+ asset.reload()
+
depreciation_series = frappe.get_cached_value(
"Company", asset.company, "series_for_depreciation_entry"
)
@@ -203,7 +209,7 @@
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series
- je.posting_date = today()
+ je.posting_date = date
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
@@ -214,7 +220,7 @@
je.flags.ignore_permissions = True
je.submit()
- frappe.db.set_value("Asset", asset_name, "disposal_date", today())
+ frappe.db.set_value("Asset", asset_name, "disposal_date", date)
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
asset.set_status("Scrapped")
@@ -225,6 +231,9 @@
def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
+ reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
+ reset_depreciation_schedule(asset, asset.disposal_date)
+
je = asset.journal_entry_for_scrap
asset.db_set("disposal_date", None)
@@ -235,7 +244,94 @@
asset.set_status()
-def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
+def depreciate_asset(asset, date):
+ asset.flags.ignore_validate_update_after_submit = True
+ asset.prepare_depreciation_data(date_of_disposal=date)
+ asset.save()
+
+ make_depreciation_entry(asset.name, date)
+
+
+def reset_depreciation_schedule(asset, date):
+ asset.flags.ignore_validate_update_after_submit = True
+
+ # recreate original depreciation schedule of the asset
+ asset.prepare_depreciation_data(date_of_return=date)
+
+ modify_depreciation_schedule_for_asset_repairs(asset)
+ asset.save()
+
+
+def modify_depreciation_schedule_for_asset_repairs(asset):
+ asset_repairs = frappe.get_all(
+ "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
+ )
+
+ for repair in asset_repairs:
+ if repair.increase_in_asset_life:
+ asset_repair = frappe.get_doc("Asset Repair", repair.name)
+ asset_repair.modify_depreciation_schedule()
+ asset.prepare_depreciation_data()
+
+
+def reverse_depreciation_entry_made_after_disposal(asset, date):
+ row = -1
+ finance_book = asset.get("schedules")[0].get("finance_book")
+ for schedule in asset.get("schedules"):
+ if schedule.finance_book != finance_book:
+ row = 0
+ finance_book = schedule.finance_book
+ else:
+ row += 1
+
+ if schedule.schedule_date == date:
+ if not disposal_was_made_on_original_schedule_date(
+ asset, schedule, row, date
+ ) or disposal_happens_in_the_future(date):
+
+ reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
+ reverse_journal_entry.posting_date = nowdate()
+ frappe.flags.is_reverse_depr_entry = True
+ reverse_journal_entry.submit()
+
+ frappe.flags.is_reverse_depr_entry = False
+ asset.flags.ignore_validate_update_after_submit = True
+ schedule.journal_entry = None
+ depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
+ asset.finance_books[0].value_after_depreciation += depreciation_amount
+ asset.save()
+
+
+def get_depreciation_amount_in_je(journal_entry):
+ if journal_entry.accounts[0].debit_in_account_currency:
+ return journal_entry.accounts[0].debit_in_account_currency
+ else:
+ return journal_entry.accounts[0].credit_in_account_currency
+
+
+# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
+def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
+ for finance_book in asset.get("finance_books"):
+ if schedule.finance_book == finance_book.finance_book:
+ orginal_schedule_date = add_months(
+ finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
+ )
+
+ if orginal_schedule_date == posting_date_of_disposal:
+ return True
+ return False
+
+
+def disposal_happens_in_the_future(posting_date_of_disposal):
+ if posting_date_of_disposal > getdate():
+ return True
+
+ return False
+
+
+def get_gl_entries_on_asset_regain(
+ asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
+):
(
fixed_asset_account,
asset,
@@ -247,28 +343,45 @@
) = get_asset_details(asset, finance_book)
gl_entries = [
- {
- "account": fixed_asset_account,
- "debit_in_account_currency": asset.gross_purchase_amount,
- "debit": asset.gross_purchase_amount,
- "cost_center": depreciation_cost_center,
- },
- {
- "account": accumulated_depr_account,
- "credit_in_account_currency": accumulated_depr_amount,
- "credit": accumulated_depr_amount,
- "cost_center": depreciation_cost_center,
- },
+ asset.get_gl_dict(
+ {
+ "account": fixed_asset_account,
+ "debit_in_account_currency": asset.gross_purchase_amount,
+ "debit": asset.gross_purchase_amount,
+ "cost_center": depreciation_cost_center,
+ "posting_date": getdate(),
+ },
+ item=asset,
+ ),
+ asset.get_gl_dict(
+ {
+ "account": accumulated_depr_account,
+ "credit_in_account_currency": accumulated_depr_amount,
+ "credit": accumulated_depr_amount,
+ "cost_center": depreciation_cost_center,
+ "posting_date": getdate(),
+ },
+ item=asset,
+ ),
]
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
if profit_amount:
- get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
+ get_profit_gl_entries(
+ asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
+ )
+
+ if voucher_type and voucher_no:
+ for entry in gl_entries:
+ entry["voucher_type"] = voucher_type
+ entry["voucher_no"] = voucher_no
return gl_entries
-def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
+def get_gl_entries_on_asset_disposal(
+ asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
+):
(
fixed_asset_account,
asset,
@@ -280,23 +393,38 @@
) = get_asset_details(asset, finance_book)
gl_entries = [
- {
- "account": fixed_asset_account,
- "credit_in_account_currency": asset.gross_purchase_amount,
- "credit": asset.gross_purchase_amount,
- "cost_center": depreciation_cost_center,
- },
- {
- "account": accumulated_depr_account,
- "debit_in_account_currency": accumulated_depr_amount,
- "debit": accumulated_depr_amount,
- "cost_center": depreciation_cost_center,
- },
+ asset.get_gl_dict(
+ {
+ "account": fixed_asset_account,
+ "credit_in_account_currency": asset.gross_purchase_amount,
+ "credit": asset.gross_purchase_amount,
+ "cost_center": depreciation_cost_center,
+ "posting_date": getdate(),
+ },
+ item=asset,
+ ),
+ asset.get_gl_dict(
+ {
+ "account": accumulated_depr_account,
+ "debit_in_account_currency": accumulated_depr_amount,
+ "debit": accumulated_depr_amount,
+ "cost_center": depreciation_cost_center,
+ "posting_date": getdate(),
+ },
+ item=asset,
+ ),
]
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
- get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
+ get_profit_gl_entries(
+ asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
+ )
+
+ if voucher_type and voucher_no:
+ for entry in gl_entries:
+ entry["voucher_type"] = voucher_type
+ entry["voucher_no"] = voucher_no
return gl_entries
@@ -333,15 +461,21 @@
)
-def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
+def get_profit_gl_entries(
+ asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
+):
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append(
- {
- "account": disposal_account,
- "cost_center": depreciation_cost_center,
- debit_or_credit: abs(profit_amount),
- debit_or_credit + "_in_account_currency": abs(profit_amount),
- }
+ asset.get_gl_dict(
+ {
+ "account": disposal_account,
+ "cost_center": depreciation_cost_center,
+ debit_or_credit: abs(profit_amount),
+ debit_or_credit + "_in_account_currency": abs(profit_amount),
+ "posting_date": getdate(),
+ },
+ item=asset,
+ )
)
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index e7af9bd..f72b524 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -4,7 +4,16 @@
import unittest
import frappe
-from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
+from frappe.utils import (
+ add_days,
+ add_months,
+ cstr,
+ flt,
+ get_first_day,
+ get_last_day,
+ getdate,
+ nowdate,
+)
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
@@ -178,28 +187,48 @@
self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_scrap_asset(self):
+ date = nowdate()
+ purchase_date = add_months(get_first_day(date), -2)
+
asset = create_asset(
calculate_depreciation=1,
- available_for_use_date="2020-01-01",
- purchase_date="2020-01-01",
+ available_for_use_date=purchase_date,
+ purchase_date=purchase_date,
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
- post_depreciation_entries(date=add_months("2020-01-01", 4))
+ post_depreciation_entries(date=add_months(purchase_date, 2))
+ asset.load_from_db()
+
+ accumulated_depr_amount = flt(
+ asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
+ asset.precision("gross_purchase_amount"),
+ )
+ self.assertEquals(accumulated_depr_amount, 18000.0)
scrap_asset(asset.name)
-
asset.load_from_db()
+
+ accumulated_depr_amount = flt(
+ asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
+ asset.precision("gross_purchase_amount"),
+ )
+ pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+ asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
+ )
+ pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
+ self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
+
self.assertEqual(asset.status, "Scrapped")
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
- ("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
+ ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
- ("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
+ ("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0),
)
gle = frappe.db.sql(
@@ -216,19 +245,27 @@
self.assertFalse(asset.journal_entry_for_scrap)
self.assertEqual(asset.status, "Partially Depreciated")
+ accumulated_depr_amount = flt(
+ asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
+ asset.precision("gross_purchase_amount"),
+ )
+ self.assertEquals(accumulated_depr_amount, 18000.0)
+
def test_gle_made_by_asset_sale(self):
+ date = nowdate()
+ purchase_date = add_months(get_first_day(date), -2)
+
asset = create_asset(
calculate_depreciation=1,
- available_for_use_date="2020-06-06",
- purchase_date="2020-01-01",
+ available_for_use_date=purchase_date,
+ purchase_date=purchase_date,
expected_value_after_useful_life=10000,
- total_number_of_depreciations=3,
- frequency_of_depreciation=10,
- depreciation_start_date="2020-12-31",
+ total_number_of_depreciations=10,
+ frequency_of_depreciation=1,
submit=1,
)
- post_depreciation_entries(date="2021-01-01")
+ post_depreciation_entries(date=add_months(purchase_date, 2))
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
si.customer = "_Test Customer"
@@ -239,10 +276,15 @@
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
+ pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+ asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
+ )
+ pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
+
expected_gle = (
- ("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
+ ("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
- ("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
+ ("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0),
("Debtors - _TC", 25000.0, 0.0),
)
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 2e6f0ad..08355f0 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -12,8 +12,11 @@
import erpnext
from erpnext.assets.doctype.asset.depreciation import (
+ depreciate_asset,
get_gl_entries_on_asset_disposal,
get_value_after_depreciation_on_disposal_date,
+ reset_depreciation_schedule,
+ reverse_depreciation_entry_made_after_disposal,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
@@ -424,11 +427,15 @@
asset = self.get_asset(item)
if asset.calculate_depreciation:
- self.depreciate_asset(asset)
+ depreciate_asset(asset, self.posting_date)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
- asset, item.asset_value, item.get("finance_book") or self.get("finance_book")
+ asset,
+ item.asset_value,
+ item.get("finance_book") or self.get("finance_book"),
+ self.get("doctype"),
+ self.get("name"),
)
asset.db_set("disposal_date", self.posting_date)
@@ -516,8 +523,8 @@
self.set_consumed_asset_status(asset)
if asset.calculate_depreciation:
- self.reverse_depreciation_entry_made_after_disposal(asset)
- self.reset_depreciation_schedule(asset)
+ reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
+ reset_depreciation_schedule(asset, self.posting_date)
def get_asset(self, item):
asset = frappe.get_doc("Asset", item.asset)
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index aad2607..28158a3 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -20,7 +20,6 @@
"maintain_same_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
- "enable_discount_accounting",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -134,13 +133,6 @@
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
- },
- {
- "default": "0",
- "description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
- "fieldname": "enable_discount_accounting",
- "fieldtype": "Check",
- "label": "Enable Discount Accounting for Buying"
}
],
"icon": "fa fa-cog",
@@ -148,7 +140,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2022-09-01 18:01:34.994657",
+ "modified": "2022-09-27 10:50:27.050252",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py
index 7b18cdb..be1ebde 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.py
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.py
@@ -5,15 +5,10 @@
import frappe
-from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document
-from frappe.utils import cint
class BuyingSettings(Document):
- def on_update(self):
- self.toggle_discount_accounting_fields()
-
def validate(self):
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
frappe.db.set_default(key, self.get(key, ""))
@@ -26,60 +21,3 @@
self.get("supp_master_name") == "Naming Series",
hide_name_field=False,
)
-
- def toggle_discount_accounting_fields(self):
- enable_discount_accounting = cint(self.enable_discount_accounting)
-
- make_property_setter(
- "Purchase Invoice Item",
- "discount_account",
- "hidden",
- not (enable_discount_accounting),
- "Check",
- validate_fields_for_doctype=False,
- )
- if enable_discount_accounting:
- make_property_setter(
- "Purchase Invoice Item",
- "discount_account",
- "mandatory_depends_on",
- "eval: doc.discount_amount",
- "Code",
- validate_fields_for_doctype=False,
- )
- else:
- make_property_setter(
- "Purchase Invoice Item",
- "discount_account",
- "mandatory_depends_on",
- "",
- "Code",
- validate_fields_for_doctype=False,
- )
-
- make_property_setter(
- "Purchase Invoice",
- "additional_discount_account",
- "hidden",
- not (enable_discount_accounting),
- "Check",
- validate_fields_for_doctype=False,
- )
- if enable_discount_accounting:
- make_property_setter(
- "Purchase Invoice",
- "additional_discount_account",
- "mandatory_depends_on",
- "eval: doc.discount_amount",
- "Code",
- validate_fields_for_doctype=False,
- )
- else:
- make_property_setter(
- "Purchase Invoice",
- "additional_discount_account",
- "mandatory_depends_on",
- "",
- "Code",
- validate_fields_for_doctype=False,
- )
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 8686cb5..22291a3 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -38,7 +38,6 @@
validate_party_frozen_disabled,
)
from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
-from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry
from erpnext.buying.utils import update_last_purchase_rate
from erpnext.controllers.print_settings import (
set_print_templates_for_item_table,
@@ -1891,88 +1890,6 @@
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
- def depreciate_asset(self, asset):
- asset.flags.ignore_validate_update_after_submit = True
- asset.prepare_depreciation_data(date_of_disposal=self.posting_date)
- asset.save()
-
- make_depreciation_entry(asset.name, self.posting_date)
-
- def reset_depreciation_schedule(self, asset):
- asset.flags.ignore_validate_update_after_submit = True
-
- # recreate original depreciation schedule of the asset
- asset.prepare_depreciation_data(date_of_return=self.posting_date)
-
- self.modify_depreciation_schedule_for_asset_repairs(asset)
- asset.save()
-
- def modify_depreciation_schedule_for_asset_repairs(self, asset):
- asset_repairs = frappe.get_all(
- "Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
- )
-
- for repair in asset_repairs:
- if repair.increase_in_asset_life:
- asset_repair = frappe.get_doc("Asset Repair", repair.name)
- asset_repair.modify_depreciation_schedule()
- asset.prepare_depreciation_data()
-
- def reverse_depreciation_entry_made_after_disposal(self, asset):
- from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
-
- posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry()
-
- row = -1
- finance_book = asset.get("schedules")[0].get("finance_book")
- for schedule in asset.get("schedules"):
- if schedule.finance_book != finance_book:
- row = 0
- finance_book = schedule.finance_book
- else:
- row += 1
-
- if schedule.schedule_date == posting_date_of_original_disposal:
- if not self.disposal_was_made_on_original_schedule_date(
- asset, schedule, row, posting_date_of_original_disposal
- ) or self.disposal_happens_in_the_future(posting_date_of_original_disposal):
-
- reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
- reverse_journal_entry.posting_date = nowdate()
- frappe.flags.is_reverse_depr_entry = True
- reverse_journal_entry.submit()
-
- frappe.flags.is_reverse_depr_entry = False
- asset.flags.ignore_validate_update_after_submit = True
- schedule.journal_entry = None
- asset.save()
-
- def get_posting_date_of_disposal_entry(self):
- if self.doctype == "Sales Invoice" and self.return_against:
- return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
- else:
- return self.posting_date
-
- # if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
- def disposal_was_made_on_original_schedule_date(
- self, asset, schedule, row, posting_date_of_disposal
- ):
- for finance_book in asset.get("finance_books"):
- if schedule.finance_book == finance_book.finance_book:
- orginal_schedule_date = add_months(
- finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
- )
-
- if orginal_schedule_date == posting_date_of_disposal:
- return True
- return False
-
- def disposal_happens_in_the_future(self, posting_date_of_disposal):
- if posting_date_of_disposal > getdate():
- return True
-
- return False
-
@frappe.whitelist()
def get_tax_rate(account_head):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 4f8b5c7..8eae0a0 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -212,21 +212,15 @@
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
- # these are handled separately
- ignored_search_fields = ("item_name", "description")
- for ignored_field in ignored_search_fields:
- if ignored_field in searchfields:
- searchfields.remove(ignored_field)
-
columns = ""
- extra_searchfields = [
- field
- for field in searchfields
- if not field in ["name", "item_group", "description", "item_name"]
- ]
+ extra_searchfields = [field for field in searchfields if not field in ["name", "description"]]
if extra_searchfields:
- columns = ", " + ", ".join(extra_searchfields)
+ columns += ", " + ", ".join(extra_searchfields)
+
+ if "description" in searchfields:
+ columns += """, if(length(tabItem.description) > 40, \
+ concat(substr(tabItem.description, 1, 40), "..."), description) as description"""
searchfields = searchfields + [
field
@@ -266,12 +260,10 @@
if frappe.db.count(doctype, cache=True) < 50000:
# scan description only if items are less than 50000
description_cond = "or tabItem.description LIKE %(txt)s"
+
return frappe.db.sql(
"""select
- tabItem.name, tabItem.item_name, tabItem.item_group,
- if(length(tabItem.description) > 40, \
- concat(substr(tabItem.description, 1, 40), "..."), description) as description
- {columns}
+ tabItem.name {columns}
from tabItem
where tabItem.docstatus < 2
and tabItem.disabled=0
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index 202a880..6bc88d1 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -841,7 +841,7 @@
for fg_item_code in fg_item_code_list:
for rm_item in rm_items:
- if rm_item.get("main_item_code") or rm_item.get("item_code") == fg_item_code:
+ if rm_item.get("main_item_code") == fg_item_code or rm_item.get("item_code") == fg_item_code:
rm_item_code = rm_item.get("rm_item_code")
items_dict = {
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index a08feb4..b8f51f8 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -391,12 +391,12 @@
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
],
"hourly": [
- "erpnext.accounts.doctype.subscription.subscription.process_all",
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
"erpnext.projects.doctype.project.project.hourly_reminder",
"erpnext.projects.doctype.project.project.collect_project_status",
],
"hourly_long": [
+ "erpnext.accounts.doctype.subscription.subscription.process_all",
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
],
@@ -508,6 +508,7 @@
"Landed Cost Item",
"Asset Value Adjustment",
"Asset Repair",
+ "Asset Capitalization",
"Loyalty Program",
"Stock Reconciliation",
"POS Profile",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 20f1503..f3640b9 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -557,37 +557,52 @@
if(!frm.doc.skip_transfer){
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
- if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))
- && frm.doc.status != 'Stopped') {
- frm.has_finish_btn = true;
+ if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') {
+ if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
+ frm.has_finish_btn = true;
- if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
- // Only show "Material Consumption" when required_qty > consumed_qty
- var counter = 0;
- var tbl = frm.doc.required_items || [];
- var tbl_lenght = tbl.length;
- for (var i = 0, len = tbl_lenght; i < len; i++) {
- let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
- if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
- counter += 1;
+ if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
+ // Only show "Material Consumption" when required_qty > consumed_qty
+ var counter = 0;
+ var tbl = frm.doc.required_items || [];
+ var tbl_lenght = tbl.length;
+ for (var i = 0, len = tbl_lenght; i < len; i++) {
+ let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
+ if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
+ counter += 1;
+ }
+ }
+ if (counter > 0) {
+ var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
+ const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
+ erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
+ });
+ consumption_btn.addClass('btn-primary');
}
}
- if (counter > 0) {
- var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
- const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
- erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
- });
- consumption_btn.addClass('btn-primary');
+
+ var finish_btn = frm.add_custom_button(__('Finish'), function() {
+ erpnext.work_order.make_se(frm, 'Manufacture');
+ });
+
+ if(doc.material_transferred_for_manufacturing>=doc.qty) {
+ // all materials transferred for manufacturing, make this primary
+ finish_btn.addClass('btn-primary');
}
- }
+ } else {
+ frappe.db.get_doc("Manufacturing Settings").then((doc) => {
+ let allowance_percentage = doc.overproduction_percentage_for_work_order;
- var finish_btn = frm.add_custom_button(__('Finish'), function() {
- erpnext.work_order.make_se(frm, 'Manufacture');
- });
+ if (allowance_percentage > 0) {
+ let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
- if(doc.material_transferred_for_manufacturing>=doc.qty) {
- // all materials transferred for manufacturing, make this primary
- finish_btn.addClass('btn-primary');
+ if ((flt(doc.produced_qty) < allowed_qty)) {
+ frm.add_custom_button(__('Finish'), function() {
+ erpnext.work_order.make_se(frm, 'Manufacture');
+ });
+ }
+ }
+ });
}
}
} else {
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2a0ca8c..fc63f12 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -315,3 +315,4 @@
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
+erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
diff --git a/erpnext/patches/v13_0/update_old_loans.py b/erpnext/patches/v13_0/update_old_loans.py
index a1d40b7..0bd3fcd 100644
--- a/erpnext/patches/v13_0/update_old_loans.py
+++ b/erpnext/patches/v13_0/update_old_loans.py
@@ -100,6 +100,7 @@
"mode_of_payment": loan.mode_of_payment,
"loan_account": loan.loan_account,
"payment_account": loan.payment_account,
+ "disbursement_account": loan.payment_account,
"interest_income_account": loan.interest_income_account,
"penalty_income_account": loan.penalty_income_account,
},
@@ -190,6 +191,7 @@
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account
+ loan_type_doc.disbursement_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account
diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_for_asset_capitalization.py b/erpnext/patches/v14_0/create_accounting_dimensions_for_asset_capitalization.py
new file mode 100644
index 0000000..09e20a9
--- /dev/null
+++ b/erpnext/patches/v14_0/create_accounting_dimensions_for_asset_capitalization.py
@@ -0,0 +1,31 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+
+
+def execute():
+ accounting_dimensions = frappe.db.get_all(
+ "Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
+ )
+
+ if not accounting_dimensions:
+ return
+
+ doctype = "Asset Capitalization"
+
+ for d in accounting_dimensions:
+ field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
+
+ if field:
+ continue
+
+ df = {
+ "fieldname": d.fieldname,
+ "label": d.label,
+ "fieldtype": "Link",
+ "options": d.document_type,
+ "insert_after": "accounting_dimensions_section",
+ }
+
+ create_custom_field(doctype, df, ignore_validate=True)
+
+ frappe.clear_cache(doctype=doctype)
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index da7576e..24375d8 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -660,7 +660,7 @@
} else {
return;
}
- } else if (available_qty < qty_needed) {
+ } else if (is_stock_item && available_qty < qty_needed) {
frappe.throw({
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
indicator: 'orange'
@@ -694,7 +694,7 @@
callback(res) {
if (!me.item_stock_map[item_code])
me.item_stock_map[item_code] = {};
- me.item_stock_map[item_code][warehouse] = res.message[0];
+ me.item_stock_map[item_code][warehouse] = res.message;
}
});
}
diff --git a/erpnext/selling/page/point_of_sale/pos_item_details.js b/erpnext/selling/page/point_of_sale/pos_item_details.js
index b75ffb2..f9b5bb2 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_details.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_details.js
@@ -242,13 +242,14 @@
if (this.value) {
me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
me.item_stock_map = me.events.get_item_stock_map();
- const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value];
+ const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0];
+ const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]);
if (available_qty === undefined) {
me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
// item stock map is updated now reset warehouse
me.warehouse_control.set_value(this.value);
})
- } else if (available_qty === 0) {
+ } else if (available_qty === 0 && is_stock_item) {
me.warehouse_control.set_value('');
const bold_item_code = me.item_row.item_code.bold();
const bold_warehouse = this.value.bold();
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 7e1476d..e61f0f5 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -10,6 +10,31 @@
frm.add_fetch('attribute', 'to_range', 'to_range');
frm.add_fetch('attribute', 'increment', 'increment');
frm.add_fetch('tax_type', 'tax_rate', 'tax_rate');
+
+ frm.make_methods = {
+ 'Sales Order': () => {
+ open_form(frm, "Sales Order", "Sales Order Item", "items");
+ },
+ 'Delivery Note': () => {
+ open_form(frm, "Delivery Note", "Delivery Note Item", "items");
+ },
+ 'Sales Invoice': () => {
+ open_form(frm, "Sales Invoice", "Sales Invoice Item", "items");
+ },
+ 'Purchase Order': () => {
+ open_form(frm, "Purchase Order", "Purchase Order Item", "items");
+ },
+ 'Purchase Receipt': () => {
+ open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items");
+ },
+ 'Purchase Invoice': () => {
+ open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items");
+ },
+ 'Material Request': () => {
+ open_form(frm, "Material Request", "Material Request Item", "items");
+ },
+ };
+
},
onload: function(frm) {
erpnext.item.setup_queries(frm);
@@ -858,3 +883,17 @@
];
+
+function open_form(frm, doctype, child_doctype, parentfield) {
+ frappe.model.with_doctype(doctype, () => {
+ let new_doc = frappe.model.get_new_doc(doctype);
+
+ let new_child_doc = frappe.model.add_child(new_doc, child_doctype, parentfield);
+ new_child_doc.item_code = frm.doc.name;
+ new_child_doc.item_name = frm.doc.item_name;
+ new_child_doc.uom = frm.doc.stock_uom;
+ new_child_doc.description = frm.doc.description;
+
+ frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
+ });
+}
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 1cee553..e35c8bf 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -5,6 +5,7 @@
import json
import frappe
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.test_runner import make_test_objects
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today
@@ -816,6 +817,30 @@
item.reload()
self.assertEqual(item.is_stock_item, 1)
+ def test_serach_fields_for_item(self):
+ from erpnext.controllers.queries import item_query
+
+ make_property_setter("Item", None, "search_fields", "item_name", "Data", for_doctype="Doctype")
+
+ item = make_item(properties={"item_name": "Test Item", "description": "Test Description"})
+ data = item_query(
+ "Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
+ )
+ self.assertEqual(data[0].name, item.name)
+ self.assertEqual(data[0].item_name, item.item_name)
+ self.assertTrue("description" not in data[0])
+
+ make_property_setter(
+ "Item", None, "search_fields", "item_name, description", "Data", for_doctype="Doctype"
+ )
+ data = item_query(
+ "Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
+ )
+ self.assertEqual(data[0].name, item.name)
+ self.assertEqual(data[0].item_name, item.item_name)
+ self.assertEqual(data[0].description, item.description)
+ self.assertTrue("description" in data[0])
+
def set_item_variant_settings(fields):
doc = frappe.get_doc("Item Variant Settings")
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index b77c3a5..6269724 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1241,6 +1241,37 @@
self.assertEqual(query[0].value, 0)
+ def test_batch_expiry_for_purchase_receipt(self):
+ from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
+ item = make_item(
+ "_Test Batch Item For Return Check",
+ {
+ "is_purchase_item": 1,
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "TBIRC.#####",
+ },
+ )
+
+ pi = make_purchase_receipt(
+ qty=1,
+ item_code=item.name,
+ update_stock=True,
+ )
+
+ pi.load_from_db()
+ batch_no = pi.items[0].batch_no
+ self.assertTrue(batch_no)
+
+ frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
+
+ return_pi = make_return_doc(pi.doctype, pi.name)
+ return_pi.save().submit()
+
+ self.assertTrue(return_pi.docstatus == 1)
+
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 738ac33..8bcd772 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1073,8 +1073,8 @@
# No work order could mean independent Manufacture entry, if so skip validation
if self.work_order and self.fg_completed_qty > allowed_qty:
frappe.throw(
- _("For quantity {0} should not be greater than work order quantity {1}").format(
- flt(self.fg_completed_qty), wo_qty
+ _("For quantity {0} should not be greater than allowed quantity {1}").format(
+ flt(self.fg_completed_qty), allowed_qty
)
)
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 329cd7d..f7f8cbe 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -153,6 +153,9 @@
def validate_batch(self):
if self.batch_no and self.voucher_type != "Stock Entry":
+ if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0:
+ return
+
expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
if expiry_date:
if getdate(self.posting_date) > getdate(expiry_date):
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index a10870d..ec1d497 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -18,7 +18,7 @@
<b class="caret"></b>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
- {% if doc.doctype == 'Purchase Order' %}
+ {% if doc.doctype == 'Purchase Order' and show_make_pi_button %}
<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
{% endif %}
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index 3e6d57a..e1e12bd 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -52,6 +52,9 @@
)
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
+ # show Make Purchase Invoice button based on permission
+ context.show_make_pi_button = frappe.has_permission("Purchase Invoice", "create")
+
def get_attachments(dt, dn):
return frappe.get_all(
diff --git a/erpnext/www/book_appointment/index.html b/erpnext/www/book_appointment/index.html
index 207175f..ad964d9 100644
--- a/erpnext/www/book_appointment/index.html
+++ b/erpnext/www/book_appointment/index.html
@@ -12,8 +12,8 @@
<!-- title: Book an appointment -->
<div id="select-date-time">
<div class="text-center mt-5">
- <h3>Book an appointment</h3>
- <p class="lead text-muted" id="lead-text">Select the date and your timezone</p>
+ <h3>{{ _("Book an appointment") }}</h3>
+ <p class="lead text-muted" id="lead-text">{{ _("Select the date and your timezone") }}</p>
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-6 align-self-center ">
@@ -31,7 +31,7 @@
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-4 mb-3">
- <button class="btn btn-primary form-control" id="next-button">Next</button>
+ <button class="btn btn-primary form-control" id="next-button">{{ _("Next") }}</button>
</div>
</div>
</div>
@@ -39,24 +39,24 @@
<!--Enter Details-->
<div id="enter-details" class="mb-5">
<div class="text-center mt-5">
- <h3>Add details</h3>
- <p class="lead text-muted">Selected date is <span class="date-span"></span> at <span class="time-span">
+ <h3>{{ _("Add details") }}</h3>
+ <p class="lead text-muted">{{ _("Selected date is") }} <span class="date-span"></span> {{ _("at") }} <span class="time-span">
</span></p>
</div>
<div class="row justify-content-center mt-3">
<div class="col-md-4 align-items-center">
<form id="customer-form" action='#'>
- <input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name (required)" required>
+ <input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="{{ _('Your Name (required)') }}" required>
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number" placeholder="+910000000000">
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype">
- <input class="form-control mt-3"type="email" name="customer_email" id="customer_email" placeholder="Email Address (required)" required>
+ <input class="form-control mt-3"type="email" name="customer_email" id="customer_email" placeholder="{{ _('Email Address (required)') }}" required>
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
- placeholder="Notes"></textarea>
+ placeholder="{{ _('Notes') }}"></textarea>
</form>
<div class="row mt-3 " id="submit-button-area">
- <div class="col-md mt-3" style="grid-area: back;"><button class="btn btn-dark form-control" onclick="initialise_select_date()">Go back</button></div>
- <div class="col-md mt-3" style="grid-area: submit;"><button class="btn btn-primary form-control " onclick="submit()" id="submit-button">Submit</button></div>
+ <div class="col-md mt-3" style="grid-area: back;"><button class="btn btn-dark form-control" onclick="initialise_select_date()">{{ _("Go back") }}</button></div>
+ <div class="col-md mt-3" style="grid-area: submit;"><button class="btn btn-primary form-control " onclick="submit()" id="submit-button">{{ _("Submit") }}</button></div>
</div>
</div>
</div>
diff --git a/erpnext/www/book_appointment/index.js b/erpnext/www/book_appointment/index.js
index 5562cbd..46ac155 100644
--- a/erpnext/www/book_appointment/index.js
+++ b/erpnext/www/book_appointment/index.js
@@ -69,7 +69,7 @@
window.selected_timezone = timezone.value;
update_time_slots(date_picker.value, timezone.value);
let lead_text = document.getElementById('lead-text');
- lead_text.innerHTML = "Select Time"
+ lead_text.innerHTML = __("Select Time")
}
async function get_time_slots(date, timezone) {
@@ -89,7 +89,7 @@
clear_time_slots();
if (window.slots.length <= 0) {
let message_div = document.createElement('p');
- message_div.innerHTML = "There are no slots available on this date";
+ message_div.innerHTML = __("There are no slots available on this date");
timeslot_container.appendChild(message_div);
return
}
@@ -128,7 +128,7 @@
let start_time_string = moment(time).tz(timezone).format("LT");
let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes');
let end_time_string = end_time.format("LT");
- return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">to ${end_time_string}</span>`;
+ return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">${__("to") } ${end_time_string}</span>`;
}
function select_time() {
@@ -227,9 +227,9 @@
},
callback: (response)=>{
if (response.message.status == "Unverified") {
- frappe.show_alert("Please check your email to confirm the appointment")
+ frappe.show_alert(__("Please check your email to confirm the appointment"))
} else {
- frappe.show_alert("Appointment Created Successfully");
+ frappe.show_alert(__("Appointment Created Successfully"));
}
setTimeout(()=>{
let redirect_url = "/";
@@ -239,7 +239,7 @@
window.location.href = redirect_url;},5000)
},
error: (err)=>{
- frappe.show_alert("Something went wrong please try again");
+ frappe.show_alert(__("Something went wrong please try again"));
button.disabled = false;
}
});
diff --git a/erpnext/www/book_appointment/verify/index.html b/erpnext/www/book_appointment/verify/index.html
index 9bcd3d2..58c07e8 100644
--- a/erpnext/www/book_appointment/verify/index.html
+++ b/erpnext/www/book_appointment/verify/index.html
@@ -8,11 +8,11 @@
{% if success==True %}
<div class="alert alert-success">
- Your email has been verified and your appointment has been scheduled
+ {{ _("Your email has been verified and your appointment has been scheduled") }}
</div>
{% else %}
<div class="alert alert-danger">
- Verification failed please check the link
+ {{ _("Verification failed please check the link") }}
</div>
{% endif %}
{% endblock%}