Merge branch 'develop' into fix-payment-request-fetch-swift-number

This commit is contained in:
Deepesh Garg 2022-10-01 16:14:27 +05:30 committed by GitHub
commit 1c560a967c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 327 additions and 254 deletions

View File

@ -98,7 +98,6 @@
"section_break_44", "section_break_44",
"apply_discount_on", "apply_discount_on",
"base_discount_amount", "base_discount_amount",
"additional_discount_account",
"column_break_46", "column_break_46",
"additional_discount_percentage", "additional_discount_percentage",
"discount_amount", "discount_amount",
@ -1387,12 +1386,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "additional_discount_account",
"fieldtype": "Link",
"label": "Additional Discount Account",
"options": "Account"
},
{ {
"default": "0", "default": "0",
"fieldname": "ignore_default_payment_terms_template", "fieldname": "ignore_default_payment_terms_template",
@ -1445,7 +1438,7 @@
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-09-13 23:39:54.525037", "modified": "2022-09-27 11:07:55.766844",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@ -669,9 +669,6 @@ class PurchaseInvoice(BuyingController):
exchange_rate_map, net_rate_map = get_purchase_document_details(self) 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( provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value( frappe.db.get_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items" "Company", self.company, "enable_provisional_accounting_for_non_stock_items"
@ -1159,9 +1156,6 @@ class PurchaseInvoice(BuyingController):
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
# tax table gl entries # tax table gl entries
valuation_tax = {} valuation_tax = {}
enable_discount_accounting = cint(
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
)
for tax in self.get("taxes"): for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, None) amount, base_amount = self.get_tax_amounts(tax, None)
@ -1249,15 +1243,6 @@ class PurchaseInvoice(BuyingController):
) )
) )
@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): def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges): if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account) account_currency = get_account_currency(self.unrealized_profit_loss_account)

View File

@ -74,7 +74,6 @@
"manufacturer_part_no", "manufacturer_part_no",
"accounting", "accounting",
"expense_account", "expense_account",
"discount_account",
"col_break5", "col_break5",
"is_fixed_asset", "is_fixed_asset",
"asset_location", "asset_location",
@ -860,12 +859,6 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "discount_account",
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
},
{ {
"fieldname": "product_bundle", "fieldname": "product_bundle",
"fieldtype": "Link", "fieldtype": "Link",
@ -877,7 +870,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2022-06-17 05:31:10.520171", "modified": "2022-09-27 10:54:23.980713",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@ -22,9 +22,12 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.assets.doctype.asset.depreciation import ( from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_disposal_account_and_cost_center, get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain, 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.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
@ -1086,18 +1089,20 @@ class SalesInvoice(SellingController):
asset.db_set("disposal_date", None) asset.db_set("disposal_date", None)
if asset.calculate_depreciation: if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_disposal(asset) posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
self.reset_depreciation_schedule(asset) reverse_depreciation_entry_made_after_disposal(asset, posting_date)
reset_depreciation_schedule(asset, self.posting_date)
else: else:
if asset.calculate_depreciation:
depreciate_asset(asset, self.posting_date)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name") asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
) )
asset.db_set("disposal_date", self.posting_date) asset.db_set("disposal_date", self.posting_date)
if asset.calculate_depreciation:
self.depreciate_asset(asset)
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against"] = self.customer gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle, item=item)) gl_entries.append(self.get_gl_dict(gle, item=item))

View File

@ -335,6 +335,9 @@ def get_advance_vouchers(
"party": ["in", parties], "party": ["in", parties],
} }
if party_type == "Customer":
filters.update({"against_voucher": ["is", "not set"]})
if company: if company:
filters["company"] = company filters["company"] = company
if from_date and to_date: if from_date and to_date:

View File

@ -370,7 +370,7 @@ def get_conditions(filters):
where parent=`tabSales Invoice`.name where parent=`tabSales Invoice`.name
and ifnull(`tab{table}`.{field}, '') = %({field})s)""" and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment") conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
conditions += get_sales_invoice_item_field_condition("cost_center") conditions += get_sales_invoice_item_field_condition("cost_center")
conditions += get_sales_invoice_item_field_condition("warehouse") conditions += get_sales_invoice_item_field_condition("warehouse")
conditions += get_sales_invoice_item_field_condition("brand") conditions += get_sales_invoice_item_field_condition("brand")

View File

@ -4,11 +4,12 @@
import frappe import frappe
from frappe import _ 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 ( from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts, 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): def post_depreciation_entries(date=None, commit=True):
@ -196,6 +197,11 @@ def scrap_asset(asset_name):
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status) _("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( depreciation_series = frappe.get_cached_value(
"Company", asset.company, "series_for_depreciation_entry" "Company", asset.company, "series_for_depreciation_entry"
) )
@ -203,7 +209,7 @@ def scrap_asset(asset_name):
je = frappe.new_doc("Journal Entry") je = frappe.new_doc("Journal Entry")
je.voucher_type = "Journal Entry" je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series je.naming_series = depreciation_series
je.posting_date = today() je.posting_date = date
je.company = asset.company je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name) je.remark = "Scrap Entry for asset {0}".format(asset_name)
@ -214,7 +220,7 @@ def scrap_asset(asset_name):
je.flags.ignore_permissions = True je.flags.ignore_permissions = True
je.submit() 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) frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
asset.set_status("Scrapped") asset.set_status("Scrapped")
@ -225,6 +231,9 @@ def scrap_asset(asset_name):
def restore_asset(asset_name): def restore_asset(asset_name):
asset = frappe.get_doc("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 je = asset.journal_entry_for_scrap
asset.db_set("disposal_date", None) asset.db_set("disposal_date", None)
@ -235,6 +244,91 @@ def restore_asset(asset_name):
asset.set_status() asset.set_status()
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( def get_gl_entries_on_asset_regain(
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
): ):

View File

@ -4,7 +4,16 @@
import unittest import unittest
import frappe 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.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
@ -178,28 +187,48 @@ class TestAsset(AssetSetup):
self.assertEqual(doc.items[0].is_fixed_asset, 1) self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_scrap_asset(self): def test_scrap_asset(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset( asset = create_asset(
calculate_depreciation=1, calculate_depreciation=1,
available_for_use_date="2020-01-01", available_for_use_date=purchase_date,
purchase_date="2020-01-01", purchase_date=purchase_date,
expected_value_after_useful_life=10000, expected_value_after_useful_life=10000,
total_number_of_depreciations=10, total_number_of_depreciations=10,
frequency_of_depreciation=1, frequency_of_depreciation=1,
submit=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) scrap_asset(asset.name)
asset.load_from_db() 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.assertEqual(asset.status, "Scrapped")
self.assertTrue(asset.journal_entry_for_scrap) self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = ( 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 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( gle = frappe.db.sql(
@ -216,19 +245,27 @@ class TestAsset(AssetSetup):
self.assertFalse(asset.journal_entry_for_scrap) self.assertFalse(asset.journal_entry_for_scrap)
self.assertEqual(asset.status, "Partially Depreciated") 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): def test_gle_made_by_asset_sale(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset( asset = create_asset(
calculate_depreciation=1, calculate_depreciation=1,
available_for_use_date="2020-06-06", available_for_use_date=purchase_date,
purchase_date="2020-01-01", purchase_date=purchase_date,
expected_value_after_useful_life=10000, expected_value_after_useful_life=10000,
total_number_of_depreciations=3, total_number_of_depreciations=10,
frequency_of_depreciation=10, frequency_of_depreciation=1,
depreciation_start_date="2020-12-31",
submit=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 = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
si.customer = "_Test Customer" si.customer = "_Test Customer"
@ -239,10 +276,15 @@ class TestAsset(AssetSetup):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") 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 = ( 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 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), ("Debtors - _TC", 25000.0, 0.0),
) )

View File

@ -12,8 +12,11 @@ from six import string_types
import erpnext import erpnext
from erpnext.assets.doctype.asset.depreciation import ( from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_disposal,
get_value_after_depreciation_on_disposal_date, 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_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import ( from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
@ -424,7 +427,7 @@ class AssetCapitalization(StockController):
asset = self.get_asset(item) asset = self.get_asset(item)
if asset.calculate_depreciation: if asset.calculate_depreciation:
self.depreciate_asset(asset) depreciate_asset(asset, self.posting_date)
asset.reload() asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal( fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
@ -520,8 +523,8 @@ class AssetCapitalization(StockController):
self.set_consumed_asset_status(asset) self.set_consumed_asset_status(asset)
if asset.calculate_depreciation: if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_disposal(asset) reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
self.reset_depreciation_schedule(asset) reset_depreciation_schedule(asset, self.posting_date)
def get_asset(self, item): def get_asset(self, item):
asset = frappe.get_doc("Asset", item.asset) asset = frappe.get_doc("Asset", item.asset)

View File

@ -20,7 +20,6 @@
"maintain_same_rate", "maintain_same_rate",
"allow_multiple_items", "allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice", "bill_for_rejected_quantity_in_purchase_invoice",
"enable_discount_accounting",
"subcontract", "subcontract",
"backflush_raw_materials_of_subcontract_based_on", "backflush_raw_materials_of_subcontract_based_on",
"column_break_11", "column_break_11",
@ -134,13 +133,6 @@
{ {
"fieldname": "column_break_12", "fieldname": "column_break_12",
"fieldtype": "Column Break" "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", "icon": "fa fa-cog",
@ -148,7 +140,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2022-09-01 18:01:34.994657", "modified": "2022-09-27 10:50:27.050252",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",

View File

@ -5,15 +5,10 @@
import frappe import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint
class BuyingSettings(Document): class BuyingSettings(Document):
def on_update(self):
self.toggle_discount_accounting_fields()
def validate(self): def validate(self):
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]: for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
frappe.db.set_default(key, self.get(key, "")) frappe.db.set_default(key, self.get(key, ""))
@ -26,60 +21,3 @@ class BuyingSettings(Document):
self.get("supp_master_name") == "Naming Series", self.get("supp_master_name") == "Naming Series",
hide_name_field=False, 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,
)

View File

@ -33,6 +33,7 @@ frappe.ui.form.on("Purchase Order", {
frm.set_query("fg_item", "items", function() { frm.set_query("fg_item", "items", function() {
return { return {
filters: { filters: {
'is_stock_item': 1,
'is_sub_contracted_item': 1, 'is_sub_contracted_item': 1,
'default_bom': ['!=', ''] 'default_bom': ['!=', '']
} }

View File

@ -38,7 +38,6 @@ from erpnext.accounts.party import (
validate_party_frozen_disabled, validate_party_frozen_disabled,
) )
from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year 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.buying.utils import update_last_purchase_rate
from erpnext.controllers.print_settings import ( from erpnext.controllers.print_settings import (
set_print_templates_for_item_table, set_print_templates_for_item_table,
@ -1891,88 +1890,6 @@ class AccountsController(TransactionBase):
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx) _("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() @frappe.whitelist()
def get_tax_rate(account_head): def get_tax_rate(account_head):

View File

@ -212,21 +212,15 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
meta = frappe.get_meta(doctype, cached=True) meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields() 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 = "" columns = ""
extra_searchfields = [ extra_searchfields = [field for field in searchfields if not field in ["name", "description"]]
field
for field in searchfields
if not field in ["name", "item_group", "description", "item_name"]
]
if extra_searchfields: 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 + [ searchfields = searchfields + [
field field
@ -266,12 +260,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
if frappe.db.count(doctype, cache=True) < 50000: if frappe.db.count(doctype, cache=True) < 50000:
# scan description only if items are less than 50000 # scan description only if items are less than 50000
description_cond = "or tabItem.description LIKE %(txt)s" description_cond = "or tabItem.description LIKE %(txt)s"
return frappe.db.sql( return frappe.db.sql(
"""select """select
tabItem.name, tabItem.item_name, tabItem.item_group, tabItem.name {columns}
if(length(tabItem.description) > 40, \
concat(substr(tabItem.description, 1, 40), "..."), description) as description
{columns}
from tabItem from tabItem
where tabItem.docstatus < 2 where tabItem.docstatus < 2
and tabItem.disabled=0 and tabItem.disabled=0

View File

@ -69,9 +69,18 @@ class SubcontractingController(StockController):
def validate_items(self): def validate_items(self):
for item in self.items: for item in self.items:
if not frappe.get_value("Item", item.item_code, "is_sub_contracted_item"): is_stock_item, is_sub_contracted_item = frappe.get_value(
"Item", item.item_code, ["is_stock_item", "is_sub_contracted_item"]
)
if not is_stock_item:
msg = f"Item {item.item_name} must be a stock item."
frappe.throw(_(msg))
if not is_sub_contracted_item:
msg = f"Item {item.item_name} must be a subcontracted item." msg = f"Item {item.item_name} must be a subcontracted item."
frappe.throw(_(msg)) frappe.throw(_(msg))
if item.bom: if item.bom:
bom = frappe.get_doc("BOM", item.bom) bom = frappe.get_doc("BOM", item.bom)
if not bom.is_active: if not bom.is_active:
@ -841,7 +850,7 @@ def make_rm_stock_entry(
for fg_item_code in fg_item_code_list: for fg_item_code in fg_item_code_list:
for rm_item in rm_items: 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") rm_item_code = rm_item.get("rm_item_code")
items_dict = { items_dict = {

View File

@ -508,6 +508,7 @@ accounting_dimension_doctypes = [
"Landed Cost Item", "Landed Cost Item",
"Asset Value Adjustment", "Asset Value Adjustment",
"Asset Repair", "Asset Repair",
"Asset Capitalization",
"Loyalty Program", "Loyalty Program",
"Stock Reconciliation", "Stock Reconciliation",
"POS Profile", "POS Profile",

View File

@ -315,3 +315,4 @@ erpnext.patches.v14_0.fix_crm_no_of_employees
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization

View File

@ -100,6 +100,7 @@ def execute():
"mode_of_payment": loan.mode_of_payment, "mode_of_payment": loan.mode_of_payment,
"loan_account": loan.loan_account, "loan_account": loan.loan_account,
"payment_account": loan.payment_account, "payment_account": loan.payment_account,
"disbursement_account": loan.payment_account,
"interest_income_account": loan.interest_income_account, "interest_income_account": loan.interest_income_account,
"penalty_income_account": loan.penalty_income_account, "penalty_income_account": loan.penalty_income_account,
}, },
@ -190,6 +191,7 @@ def create_loan_type(loan, loan_type_name, penalty_account):
loan_type_doc.company = loan.company loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account 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.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account loan_type_doc.penalty_income_account = penalty_account

View File

@ -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)

View File

@ -10,6 +10,31 @@ frappe.ui.form.on("Item", {
frm.add_fetch('attribute', 'to_range', 'to_range'); frm.add_fetch('attribute', 'to_range', 'to_range');
frm.add_fetch('attribute', 'increment', 'increment'); frm.add_fetch('attribute', 'increment', 'increment');
frm.add_fetch('tax_type', 'tax_rate', 'tax_rate'); 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) { onload: function(frm) {
erpnext.item.setup_queries(frm); erpnext.item.setup_queries(frm);
@ -858,3 +883,17 @@ frappe.tour['Item'] = [
]; ];
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);
});
}

View File

@ -937,17 +937,21 @@ class Item(Document):
"Purchase Order Item", "Purchase Order Item",
"Material Request Item", "Material Request Item",
"Product Bundle", "Product Bundle",
"BOM",
] ]
for doctype in linked_doctypes: for doctype in linked_doctypes:
filters = {"item_code": self.name, "docstatus": 1} filters = {"item_code": self.name, "docstatus": 1}
if doctype in ("Product Bundle", "BOM"):
if doctype == "Product Bundle": if doctype == "Product Bundle":
filters = {"new_item_code": self.name} filters = {"new_item_code": self.name}
fieldname = "new_item_code as docname"
else:
filters = {"item": self.name, "docstatus": 1}
fieldname = "name as docname"
if linked_doc := frappe.db.get_value( if linked_doc := frappe.db.get_value(doctype, filters, fieldname, as_dict=True):
doctype, filters, ["new_item_code as docname"], as_dict=True
):
return linked_doc.update({"doctype": doctype}) return linked_doc.update({"doctype": doctype})
elif doctype in ( elif doctype in (

View File

@ -5,6 +5,7 @@
import json import json
import frappe import frappe
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects
from frappe.tests.utils import FrappeTestCase, change_settings from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today from frappe.utils import add_days, today
@ -816,6 +817,30 @@ class TestItem(FrappeTestCase):
item.reload() item.reload()
self.assertEqual(item.is_stock_item, 1) 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): def set_item_variant_settings(fields):
doc = frappe.get_doc("Item Variant Settings") doc = frappe.get_doc("Item Variant Settings")

View File

@ -18,7 +18,7 @@
<b class="caret"></b> <b class="caret"></b>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right" role="menu"> <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> <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 %} {% endif %}
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' <a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'

View File

@ -52,6 +52,9 @@ def get_context(context):
) )
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points")) 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): def get_attachments(dt, dn):
return frappe.get_all( return frappe.get_all(

View File

@ -12,8 +12,8 @@
<!-- title: Book an appointment --> <!-- title: Book an appointment -->
<div id="select-date-time"> <div id="select-date-time">
<div class="text-center mt-5"> <div class="text-center mt-5">
<h3>Book an appointment</h3> <h3>{{ _("Book an appointment") }}</h3>
<p class="lead text-muted" id="lead-text">Select the date and your timezone</p> <p class="lead text-muted" id="lead-text">{{ _("Select the date and your timezone") }}</p>
</div> </div>
<div class="row justify-content-center mt-3"> <div class="row justify-content-center mt-3">
<div class="col-md-6 align-self-center "> <div class="col-md-6 align-self-center ">
@ -31,7 +31,7 @@
</div> </div>
<div class="row justify-content-center mt-3"> <div class="row justify-content-center mt-3">
<div class="col-md-4 mb-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> </div>
</div> </div>
@ -39,24 +39,24 @@
<!--Enter Details--> <!--Enter Details-->
<div id="enter-details" class="mb-5"> <div id="enter-details" class="mb-5">
<div class="text-center mt-5"> <div class="text-center mt-5">
<h3>Add details</h3> <h3>{{ _("Add details") }}</h3>
<p class="lead text-muted">Selected date is <span class="date-span"></span> at <span class="time-span"> <p class="lead text-muted">{{ _("Selected date is") }} <span class="date-span"></span> {{ _("at") }} <span class="time-span">
</span></p> </span></p>
</div> </div>
<div class="row justify-content-center mt-3"> <div class="row justify-content-center mt-3">
<div class="col-md-4 align-items-center"> <div class="col-md-4 align-items-center">
<form id="customer-form" action='#'> <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="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="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" <textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
placeholder="Notes"></textarea> placeholder="{{ _('Notes') }}"></textarea>
</form> </form>
<div class="row mt-3 " id="submit-button-area"> <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: 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: submit;"><button class="btn btn-primary form-control " onclick="submit()" id="submit-button">{{ _("Submit") }}</button></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -69,7 +69,7 @@ function on_date_or_timezone_select() {
window.selected_timezone = timezone.value; window.selected_timezone = timezone.value;
update_time_slots(date_picker.value, timezone.value); update_time_slots(date_picker.value, timezone.value);
let lead_text = document.getElementById('lead-text'); let lead_text = document.getElementById('lead-text');
lead_text.innerHTML = "Select Time" lead_text.innerHTML = __("Select Time")
} }
async function get_time_slots(date, timezone) { async function get_time_slots(date, timezone) {
@ -89,7 +89,7 @@ async function update_time_slots(selected_date, selected_timezone) {
clear_time_slots(); clear_time_slots();
if (window.slots.length <= 0) { if (window.slots.length <= 0) {
let message_div = document.createElement('p'); 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); timeslot_container.appendChild(message_div);
return return
} }
@ -128,7 +128,7 @@ function get_slot_layout(time) {
let start_time_string = moment(time).tz(timezone).format("LT"); 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 = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes');
let end_time_string = end_time.format("LT"); 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() { function select_time() {
@ -227,9 +227,9 @@ async function submit() {
}, },
callback: (response)=>{ callback: (response)=>{
if (response.message.status == "Unverified") { 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 { } else {
frappe.show_alert("Appointment Created Successfully"); frappe.show_alert(__("Appointment Created Successfully"));
} }
setTimeout(()=>{ setTimeout(()=>{
let redirect_url = "/"; let redirect_url = "/";
@ -239,7 +239,7 @@ async function submit() {
window.location.href = redirect_url;},5000) window.location.href = redirect_url;},5000)
}, },
error: (err)=>{ error: (err)=>{
frappe.show_alert("Something went wrong please try again"); frappe.show_alert(__("Something went wrong please try again"));
button.disabled = false; button.disabled = false;
} }
}); });

View File

@ -8,11 +8,11 @@
{% if success==True %} {% if success==True %}
<div class="alert alert-success"> <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> </div>
{% else %} {% else %}
<div class="alert alert-danger"> <div class="alert alert-danger">
Verification failed please check the link {{ _("Verification failed please check the link") }}
</div> </div>
{% endif %} {% endif %}
{% endblock%} {% endblock%}