diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 64071597eab..08656d1c0be 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -4,6 +4,7 @@ import frappe from frappe import _ +from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate from six import iteritems @@ -675,18 +676,22 @@ def get_bin_qty(item_code, warehouse): def get_pos_reserved_qty(item_code, warehouse): - reserved_qty = frappe.db.sql( - """select sum(p_item.qty) as qty - from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item - where p.name = p_item.parent - and ifnull(p.consolidated_invoice, '') = '' - and p_item.docstatus = 1 - and p_item.item_code = %s - and p_item.warehouse = %s - """, - (item_code, warehouse), - as_dict=1, - ) + p_inv = frappe.qb.DocType("POS Invoice") + p_item = frappe.qb.DocType("POS Invoice Item") + + reserved_qty = ( + frappe.qb.from_(p_inv) + .from_(p_item) + .select(Sum(p_item.qty).as_("qty")) + .where( + (p_inv.name == p_item.parent) + & (IfNull(p_inv.consolidated_invoice, "") == "") + & (p_inv.is_return == 0) + & (p_item.docstatus == 1) + & (p_item.item_code == item_code) + & (p_item.warehouse == warehouse) + ) + ).run(as_dict=True) return reserved_qty[0].qty or 0 if reserved_qty else 0 diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index dde3947bd9d..b178f792d7a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -1107,7 +1107,7 @@ class SalesInvoice(SellingController): 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.posting_date ) asset.db_set("disposal_date", None) @@ -1122,7 +1122,7 @@ class SalesInvoice(SellingController): 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.posting_date ) asset.db_set("disposal_date", self.posting_date) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 35213121b32..c93d53fcb94 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -666,7 +666,7 @@ class GrossProfitGenerator(object): def load_invoice_items(self): conditions = "" if self.filters.company: - conditions += " and company = %(company)s" + conditions += " and `tabSales Invoice`.company = %(company)s" if self.filters.from_date: conditions += " and posting_date >= %(from_date)s" if self.filters.to_date: diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index d6de519d232..7c25850976b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -339,13 +339,9 @@ class Asset(AccountsController): if should_get_last_day: schedule_date = get_last_day(schedule_date) - # schedule date will be a year later from start date - # so monthly schedule date is calculated by removing 11 months from it - monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1) - # if asset is being sold if date_of_disposal: - from_date = self.get_from_date(finance_book.finance_book) + from_date = self.get_from_date_for_disposal(finance_book) depreciation_amount, days, months = self.get_pro_rata_amt( finance_book, depreciation_amount, @@ -368,14 +364,20 @@ class Asset(AccountsController): break # For first row - if ( - (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata) - and not self.opening_accumulated_depreciation - and n == 0 - ): - from_date = add_days( - self.available_for_use_date, -1 - ) # needed to calc depr amount for available_for_use_date too + if n == 0 and has_pro_rata and not self.opening_accumulated_depreciation: + from_date = add_days(self.available_for_use_date, -1) + depreciation_amount, days, months = self.get_pro_rata_amt( + finance_book, + depreciation_amount, + from_date, + finance_book.depreciation_start_date, + has_wdv_or_dd_non_yearly_pro_rata, + ) + elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation: + from_date = add_months( + getdate(self.available_for_use_date), + (self.number_of_depreciations_booked * finance_book.frequency_of_depreciation), + ) depreciation_amount, days, months = self.get_pro_rata_amt( finance_book, depreciation_amount, @@ -383,10 +385,6 @@ class Asset(AccountsController): finance_book.depreciation_start_date, has_wdv_or_dd_non_yearly_pro_rata, ) - - # For first depr schedule date will be the start date - # so monthly schedule date is calculated by removing month difference between use date and start date - monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1) # For last row elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: @@ -411,9 +409,7 @@ class Asset(AccountsController): depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book ) - monthly_schedule_date = add_months(schedule_date, 1) schedule_date = add_days(schedule_date, days) - last_schedule_date = schedule_date if not depreciation_amount: continue @@ -490,16 +486,19 @@ class Asset(AccountsController): for idx, s in enumerate(self.schedules, 1): s.idx = idx - def get_from_date(self, finance_book): + def get_from_date_for_disposal(self, finance_book): if not self.get("schedules"): - return self.available_for_use_date + return add_months( + getdate(self.available_for_use_date), + (self.number_of_depreciations_booked * finance_book.frequency_of_depreciation), + ) if len(self.finance_books) == 1: return self.schedules[-1].schedule_date from_date = "" for schedule in self.get("schedules"): - if schedule.finance_book == finance_book: + if schedule.finance_book == finance_book.finance_book: from_date = schedule.schedule_date if from_date: diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 56d2fef2209..0cfe328270a 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -279,7 +279,7 @@ def scrap_asset(asset_name): je.company = asset.company je.remark = "Scrap Entry for asset {0}".format(asset_name) - for entry in get_gl_entries_on_asset_disposal(asset): + for entry in get_gl_entries_on_asset_disposal(asset, date): entry.update({"reference_type": "Asset", "reference_name": asset_name}) je.append("accounts", entry) @@ -403,7 +403,10 @@ def disposal_happens_in_the_future(posting_date_of_disposal): return False -def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): +def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None, date=None): + if not date: + date = getdate() + ( fixed_asset_account, asset, @@ -420,23 +423,30 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None): "debit_in_account_currency": asset.gross_purchase_amount, "debit": asset.gross_purchase_amount, "cost_center": depreciation_cost_center, + "posting_date": date, }, { "account": accumulated_depr_account, "credit_in_account_currency": accumulated_depr_amount, "credit": accumulated_depr_amount, "cost_center": depreciation_cost_center, + "posting_date": date, }, ] 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( + profit_amount, gl_entries, disposal_account, depreciation_cost_center, date + ) 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, date=None): + if not date: + date = getdate() + ( fixed_asset_account, asset, @@ -453,18 +463,22 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None) "credit_in_account_currency": asset.gross_purchase_amount, "credit": asset.gross_purchase_amount, "cost_center": depreciation_cost_center, + "posting_date": date, }, { "account": accumulated_depr_account, "debit_in_account_currency": accumulated_depr_amount, "debit": accumulated_depr_amount, "cost_center": depreciation_cost_center, + "posting_date": date, }, ] 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( + profit_amount, gl_entries, disposal_account, depreciation_cost_center, date + ) return gl_entries @@ -491,7 +505,12 @@ def get_asset_details(asset, finance_book=None): ) -def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center): +def get_profit_gl_entries( + profit_amount, gl_entries, disposal_account, depreciation_cost_center, date=None +): + if not date: + date = getdate() + debit_or_credit = "debit" if profit_amount < 0 else "credit" gl_entries.append( { @@ -499,6 +518,7 @@ def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciat "cost_center": depreciation_cost_center, debit_or_credit: abs(profit_amount), debit_or_credit + "_in_account_currency": abs(profit_amount), + "posting_date": date, } ) diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index 55154385947..4988846463d 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -298,6 +298,79 @@ class TestAsset(AssetSetup): si.cancel() self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") + def test_gle_made_by_asset_sale_for_existing_asset(self): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + + asset = create_asset( + calculate_depreciation=1, + available_for_use_date="2020-04-01", + purchase_date="2020-04-01", + expected_value_after_useful_life=0, + total_number_of_depreciations=5, + number_of_depreciations_booked=2, + frequency_of_depreciation=12, + depreciation_start_date="2023-03-31", + opening_accumulated_depreciation=24000, + gross_purchase_amount=60000, + submit=1, + ) + + expected_depr_values = [ + ["2023-03-31", 12000, 36000], + ["2024-03-31", 12000, 48000], + ["2025-03-31", 12000, 60000], + ] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_depr_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_depr_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_depr_values[i][2], schedule.accumulated_depreciation_amount) + + post_depreciation_entries(date="2023-03-31") + + si = create_sales_invoice( + item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23") + ) + asset.load_from_db() + + self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") + + expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]] + + for i, schedule in enumerate(asset.schedules): + self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date) + self.assertEqual(expected_values[i][1], schedule.depreciation_amount) + self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount) + self.assertTrue(schedule.journal_entry) + + expected_gle = ( + ( + "_Test Accumulated Depreciations - _TC", + 37742.47, + 0.0, + ), + ( + "_Test Fixed Asset - _TC", + 0.0, + 60000.0, + ), + ( + "_Test Gain/Loss on Asset Disposal - _TC", + 0.0, + 17742.47, + ), + ("Debtors - _TC", 40000.0, 0.0), + ) + + gle = frappe.db.sql( + """select account, debit, credit from `tabGL Entry` + where voucher_type='Sales Invoice' and voucher_no = %s + order by account""", + si.name, + ) + + self.assertSequenceEqual(gle, expected_gle) + def test_asset_with_maintenance_required_status_after_sale(self): asset = create_asset( calculate_depreciation=1, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 3a08051b2f3..cd947a8a564 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -678,7 +678,7 @@ class StockController(AccountsController): message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link) return message - def repost_future_sle_and_gle(self): + def repost_future_sle_and_gle(self, force=False): args = frappe._dict( { "posting_date": self.posting_date, @@ -689,7 +689,10 @@ class StockController(AccountsController): } ) - if future_sle_exists(args) or repost_required_for_queue(self): + if self.docstatus == 2: + force = True + + if force or future_sle_exists(args) or repost_required_for_queue(self): item_based_reposting = cint( frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting") ) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index db19e0d8ba4..60d3f78534e 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1,17 +1,18 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -import datetime -import math +from datetime import date import frappe from frappe import _, msgprint from frappe.model.naming import make_autoname from frappe.utils import ( add_days, + ceil, cint, cstr, date_diff, + floor, flt, formatdate, get_first_day, @@ -57,8 +58,10 @@ class SalarySlip(TransactionBase): "float": float, "long": int, "round": round, - "date": datetime.date, + "date": date, "getdate": getdate, + "ceil": ceil, + "floor": floor, } def autoname(self): @@ -959,7 +962,7 @@ class SalarySlip(TransactionBase): tax_slab.allow_tax_exemption, payroll_period=payroll_period ) future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * ( - math.ceil(remaining_sub_periods) - 1 + ceil(remaining_sub_periods) - 1 ) # get taxable_earnings, addition_earnings for current actual payment days diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py index b9e2ed1beb9..78d6417bfa8 100644 --- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py @@ -294,3 +294,19 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin): self.assertRaises(frappe.ValidationError, riv.save) accounts_settings.acc_frozen_upto = "" accounts_settings.save() + + def test_create_repost_entry_for_cancelled_document(self): + pr = make_purchase_receipt( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + get_multiple_items=True, + ) + + self.assertTrue(pr.docstatus == 1) + self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name})) + + pr.load_from_db() + + pr.cancel() + self.assertTrue(pr.docstatus == 2) + self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name})) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 4d96e544676..8d6e93fa8c5 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2271,7 +2271,7 @@ def move_sample_to_retention_warehouse(company, items): "basic_rate": item.get("valuation_rate"), "uom": item.get("uom"), "stock_uom": item.get("stock_uom"), - "conversion_factor": 1.0, + "conversion_factor": item.get("conversion_factor") or 1.0, "serial_no": sample_serial_nos, "batch_no": item.get("batch_no"), },