diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 1ad327da5ee..4a9236066cc 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -3,7 +3,7 @@ import inspect
import frappe
-__version__ = "15.22.2"
+__version__ = "15.28.2"
def get_default_company(user=None):
diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js
index 411b90f84ee..ff44723afee 100644
--- a/erpnext/accounts/doctype/account/account.js
+++ b/erpnext/accounts/doctype/account/account.js
@@ -22,8 +22,7 @@ frappe.ui.form.on("Account", {
// hide fields if group
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0);
- // disable fields
- frm.toggle_enable(["is_group", "company"], false);
+ frm.toggle_enable(["is_group", "company", "account_number"], frm.is_new());
if (cint(frm.doc.is_group) == 0) {
frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account);
diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json
index 78f73efff11..e87b59ea9cb 100644
--- a/erpnext/accounts/doctype/account/account.json
+++ b/erpnext/accounts/doctype/account/account.json
@@ -55,8 +55,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"in_standard_filter": 1,
- "label": "Account Number",
- "read_only": 1
+ "label": "Account Number"
},
{
"default": "0",
@@ -72,7 +71,6 @@
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
- "read_only": 1,
"remember_last_selected_value": 1,
"reqd": 1
},
@@ -123,7 +121,8 @@
"label": "Account Type",
"oldfieldname": "account_type",
"oldfieldtype": "Select",
- "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
+ "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
+ "search_index": 1
},
{
"description": "Rate at which this tax is applied",
@@ -192,7 +191,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
- "modified": "2023-07-20 18:18:44.405723",
+ "modified": "2024-06-27 16:23:04.444354",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account",
@@ -253,4 +252,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json
deleted file mode 100644
index dfc821eb53e..00000000000
--- a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/tr_l10ntr_tek_duzen_hesap.json
+++ /dev/null
@@ -1,531 +0,0 @@
-{
- "country_code": "tr",
- "name": "Turkey - Tek D\u00fczen Hesap Plan\u0131",
- "tree": {
- "Duran Varl\u0131klar": {
- "Di\u011fer Alacaklar": {
- "Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {},
- "Di\u011fer Alacak Senetleri Reeskontu(-)": {},
- "Di\u011fer \u00c7e\u015fitli Alacaklar": {},
- "Ortaklardan Alacaklar": {},
- "Personelden Alacaklar": {},
- "\u0130\u015ftiraklerden Alacaklar": {},
- "\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
- },
- "Di\u011fer Duran Varl\u0131klar": {
- "Birikmi\u015f Amortismanlar(-)": {},
- "Di\u011fer KDV": {},
- "Di\u011fer \u00c7e\u015fitli Duran Varl\u0131klar": {},
- "Elden \u00c7\u0131kar\u0131lacak Stoklar Ve Maddi Duran Varl\u0131klar": {},
- "Gelecek Y\u0131llar \u0130htiyac\u0131 Stoklar": {},
- "Gelecek Y\u0131llarda \u0130ndirilecek KDV": {},
- "Pe\u015fin \u00d6denen Vergi Ve Fonlar": {},
- "Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
- },
- "Gelecek Y\u0131llara Ait Giderler ve Gelir Tahakkuklar\u0131": {
- "Gelecek Y\u0131llara Ait Giderler": {},
- "Gelir Tahakkuklar\u0131": {}
- },
- "Maddi Duran Varl\u0131klar": {
- "Arazi Ve Arsalar": {},
- "Binalar": {},
- "Birikmi\u015f Amortismanlar(-)": {},
- "Demirba\u015flar": {},
- "Di\u011fer Maddi Duran Varl\u0131klar": {},
- "Ta\u015f\u0131tlar": {},
- "Tesis, Makine Ve Cihazlar": {},
- "Verilen Avanslar": {},
- "Yap\u0131lmakta Olan Yat\u0131r\u0131mlar": {},
- "Yer Alt\u0131 Ve Yer \u00dcst\u00fc D\u00fczenleri": {}
- },
- "Maddi Olmayan Duran Varl\u0131klar": {
- "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
- "Birikmi\u015f Amortismanlar(-)": {},
- "Di\u011fer Maddi Olmayan Duran Varl\u0131klar": {},
- "Haklar": {},
- "Kurulu\u015f Ve \u00d6rg\u00fctlenme Giderleri": {},
- "Verilen Avanslar": {},
- "\u00d6zel Maliyetler": {},
- "\u015eerefiye": {}
- },
- "Mali Duran Varl\u0131klar": {
- "Ba\u011fl\u0131 Menkul K\u0131ymetler": {},
- "Ba\u011fl\u0131 Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "Ba\u011fl\u0131 Ortakl\u0131klar": {},
- "Ba\u011fl\u0131 Ortakl\u0131klar Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "Ba\u011fl\u0131 Ortakl\u0131klara Sermaye Taahh\u00fctleri(-)": {},
- "Di\u011fer Mali Duran Varl\u0131klar": {},
- "Di\u011fer Mali Duran Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "\u0130\u015ftirakler": {},
- "\u0130\u015ftirakler Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "\u0130\u015ftiraklere Sermaye Taahh\u00fctleri(-)": {}
- },
- "Ticari Alacaklar": {
- "Alacak Senetleri": {},
- "Alacak Senetleri Reeskontu(-)": {},
- "Al\u0131c\u0131lar": {},
- "Kazaqn\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {},
- "Verilen Depozito Ve Teminatlar": {},
- "\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
- },
- "root_type": "",
- "\u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {
- "Arama Giderleri": {},
- "Birikmi\u015f T\u00fckenme Paylar\u0131(-)": {},
- "Di\u011fer \u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {},
- "Haz\u0131rl\u0131k Ve Geli\u015ftirme Giderleri": {},
- "Verilen Avanslar": {}
- }
- },
- "D\u00f6nen Varl\u0131klar": {
- "Di\u011fer Alacaklar": {
- "Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {},
- "Di\u011fer Alacak Senetleri Reeskontu(-)": {},
- "Di\u011fer \u00c7e\u015fitli Alacaklar": {},
- "Ortaklardan Alacaklar": {},
- "Personelden Alacaklar": {},
- "\u0130\u015ftiraklerden Alacaklar": {},
- "\u015e\u00fcpheli Di\u011fer Alacaklar": {},
- "\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
- },
- "Di\u011fer D\u00f6nen Varl\u0131klar": {
- "Devreden KDV": {},
- "Di\u011fer D\u00f6nen Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "Di\u011fer KDV": {},
- "Di\u011fer \u00c7e\u015fitli D\u00f6nen Varl\u0131klar": {},
- "Personel Avanslar\u0131": {},
- "Pe\u015fin \u00d6denen Vergiler Ve Fonlar": {},
- "Say\u0131m Ve Tesell\u00fcm Noksanlar\u0131": {},
- "\u0130ndirilecek KDV": {},
- "\u0130\u015f Avanslar\u0131": {}
- },
- "Gelecek Aylara Ait Giderler ve Gelir Tahakkuklar\u0131": {
- "Gelecek Aylara Ait Giderler": {},
- "Gelir Tahakkuklar\u0131": {}
- },
- "Haz\u0131r De\u011ferler": {
- "Al\u0131nan \u00c7ekler": {},
- "Bankalar": {
- "account_type": "Bank"
- },
- "Di\u011fer Haz\u0131r De\u011ferler": {},
- "Kasa": {
- "account_type": "Cash"
- },
- "Verilen \u00c7ekler ve \u00d6deme Emirleri(-)": {}
- },
- "Menkul K\u0131ymetler": {
- "Di\u011fer Menkul K\u0131ymetler": {},
- "Hisse Senetleri": {},
- "Kamu Kesimi Tahvil, Senet ve Bonolar\u0131": {},
- "Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "\u00d6zel Kesim Tahvil Senet Ve Bonolar\u0131": {}
- },
- "Stoklar": {
- "Mamuller": {},
- "Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
- "Ticari Mallar": {},
- "Verilen Sipari\u015f Avanslar\u0131": {},
- "Yar\u0131 Mamuller": {},
- "\u0130lk Madde Malzeme": {}
- },
- "Ticari Alacaklar": {
- "Alacak Senetleri": {},
- "Alacak Senetleri Reeskontu(-)": {},
- "Al\u0131c\u0131lar": {},
- "Di\u011fer Ticari Alacaklar": {},
- "Kazan\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {},
- "Verilen Depozito ve Teminatlar": {},
- "\u015e\u00fcpheli Ticari Alacaklar": {},
- "\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131": {}
- },
- "Y\u0131llara Yayg\u0131n \u0130n\u015faat ve Onar\u0131m Maliyetleri": {
- "Ta\u015feronlara Verilen Avanslar": {},
- "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Maliyetleri": {}
- },
- "root_type": ""
- },
- "Gelir Tablosu Hesaplar\u0131": {
- "Br\u00fct Sat\u0131\u015flar": {
- "Di\u011fer Gelirler": {},
- "Yurt D\u0131\u015f\u0131 Sat\u0131\u015flar": {},
- "Yurt \u0130\u00e7i Sat\u0131\u015flar": {}
- },
- "Di\u011fer Faaliyetlerden Olu\u015fan Gelir ve K\u00e2rlar": {
- "Ba\u011fl\u0131 Ortakl\u0131klardan Temett\u00fc Gelirleri": {},
- "Di\u011fer Ola\u011fan Gelir Ve K\u00e2rlar": {},
- "Enflasyon D\u00fczeltme K\u00e2rlar\u0131": {},
- "Faiz Gelirleri": {},
- "Kambiyo K\u00e2rlar\u0131": {},
- "Komisyon Gelirleri": {},
- "Konusu Kalmayan Kar\u015f\u0131l\u0131klar": {},
- "Menkul K\u0131ymet Sat\u0131\u015f K\u00e2rlar\u0131": {},
- "Reeskont Faiz Gelirleri": {},
- "\u0130\u015ftiraklerden Temett\u00fc Gelirleri": {}
- },
- "Di\u011fer Faaliyetlerden Olu\u015fan Gider ve Zararlar (-)": {
- "Di\u011fer Ola\u011fan Gider Ve Zararlar(-)": {},
- "Enflasyon D\u00fczeltmesi Zararlar\u0131(-)": {},
- "Kambiyo Zararlar\u0131(-)": {},
- "Kar\u015f\u0131l\u0131k Giderleri(-)": {},
- "Komisyon Giderleri(-)": {},
- "Menkul K\u0131ymet Sat\u0131\u015f Zararlar\u0131(-)": {},
- "Reeskont Faiz Giderleri(-)": {}
- },
- "D\u00f6nem Net K\u00e2r\u0131 Ve Zarar\u0131": {
- "D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131(-)": {},
- "D\u00f6nem K\u00e2r\u0131 Veya Zarar\u0131": {},
- "D\u00f6nem Net K\u00e2r\u0131 Veya Zarar\u0131": {},
- "Enflasyon D\u00fczeltme Hesab\u0131": {},
- "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Enflasyon D\u00fczeltme Hesab\u0131": {}
- },
- "Faaliyet Giderleri(-)": {
- "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri(-)": {},
- "Genel Y\u00f6netim Giderleri(-)": {},
- "Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri(-)": {}
- },
- "Finansman Giderleri": {
- "K\u0131sa Vadeli Bor\u00e7lanma Giderleri(-)": {},
- "Uzun Vadeli Bor\u00e7lanma Giderleri(-)": {}
- },
- "Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {
- "Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {},
- "\u00d6nceki D\u00f6nem Gelir Ve K\u00e2rlar\u0131": {}
- },
- "Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zaralar(-)": {
- "Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zararlar(-)": {},
- "\u00c7al\u0131\u015fmayan K\u0131s\u0131m Gider Ve Zararlar\u0131(-)": {},
- "\u00d6nceki D\u00f6nem Gider Ve Zararlar\u0131(-)": {}
- },
- "Sat\u0131\u015f \u0130ndirimleri (-)": {
- "Di\u011fer \u0130ndirimler": {},
- "Sat\u0131\u015f \u0130ndirimleri(-)": {},
- "Sat\u0131\u015ftan \u0130adeler(-)": {}
- },
- "Sat\u0131\u015flar\u0131n Maliyeti(-)": {
- "Di\u011fer Sat\u0131\u015flar\u0131n Maliyeti(-)": {},
- "Sat\u0131lan Hizmet Maliyeti(-)": {},
- "Sat\u0131lan Mamuller Maliyeti(-)": {},
- "Sat\u0131lan Ticari Mallar Maliyeti(-)": {}
- },
- "root_type": ""
- },
- "K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": {
- "Al\u0131nan Avanslar": {
- "Al\u0131nan Di\u011fer Avanslar": {
- "account_type": "Payable"
- },
- "Al\u0131nan Sipari\u015f Avanslar\u0131": {
- "account_type": "Payable"
- },
- "account_type": "Payable"
- },
- "Bor\u00e7 ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
- "Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
- "account_type": "Payable"
- },
- "D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131": {
- "account_type": "Tax"
- },
- "D\u00f6nem K\u00e2r\u0131n\u0131n Pe\u015fin \u00d6denen Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler(-)": {
- "account_type": "Tax"
- },
- "K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {},
- "Maliyet Giderleri Kar\u015f\u0131l\u0131\u011f\u0131": {},
- "account_type": "Payable"
- },
- "Di\u011fer Bor\u00e7lar": {
- "Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": {
- "account_type": "Payable"
- },
- "Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Ortaklara Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Personele Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "account_type": "Payable",
- "\u0130\u015ftiraklere Bor\u00e7lar": {
- "account_type": "Payable"
- }
- },
- "Di\u011fer K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": {
- "Di\u011fer KDV": {
- "account_type": "Tax"
- },
- "Di\u011fer \u00c7e\u015fitli Yabanc\u0131 Kaynaklar": {},
- "Hesaplanan KDV": {
- "account_type": "Tax"
- },
- "Merkez Ve \u015eubeler Cari Hesab\u0131": {},
- "Say\u0131m Ve Tesell\u00fcm Fazlalar\u0131": {},
- "account_type": "Payable"
- },
- "Gelecek Aylara Ait Gelirler Ve Gider Tahakkuklar\u0131": {
- "Gelecek Aylara Ait Gelirler": {},
- "Gider Tahakkuklar\u0131": {}
- },
- "Mali Bor\u00e7lar": {
- "Banka Kredileri": {
- "account_type": "Payable"
- },
- "Di\u011fer Mali Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": {
- "account_type": "Payable"
- },
- "Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": {
- "account_type": "Payable"
- },
- "Tahvil Anapara Bor\u00e7, Taksit Ve Faizleri": {
- "account_type": "Payable"
- },
- "Uzun Vadeli Kredilerin Anapara Taksitleri Ve Faizleri": {
- "account_type": "Payable"
- },
- "account_type": "Payable",
- "\u00c7\u0131kar\u0131lan Bonolar Ve Senetler": {
- "account_type": "Payable"
- },
- "\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": {
- "account_type": "Payable"
- }
- },
- "Ticari Bor\u00e7lar": {
- "Al\u0131nan Depozito Ve Teminatlar": {
- "account_type": "Payable"
- },
- "Bor\u00e7 Senetleri": {
- "account_type": "Payable"
- },
- "Bor\u00e7 Senetleri Reeskontu(-)": {
- "account_type": "Payable"
- },
- "Di\u011fer Ticari Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Sat\u0131c\u0131lar": {
- "account_type": "Payable"
- },
- "account_type": "Payable"
- },
- "Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri": {
- "350 Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri Bedelleri": {
- "account_type": "Payable"
- },
- "account_type": "Payable"
- },
- "root_type": "",
- "\u00d6denecek Vergi ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
- "Vadesi Ge\u00e7mi\u015f, Ertelenmi\u015f Veya Taksitlendirilmi\u015f Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
- "account_type": "Tax"
- },
- "account_type": "Tax",
- "\u00d6denecek Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
- "account_type": "Tax"
- },
- "\u00d6denecek Sosyal G\u00fcvenl\u00fck Kesintileri": {
- "account_type": "Tax"
- },
- "\u00d6denecek Vergi Ve Fonlar": {
- "account_type": "Tax"
- }
- }
- },
- "Maliyet Hesaplar\u0131": {
- "Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
- "Direkt \u0130lk Madde Ve Malzeme Giderleri": {
- "Direk \u0130lk Madde Ve Malzeme Giderleri Hesab\u0131": {},
- "Direkt \u0130lk Madde Ve Malzeme Fiyat Fark\u0131": {},
- "Direkt \u0130lk Madde Ve Malzeme Miktar Fark\u0131": {},
- "Direkt \u0130lk Madde Ve Malzeme Yans\u0131tma Hesab\u0131": {}
- },
- "Direkt \u0130\u015f\u00e7ilik Giderleri": {
- "Direkt \u0130\u015f\u00e7ilik Giderleri": {},
- "Direkt \u0130\u015f\u00e7ilik Giderleri Yans\u0131tma Hesab\u0131": {},
- "Direkt \u0130\u015f\u00e7ilik S\u00fcre Farklar\u0131": {},
- "Direkt \u0130\u015f\u00e7ilik \u00dccret Farklar\u0131": {}
- },
- "Finansman Giderleri": {
- "Finansman Giderleri": {},
- "Finansman Giderleri Fark Hesab\u0131": {},
- "Finansman Giderleri Yans\u0131tma Hesab\u0131": {}
- },
- "Genel Y\u00f6netim Giderleri": {
- "Genel Y\u00f6netim Gider Farklar\u0131 Hesab\u0131": {},
- "Genel Y\u00f6netim Giderleri": {},
- "Genel Y\u00f6netim Giderleri Yans\u0131tma Hesab\u0131": {}
- },
- "Genel \u00dcretim Giderleri": {
- "Genel \u00dcretim Giderleri": {},
- "Genel \u00dcretim Giderleri B\u00fct\u00e7e Farklar\u0131": {},
- "Genel \u00dcretim Giderleri Kapasite Farklar\u0131": {},
- "Genel \u00dcretim Giderleri Verimlilik Giderleri": {},
- "Genel \u00dcretim Giderleri Yans\u0131tma Hesab\u0131": {}
- },
- "Hizmet \u00dcretim Maliyeti": {
- "Hizmet \u00dcretim Maliyeti": {},
- "Hizmet \u00dcretim Maliyeti Fark Hesaplar\u0131": {},
- "Hizmet \u00dcretim Maliyeti Yans\u0131tma Hesab\u0131": {}
- },
- "Maliyet Muhasebesi Ba\u011flant\u0131 Hesaplar\u0131": {
- "Maliyet Muhasebesi Ba\u011flant\u0131 Hesab\u0131": {},
- "Maliyet Muhasebesi Yans\u0131tma Hesab\u0131": {}
- },
- "Pazarlama, Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri": {
- "Atra\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
- "Pazarlama Sat\u0131\u015f Ve Dag\u0131t\u0131m Giderleri Yans\u0131tma Hesab\u0131": {},
- "Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri Fark Hesab\u0131": {}
- },
- "root_type": ""
- },
- "Naz\u0131m Hesaplar": {
- "root_type": ""
- },
- "Serbest Hesaplar": {
- "root_type": ""
- },
- "Uzun Vadeli Yabanc\u0131 Kaynaklar": {
- "Al\u0131nan Avanslar": {
- "Al\u0131nan Di\u011fer Avanslar": {
- "account_type": "Payable"
- },
- "Al\u0131nan Sipari\u015f Avanslar\u0131": {
- "account_type": "Payable"
- },
- "account_type": "Payable"
- },
- "Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
- "Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
- "account_type": "Payable"
- },
- "K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {},
- "account_type": "Payable"
- },
- "Di\u011fer Bor\u00e7lar": {
- "Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": {
- "account_type": "Payable"
- },
- "Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Kamuya Olan Ertelenmi\u015f Veya Taksitlendirilmi\u015f Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Ortaklara Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "account_type": "Payable",
- "\u0130\u015ftiraklere Bor\u00e7lar": {
- "account_type": "Payable"
- }
- },
- "Di\u011fer Uzun Vadeli Yabanc\u0131 Kaynaklar": {
- "Di\u011fer \u00c7e\u015fitli Uzun Vadeli Yabanc\u0131 Kaynaklar": {
- "account_type": "Payable"
- },
- "Gelecek Y\u0131llara Ertelenmi\u015f Veya Terkin Edilecek KDV": {
- "account_type": "Payable"
- },
- "Tesise Kat\u0131lma Paylar\u0131": {
- "account_type": "Payable"
- },
- "account_type": "Payable"
- },
- "Gelecek Y\u0131llara Ait Gelirler Ve Gider Tahakkuklar\u0131": {
- "Gelecek Y\u0131llara Ait Gelirler": {},
- "Gider Tahakkuklar\u0131": {}
- },
- "Mali Bor\u00e7lar": {
- "Banka Kredileri": {
- "account_type": "Payable"
- },
- "Di\u011fer Mali Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": {
- "account_type": "Payable"
- },
- "Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": {
- "account_type": "Payable"
- },
- "account_type": "Payable",
- "\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": {
- "account_type": "Payable"
- },
- "\u00c7\u0131kar\u0131lm\u0131\u015f Tahviller": {
- "account_type": "Payable"
- }
- },
- "Ticari Bor\u00e7lar": {
- "Al\u0131nan Depozito Ve Teminatlar": {
- "account_type": "Payable"
- },
- "Bor\u00e7 Senetleri": {
- "account_type": "Payable"
- },
- "Bor\u00e7 Senetleri Reeskontu(-)": {
- "account_type": "Payable"
- },
- "Di\u011fer Ticari Bor\u00e7lar": {
- "account_type": "Payable"
- },
- "Sat\u0131c\u0131lar": {
- "account_type": "Payable"
- },
- "account_type": "Payable"
- },
- "root_type": ""
- },
- "\u00d6z Kaynaklar": {
- "D\u00f6nem Net K\u00e2r\u0131 (Zarar\u0131)": {
- "D\u00f6nem Net K\u00e2r\u0131": {},
- "D\u00f6nem Net Zarar\u0131(-)": {}
- },
- "Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {
- "Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {}
- },
- "Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {
- "Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {}
- },
- "K\u00e2r Yedekleri": {
- "Di\u011fer K\u00e2r Yedekleri": {},
- "Ola\u011fan\u00fcst\u00fc Yedekler": {},
- "Stat\u00fc Yedekleri": {},
- "Yasal Yedekler": {},
- "\u00d6zel Fonlar": {}
- },
- "Sermaye Yedekleri": {
- "Di\u011fer Sermaye Yedekleri": {},
- "Hisse Senedi \u0130ptal K\u00e2rlar\u0131": {},
- "Hisse Senetleri \u0130hra\u00e7 Primleri": {},
- "Maddi Duran Varl\u0131k Yeniden De\u011ferlenme Art\u0131\u015flar\u0131": {},
- "Maliyet Art\u0131\u015flar\u0131 Fonu": {},
- "\u0130\u015ftirakler Yeniden De\u011ferleme Art\u0131\u015flar\u0131": {}
- },
- "root_type": "",
- "\u00d6denmi\u015f Sermaye": {
- "Sermaye": {},
- "\u00d6denmi\u015f Sermaye(-)": {
- "account_type": "Payable"
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json
new file mode 100644
index 00000000000..fa8860883d2
--- /dev/null
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/tr_chart_of_accounts.json
@@ -0,0 +1,1473 @@
+{
+ "country_code": "tr",
+ "name": "Turkey - Chart of Accounts",
+ "tree": {
+ "D\u00d6NEN VARLIKLAR": {
+ "account_number": "1",
+ "root_type": "Asset",
+ "HAZIR DE\u011eERLER": {
+ "account_number": "10",
+ "KASA": {
+ "account_number": "100",
+ "is_group": 1,
+ "account_type": "Cash",
+ "TL KASA": {
+ "account_number": "100.01",
+ "account_type": "Cash"
+ },
+ "EUR KASA": {
+ "account_number": "100.02",
+ "account_type": "Cash"
+ },
+ "USD KASA": {
+ "account_number": "100.03",
+ "account_type": "Cash"
+ }
+ },
+ "ALINAN \u00c7EKLER": {
+ "account_number": "101",
+ "is_group": 1
+ },
+ "BANKALAR": {
+ "account_number": "102",
+ "is_group": 1,
+ "account_type": "Bank"
+ },
+ "VER\u0130LEN \u00c7EKLER VE \u00d6DEME EM\u0130RLER\u0130(-)": {
+ "account_number": "103",
+ "is_group": 1
+ },
+ "D\u0130\u011eER HAZIR DE\u011eERLER": {
+ "account_number": "108",
+ "is_group": 1
+ }
+ },
+ "MENKUL KIYMETLER": {
+ "account_number": "11",
+ "H\u0130SSE SENETLER\u0130": {
+ "account_number": "110",
+ "is_group": 1
+ },
+ "\u00d6ZEL KES\u0130M TAHV\u0130L SENET VE BONOLARI": {
+ "account_number": "111",
+ "is_group": 1
+ },
+ "KAMU KES\u0130M\u0130 TAHV\u0130L, SENET VE BONOLARI": {
+ "account_number": "112",
+ "is_group": 1
+ },
+ "D\u0130\u011eER MENKUL KIYMETLER": {
+ "account_number": "118",
+ "is_group": 1
+ },
+ "MENKUL KIYMETLER DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": {
+ "account_number": "119",
+ "is_group": 1
+ }
+ },
+ "T\u0130CAR\u0130 ALACAKLAR": {
+ "account_number": "12",
+ "ALICILAR": {
+ "account_number": "120",
+ "ALICILAR": {
+ "account_number": "120.01",
+ "account_type": "Receivable"
+ }
+ },
+ "ALACAK SENETLER\u0130": {
+ "account_number": "121",
+ "is_group": 1
+ },
+ "ALACAK SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "122",
+ "is_group": 1
+ },
+ "KAZANILMAMI\u015e F\u0130NANSAL K\u0130RALAMA FA\u0130Z GEL\u0130RLER\u0130 (-)": {
+ "account_number": "124",
+ "is_group": 1
+ },
+ "VER\u0130LEN DEPOZ\u0130TO VE TEM\u0130NATLAR": {
+ "account_number": "126",
+ "is_group": 1
+ },
+ "D\u0130\u011eER T\u0130CAR\u0130 ALACAKLAR": {
+ "account_number": "127",
+ "is_group": 1
+ },
+ "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR": {
+ "account_number": "128",
+ "is_group": 1
+ },
+ "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR KAR\u015eILI\u011eI(-)": {
+ "account_number": "129",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER ALACAKLAR": {
+ "account_number": "13",
+ "ORTAKLARDAN ALACAKLAR": {
+ "account_number": "131",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLERDEN ALACAKLAR": {
+ "account_number": "132",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLARDAN ALACAKLAR": {
+ "account_number": "133",
+ "is_group": 1
+ },
+ "PERSONELDEN ALACAKLAR": {
+ "account_number": "135",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 ALACAKLAR": {
+ "account_number": "136",
+ "is_group": 1
+ },
+ "D\u0130\u011eER ALACAK SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "137",
+ "is_group": 1
+ },
+ "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR": {
+ "account_number": "138",
+ "is_group": 1
+ },
+ "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR KAR\u015eILI\u011eI(-)": {
+ "account_number": "139",
+ "is_group": 1
+ }
+ },
+ "STOKLAR": {
+ "account_number": "15",
+ "\u0130LK MADDE MALZEME": {
+ "account_number": "150",
+ "is_group": 1
+ },
+ "YARI MAMULLER": {
+ "account_number": "151",
+ "is_group": 1
+ },
+ "MAMULLER": {
+ "account_number": "152",
+ "is_group": 1
+ },
+ "T\u0130CAR\u0130 MALLAR": {
+ "account_number": "153",
+ "is_group": 1
+ },
+ "D\u0130\u011eER STOKLAR": {
+ "account_number": "157",
+ "D\u0130\u011eR. STOK.": {
+ "account_number": "157.01",
+ "account_type": "Stock"
+ }
+ },
+ "STOK DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": {
+ "account_number": "158",
+ "is_group": 1
+ },
+ "VER\u0130LEN S\u0130PAR\u0130\u015e AVANSLARI": {
+ "account_number": "159",
+ "is_group": 1
+ }
+ },
+ "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130": {
+ "account_number": "17",
+ "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130": {
+ "account_number": "170",
+ "is_group": 1
+ },
+ "(YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM MAL\u0130YETLER\u0130)": {
+ "account_number": "177",
+ "is_group": 1
+ },
+ "(YILLARA YAYGIN \u0130N\u015eAAT ENFLASYON D\u00dcZELTME)": {
+ "account_number": "178",
+ "is_group": 1
+ },
+ "TA\u015eERONLARA VER\u0130LEN AVANSLAR": {
+ "account_number": "179",
+ "is_group": 1
+ }
+ },
+ "GELECEK AYLARA A\u0130T G\u0130DERLER VE GEL\u0130R TAHAKKUKLARI": {
+ "account_number": "18",
+ "GELECEK AYLARA A\u0130T G\u0130DERLER": {
+ "account_number": "180",
+ "is_group": 1
+ },
+ "GEL\u0130R TAHAKKUKLARI": {
+ "account_number": "181",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER D\u00d6NEN VARLIKLAR": {
+ "account_number": "19",
+ "DEVREDEN KDV": {
+ "account_number": "190",
+ "is_group": 1
+ },
+ "\u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191",
+ "\u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191.01",
+ "% 18'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191.01.01",
+ "account_type": "Tax"
+ },
+ "% 8'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191.01.02",
+ "account_type": "Tax"
+ },
+ "% 1'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191.01.03",
+ "account_type": "Tax"
+ },
+ "% 20'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191.01.04",
+ "account_type": "Tax"
+ },
+ "% 10'L\u0130K \u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "191.01.05",
+ "account_type": "Tax"
+ }
+ }
+ },
+ "D\u0130\u011eER KDV": {
+ "account_number": "192",
+ "is_group": 1
+ },
+ "PE\u015e\u0130N \u00d6DENEN VERG\u0130LER VE FONLAR": {
+ "account_number": "193",
+ "is_group": 1
+ },
+ "\u0130\u015e AVANSLARI": {
+ "account_number": "195",
+ "is_group": 1
+ },
+ "PERSONEL AVANSLARI": {
+ "account_number": "196",
+ "is_group": 1
+ },
+ "SAYIM VE TESELL\u00dcM NOKSANLARI": {
+ "account_number": "197",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 D\u00d6NEN VARLIKLAR": {
+ "account_number": "198",
+ "is_group": 1
+ },
+ "D\u0130\u011eER D\u00d6NEN VARLIKLAR KAR\u015eILI\u011eI(-)": {
+ "account_number": "199",
+ "is_group": 1
+ }
+ }
+ },
+ "DURAN VARLIKLAR": {
+ "account_number": "2",
+ "root_type": "Asset",
+ "T\u0130CAR\u0130 ALACAKLAR": {
+ "account_number": "22",
+ "ALICILAR": {
+ "account_number": "220",
+ "is_group": 1
+ },
+ "ALACAK SENETLER\u0130": {
+ "account_number": "221",
+ "is_group": 1
+ },
+ "ALACAK SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "222",
+ "is_group": 1
+ },
+ "KAZANILMAMI\u015e F\u0130NANSAL K\u0130RALAMA FA\u0130Z GEL\u0130RLER\u0130 (-)-": {
+ "account_number": "224",
+ "is_group": 1
+ },
+ "VER\u0130LEN DEPOZ\u0130TO VE TEM\u0130NATLAR": {
+ "account_number": "226",
+ "is_group": 1
+ },
+ "\u015e\u00dcPHEL\u0130 T\u0130CAR\u0130 ALACAKLAR KAR\u015eILI\u011eI(-)": {
+ "account_number": "229",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER ALACAKLAR": {
+ "account_number": "23",
+ "ORTAKLARDAN ALACAKLAR": {
+ "account_number": "231",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLERDEN ALACAKLAR": {
+ "account_number": "232",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLARDAN ALACAKLAR": {
+ "account_number": "233",
+ "is_group": 1
+ },
+ "PERSONELDEN ALACAKLAR": {
+ "account_number": "235",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 ALACAKLAR": {
+ "account_number": "236",
+ "is_group": 1
+ },
+ "D\u0130\u011eER ALACAK SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "237",
+ "is_group": 1
+ },
+ "\u015e\u00dcPHEL\u0130 D\u0130\u011eER ALACAKLAR KAR\u015eILI\u011eI(-)": {
+ "account_number": "239",
+ "is_group": 1
+ }
+ },
+ "MAL\u0130 DURAN VARLIKLAR": {
+ "account_number": "24",
+ "BA\u011eLI MENKUL KIYMETLER": {
+ "account_number": "240",
+ "BA\u011eL. MEKL. KIYM.": {
+ "account_number": "240.01",
+ "account_type": "Fixed Asset"
+ }
+ },
+ "BA\u011eLI MENKUL KIYMETLER DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": {
+ "account_number": "241",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLER": {
+ "account_number": "242",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLERE SERMAYE TAAHH\u00dcTLER\u0130(-)": {
+ "account_number": "243",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLER SERMAYE PAYLARI DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": {
+ "account_number": "244",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLAR": {
+ "account_number": "245",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLARA SERMAYE TAAHH\u00dcTLER\u0130(-)": {
+ "account_number": "246",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLAR SERMAYE PAYLARI DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": {
+ "account_number": "247",
+ "is_group": 1
+ },
+ "D\u0130\u011eER MAL\u0130 DURAN VARLIKLAR": {
+ "account_number": "248",
+ "is_group": 1
+ },
+ "D\u0130\u011eER MAL\u0130 DURAN VARLIKLAR KAR\u015eILI\u011eI(-)": {
+ "account_number": "249",
+ "is_group": 1
+ }
+ },
+ "MADD\u0130 DURAN VARLIKLAR": {
+ "account_number": "25",
+ "ARAZ\u0130 VE ARSALAR": {
+ "account_number": "250",
+ "is_group": 1
+ },
+ "YER ALTI VE YER \u00dcST\u00dc D\u00dcZENLER\u0130": {
+ "account_number": "251",
+ "is_group": 1
+ },
+ "B\u0130NALAR": {
+ "account_number": "252",
+ "is_group": 1
+ },
+ "TES\u0130S, MAK\u0130NE VE C\u0130HAZLAR": {
+ "account_number": "253",
+ "is_group": 1
+ },
+ "TA\u015eITLAR": {
+ "account_number": "254",
+ "is_group": 1
+ },
+ "DEM\u0130RBA\u015eLAR": {
+ "account_number": "255",
+ "is_group": 1
+ },
+ "D\u0130\u011eER MADD\u0130 DURAN VARLIKLAR": {
+ "account_number": "256",
+ "is_group": 1
+ },
+ "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": {
+ "account_number": "257",
+ "is_group": 1
+ },
+ "YAPILMAKTA OLAN YATIRIMLAR": {
+ "account_number": "258",
+ "is_group": 1
+ },
+ "VER\u0130LEN AVANSLAR": {
+ "account_number": "259",
+ "is_group": 1
+ }
+ },
+ "MADD\u0130 OLMAYAN DURAN VARLIKLAR": {
+ "account_number": "26",
+ "HAKLAR": {
+ "account_number": "260",
+ "is_group": 1
+ },
+ "\u015eEREF\u0130YE": {
+ "account_number": "261",
+ "is_group": 1
+ },
+ "KURULU\u015e VE \u00d6RG\u00dcTLENME G\u0130DERLER\u0130": {
+ "account_number": "262",
+ "is_group": 1
+ },
+ "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": {
+ "account_number": "263",
+ "is_group": 1
+ },
+ "\u00d6ZEL MAL\u0130YETLER": {
+ "account_number": "264",
+ "\u00d6ZL. MAL\u0130YT.": {
+ "account_number": "264.01",
+ "account_type": "Depreciation"
+ }
+ },
+ "D\u0130\u011eER MADD\u0130 OLMAYAN DURAN VARLIKLAR": {
+ "account_number": "267",
+ "is_group": 1
+ },
+ "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": {
+ "account_number": "268",
+ "is_group": 1
+ },
+ "VER\u0130LEN AVANSLAR": {
+ "account_number": "269",
+ "is_group": 1
+ }
+ },
+ "\u00d6ZEL T\u00dcKENMEYE TAB\u0130 VARLIKLAR": {
+ "account_number": "27",
+ "ARAMA G\u0130DERLER\u0130": {
+ "account_number": "271",
+ "is_group": 1
+ },
+ "HAZIRLIK VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": {
+ "account_number": "272",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00d6ZEL T\u00dcKENMEYE TAB\u0130 VARLIKLAR": {
+ "account_number": "277",
+ "is_group": 1
+ },
+ "B\u0130R\u0130KM\u0130\u015e T\u00dcKENME PAYLARI(-)": {
+ "account_number": "278",
+ "is_group": 1
+ },
+ "VER\u0130LEN AVANSLAR": {
+ "account_number": "279",
+ "is_group": 1
+ }
+ },
+ "GELECEK YILLARA A\u0130T G\u0130DERLER VE GEL\u0130R TAHAKKUKLARI": {
+ "account_number": "28",
+ "GELECEK YILLARA A\u0130T G\u0130DERLER": {
+ "account_number": "280",
+ "is_group": 1
+ },
+ "GEL\u0130R TAHAKKUKLARI": {
+ "account_number": "281",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER DURAN VARLIKLAR": {
+ "account_number": "29",
+ "GELECEK YILLARDA \u0130ND\u0130R\u0130LECEK KDV": {
+ "account_number": "291",
+ "is_group": 1
+ },
+ "D\u0130\u011eER KDV": {
+ "account_number": "292",
+ "is_group": 1
+ },
+ "GELECEK YILLAR \u0130HT\u0130YACI STOKLAR": {
+ "account_number": "293",
+ "is_group": 1
+ },
+ "ELDEN \u00c7IKARILACAK STOKLAR VE MADD\u0130 DURAN VARLIKLAR": {
+ "account_number": "294",
+ "is_group": 1
+ },
+ "PE\u015e\u0130N \u00d6DENEN VERG\u0130 VE FONLAR": {
+ "account_number": "295",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 DURAN VARLIKLAR": {
+ "account_number": "297",
+ "is_group": 1
+ },
+ "STOK DE\u011eER D\u00dc\u015e\u00dcKL\u00dc\u011e\u00dc KAR\u015eILI\u011eI(-)": {
+ "account_number": "298",
+ "is_group": 1
+ },
+ "B\u0130R\u0130KM\u0130\u015e AMORT\u0130SMANLAR(-)": {
+ "account_number": "299",
+ "is_group": 1
+ }
+ }
+ },
+ "KISA VADEL\u0130 YABANCI KAYNAKLAR": {
+ "account_number": "3",
+ "root_type": "Liability",
+ "MAL\u0130 BOR\u00c7LAR": {
+ "account_number": "30",
+ "BANKA KRED\u0130LER\u0130": {
+ "account_number": "300",
+ "is_group": 1
+ },
+ "F\u0130NANSAL K\u0130RALAMA \u0130\u015eLEMLER\u0130NDEN BOR\u00c7LAR": {
+ "account_number": "301",
+ "is_group": 1
+ },
+ "ERTELENM\u0130\u015e F\u0130NANSAL K\u0130RALAMA BOR\u00c7LANMA MAL\u0130YETLER\u0130(-)": {
+ "account_number": "302",
+ "is_group": 1
+ },
+ "UZUN VADEL\u0130 KRED\u0130LER\u0130N ANAPARA TAKS\u0130TLER\u0130 VE FA\u0130ZLER\u0130": {
+ "account_number": "303",
+ "is_group": 1
+ },
+ "TAHV\u0130L ANAPARA BOR\u00c7, TAKS\u0130T VE FA\u0130ZLER\u0130": {
+ "account_number": "304",
+ "is_group": 1
+ },
+ "\u00c7IKARILAN BONOLAR VE SENETLER": {
+ "account_number": "305",
+ "is_group": 1
+ },
+ "\u00c7IKARILMI\u015e D\u0130\u011eER MENKUL KIYMETLER": {
+ "account_number": "306",
+ "is_group": 1
+ },
+ "MENKUL KIYMETLER \u0130HRA\u00c7 FARKI(-)": {
+ "account_number": "308",
+ "is_group": 1
+ },
+ "D\u0130\u011eER MAL\u0130 BOR\u00c7LAR": {
+ "account_number": "309",
+ "is_group": 1
+ }
+ },
+ "T\u0130CAR\u0130 BOR\u00c7LAR": {
+ "account_number": "32",
+ "SATICILAR": {
+ "account_number": "320",
+ "SATICILAR TRY": {
+ "account_number": "320.01"
+ }
+ },
+ "BOR\u00c7 SENETLER\u0130": {
+ "account_number": "321",
+ "is_group": 1
+ },
+ "BOR\u00c7 SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "322",
+ "is_group": 1
+ },
+ "ALINAN DEPOZ\u0130TO VE TEM\u0130NATLAR": {
+ "account_number": "326",
+ "is_group": 1
+ },
+ "D\u0130\u011eER T\u0130CAR\u0130 BOR\u00c7LAR": {
+ "account_number": "329",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER BOR\u00c7LAR": {
+ "account_number": "33",
+ "ORTAKLARA BOR\u00c7LAR": {
+ "account_number": "331",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLERE BOR\u00c7LAR": {
+ "account_number": "332",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLARA BOR\u00c7LAR": {
+ "account_number": "333",
+ "is_group": 1
+ },
+ "PERSONELE BOR\u00c7LAR": {
+ "account_number": "335",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 BOR\u00c7LAR": {
+ "account_number": "336",
+ "is_group": 1
+ },
+ "D\u0130\u011eER BOR\u00c7 SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "337",
+ "is_group": 1
+ }
+ },
+ "ALINAN AVANSLAR": {
+ "account_number": "34",
+ "ALINAN S\u0130PAR\u0130\u015e AVANSLARI": {
+ "account_number": "340",
+ "is_group": 1
+ },
+ "ALINAN D\u0130\u011eER AVANSLAR": {
+ "account_number": "349",
+ "is_group": 1
+ }
+ },
+ "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM HAKED\u0130\u015eLER\u0130": {
+ "account_number": "35",
+ "YILLARA YAYGIN \u0130N\u015eAAT VE ONARIM HAKED\u0130\u015e BEDELLER\u0130": {
+ "account_number": "358",
+ "is_group": 1
+ }
+ },
+ "\u00d6DENECEK VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": {
+ "account_number": "36",
+ "\u00d6DENECEK VERG\u0130 VE FONLAR": {
+ "account_number": "360",
+ "is_group": 1
+ },
+ "\u00d6DENECEK SOSYAL G\u00dcVENL\u0130K KES\u0130NT\u0130LER\u0130": {
+ "account_number": "361",
+ "is_group": 1
+ },
+ "VADES\u0130 GE\u00c7M\u0130\u015e, ERTELENM\u0130\u015e VEYA TAKS\u0130TLEND\u0130R\u0130LM\u0130\u015e VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": {
+ "account_number": "368",
+ "is_group": 1
+ },
+ "\u00d6DENECEK D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER": {
+ "account_number": "369",
+ "is_group": 1
+ }
+ },
+ "BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": {
+ "account_number": "37",
+ "D\u00d6NEM K\u00c2RI VERG\u0130 VE D\u0130\u011eER YASAL Y\u00dcK\u00dcML\u00dcL\u00dcK KAR\u015eILIKLARI": {
+ "account_number": "370",
+ "is_group": 1
+ },
+ "D\u00d6NEM K\u00c2RININ PE\u015e\u0130N \u00d6DENEN VERG\u0130 VE D\u0130\u011eER Y\u00dcK\u00dcML\u00dcL\u00dcKLER\u0130(-)": {
+ "account_number": "371",
+ "is_group": 1
+ },
+ "KIDEM TAZM\u0130NATI KAR\u015eILI\u011eI": {
+ "account_number": "372",
+ "is_group": 1
+ },
+ "MAL\u0130YET G\u0130DERLER\u0130 KAR\u015eILI\u011eI": {
+ "account_number": "373",
+ "is_group": 1
+ },
+ "D\u0130\u011eER BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": {
+ "account_number": "379",
+ "is_group": 1
+ }
+ },
+ "GELECEK AYLARA A\u0130T GEL\u0130RLER VE G\u0130DER TAHAKKUKLARI": {
+ "account_number": "38",
+ "GELECEK AYLARA A\u0130T GEL\u0130RLER": {
+ "account_number": "380",
+ "is_group": 1
+ },
+ "G\u0130DER TAHAKKUKLARI": {
+ "account_number": "381",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER KISA VADEL\u0130 YABANCI KAYNAKLAR": {
+ "account_number": "39",
+ "HESAPLANAN KDV": {
+ "account_number": "391",
+ "HESAPL. KDV": {
+ "account_number": "391.01",
+ "% 18 HESAPLAN KDV": {
+ "account_number": "391.01.01",
+ "account_type": "Tax"
+ },
+ "% 8 HESAPLANAN KDV": {
+ "account_number": "391.01.02",
+ "account_type": "Tax"
+ },
+ "% 1 HESAPLANAN KDV": {
+ "account_number": "391.01.03",
+ "account_type": "Tax"
+ },
+ "% 20 HESAPLANAN KDV": {
+ "account_number": "391.01.04",
+ "account_type": "Tax"
+ },
+ "% 10 HESAPLANAN KDV": {
+ "account_number": "391.01.05",
+ "account_type": "Tax"
+ }
+ }
+ },
+ "D\u0130\u011eER KDV": {
+ "account_number": "392",
+ "is_group": 1
+ },
+ "MERKEZ VE \u015eUBELER CAR\u0130 HESABI": {
+ "account_number": "393",
+ "is_group": 1
+ },
+ "SAYIM VE TESELL\u00dcM FAZLALARI": {
+ "account_number": "397",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 YABANCI KAYNAKLAR": {
+ "account_number": "399",
+ "is_group": 1
+ }
+ }
+ },
+ "UZUN VADEL\u0130 YABANCI KAYNAKLAR": {
+ "account_number": "4",
+ "root_type": "Liability",
+ "MAL\u0130 BOR\u00c7LAR": {
+ "account_number": "40",
+ "BANKA KRED\u0130LER\u0130": {
+ "account_number": "400",
+ "is_group": 1
+ },
+ "F\u0130NANSAL K\u0130RALAMA \u0130\u015eLEMLER\u0130NDEN BOR\u00c7LAR-": {
+ "account_number": "401",
+ "is_group": 1
+ },
+ "ERTELENM\u0130\u015e F\u0130NANSAL K\u0130RALAMA BOR\u00c7LANMA MAL\u0130YETLER\u0130 (-)": {
+ "account_number": "402",
+ "is_group": 1
+ },
+ "\u00c7IKARILMI\u015e TAHV\u0130LLER": {
+ "account_number": "405",
+ "is_group": 1
+ },
+ "\u00c7IKARILMI\u015e D\u0130\u011eER MENKUL KIYMETLER": {
+ "account_number": "407",
+ "is_group": 1
+ },
+ "MENKUL KIYMETLER \u0130HRA\u00c7 FARKI(-)": {
+ "account_number": "408",
+ "is_group": 1
+ },
+ "D\u0130\u011eER MAL\u0130 BOR\u00c7LAR": {
+ "account_number": "409",
+ "is_group": 1
+ }
+ },
+ "T\u0130CAR\u0130 BOR\u00c7LAR": {
+ "account_number": "42",
+ "SATICILAR": {
+ "account_number": "420",
+ "is_group": 1
+ },
+ "BOR\u00c7 SENETLER\u0130": {
+ "account_number": "421",
+ "is_group": 1
+ },
+ "BOR\u00c7 SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "422",
+ "is_group": 1
+ },
+ "ALINAN DEPOZ\u0130TO VE TEM\u0130NATLAR": {
+ "account_number": "426",
+ "is_group": 1
+ },
+ "D\u0130\u011eER T\u0130CAR\u0130 BOR\u00c7LAR": {
+ "account_number": "429",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER BOR\u00c7LAR": {
+ "account_number": "43",
+ "ORTAKLARA BOR\u00c7LAR": {
+ "account_number": "431",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLERE BOR\u00c7LAR": {
+ "account_number": "432",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLARA BOR\u00c7LAR": {
+ "account_number": "433",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 BOR\u00c7LAR": {
+ "account_number": "436",
+ "is_group": 1
+ },
+ "D\u0130\u011eER BOR\u00c7 SENETLER\u0130 REESKONTU(-)": {
+ "account_number": "437",
+ "is_group": 1
+ },
+ "KAMUYA OLAN ERTELENM\u0130\u015e VEYA TAKS\u0130TLEND\u0130R\u0130LM\u0130\u015e BOR\u00c7LAR": {
+ "account_number": "438",
+ "is_group": 1
+ }
+ },
+ "ALINAN AVANSLAR": {
+ "account_number": "44",
+ "ALINAN S\u0130PAR\u0130\u015e AVANSLARI": {
+ "account_number": "440",
+ "is_group": 1
+ },
+ "ALINAN D\u0130\u011eER AVANSLAR": {
+ "account_number": "449",
+ "is_group": 1
+ }
+ },
+ "BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": {
+ "account_number": "47",
+ "KIDEM TAZM\u0130NATI KAR\u015eILI\u011eI": {
+ "account_number": "472",
+ "is_group": 1
+ },
+ "D\u0130\u011eER BOR\u00c7 VE G\u0130DER KAR\u015eILIKLARI": {
+ "account_number": "479",
+ "is_group": 1
+ }
+ },
+ "GELECEK YILLARA A\u0130T GEL\u0130RLER VE G\u0130DER TAHAKKUKLARI": {
+ "account_number": "48",
+ "GELECEK YILLARA A\u0130T GEL\u0130RLER": {
+ "account_number": "480",
+ "is_group": 1
+ },
+ "G\u0130DER TAHAKKUKLARI": {
+ "account_number": "481",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER UZUN VADEL\u0130 YABANCI KAYNAKLAR": {
+ "account_number": "49",
+ "GELECEK YILLARA ERTELENM\u0130\u015e VEYA TERK\u0130N ED\u0130LECEK KDV": {
+ "account_number": "492",
+ "is_group": 1
+ },
+ "TES\u0130SE KATILMA PAYLARI": {
+ "account_number": "493",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u00c7E\u015e\u0130TL\u0130 UZUN VADEL\u0130 YABANCI KAYNAKLAR": {
+ "account_number": "499",
+ "is_group": 1
+ }
+ }
+ },
+ "\u00d6Z KAYNAKLAR": {
+ "account_number": "5",
+ "root_type": "Equity",
+ "\u00d6DENM\u0130\u015e SERMAYE": {
+ "account_number": "50",
+ "SERMAYE": {
+ "account_number": "500",
+ "is_group": 1
+ },
+ "\u00d6DENMEM\u0130\u015e SERMAYE(-)": {
+ "account_number": "501",
+ "is_group": 1
+ },
+ "SERMAYE D\u00dcZELTMES\u0130 OLUMLU FARKLARI": {
+ "account_number": "502",
+ "is_group": 1
+ },
+ "SERMAYE D\u00dcZELTMES\u0130 OLUMSUZ FARKLARI (-)": {
+ "account_number": "503",
+ "is_group": 1
+ }
+ },
+ "SERMAYE YEDEKLER\u0130": {
+ "account_number": "52",
+ "H\u0130SSE SENETLER\u0130 \u0130HRA\u00c7 PR\u0130MLER\u0130": {
+ "account_number": "520",
+ "is_group": 1
+ },
+ "H\u0130SSE SENED\u0130 \u0130PTAL K\u00c2RLARI": {
+ "account_number": "521",
+ "is_group": 1
+ },
+ "MADD\u0130 DURAN VARLIK YEN\u0130DEN DE\u011eERLEME ARTI\u015eLARI": {
+ "account_number": "522",
+ "is_group": 1
+ },
+ "\u0130\u015eT\u0130RAKLER YEN\u0130DEN DE\u011eERLEME ARTI\u015eLARI": {
+ "account_number": "523",
+ "is_group": 1
+ },
+ "D\u0130\u011eER SERMAYE YEDEKLER\u0130": {
+ "account_number": "529",
+ "is_group": 1
+ }
+ },
+ "K\u00c2R YEDEKLER\u0130": {
+ "account_number": "54",
+ "YASAL YEDEKLER": {
+ "account_number": "540",
+ "is_group": 1
+ },
+ "STAT\u00dc YEDEKLER\u0130": {
+ "account_number": "541",
+ "is_group": 1
+ },
+ "OLA\u011eAN\u00dcST\u00dc YEDEKLER": {
+ "account_number": "542",
+ "is_group": 1
+ },
+ "D\u0130\u011eER K\u00c2R YEDEKLER\u0130": {
+ "account_number": "548",
+ "is_group": 1
+ },
+ "\u00d6ZEL FONLAR": {
+ "account_number": "549",
+ "is_group": 1
+ }
+ },
+ "GE\u00c7M\u0130\u015e YILLAR K\u00c2RLARI": {
+ "account_number": "57",
+ "GE\u00c7M\u0130\u015e YILLAR K\u00c2RLARI": {
+ "account_number": "570",
+ "is_group": 1
+ }
+ },
+ "GE\u00c7M\u0130\u015e YILLAR ZARARLARI(-)": {
+ "account_number": "58",
+ "GE\u00c7M\u0130\u015e YILLAR ZARARLARI(-)": {
+ "account_number": "580",
+ "is_group": 1
+ }
+ },
+ "D\u00d6NEM NET K\u00c2RI (ZARARI)": {
+ "account_number": "59",
+ "D\u00d6NEM NET K\u00c2RI": {
+ "account_number": "590",
+ "is_group": 1
+ },
+ "D\u00d6NEM NET ZARARI(-)": {
+ "account_number": "591",
+ "is_group": 1
+ }
+ }
+ },
+ "GEL\u0130R TABLOSU HESAPLARI": {
+ "account_number": "6",
+ "root_type": "Income",
+ "BR\u00dcT SATI\u015eLAR": {
+ "account_number": "60",
+ "YURT \u0130\u00c7\u0130 SATI\u015eLAR": {
+ "account_number": "600",
+ "is_group": 1
+ },
+ "YURT DI\u015eI SATI\u015eLAR": {
+ "account_number": "601",
+ "is_group": 1
+ },
+ "D\u0130\u011eER GEL\u0130RLER": {
+ "account_number": "602",
+ "is_group": 1
+ }
+ },
+ "SATI\u015e \u0130ND\u0130R\u0130MLER\u0130(-)": {
+ "account_number": "61",
+ "SATI\u015eTAN \u0130ADELER(-)": {
+ "account_number": "610",
+ "is_group": 1
+ },
+ "SATI\u015e \u0130ND\u0130R\u0130MLER\u0130(-)": {
+ "account_number": "611",
+ "is_group": 1
+ },
+ "D\u0130\u011eER \u0130ND\u0130R\u0130MLER(-)": {
+ "account_number": "612",
+ "is_group": 1
+ }
+ },
+ "SATI\u015eLARIN MAL\u0130YET\u0130(-)": {
+ "account_number": "62",
+ "SATILAN MAMULLER MAL\u0130YET\u0130(-)": {
+ "account_number": "620",
+ "is_group": 1
+ },
+ "SATILAN T\u0130CAR\u0130 MALLAR MAL\u0130YET\u0130(-)": {
+ "account_number": "621",
+ "is_group": 1
+ },
+ "SATILAN H\u0130ZMET MAL\u0130YET\u0130(-)": {
+ "account_number": "622",
+ "is_group": 1
+ },
+ "D\u0130\u011eER SATI\u015eLARIN MAL\u0130YET\u0130(-)": {
+ "account_number": "623",
+ "D\u0130\u011eR. SAT\u015e. MALYT.": {
+ "account_number": "623.01"
+ }
+ }
+ },
+ "FAAL\u0130YET G\u0130DERLER\u0130(-)": {
+ "account_number": "63",
+ "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130(-)": {
+ "account_number": "630",
+ "is_group": 1
+ },
+ "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130(-)": {
+ "account_number": "631",
+ "is_group": 1
+ },
+ "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130(-)": {
+ "account_number": "632",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER FAAL\u0130YETLERDEN OLA\u011eAN GEL\u0130R VE K\u00c2RLAR": {
+ "account_number": "64",
+ "\u0130\u015eT\u0130RAKLERDEN TEMETT\u00dc GEL\u0130RLER\u0130": {
+ "account_number": "640",
+ "is_group": 1
+ },
+ "BA\u011eLI ORTAKLIKLARDAN TEMETT\u00dc GEL\u0130RLER\u0130": {
+ "account_number": "641",
+ "is_group": 1
+ },
+ "FA\u0130Z GEL\u0130RLER\u0130": {
+ "account_number": "642",
+ "is_group": 1
+ },
+ "KOM\u0130SYON GEL\u0130RLER\u0130": {
+ "account_number": "643",
+ "is_group": 1
+ },
+ "KONUSU KALMAYAN KAR\u015eILIKLAR": {
+ "account_number": "644",
+ "is_group": 1
+ },
+ "MENKUL KIYMET SATI\u015e K\u00c2RLARI": {
+ "account_number": "645",
+ "is_group": 1
+ },
+ "KAMB\u0130YO K\u00c2RLARI": {
+ "account_number": "646",
+ "is_group": 1
+ },
+ "REESKONT FA\u0130Z GEL\u0130RLER\u0130": {
+ "account_number": "647",
+ "is_group": 1
+ },
+ "ENFLASYON D\u00dcZELTME K\u00c2RLARI": {
+ "account_number": "648",
+ "is_group": 1
+ },
+ "D\u0130\u011eER OLA\u011eAN GEL\u0130R VE K\u00c2RLAR": {
+ "account_number": "649",
+ "is_group": 1
+ }
+ },
+ "D\u0130\u011eER FAAL\u0130YETLERDEN OLA\u011eAN G\u0130DER VE ZARARLAR(-)": {
+ "account_number": "65",
+ "KOM\u0130SYON G\u0130DERLER\u0130(-)": {
+ "account_number": "653",
+ "is_group": 1
+ },
+ "KAR\u015eILIK G\u0130DERLER\u0130(-)": {
+ "account_number": "654",
+ "is_group": 1
+ },
+ "MENKUL KIYMET SATI\u015e ZARARLARI(-)": {
+ "account_number": "655",
+ "is_group": 1
+ },
+ "KAMB\u0130YO ZARARLARI(-)": {
+ "account_number": "656",
+ "is_group": 1
+ },
+ "REESKONT FA\u0130Z G\u0130DERLER\u0130(-)": {
+ "account_number": "657",
+ "is_group": 1
+ },
+ "ENFLASYON D\u00dcZELTMES\u0130 ZARARLARI(-)": {
+ "account_number": "658",
+ "is_group": 1
+ },
+ "D\u0130\u011eER OLA\u011eAN G\u0130DER VE ZARARLAR(-)": {
+ "account_number": "659",
+ "is_group": 1
+ }
+ },
+ "F\u0130NANSMAN G\u0130DERLER\u0130(-)": {
+ "account_number": "66",
+ "KISA VADEL\u0130 BOR\u00c7LANMA G\u0130DERLER\u0130(-)": {
+ "account_number": "660",
+ "is_group": 1
+ },
+ "UZUN VADEL\u0130 BOR\u00c7LANMA G\u0130DERLER\u0130(-)": {
+ "account_number": "661",
+ "is_group": 1
+ }
+ },
+ "OLA\u011eAN DI\u015eI GEL\u0130R VE K\u00c2RLAR": {
+ "account_number": "67",
+ "\u00d6NCEK\u0130 D\u00d6NEM GEL\u0130R VE K\u00c2RLARI": {
+ "account_number": "671",
+ "is_group": 1
+ },
+ "D\u0130\u011eER OLA\u011eAN DI\u015eI GEL\u0130R VE K\u00c2RLAR": {
+ "account_number": "679",
+ "is_group": 1
+ }
+ },
+ "OLA\u011eAN DI\u015eI G\u0130DER VE ZARARLAR(-)": {
+ "account_number": "68",
+ "\u00c7ALI\u015eMAYAN KISIM G\u0130DER VE ZARARLARI(-)": {
+ "account_number": "680",
+ "is_group": 1
+ },
+ "\u00d6NCEK\u0130 D\u00d6NEM G\u0130DER VE ZARARLARI(-)": {
+ "account_number": "681",
+ "is_group": 1
+ },
+ "D\u0130\u011eER OLA\u011eAN DI\u015eI G\u0130DER VE ZARARLAR(-)": {
+ "account_number": "689",
+ "is_group": 1
+ }
+ },
+ "D\u00d6NEM NET K\u00c2RI VEYA ZARARI": {
+ "account_number": "69",
+ "D\u00d6NEM K\u00c2RI VEYA ZARARI": {
+ "account_number": "690",
+ "is_group": 1
+ },
+ "D\u00d6NEM K\u00c2RI VERG\u0130 VE D\u0130\u011eER YASAL Y\u00dcK\u00dcML\u00dcL\u00dcK KAR\u015eILIKLARI(-)": {
+ "account_number": "691",
+ "is_group": 1
+ },
+ "D\u00d6NEM NET K\u00c2RI VEYA ZARARI": {
+ "account_number": "692",
+ "is_group": 1
+ },
+ "YILLARA YAYGIN \u0130N\u015eAAT VE ENFLASYON D\u00dcZELTME HESABI": {
+ "account_number": "697",
+ "is_group": 1
+ },
+ "ENFLASYON D\u00dcZELTME HESABI": {
+ "account_number": "698",
+ "is_group": 1
+ }
+ }
+ },
+ "MAL\u0130YET HESAPLARI (7/A SE\u00c7ENE\u011e\u0130)": {
+ "account_number": "7",
+ "root_type": "Expense",
+ "MAL\u0130YET MUHASEBES\u0130 BA\u011eLANTI HESAPLARI": {
+ "account_number": "70",
+ "MAL\u0130YET MUHASEBES\u0130 BA\u011eLANTI HESABI": {
+ "account_number": "700",
+ "is_group": 1
+ },
+ "MAL\u0130YET MUHASEBES\u0130 YANSITMA HESABI": {
+ "account_number": "701",
+ "is_group": 1
+ }
+ },
+ "D\u0130REKT \u0130LK MADDE VE MALZEME G\u0130DERLER\u0130": {
+ "account_number": "71",
+ "D\u0130REKT \u0130LK MADDE VE MALZEME G\u0130DERLER\u0130 HESABI": {
+ "account_number": "710",
+ "is_group": 1
+ },
+ "D\u0130REKT \u0130LK MADDE VE MALZEME YANSITMA HESABI": {
+ "account_number": "711",
+ "is_group": 1
+ },
+ "D\u0130REKT \u0130LK MADDE VE MALZEME F\u0130YAT FARKI": {
+ "account_number": "712",
+ "is_group": 1
+ },
+ "D\u0130REKT \u0130LK MADDE VE MALZEME M\u0130KTAR FARKI": {
+ "account_number": "713",
+ "is_group": 1
+ }
+ },
+ "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130": {
+ "account_number": "72",
+ "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130": {
+ "account_number": "720",
+ "is_group": 1
+ },
+ "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K G\u0130DERLER\u0130 YANSITMA HESABI": {
+ "account_number": "721",
+ "is_group": 1
+ },
+ "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K \u00dcCRET FARKLARI": {
+ "account_number": "722",
+ "is_group": 1
+ },
+ "D\u0130REKT \u0130\u015e\u00c7\u0130L\u0130K S\u00dcRE FARKLARI": {
+ "account_number": "723",
+ "is_group": 1
+ }
+ },
+ "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130": {
+ "account_number": "73",
+ "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130": {
+ "account_number": "730",
+ "is_group": 1
+ },
+ "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 YANSITMA HESABI": {
+ "account_number": "731",
+ "is_group": 1
+ },
+ "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 B\u00dcT\u00c7E FARKLARI": {
+ "account_number": "732",
+ "is_group": 1
+ },
+ "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 VER\u0130ML\u0130L\u0130K G\u0130DERLER\u0130": {
+ "account_number": "733",
+ "is_group": 1
+ },
+ "GENEL \u00dcRET\u0130M G\u0130DERLER\u0130 KAPAS\u0130TE FARKLARI": {
+ "account_number": "734",
+ "is_group": 1
+ }
+ },
+ "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130": {
+ "account_number": "74",
+ "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130": {
+ "account_number": "740",
+ "is_group": 1
+ },
+ "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130 YANSITMA HESABI": {
+ "account_number": "741",
+ "is_group": 1
+ },
+ "H\u0130ZMET \u00dcRET\u0130M MAL\u0130YET\u0130 FARK HESAPLARI": {
+ "account_number": "742",
+ "is_group": 1
+ }
+ },
+ "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": {
+ "account_number": "75",
+ "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130": {
+ "account_number": "750",
+ "is_group": 1
+ },
+ "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DERLER\u0130 YANSITMA HESABI": {
+ "account_number": "751",
+ "is_group": 1
+ },
+ "ARA\u015eTIRMA VE GEL\u0130\u015eT\u0130RME G\u0130DER FARKLARI": {
+ "account_number": "752",
+ "is_group": 1
+ }
+ },
+ "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130": {
+ "account_number": "76",
+ "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130": {
+ "account_number": "760",
+ "is_group": 1
+ },
+ "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130 YANSITMA HESABI": {
+ "account_number": "761",
+ "is_group": 1
+ },
+ "PAZARLAMA SATI\u015e VE DA\u011eITIM G\u0130DERLER\u0130 FARK HESABI": {
+ "account_number": "762",
+ "is_group": 1
+ }
+ },
+ "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130": {
+ "account_number": "77",
+ "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130": {
+ "account_number": "770",
+ "is_group": 1
+ },
+ "GENEL Y\u00d6NET\u0130M G\u0130DERLER\u0130 YANSITMA HESABI": {
+ "account_number": "771",
+ "is_group": 1
+ },
+ "GENEL Y\u00d6NET\u0130M G\u0130DER FARKLARI HESABI": {
+ "account_number": "772",
+ "is_group": 1
+ }
+ },
+ "F\u0130NANSMAN G\u0130DERLER\u0130": {
+ "account_number": "78",
+ "F\u0130NANSMAN G\u0130DERLER\u0130": {
+ "account_number": "780",
+ "is_group": 1
+ },
+ "F\u0130NANSMAN G\u0130DERLER\u0130 YANSITMA HESABI": {
+ "account_number": "781",
+ "is_group": 1
+ },
+ "F\u0130NANSMAN G\u0130DERLER\u0130 FARK HESABI": {
+ "account_number": "782",
+ "is_group": 1
+ }
+ },
+ "G\u0130DER \u00c7E\u015e\u0130TLER\u0130": {
+ "account_number": "79",
+ "\u0130LK MADDE VE MALZEME G\u0130DERLER\u0130": {
+ "account_number": "790",
+ "is_group": 1
+ },
+ "\u0130\u015e\u00c7\u0130 \u00dcCRET VE G\u0130DERLER\u0130": {
+ "account_number": "791",
+ "is_group": 1
+ },
+ "MEMUR \u00dcCRET VE G\u0130DERLER\u0130": {
+ "account_number": "792",
+ "is_group": 1
+ },
+ "DI\u015eARIDAN SA\u011eLANAN FAYDA VE H\u0130ZMETLER": {
+ "account_number": "793",
+ "is_group": 1
+ },
+ "\u00c7E\u015e\u0130TL\u0130 G\u0130DERLER": {
+ "account_number": "794",
+ "is_group": 1
+ },
+ "VERG\u0130, RES\u0130M VE HAR\u00c7LAR": {
+ "account_number": "795",
+ "is_group": 1
+ },
+ "AMORT\u0130SMAN VE T\u00dcKENME PAYLARI": {
+ "account_number": "796",
+ "is_group": 1
+ },
+ "F\u0130NANSMAN G\u0130DERLER\u0130": {
+ "account_number": "797",
+ "is_group": 1
+ },
+ "G\u0130DER \u00c7E\u015e\u0130TLER\u0130 YANSITMA HESABI": {
+ "account_number": "798",
+ "is_group": 1
+ },
+ "\u00dcRET\u0130M MAL\u0130YET HESABI": {
+ "account_number": "799",
+ "is_group": 1
+ }
+ }
+ },
+ "SERBEST HESAPLAR": {
+ "account_number": "8",
+ "root_type": "Asset",
+ "B\u00dcT\u00c7E GEL\u0130RLER\u0130 HESABI": {
+ "account_number": "800",
+ "is_group": 1
+ },
+ "GEL\u0130R YANSITMA HESABI": {
+ "account_number": "805",
+ "is_group": 1
+ },
+ "B\u00dcT\u00c7E GEL\u0130RLER\u0130NDEN RET VE \u0130ADELER HESABI": {
+ "account_number": "810",
+ "is_group": 1
+ },
+ "B\u00dcT\u00c7E G\u0130DERLER\u0130 HESABI": {
+ "account_number": "830",
+ "is_group": 1
+ },
+ "B\u00dcT\u00c7EDEN MAHSUP ED\u0130LECEK \u00d6DEMELER HESABI": {
+ "account_number": "833",
+ "is_group": 1
+ },
+ "GE\u00c7EN YIL B\u00dcT\u00c7E MAHSUPLARI HESABI": {
+ "account_number": "834",
+ "is_group": 1
+ },
+ "G\u0130DER YANSITMA HESABI": {
+ "account_number": "835",
+ "is_group": 1
+ },
+ "B\u00dcT\u00c7E UYGULAMA SONU\u00c7LARI HESABI": {
+ "account_number": "895",
+ "is_group": 1
+ }
+ },
+ "NAZIM HESAPLAR": {
+ "account_number": "9",
+ "root_type": "Expense",
+ "G\u00d6NDER\u0130LECEK B\u00dcT\u00c7E \u00d6DENEKLER\u0130 HESABI": {
+ "account_number": "900",
+ "is_group": 1
+ },
+ "B\u00dcT\u00c7E \u00d6DENEKLER\u0130 HESABI": {
+ "account_number": "901",
+ "is_group": 1
+ },
+ "B\u00dcT\u00c7E \u00d6DENEK HAREKETLER\u0130 HESABI": {
+ "account_number": "902",
+ "is_group": 1
+ },
+ "KULLANILACAK \u00d6DENEKLER HESABI": {
+ "account_number": "903",
+ "is_group": 1
+ },
+ "\u00d6DENEKLER HESABI": {
+ "account_number": "904",
+ "is_group": 1
+ },
+ "\u00d6DENEKL\u0130 G\u0130DERLER HESABI": {
+ "account_number": "905",
+ "is_group": 1
+ },
+ "MAHSUP D\u00d6NEM\u0130NE AKTARILAN KULLANILACAK \u00d6DENEKLER HESABI": {
+ "account_number": "906",
+ "is_group": 1
+ },
+ "MAHSUP D\u00d6NEM\u0130NE AKTARILAN \u00d6DENEKLER HESABI": {
+ "account_number": "907",
+ "is_group": 1
+ },
+ "TEM\u0130NAT MEKTUPLARI HESABI": {
+ "account_number": "910",
+ "is_group": 1
+ },
+ "TEM\u0130NAT MEKTUPLARI EMANETLER\u0130 HESABI": {
+ "account_number": "911",
+ "is_group": 1
+ },
+ "K\u0130\u015e\u0130LERE A\u0130T MENKUL KIYMETLER HESABI": {
+ "account_number": "912",
+ "is_group": 1
+ },
+ "K\u0130\u015e\u0130LERE A\u0130T MENKUL KIYMET EMANETLER\u0130 HESABI": {
+ "account_number": "913",
+ "is_group": 1
+ },
+ "G\u0130DER TAAHH\u00dcTLER\u0130 HESABI": {
+ "account_number": "920",
+ "is_group": 1
+ },
+ "G\u0130DER TAAHH\u00dcTLER\u0130 KAR\u015eILI\u011eI HESABI": {
+ "account_number": "921",
+ "is_group": 1
+ },
+ "VER\u0130LEN GARANT\u0130LER HESABI": {
+ "account_number": "930",
+ "is_group": 1
+ },
+ "VER\u0130LEN GARANT\u0130LER KAR\u015eILI\u011eI HESABI": {
+ "account_number": "931",
+ "is_group": 1
+ },
+ "STOK AYARLAMA": {
+ "account_number": "940",
+ "STOK SAYIM AYARLAMA": {
+ "account_number": "940.01",
+ "account_type": "Stock Adjustment"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 7a2f4e4b71b..db99bcd223b 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -255,14 +255,16 @@ def get_accounting_dimensions(as_list=True, filters=None):
def get_checks_for_pl_and_bs_accounts():
- dimensions = frappe.db.sql(
- """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
- FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
- WHERE p.name = c.parent""",
- as_dict=1,
- )
+ if frappe.flags.accounting_dimensions_details is None:
+ # nosemgrep
+ frappe.flags.accounting_dimensions_details = frappe.db.sql(
+ """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
+ FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
+ WHERE p.name = c.parent""",
+ as_dict=1,
+ )
- return dimensions
+ return frappe.flags.accounting_dimensions_details
def get_dimension_with_children(doctype, dimensions):
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index cb7f5f5da78..10dbe3bab0f 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -78,6 +78,8 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self):
disable_dimension()
+ frappe.flags.accounting_dimensions_details = None
+ frappe.flags.dimension_filter_map = None
def create_dimension():
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
index 01f6e60bf3b..1954b4b0efe 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
@@ -66,37 +66,41 @@ class AccountingDimensionFilter(Document):
def get_dimension_filter_map():
- filters = frappe.db.sql(
- """
- SELECT
- a.applicable_on_account, d.dimension_value, p.accounting_dimension,
- p.allow_or_restrict, a.is_mandatory
- FROM
- `tabApplicable On Account` a,
- `tabAccounting Dimension Filter` p
- LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
- WHERE
- p.name = a.parent
- AND p.disabled = 0
- """,
- as_dict=1,
- )
-
- dimension_filter_map = {}
-
- for f in filters:
- f.fieldname = scrub(f.accounting_dimension)
-
- build_map(
- dimension_filter_map,
- f.fieldname,
- f.applicable_on_account,
- f.dimension_value,
- f.allow_or_restrict,
- f.is_mandatory,
+ if not frappe.flags.get("dimension_filter_map"):
+ # nosemgrep
+ filters = frappe.db.sql(
+ """
+ SELECT
+ a.applicable_on_account, d.dimension_value, p.accounting_dimension,
+ p.allow_or_restrict, a.is_mandatory
+ FROM
+ `tabApplicable On Account` a, `tabAllowed Dimension` d,
+ `tabAccounting Dimension Filter` p
+ WHERE
+ p.name = a.parent
+ AND p.disabled = 0
+ AND p.name = d.parent
+ """,
+ as_dict=1,
)
- return dimension_filter_map
+ dimension_filter_map = {}
+
+ for f in filters:
+ f.fieldname = scrub(f.accounting_dimension)
+
+ build_map(
+ dimension_filter_map,
+ f.fieldname,
+ f.applicable_on_account,
+ f.dimension_value,
+ f.allow_or_restrict,
+ f.is_mandatory,
+ )
+
+ frappe.flags.dimension_filter_map = dimension_filter_map
+
+ return frappe.flags.dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
index 3dec7c87020..77057c1e20e 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -47,6 +47,8 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def tearDown(self):
disable_dimension_filter()
disable_dimension()
+ frappe.flags.accounting_dimensions_details = None
+ frappe.flags.dimension_filter_map = None
for si in self.invoice_list:
si.load_from_db()
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 9982b4f9d88..7bf3826e781 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -469,7 +469,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2024-03-15 12:11:36.085158",
+ "modified": "2024-05-11 23:19:44.673975",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py
index 75f02e659f4..a1271b9c32a 100644
--- a/erpnext/accounts/doctype/bank_transaction/auto_match_party.py
+++ b/erpnext/accounts/doctype/bank_transaction/auto_match_party.py
@@ -68,6 +68,9 @@ class AutoMatchbyAccountIBAN:
party, or_filters=or_filters, pluck="name", limit_page_length=1
)
+ if "bank_ac_no" in or_filters:
+ or_filters["bank_account_no"] = or_filters.pop("bank_ac_no")
+
if party_result:
result = (
party,
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 11f78ae1763..145480138d6 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -142,6 +142,8 @@ class Budget(Document):
def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args)
+ if not frappe.get_all("Budget", limit=1):
+ return
if args.get("company") and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
@@ -149,6 +151,9 @@ def validate_expense_against_budget(args, expense_amount=0):
"Company", args.get("company"), "exception_budget_approver_role"
)
+ if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec
+ return
+
if not args.account:
args.account = args.get("expense_account")
@@ -175,12 +180,12 @@ def validate_expense_against_budget(args, expense_amount=0):
if (
args.get(budget_against)
and args.account
- and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
+ and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense")
):
doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"):
- lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
+ lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = f"""and exists(select name from `tab{doctype}`
where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec
args.is_tree = True
diff --git a/erpnext/accounts/doctype/budget_account/budget_account.json b/erpnext/accounts/doctype/budget_account/budget_account.json
index ead07614a7f..c7d872647f1 100644
--- a/erpnext/accounts/doctype/budget_account/budget_account.json
+++ b/erpnext/accounts/doctype/budget_account/budget_account.json
@@ -1,94 +1,42 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-05-16 11:54:09.286135",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-05-16 11:54:09.286135",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "account",
+ "budget_amount"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "budget_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Budget Amount",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "budget_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Budget Amount",
+ "options": "Company:company:default_currency",
+ "reqd": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-01-02 17:02:53.339420",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Budget Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2024-03-04 15:43:27.016947",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Budget Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
index 784ad27dd47..34b2ec68743 100644
--- a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
@@ -4,7 +4,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import add_days, format_date, getdate
+from frappe.utils import add_days, flt, format_date, getdate
class MainCostCenterCantBeChild(frappe.ValidationError):
@@ -60,7 +60,7 @@ class CostCenterAllocation(Document):
self.validate_child_cost_centers()
def validate_total_allocation_percentage(self):
- total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
+ total_percentage = sum([flt(d.percentage) for d in self.get("allocation_percentages", [])])
if total_percentage != 100:
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
index fb997847448..66db37fe13b 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.json
@@ -82,7 +82,7 @@
"icon": "fa fa-calendar",
"idx": 1,
"links": [],
- "modified": "2024-01-30 12:35:38.645968",
+ "modified": "2024-05-27 17:29:55.560840",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Fiscal Year",
@@ -127,6 +127,10 @@
{
"read": 1,
"role": "Stock Manager"
+ },
+ {
+ "read": 1,
+ "role": "Auditor"
}
],
"show_name_in_global_search": 1,
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index 0800971269b..2d106ad8cee 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -179,7 +179,8 @@
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"label": "Voucher Detail No",
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "project",
@@ -290,7 +291,7 @@
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2023-12-18 15:38:14.006208",
+ "modified": "2024-07-02 14:31:51.496466",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
@@ -322,7 +323,7 @@
],
"quick_entry": 1,
"search_fields": "voucher_no,account,posting_date,against_voucher",
- "sort_field": "modified",
+ "sort_field": "creation",
"sort_order": "DESC",
"states": []
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index 594c3054cfd..d74224c4aa2 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -32,8 +32,6 @@ class GLEntry(Document):
account: DF.Link | None
account_currency: DF.Link | None
against: DF.Text | None
- against_link: DF.DynamicLink | None
- against_type: DF.Link | None
against_voucher: DF.DynamicLink | None
against_voucher_type: DF.Link | None
company: DF.Link | None
@@ -328,7 +326,7 @@ def update_outstanding_amt(
party_condition = ""
if against_voucher_type == "Sales Invoice":
- party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
+ party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})"
else:
account_condition = f" and account = {frappe.db.escape(account)}"
@@ -392,7 +390,9 @@ def update_outstanding_amt(
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == "Yes" and not adv_adj:
- frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", None, "frozen_accounts_modifier")
+ frozen_accounts_modifier = frappe.get_cached_value(
+ "Accounts Settings", None, "frozen_accounts_modifier"
+ )
if not frozen_accounts_modifier:
frappe.throw(_("Account {0} is frozen").format(account))
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 402f19ee968..39f9aaf7a6c 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -194,6 +194,7 @@ class JournalEntry(AccountsController):
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
+ self.update_booked_depreciation()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
@@ -225,6 +226,7 @@ class JournalEntry(AccountsController):
self.unlink_inter_company_jv()
self.unlink_asset_adjustment_entry()
self.update_invoice_discounting()
+ self.update_booked_depreciation(1)
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
@@ -439,6 +441,25 @@ class JournalEntry(AccountsController):
if status:
inv_disc_doc.set_status(status=status)
+ def update_booked_depreciation(self, cancel=0):
+ for d in self.get("accounts"):
+ if (
+ self.voucher_type == "Depreciation Entry"
+ and d.reference_type == "Asset"
+ and d.reference_name
+ and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
+ and d.debit
+ ):
+ asset = frappe.get_doc("Asset", d.reference_name)
+ for fb_row in asset.get("finance_books"):
+ if fb_row.finance_book == self.finance_book:
+ if cancel:
+ fb_row.total_number_of_booked_depreciations -= 1
+ else:
+ fb_row.total_number_of_booked_depreciations += 1
+ fb_row.db_update()
+ break
+
def unlink_advance_entry_reference(self):
for d in self.get("accounts"):
if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
@@ -1031,6 +1052,17 @@ class JournalEntry(AccountsController):
def build_gl_map(self):
gl_map = []
+
+ company_currency = erpnext.get_company_currency(self.company)
+ if self.multi_currency:
+ for row in self.get("accounts"):
+ if row.account_currency != company_currency:
+ self.currency = row.account_currency
+ self.conversion_rate = row.exchange_rate
+ break
+ else:
+ self.currency = company_currency
+
for d in self.get("accounts"):
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
r = [d.user_remark, self.remark]
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index 6b0115d3f56..d420bcca342 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -89,6 +89,7 @@
"custom_remarks",
"remarks",
"base_in_words",
+ "is_opening",
"column_break_16",
"letter_head",
"print_heading",
@@ -777,6 +778,16 @@
"label": "Reconcile on Advance Payment Date",
"no_copy": 1,
"read_only": 1
+ },
+ {
+ "default": "No",
+ "depends_on": "eval: doc.book_advance_payments_in_separate_party_account == 1",
+ "fieldname": "is_opening",
+ "fieldtype": "Select",
+ "label": "Is Opening",
+ "options": "No\nYes",
+ "print_hide": 1,
+ "search_index": 1
}
],
"index_web_pages_for_search": 1,
@@ -790,7 +801,7 @@
"table_fieldname": "payment_entries"
}
],
- "modified": "2024-05-17 10:21:11.199445",
+ "modified": "2024-05-31 17:07:06.197249",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index e542ff68176..fb3500e2c58 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -76,7 +76,6 @@ class PaymentEntry(AccountsController):
self.setup_party_account_field()
self.set_missing_values()
self.set_liability_account()
- self.validate_advance_account_currency()
self.set_missing_ref_details(force=True)
self.validate_payment_type()
self.validate_party_details()
@@ -116,11 +115,13 @@ class PaymentEntry(AccountsController):
self.book_advance_payments_in_separate_party_account = False
if self.party_type not in ("Customer", "Supplier"):
+ self.is_opening = "No"
return
if not frappe.db.get_value(
"Company", self.company, "book_advance_payments_in_separate_party_account"
):
+ self.is_opening = "No"
return
# Important to set this flag for the gl building logic to work properly
@@ -132,6 +133,7 @@ class PaymentEntry(AccountsController):
if (account_type == "Payable" and self.party_type == "Customer") or (
account_type == "Receivable" and self.party_type == "Supplier"
):
+ self.is_opening = "No"
return
if self.references:
@@ -141,6 +143,7 @@ class PaymentEntry(AccountsController):
# If there are referencers other than `allowed_types`, treat this as a normal payment entry
if reference_types - allowed_types:
self.book_advance_payments_in_separate_party_account = False
+ self.is_opening = "No"
return
liability_account = get_party_account(
@@ -159,22 +162,6 @@ class PaymentEntry(AccountsController):
alert=True,
)
- def validate_advance_account_currency(self):
- if self.book_advance_payments_in_separate_party_account is True:
- company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
- if self.payment_type == "Receive" and self.paid_from_account_currency != company_currency:
- frappe.throw(
- _("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format(
- frappe.bold(self.paid_from), frappe.bold(self.paid_from_account_currency)
- )
- )
- if self.payment_type == "Pay" and self.paid_to_account_currency != company_currency:
- frappe.throw(
- _("Booking advances in foreign currency account: {0} ({1}) is not yet supported.").format(
- frappe.bold(self.paid_to), frappe.bold(self.paid_to_account_currency)
- )
- )
-
def on_cancel(self):
self.ignore_linked_doctypes = (
"GL Entry",
@@ -1230,13 +1217,21 @@ class PaymentEntry(AccountsController):
if reference.reference_doctype == "Sales Invoice":
return "credit", reference.account
+ if reference.reference_doctype == "Purchase Invoice":
+ return "debit", reference.account
+
if reference.reference_doctype == "Payment Entry":
+ # reference.account_type and reference.payment_type is only available for Reverse payments
if reference.account_type == "Receivable" and reference.payment_type == "Pay":
return "credit", self.party_account
else:
return "debit", self.party_account
- return "debit", reference.account
+ if reference.reference_doctype == "Journal Entry":
+ if self.party_type == "Customer" and self.payment_type == "Receive":
+ return "credit", reference.account
+ else:
+ return "debit", reference.account
def add_advance_gl_for_reference(self, gl_entries, invoice):
args_dict = {
@@ -1262,7 +1257,7 @@ class PaymentEntry(AccountsController):
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
args_dict["account"] = account
- args_dict[dr_or_cr] = invoice.allocated_amount
+ args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict.update(
{
@@ -1281,7 +1276,7 @@ class PaymentEntry(AccountsController):
args_dict[dr_or_cr + "_in_account_currency"] = 0
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
args_dict["account"] = self.party_account
- args_dict[dr_or_cr] = invoice.allocated_amount
+ args_dict[dr_or_cr] = self.calculate_base_allocated_amount_for_reference(invoice)
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
args_dict.update(
{
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index d22350d20fc..cc03dc260bb 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -82,7 +82,7 @@ class TestPaymentEntry(FrappeTestCase):
expected_gle = dict(
(d[0], d)
- for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], ["Cash - _TC", 5500.0, 0, None]]
+ for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]]
)
self.validate_gl_entries(pe.name, expected_gle)
@@ -1729,6 +1729,68 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries()
self.check_pl_entries()
+ def test_opening_flag_for_advance_as_liability(self):
+ company = "_Test Company"
+
+ advance_account = create_account(
+ parent_account="Current Assets - _TC",
+ account_name="Advances Received",
+ company=company,
+ account_type="Receivable",
+ )
+
+ # Enable Advance in separate party account
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": 1,
+ "default_advance_received_account": advance_account,
+ },
+ )
+ # Advance Payment
+ adv = create_payment_entry(
+ party_type="Customer",
+ party="_Test Customer",
+ payment_type="Receive",
+ paid_from="Debtors - _TC",
+ paid_to="_Test Cash - _TC",
+ )
+ adv.is_opening = "Yes"
+ adv.save() # use save() to trigger set_liability_account()
+ adv.submit()
+
+ gl_with_opening_set = frappe.db.get_all(
+ "GL Entry", filters={"voucher_no": adv.name, "is_opening": "Yes"}
+ )
+ # 'Is Opening' can be 'Yes' for Advances in separate party account
+ self.assertNotEqual(gl_with_opening_set, [])
+
+ # Disable Advance in separate party account
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": 0,
+ "default_advance_received_account": None,
+ },
+ )
+ payment = create_payment_entry(
+ party_type="Customer",
+ party="_Test Customer",
+ payment_type="Receive",
+ paid_from="Debtors - _TC",
+ paid_to="_Test Cash - _TC",
+ )
+ payment.is_opening = "Yes"
+ payment.save()
+ payment.submit()
+ gl_with_opening_set = frappe.db.get_all(
+ "GL Entry", filters={"voucher_no": payment.name, "is_opening": "Yes"}
+ )
+ # 'Is Opening' should always be 'No' for normal advance payments
+ self.assertEqual(gl_with_opening_set, [])
+
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
index ca591d42073..2bc44893c20 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
@@ -161,11 +161,12 @@ class PaymentLedgerEntry(Document):
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
- self.validate_account_details()
- self.validate_dimensions_for_pl_and_bs()
- self.validate_allowed_dimensions()
- validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
+ if not self.delinked:
+ self.validate_account_details()
+ self.validate_dimensions_for_pl_and_bs()
+ self.validate_allowed_dimensions()
+ validate_balance_type(self.account, adv_adj)
# update outstanding amount
if (
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
index 3eac98d7910..9a33a7ccf6d 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
+++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
@@ -509,7 +509,11 @@ class TestPaymentLedgerEntry(FrappeTestCase):
@change_settings(
"Accounts Settings",
- {"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
+ {
+ "unlink_payment_on_cancellation_of_invoice": 1,
+ "delete_linked_ledger_entries": 1,
+ "unlink_advance_payment_on_cancelation_of_order": 1,
+ },
)
def test_advance_payment_unlink_on_order_cancellation(self):
transaction_date = nowdate()
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 53f69a47e75..a5295585221 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -109,6 +109,14 @@ class TestPaymentReconciliation(FrappeTestCase):
"account_currency": "INR",
"account_type": "Payable",
},
+ # 'Receivable' account for capturing advance received, under 'Liabilities' group
+ {
+ "attribute": "advance_receivable_account",
+ "account_name": "Advance Received",
+ "parent_account": "Current Liabilities - _PR",
+ "account_currency": "INR",
+ "account_type": "Receivable",
+ },
]
for x in accounts:
@@ -1574,6 +1582,229 @@ class TestPaymentReconciliation(FrappeTestCase):
)
self.assertEqual(len(pl_entries), 3)
+ def test_advance_payment_reconciliation_against_journal_for_customer(self):
+ frappe.db.set_value(
+ "Company",
+ self.company,
+ {
+ "book_advance_payments_in_separate_party_account": 1,
+ "default_advance_received_account": self.advance_receivable_account,
+ "reconcile_on_advance_payment_date": 0,
+ },
+ )
+ amount = 200.0
+ je = self.create_journal_entry(self.debit_to, self.bank, amount)
+ je.accounts[0].cost_center = self.main_cc.name
+ je.accounts[0].party_type = "Customer"
+ je.accounts[0].party = self.customer
+ je.accounts[1].cost_center = self.main_cc.name
+ je = je.save().submit()
+
+ pe = self.create_payment_entry(amount=amount).save().submit()
+
+ pr = self.create_payment_reconciliation()
+ pr.default_advance_account = self.advance_receivable_account
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+ invoices = [invoice.as_dict() for invoice in pr.invoices]
+ payments = [payment.as_dict() for payment in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+
+ # Assert Ledger Entries
+ gl_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={"voucher_no": pe.name, "is_cancelled": 0},
+ )
+ self.assertEqual(len(gl_entries), 4)
+ pl_entries = frappe.db.get_all(
+ "Payment Ledger Entry",
+ filters={"voucher_no": pe.name, "delinked": 0},
+ )
+ self.assertEqual(len(pl_entries), 3)
+
+ gl_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={"voucher_no": pe.name, "is_cancelled": 0},
+ fields=["account", "voucher_no", "against_voucher", "debit", "credit"],
+ order_by="account, against_voucher, debit",
+ )
+ expected_gle = [
+ {
+ "account": self.advance_receivable_account,
+ "voucher_no": pe.name,
+ "against_voucher": pe.name,
+ "debit": 0.0,
+ "credit": amount,
+ },
+ {
+ "account": self.advance_receivable_account,
+ "voucher_no": pe.name,
+ "against_voucher": pe.name,
+ "debit": amount,
+ "credit": 0.0,
+ },
+ {
+ "account": self.debit_to,
+ "voucher_no": pe.name,
+ "against_voucher": je.name,
+ "debit": 0.0,
+ "credit": amount,
+ },
+ {
+ "account": self.bank,
+ "voucher_no": pe.name,
+ "against_voucher": None,
+ "debit": amount,
+ "credit": 0.0,
+ },
+ ]
+ self.assertEqual(gl_entries, expected_gle)
+
+ pl_entries = frappe.db.get_all(
+ "Payment Ledger Entry",
+ filters={"voucher_no": pe.name},
+ fields=["account", "voucher_no", "against_voucher_no", "amount"],
+ order_by="account, against_voucher_no, amount",
+ )
+ expected_ple = [
+ {
+ "account": self.advance_receivable_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": -amount,
+ },
+ {
+ "account": self.advance_receivable_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": amount,
+ },
+ {
+ "account": self.debit_to,
+ "voucher_no": pe.name,
+ "against_voucher_no": je.name,
+ "amount": -amount,
+ },
+ ]
+ self.assertEqual(pl_entries, expected_ple)
+
+ def test_advance_payment_reconciliation_against_journal_for_supplier(self):
+ self.supplier = make_supplier("_Test Supplier")
+ frappe.db.set_value(
+ "Company",
+ self.company,
+ {
+ "book_advance_payments_in_separate_party_account": 1,
+ "default_advance_paid_account": self.advance_payable_account,
+ "reconcile_on_advance_payment_date": 0,
+ },
+ )
+ amount = 200.0
+ je = self.create_journal_entry(self.creditors, self.bank, -amount)
+ je.accounts[0].cost_center = self.main_cc.name
+ je.accounts[0].party_type = "Supplier"
+ je.accounts[0].party = self.supplier
+ je.accounts[1].cost_center = self.main_cc.name
+ je = je.save().submit()
+
+ pe = self.create_payment_entry(amount=amount)
+ pe.payment_type = "Pay"
+ pe.party_type = "Supplier"
+ pe.paid_from = self.bank
+ pe.paid_to = self.creditors
+ pe.party = self.supplier
+ pe.save().submit()
+
+ pr = self.create_payment_reconciliation(party_is_customer=False)
+ pr.default_advance_account = self.advance_payable_account
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+ invoices = [invoice.as_dict() for invoice in pr.invoices]
+ payments = [payment.as_dict() for payment in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+
+ # Assert Ledger Entries
+ gl_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={"voucher_no": pe.name, "is_cancelled": 0},
+ )
+ self.assertEqual(len(gl_entries), 4)
+ pl_entries = frappe.db.get_all(
+ "Payment Ledger Entry",
+ filters={"voucher_no": pe.name, "delinked": 0},
+ )
+ self.assertEqual(len(pl_entries), 3)
+
+ gl_entries = frappe.db.get_all(
+ "GL Entry",
+ filters={"voucher_no": pe.name, "is_cancelled": 0},
+ fields=["account", "voucher_no", "against_voucher", "debit", "credit"],
+ order_by="account, against_voucher, debit",
+ )
+ expected_gle = [
+ {
+ "account": self.advance_payable_account,
+ "voucher_no": pe.name,
+ "against_voucher": pe.name,
+ "debit": 0.0,
+ "credit": amount,
+ },
+ {
+ "account": self.advance_payable_account,
+ "voucher_no": pe.name,
+ "against_voucher": pe.name,
+ "debit": amount,
+ "credit": 0.0,
+ },
+ {
+ "account": self.creditors,
+ "voucher_no": pe.name,
+ "against_voucher": je.name,
+ "debit": amount,
+ "credit": 0.0,
+ },
+ {
+ "account": self.bank,
+ "voucher_no": pe.name,
+ "against_voucher": None,
+ "debit": 0.0,
+ "credit": amount,
+ },
+ ]
+ self.assertEqual(gl_entries, expected_gle)
+
+ pl_entries = frappe.db.get_all(
+ "Payment Ledger Entry",
+ filters={"voucher_no": pe.name},
+ fields=["account", "voucher_no", "against_voucher_no", "amount"],
+ order_by="account, against_voucher_no, amount",
+ )
+ expected_ple = [
+ {
+ "account": self.advance_payable_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": -amount,
+ },
+ {
+ "account": self.advance_payable_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": amount,
+ },
+ {
+ "account": self.creditors,
+ "voucher_no": pe.name,
+ "against_voucher_no": je.name,
+ "amount": -amount,
+ },
+ ]
+ self.assertEqual(pl_entries, expected_ple)
+
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 420fd3bee9d..de29ec0f004 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -139,6 +139,7 @@ class PricingRule(Document):
self.validate_price_list_with_currency()
self.validate_dates()
self.validate_condition()
+ self.validate_mixed_with_recursion()
if not self.margin_type:
self.margin_rate_or_amount = 0.0
@@ -308,6 +309,10 @@ class PricingRule(Document):
):
frappe.throw(_("Invalid condition expression"))
+ def validate_mixed_with_recursion(self):
+ if self.mixed_conditions and self.is_recursive:
+ frappe.throw(_("Recursive Discounts with Mixed condition is not supported by the system"))
+
# --------------------------------------------------------------------------------
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 7f9c55ff24f..87f6fa76bc8 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -1237,6 +1237,80 @@ class TestPricingRule(unittest.TestCase):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
+ def test_pricing_rules_with_and_without_apply_multiple(self):
+ item = make_item("PR Item 99")
+
+ test_records = [
+ {
+ "doctype": "Pricing Rule",
+ "title": "_Test discount on item group",
+ "name": "_Test discount on item group",
+ "apply_on": "Item Group",
+ "item_groups": [
+ {
+ "item_group": "Products",
+ }
+ ],
+ "selling": 1,
+ "price_or_product_discount": "Price",
+ "rate_or_discount": "Discount Percentage",
+ "discount_percentage": 60,
+ "has_priority": 1,
+ "company": "_Test Company",
+ "apply_multiple_pricing_rules": True,
+ },
+ {
+ "doctype": "Pricing Rule",
+ "title": "_Test fixed rate on item code",
+ "name": "_Test fixed rate on item code",
+ "apply_on": "Item Code",
+ "items": [
+ {
+ "item_code": item.name,
+ }
+ ],
+ "selling": 1,
+ "price_or_product_discount": "Price",
+ "rate_or_discount": "Rate",
+ "rate": 25,
+ "has_priority": 1,
+ "company": "_Test Company",
+ "apply_multiple_pricing_rules": False,
+ },
+ ]
+
+ for item_group_priority, item_code_priority in [(2, 4), (4, 2)]:
+ item_group_rule = frappe.get_doc(test_records[0].copy())
+ item_group_rule.priority = item_group_priority
+ item_group_rule.insert()
+
+ item_code_rule = frappe.get_doc(test_records[1].copy())
+ item_code_rule.priority = item_code_priority
+ item_code_rule.insert()
+
+ si = create_sales_invoice(qty=5, customer="_Test Customer 1", item=item.name, do_not_submit=True)
+ si.save()
+ self.assertEqual(len(si.pricing_rules), 1)
+ # Item Code rule should've applied as it has higher priority
+ expected_rule = item_group_rule if item_group_priority > item_code_priority else item_code_rule
+ self.assertEqual(si.pricing_rules[0].pricing_rule, expected_rule.name)
+
+ si.delete()
+ item_group_rule.delete()
+ item_code_rule.delete()
+
+ def test_validation_on_mixed_condition_with_recursion(self):
+ pricing_rule = make_pricing_rule(
+ discount_percentage=10,
+ selling=1,
+ priority=2,
+ min_qty=4,
+ title="_Test Pricing Rule with Min Qty - 2",
+ )
+ pricing_rule.mixed_conditions = True
+ pricing_rule.is_recursive = True
+ self.assertRaises(frappe.ValidationError, pricing_rule.save)
+
test_dependencies = ["Campaign"]
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 60c9e26aabe..9c7911d7cae 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -174,12 +174,9 @@ def _get_pricing_rules(apply_on, args, values):
def apply_multiple_pricing_rules(pricing_rules):
- apply_multiple_rule = [
- d.apply_multiple_pricing_rules for d in pricing_rules if d.apply_multiple_pricing_rules
- ]
-
- if not apply_multiple_rule:
- return False
+ for d in pricing_rules:
+ if not d.apply_multiple_pricing_rules:
+ return False
return True
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index ed4e3d2a8f3..71fe156dd7a 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -146,6 +146,7 @@ class PromotionalScheme(Document):
self.validate_applicable_for()
self.validate_pricing_rules()
+ self.validate_mixed_with_recursion()
def validate_applicable_for(self):
if self.applicable_for:
@@ -163,7 +164,7 @@ class PromotionalScheme(Document):
docnames = []
# If user has changed applicable for
- if self._doc_before_save.applicable_for == self.applicable_for:
+ if self.get_doc_before_save() and self.get_doc_before_save().applicable_for == self.applicable_for:
return
docnames = frappe.get_all("Pricing Rule", filters={"promotional_scheme": self.name})
@@ -177,6 +178,7 @@ class PromotionalScheme(Document):
frappe.delete_doc("Pricing Rule", docname.name)
def on_update(self):
+ self.validate()
pricing_rules = (
frappe.get_all(
"Pricing Rule",
@@ -188,6 +190,15 @@ class PromotionalScheme(Document):
)
self.update_pricing_rules(pricing_rules)
+ def validate_mixed_with_recursion(self):
+ if self.mixed_conditions:
+ if self.product_discount_slabs:
+ for slab in self.product_discount_slabs:
+ if slab.is_recursive:
+ frappe.throw(
+ _("Recursive Discounts with Mixed condition is not supported by the system")
+ )
+
def update_pricing_rules(self, pricing_rules):
rules = {}
count = 0
diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
index 9b40c98829c..74ba6cf923c 100644
--- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
@@ -129,6 +129,25 @@ class TestPromotionalScheme(unittest.TestCase):
[pr.min_qty, pr.free_item, pr.free_qty, pr.recurse_for], [12, "_Test Item 2", 1, 12]
)
+ def test_validation_on_recurse_with_mixed_condition(self):
+ ps = make_promotional_scheme()
+ ps.set("price_discount_slabs", [])
+ ps.set(
+ "product_discount_slabs",
+ [
+ {
+ "rule_description": "12+1",
+ "min_qty": 12,
+ "free_item": "_Test Item 2",
+ "free_qty": 1,
+ "is_recursive": 1,
+ "recurse_for": 12,
+ }
+ ],
+ )
+ ps.mixed_conditions = True
+ self.assertRaises(frappe.ValidationError, ps.save)
+
def make_promotional_scheme(**args):
args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index c56a083808d..c0a43395216 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -677,7 +677,7 @@ frappe.ui.form.on("Purchase Invoice", {
if (frm.doc.supplier) {
frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
}
- if (!frm.doc.__onload.supplier_tds) {
+ if (!frm.doc.__onload.enable_apply_tds) {
frm.set_df_property("apply_tds", "read_only", 1);
}
}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index afccc21c313..0f64c9d697f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -3,7 +3,7 @@
import frappe
-from frappe import _, throw
+from frappe import _, qb, throw
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder.functions import Sum
from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
@@ -347,6 +347,22 @@ class PurchaseInvoice(BuyingController):
self.tax_withholding_category = tds_category
self.set_onload("supplier_tds", tds_category)
+ # If Linked Purchase Order has TDS applied, enable 'apply_tds' checkbox
+ if purchase_orders := [x.purchase_order for x in self.items if x.purchase_order]:
+ po = qb.DocType("Purchase Order")
+ po_with_tds = (
+ qb.from_(po)
+ .select(po.name)
+ .where(
+ po.docstatus.eq(1)
+ & (po.name.isin(purchase_orders))
+ & (po.apply_tds.eq(1))
+ & (po.tax_withholding_category.notnull())
+ )
+ .run()
+ )
+ self.set_onload("enable_apply_tds", True if po_with_tds else False)
+
super().set_missing_values(for_validate)
def validate_credit_to_acc(self):
@@ -533,6 +549,21 @@ class PurchaseInvoice(BuyingController):
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset:
account = None
+ if not item.pr_detail and item.po_detail:
+ receipt_item = frappe.get_cached_value(
+ "Purchase Receipt Item",
+ {
+ "purchase_order": item.purchase_order,
+ "purchase_order_item": item.po_detail,
+ "docstatus": 1,
+ },
+ ["name", "parent"],
+ as_dict=1,
+ )
+ if receipt_item:
+ item.pr_detail = receipt_item.name
+ item.purchase_receipt = receipt_item.parent
+
if item.pr_detail:
if not self.asset_received_but_not_billed:
self.asset_received_but_not_billed = self.get_company_default(
@@ -577,7 +608,7 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"):
validate_account_head(item.idx, item.expense_account, self.company, "Expense")
- def set_against_expense_account(self):
+ def set_against_expense_account(self, force=False):
against_accounts = []
for item in self.get("items"):
if item.expense_account and (item.expense_account not in against_accounts):
@@ -585,6 +616,10 @@ class PurchaseInvoice(BuyingController):
self.against_expense_account = ",".join(against_accounts)
+ def force_set_against_expense_account(self):
+ self.set_against_expense_account()
+ frappe.db.set_value(self.doctype, self.name, "against_expense_account", self.against_expense_account)
+
def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes":
if frappe.get_value(
@@ -667,6 +702,19 @@ class PurchaseInvoice(BuyingController):
where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""",
}
)
+ self.status_updater.append(
+ {
+ "source_dt": "Purchase Invoice Item",
+ "target_dt": "Material Request Item",
+ "join_field": "material_request_item",
+ "target_field": "received_qty",
+ "target_parent_dt": "Material Request",
+ "target_parent_field": "per_received",
+ "target_ref_field": "stock_qty",
+ "source_field": "stock_qty",
+ "percent_join_field": "material_request",
+ }
+ )
if cint(self.is_return):
self.status_updater.append(
{
@@ -762,13 +810,12 @@ class PurchaseInvoice(BuyingController):
self.db_set("repost_required", self.needs_repost)
def make_gl_entries(self, gl_entries=None, from_repost=False):
- if not gl_entries:
- gl_entries = self.get_gl_entries()
+ update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
+ if self.docstatus == 1:
+ if not gl_entries:
+ gl_entries = self.get_gl_entries()
- if gl_entries:
- update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
-
- if self.docstatus == 1:
+ if gl_entries:
make_gl_entries(
gl_entries,
update_outstanding=update_outstanding,
@@ -776,32 +823,43 @@ class PurchaseInvoice(BuyingController):
from_repost=from_repost,
)
self.make_exchange_gain_loss_journal()
- elif self.docstatus == 2:
- provisional_entries = [a for a in gl_entries if a.voucher_type == "Purchase Receipt"]
- make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
- if provisional_entries:
- for entry in provisional_entries:
- frappe.db.set_value(
- "GL Entry",
- {
- "voucher_type": "Purchase Receipt",
- "voucher_detail_no": entry.voucher_detail_no,
- },
- "is_cancelled",
- 1,
- )
-
- if update_outstanding == "No":
- update_outstanding_amt(
- self.credit_to,
- "Supplier",
- self.supplier,
- self.doctype,
- self.return_against if cint(self.is_return) and self.return_against else self.name,
- )
-
- elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
+ elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
+ self.cancel_provisional_entries()
+
+ self.update_supplier_outstanding(update_outstanding)
+
+ def cancel_provisional_entries(self):
+ rows = set()
+ purchase_receipts = set()
+ for d in self.items:
+ if d.purchase_receipt:
+ purchase_receipts.add(d.purchase_receipt)
+ rows.add(d.name)
+
+ if rows:
+ # cancel gl entries
+ gle = qb.DocType("GL Entry")
+ gle_update_query = (
+ qb.update(gle)
+ .set(gle.is_cancelled, 1)
+ .where(
+ (gle.voucher_type == "Purchase Receipt")
+ & (gle.voucher_no.isin(purchase_receipts))
+ & (gle.voucher_detail_no.isin(rows))
+ )
+ )
+ gle_update_query.run()
+
+ def update_supplier_outstanding(self, update_outstanding):
+ if update_outstanding == "No":
+ update_outstanding_amt(
+ self.credit_to,
+ "Supplier",
+ self.supplier,
+ self.doctype,
+ self.return_against if cint(self.is_return) and self.return_against else self.name,
+ )
def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
@@ -914,8 +972,8 @@ class PurchaseInvoice(BuyingController):
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
)
-
- purchase_receipt_doc_map = {}
+ if provisional_accounting_for_non_stock_items:
+ self.get_provisional_accounts()
for item in self.get("items"):
if flt(item.base_net_amount):
@@ -1054,49 +1112,7 @@ class PurchaseInvoice(BuyingController):
dummy, amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
- if item.purchase_receipt:
- provisional_account, pr_qty, pr_base_rate, pr_rate = frappe.get_cached_value(
- "Purchase Receipt Item",
- item.pr_detail,
- ["provisional_expense_account", "qty", "base_rate", "rate"],
- )
- provisional_account = provisional_account or self.get_company_default(
- "default_provisional_account"
- )
- purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
-
- if not purchase_receipt_doc:
- purchase_receipt_doc = frappe.get_doc(
- "Purchase Receipt", item.purchase_receipt
- )
- purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
-
- # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
- expense_booked_in_pr = frappe.db.get_value(
- "GL Entry",
- {
- "is_cancelled": 0,
- "voucher_type": "Purchase Receipt",
- "voucher_no": item.purchase_receipt,
- "voucher_detail_no": item.pr_detail,
- "account": provisional_account,
- },
- "name",
- )
-
- if expense_booked_in_pr:
- # Intentionally passing purchase invoice item to handle partial billing
- purchase_receipt_doc.add_provisional_gl_entry(
- item,
- gl_entries,
- self.posting_date,
- provisional_account,
- reverse=1,
- item_amount=(
- (min(item.qty, pr_qty) * pr_rate)
- * purchase_receipt_doc.get("conversion_rate")
- ),
- )
+ self.make_provisional_gl_entry(gl_entries, item)
if not self.is_internal_transfer():
gl_entries.append(
@@ -1192,6 +1208,59 @@ class PurchaseInvoice(BuyingController):
if item.is_fixed_asset and item.landed_cost_voucher_amount:
self.update_gross_purchase_amount_for_linked_assets(item)
+ def get_provisional_accounts(self):
+ self.provisional_accounts = frappe._dict()
+ linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt])
+ pr_items = frappe.get_all(
+ "Purchase Receipt Item",
+ filters={"parent": ("in", linked_purchase_receipts)},
+ fields=["name", "provisional_expense_account", "qty", "base_rate"],
+ )
+ default_provisional_account = self.get_company_default("default_provisional_account")
+ provisional_accounts = set(
+ [
+ d.provisional_expense_account
+ if d.provisional_expense_account
+ else default_provisional_account
+ for d in pr_items
+ ]
+ )
+
+ provisional_gl_entries = frappe.get_all(
+ "GL Entry",
+ filters={
+ "voucher_type": "Purchase Receipt",
+ "voucher_no": ("in", linked_purchase_receipts),
+ "account": ("in", provisional_accounts),
+ "is_cancelled": 0,
+ },
+ fields=["voucher_detail_no"],
+ )
+ rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries]
+ for item in pr_items:
+ self.provisional_accounts[item.name] = {
+ "provisional_account": item.provisional_expense_account or default_provisional_account,
+ "qty": item.qty,
+ "base_rate": item.base_rate,
+ "has_provisional_entry": item.name in rows_with_provisional_entries,
+ }
+
+ def make_provisional_gl_entry(self, gl_entries, item):
+ if item.purchase_receipt:
+ pr_item = self.provisional_accounts.get(item.pr_detail, {})
+ if pr_item.get("has_provisional_entry"):
+ purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt)
+
+ # Intentionally passing purchase invoice item to handle partial billing
+ purchase_receipt_doc.add_provisional_gl_entry(
+ item,
+ gl_entries,
+ self.posting_date,
+ pr_item.get("provisional_account"),
+ reverse=1,
+ item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
+ )
+
def update_gross_purchase_amount_for_linked_assets(self, item):
assets = frappe.db.get_all(
"Asset",
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 3ea88f195d8..4980c22fac1 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -10,13 +10,19 @@ import erpnext
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
-from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice as make_pi_from_po
+from erpnext.buying.doctype.purchase_order.test_purchase_order import (
+ create_pr_against_po,
+ create_purchase_order,
+)
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.exceptions import InvalidCurrency
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.material_request.material_request import make_purchase_order
+from erpnext.stock.doctype.material_request.test_material_request import make_material_request
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as create_purchase_invoice_from_receipt,
)
@@ -72,6 +78,31 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
# teardown
pi.delete()
+ def test_update_received_qty_in_material_request(self):
+ from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
+
+ """
+ Test if the received_qty in Material Request is updated correctly when
+ a Purchase Invoice with update_stock=True is submitted.
+ """
+ mr = make_material_request(item_code="_Test Item", qty=10)
+ mr.save()
+ mr.submit()
+ po = make_purchase_order(mr.name)
+ po.supplier = "_Test Supplier"
+ po.save()
+ po.submit()
+
+ # Create a Purchase Invoice with update_stock=True
+ pi = make_purchase_invoice(po.name)
+ pi.update_stock = True
+ pi.insert()
+ pi.submit()
+
+ # Check if the received quantity is updated in Material Request
+ mr.reload()
+ self.assertEqual(mr.items[0].received_qty, 10)
+
def test_gl_entries_without_perpetual_inventory(self):
frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
pi = frappe.copy_doc(test_records[0])
@@ -2158,6 +2189,56 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
self.assertEqual(row.rejected_serial_no, serial_nos[2])
+ def test_make_pr_and_pi_from_po(self):
+ from erpnext.assets.doctype.asset.test_asset import create_asset_category
+
+ if not frappe.db.exists("Asset Category", "Computers"):
+ create_asset_category()
+
+ item = create_item(
+ item_code="_Test_Item", is_stock_item=0, is_fixed_asset=1, asset_category="Computers"
+ )
+ po = create_purchase_order(item_code=item.item_code)
+ pr = create_pr_against_po(po.name, 10)
+ pi = make_pi_from_po(po.name)
+ pi.insert()
+ pi.submit()
+
+ pr_gl_entries = frappe.db.sql(
+ """select account, debit, credit
+ from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s
+ order by account asc""",
+ pr.name,
+ as_dict=1,
+ )
+
+ pr_expected_values = [
+ ["Asset Received But Not Billed - _TC", 0, 5000],
+ ["CWIP Account - _TC", 5000, 0],
+ ]
+
+ for i, gle in enumerate(pr_gl_entries):
+ self.assertEqual(pr_expected_values[i][0], gle.account)
+ self.assertEqual(pr_expected_values[i][1], gle.debit)
+ self.assertEqual(pr_expected_values[i][2], gle.credit)
+
+ pi_gl_entries = frappe.db.sql(
+ """select account, debit, credit
+ from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
+ order by account asc""",
+ pi.name,
+ as_dict=1,
+ )
+ pi_expected_values = [
+ ["Asset Received But Not Billed - _TC", 5000, 0],
+ ["Creditors - _TC", 0, 5000],
+ ]
+
+ for i, gle in enumerate(pi_gl_entries):
+ self.assertEqual(pi_expected_values[i][0], gle.account)
+ self.assertEqual(pi_expected_values[i][1], gle.debit)
+ self.assertEqual(pi_expected_values[i][2], gle.credit)
+
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(
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 66df76a3af0..1d7b0c2f461 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -105,6 +105,8 @@
"purchase_receipt",
"pr_detail",
"sales_invoice_item",
+ "material_request",
+ "material_request_item",
"item_weight_details",
"weight_per_unit",
"total_weight",
@@ -934,12 +936,34 @@
{
"fieldname": "column_break_vbbb",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "material_request",
+ "fieldtype": "Link",
+ "label": "Material Request",
+ "no_copy": 1,
+ "options": "Material Request",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
+ },
+ {
+ "fieldname": "material_request_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Material Request Item",
+ "no_copy": 1,
+ "oldfieldname": "pr_detail",
+ "oldfieldtype": "Data",
+ "print_hide": 1,
+ "read_only": 1,
+ "search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2024-03-19 19:09:47.210965",
+ "modified": "2024-06-14 11:57:07.171700",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
@@ -949,4 +973,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
index ccbc34749d7..baeece4815c 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.py
@@ -52,6 +52,8 @@ class PurchaseInvoiceItem(Document):
manufacturer_part_no: DF.Data | None
margin_rate_or_amount: DF.Float
margin_type: DF.Literal["", "Percentage", "Amount"]
+ material_request: DF.Link | None
+ material_request_item: DF.Data | None
net_amount: DF.Currency
net_rate: DF.Currency
page_break: DF.Check
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
index f9344ce4f3a..0d74b2150f7 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.json
@@ -53,13 +53,15 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-05-23 17:00:42.984798",
+ "modified": "2024-06-03 17:30:37.012593",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -68,7 +70,9 @@
"read": 1,
"report": 1,
"role": "System Manager",
+ "select": 1,
"share": 1,
+ "submit": 1,
"write": 1
}
],
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 6bc19222c98..8c8ba633df0 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -167,6 +167,10 @@ def start_repost(account_repost_doc=str) -> None:
doc.make_gl_entries_on_cancel()
doc.docstatus = 1
+ if doc.doctype == "Sales Invoice":
+ doc.force_set_against_income_account()
+ else:
+ doc.force_set_against_expense_account()
doc.make_gl_entries()
elif doc.doctype in ["Payment Entry", "Journal Entry", "Expense Claim"]:
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json
index 8aa0a840c7e..dd314ca6601 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json
+++ b/erpnext/accounts/doctype/repost_accounting_ledger_settings/repost_accounting_ledger_settings.json
@@ -17,7 +17,7 @@
"in_create": 1,
"issingle": 1,
"links": [],
- "modified": "2023-11-07 14:24:13.321522",
+ "modified": "2024-06-06 13:56:37.908879",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger Settings",
@@ -30,13 +30,17 @@
"print": 1,
"read": 1,
"role": "Administrator",
+ "select": 1,
"share": 1,
"write": 1
},
{
+ "create": 1,
+ "delete": 1,
"read": 1,
"role": "System Manager",
- "select": 1
+ "select": 1,
+ "write": 1
}
],
"sort_field": "modified",
diff --git a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
index 4ecff8cac3b..2b4efead7f3 100644
--- a/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
+++ b/erpnext/accounts/doctype/repost_payment_ledger/repost_payment_ledger.json
@@ -98,13 +98,15 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2024-05-23 17:00:31.540640",
+ "modified": "2024-06-03 17:31:04.472279",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",
"owner": "Administrator",
"permissions": [
{
+ "amend": 1,
+ "cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
@@ -113,7 +115,9 @@
"read": 1,
"report": 1,
"role": "System Manager",
+ "select": 1,
"share": 1,
+ "submit": 1,
"write": 1
},
{
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 1b36bf50f86..90b6e768092 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -787,6 +787,7 @@
"hide_days": 1,
"hide_seconds": 1,
"label": "Time Sheets",
+ "no_copy": 1,
"options": "Sales Invoice Timesheet",
"print_hide": 1
},
@@ -2187,7 +2188,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2024-05-08 18:02:28.549041",
+ "modified": "2024-06-07 16:49:32.458402",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 68da9ea1657..babcb417f23 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -943,6 +943,10 @@ class SalesInvoice(SellingController):
against_acc.append(d.income_account)
self.against_income_account = ",".join(against_acc)
+ def force_set_against_income_account(self):
+ self.set_against_income_account()
+ frappe.db.set_value(self.doctype, self.name, "against_income_account", self.against_income_account)
+
def add_remarks(self):
if not self.remarks:
if self.po_no and self.po_date:
@@ -2678,6 +2682,10 @@ def create_dunning(source_name, target_doc=None, ignore_permissions=False):
target.closing_text = letter_text.get("closing_text")
target.language = letter_text.get("language")
+ # update outstanding
+ if source.payment_schedule and len(source.payment_schedule) == 1:
+ target.overdue_payments[0].outstanding = source.get("outstanding_amount")
+
target.validate()
return get_mapped_doc(
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 5cffe11e995..b0d62339be0 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2202,13 +2202,14 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(si.total_taxes_and_charges, 228.82)
self.assertEqual(si.rounding_adjustment, -0.01)
- expected_values = [
- ["_Test Account Service Tax - _TC", 0.0, 114.41],
- ["_Test Account VAT - _TC", 0.0, 114.41],
- [si.debit_to, 1500, 0.0],
- ["Round Off - _TC", 0.01, 0.01],
- ["Sales - _TC", 0.0, 1271.18],
- ]
+ round_off_account = frappe.get_cached_value("Company", "_Test Company", "round_off_account")
+ expected_values = {
+ "_Test Account Service Tax - _TC": [0.0, 114.41],
+ "_Test Account VAT - _TC": [0.0, 114.41],
+ si.debit_to: [1500, 0.0],
+ round_off_account: [0.01, 0.01],
+ "Sales - _TC": [0.0, 1271.18],
+ }
gl_entries = frappe.db.sql(
"""select account, sum(debit) as debit, sum(credit) as credit
@@ -2219,10 +2220,10 @@ class TestSalesInvoice(FrappeTestCase):
as_dict=1,
)
- for i, gle in enumerate(gl_entries):
- self.assertEqual(expected_values[i][0], gle.account)
- self.assertEqual(expected_values[i][1], gle.debit)
- self.assertEqual(expected_values[i][2], gle.credit)
+ for gle in gl_entries:
+ expected_account_values = expected_values[gle.account]
+ self.assertEqual(expected_account_values[0], gle.debit)
+ self.assertEqual(expected_account_values[1], gle.credit)
def test_rounding_adjustment_3(self):
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
@@ -2270,6 +2271,7 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(si.total_taxes_and_charges, 480.86)
self.assertEqual(si.rounding_adjustment, -0.02)
+ round_off_account = frappe.get_cached_value("Company", "_Test Company", "round_off_account")
expected_values = dict(
(d[0], d)
for d in [
@@ -2277,7 +2279,7 @@ class TestSalesInvoice(FrappeTestCase):
["_Test Account Service Tax - _TC", 0.0, 240.43],
["_Test Account VAT - _TC", 0.0, 240.43],
["Sales - _TC", 0.0, 4007.15],
- ["Round Off - _TC", 0.02, 0.01],
+ [round_off_account, 0.02, 0.01],
]
)
@@ -2306,8 +2308,9 @@ class TestSalesInvoice(FrappeTestCase):
as_dict=1,
)
- self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC")
- self.assertEqual(round_off_gle.location, "Block 1")
+ if round_off_gle:
+ self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC")
+ self.assertEqual(round_off_gle.location, "Block 1")
disable_dimension()
diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py
index cb4f0200478..af3916ae469 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -476,7 +476,7 @@ class TestSubscription(FrappeTestCase):
start_date="2021-01-01",
submit_invoice=0,
generate_new_invoices_past_due_date=1,
- party="_Test Subscription Customer",
+ party="_Test Subscription Customer John Doe",
)
# create invoices for the first two moths
@@ -565,10 +565,16 @@ def create_parties():
if not frappe.db.exists("Customer", "_Test Subscription Customer"):
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test Subscription Customer"
- customer.billing_currency = "USD"
+ customer.default_currency = "USD"
customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable USD - _TC"})
customer.insert()
+ if not frappe.db.exists("Customer", "_Test Subscription Customer John Doe"):
+ customer = frappe.new_doc("Customer")
+ customer.customer_name = "_Test Subscription Customer John Doe"
+ customer.append("accounts", {"company": "_Test Company", "account": "_Test Receivable - _TC"})
+ customer.insert()
+
def reset_settings():
settings = frappe.get_single("Subscription Settings")
diff --git a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
index 882dd1d6dab..43dfbfaef60 100644
--- a/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
+++ b/erpnext/accounts/doctype/unreconcile_payment/test_unreconcile_payment.py
@@ -107,7 +107,7 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
self.assertEqual(len(pe.references), 1)
self.assertEqual(pe.unallocated_amount, 100)
- def test_02_unreconcile_one_payment_from_multi_payments(self):
+ def test_02_unreconcile_one_payment_among_multi_payments(self):
"""
Scenario: 2 payments, both split against 2 different invoices
Unreconcile only one payment from one invoice
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 3f01dee888d..2fd7b5d3c81 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -7,7 +7,7 @@ import copy
import frappe
from frappe import _
from frappe.model.meta import get_field_precision
-from frappe.utils import cint, cstr, flt, formatdate, getdate, now
+from frappe.utils import cint, flt, formatdate, getdate, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -228,11 +228,13 @@ def get_cost_center_allocation_data(company, posting_date):
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
+ merge_properties = get_merge_properties(accounting_dimensions)
for entry in gl_map:
+ entry.merge_key = get_merge_key(entry, merge_properties)
# if there is already an entry in this account then just add it
# to that entry
- same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
+ same_head = check_if_in_list(entry, merged_gl_map)
if same_head:
same_head.debit = flt(same_head.debit) + flt(entry.debit)
same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
@@ -273,34 +275,35 @@ def merge_similar_entries(gl_map, precision=None):
return merged_gl_map
-def check_if_in_list(gle, gl_map, dimensions=None):
- account_head_fieldnames = [
- "voucher_detail_no",
- "party",
- "against_voucher",
+def get_merge_properties(dimensions=None):
+ merge_properties = [
+ "account",
"cost_center",
- "against_voucher_type",
+ "party",
"party_type",
+ "voucher_detail_no",
+ "against_voucher",
+ "against_voucher_type",
"project",
"finance_book",
"voucher_no",
]
-
if dimensions:
- account_head_fieldnames = account_head_fieldnames + dimensions
+ merge_properties.extend(dimensions)
+ return merge_properties
+
+def get_merge_key(entry, merge_properties):
+ merge_key = []
+ for fieldname in merge_properties:
+ merge_key.append(entry.get(fieldname, ""))
+
+ return tuple(merge_key)
+
+
+def check_if_in_list(gle, gl_map):
for e in gl_map:
- same_head = True
- if e.account != gle.account:
- same_head = False
- continue
-
- for fieldname in account_head_fieldnames:
- if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
- same_head = False
- break
-
- if same_head:
+ if e.merge_key == gle.merge_key:
return e
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index d285f28d8e3..e1545bdcd87 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -4,6 +4,7 @@
import frappe
from frappe import _
+from frappe.query_builder import DocType
from frappe.utils import cstr, flt
@@ -75,11 +76,24 @@ def get_data(filters):
asset_data = assets_details.get(d.against_voucher)
if asset_data:
if not asset_data.get("accumulated_depreciation_amount"):
- asset_data.accumulated_depreciation_amount = d.debit + asset_data.get(
- "opening_accumulated_depreciation"
- )
+ AssetDepreciationSchedule = DocType("Asset Depreciation Schedule")
+ DepreciationSchedule = DocType("Depreciation Schedule")
+ query = (
+ frappe.qb.from_(DepreciationSchedule)
+ .join(AssetDepreciationSchedule)
+ .on(DepreciationSchedule.parent == AssetDepreciationSchedule.name)
+ .select(DepreciationSchedule.accumulated_depreciation_amount)
+ .where(
+ (AssetDepreciationSchedule.asset == d.against_voucher)
+ & (DepreciationSchedule.parenttype == "Asset Depreciation Schedule")
+ & (DepreciationSchedule.schedule_date == d.posting_date)
+ )
+ ).run(as_dict=True)
+ asset_data.accumulated_depreciation_amount = query[0]["accumulated_depreciation_amount"]
+
else:
asset_data.accumulated_depreciation_amount += d.debit
+ asset_data.opening_accumulated_depreciation = asset_data.accumulated_depreciation_amount - d.debit
row = frappe._dict(asset_data)
row.update(
diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
index 4fa485f54b3..f9a008ade7f 100644
--- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
+++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py
@@ -69,48 +69,50 @@ def get_asset_categories_for_grouped_by_category(filters):
condition = ""
if filters.get("asset_category"):
condition += " and asset_category = %(asset_category)s"
+ # nosemgrep
return frappe.db.sql(
f"""
- SELECT asset_category,
- ifnull(sum(case when purchase_date < %(from_date)s then
- case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then
- gross_purchase_amount
+ SELECT a.asset_category,
+ ifnull(sum(case when a.purchase_date < %(from_date)s then
+ case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then
+ a.gross_purchase_amount
else
0
end
else
0
end), 0) as cost_as_on_from_date,
- ifnull(sum(case when purchase_date >= %(from_date)s then
- gross_purchase_amount
+ ifnull(sum(case when a.purchase_date >= %(from_date)s then
+ a.gross_purchase_amount
else
0
end), 0) as cost_of_new_purchase,
- ifnull(sum(case when ifnull(disposal_date, 0) != 0
- and disposal_date >= %(from_date)s
- and disposal_date <= %(to_date)s then
- case when status = "Sold" then
- gross_purchase_amount
+ ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
+ and a.disposal_date >= %(from_date)s
+ and a.disposal_date <= %(to_date)s then
+ case when a.status = "Sold" then
+ a.gross_purchase_amount
else
0
end
else
0
end), 0) as cost_of_sold_asset,
- ifnull(sum(case when ifnull(disposal_date, 0) != 0
- and disposal_date >= %(from_date)s
- and disposal_date <= %(to_date)s then
- case when status = "Scrapped" then
- gross_purchase_amount
+ ifnull(sum(case when ifnull(a.disposal_date, 0) != 0
+ and a.disposal_date >= %(from_date)s
+ and a.disposal_date <= %(to_date)s then
+ case when a.status = "Scrapped" then
+ a.gross_purchase_amount
else
0
end
else
0
end), 0) as cost_of_scrapped_asset
- from `tabAsset`
+ from `tabAsset` a
where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition}
- group by asset_category
+ and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name)
+ group by a.asset_category
""",
{
"to_date": filters.to_date,
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 2946cfa0e82..e884deb2a17 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -421,6 +421,8 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
if filters.get("show_net_values_in_party_account"):
account_type_map = get_account_type_map(filters.get("company"))
+ immutable_ledger = frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger")
+
def update_value_in_dict(data, key, gle):
data[key].debit += gle.debit
data[key].credit += gle.credit
@@ -482,12 +484,17 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
elif group_by_voucher_consolidated:
keylist = [
+ gle.get("posting_date"),
gle.get("voucher_type"),
gle.get("voucher_no"),
gle.get("account"),
gle.get("party_type"),
gle.get("party"),
]
+
+ if immutable_ledger:
+ keylist.append(gle.get("creation"))
+
if filters.get("include_dimensions"):
for dim in accounting_dimensions:
keylist.append(gle.get(dim))
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index fad6586466e..ad194ee90a2 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -36,7 +36,7 @@ frappe.query_reports["Gross Profit"] = {
label: __("Group By"),
fieldtype: "Select",
options:
- "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term",
+ "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nCost Center\nMonthly\nPayment Term",
default: "Invoice",
},
{
@@ -63,6 +63,26 @@ frappe.query_reports["Gross Profit"] = {
};
},
},
+ {
+ fieldname: "cost_center",
+ label: __("Cost Center"),
+ fieldtype: "MultiSelectList",
+ get_data: function (txt) {
+ return frappe.db.get_link_options("Cost Center", txt, {
+ company: frappe.query_report.get_filter_value("company"),
+ });
+ },
+ },
+ {
+ fieldname: "project",
+ label: __("Project"),
+ fieldtype: "MultiSelectList",
+ get_data: function (txt) {
+ return frappe.db.get_link_options("Project", txt, {
+ company: frappe.query_report.get_filter_value("company"),
+ });
+ },
+ },
],
tree: true,
name_field: "parent",
@@ -85,3 +105,5 @@ frappe.query_reports["Gross Profit"] = {
return value;
},
};
+
+erpnext.utils.add_dimensions("Gross Profit", 15);
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index c8c8dd9b494..fe2746660eb 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -8,6 +8,11 @@ from frappe import _, qb, scrub
from frappe.query_builder import Order
from frappe.utils import cint, flt, formatdate
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+ get_accounting_dimensions,
+ get_dimension_with_children,
+)
+from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
from erpnext.controllers.queries import get_match_cond
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
from erpnext.stock.utils import get_incoming_rate
@@ -120,6 +125,13 @@ def execute(filters=None):
"gross_profit_percent",
],
"project": ["project", "base_amount", "buying_amount", "gross_profit", "gross_profit_percent"],
+ "cost_center": [
+ "cost_center",
+ "base_amount",
+ "buying_amount",
+ "gross_profit",
+ "gross_profit_percent",
+ ],
"territory": [
"territory",
"base_amount",
@@ -299,7 +311,14 @@ def get_columns(group_wise_columns, filters):
"fieldname": "project",
"fieldtype": "Link",
"options": "Project",
- "width": 100,
+ "width": 140,
+ },
+ "cost_center": {
+ "label": _("Cost Center"),
+ "fieldname": "cost_center",
+ "fieldtype": "Link",
+ "options": "Cost Center",
+ "width": 140,
},
"sales_person": {
"label": _("Sales Person"),
@@ -787,6 +806,31 @@ class GrossProfitGenerator:
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
+ if self.filters.get("cost_center"):
+ self.filters.cost_center = frappe.parse_json(self.filters.get("cost_center"))
+ self.filters.cost_center = get_cost_centers_with_children(self.filters.cost_center)
+ conditions += " and `tabSales Invoice Item`.cost_center in %(cost_center)s"
+
+ if self.filters.get("project"):
+ self.filters.project = frappe.parse_json(self.filters.get("project"))
+ conditions += " and `tabSales Invoice Item`.project in %(project)s"
+
+ accounting_dimensions = get_accounting_dimensions(as_list=False)
+ if accounting_dimensions:
+ for dimension in accounting_dimensions:
+ if self.filters.get(dimension.fieldname):
+ if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
+ self.filters[dimension.fieldname] = get_dimension_with_children(
+ dimension.document_type, self.filters.get(dimension.fieldname)
+ )
+ conditions += (
+ f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
+ )
+ else:
+ conditions += (
+ f" and `tabSales Invoice Item`.{dimension.fieldname} in %({dimension.fieldname})s"
+ )
+
if self.filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index ebdea0602d1..1cd9b872a9b 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -5,14 +5,15 @@
import frappe
from frappe import _
from frappe.utils import flt
+from pypika import Order
import erpnext
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (
add_sub_total_row,
add_total_row,
+ apply_group_by_conditions,
get_grand_total,
get_group_by_and_display_fields,
- get_group_by_conditions,
get_tax_accounts,
)
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
@@ -29,7 +30,7 @@ def _execute(filters=None, additional_table_columns=None):
company_currency = erpnext.get_company_currency(filters.company)
- item_list = get_items(filters, get_query_columns(additional_table_columns))
+ item_list = get_items(filters, additional_table_columns)
aii_account_map = get_aii_accounts()
if item_list:
itemised_tax, tax_columns = get_tax_accounts(
@@ -287,59 +288,90 @@ def get_columns(additional_table_columns, filters):
return columns
-def get_conditions(filters):
- conditions = ""
+def apply_conditions(query, pi, pii, filters):
+ for opts in ("company", "supplier", "mode_of_payment"):
+ if filters.get(opts):
+ query = query.where(pi[opts] == filters[opts])
- for opts in (
- ("company", " and `tabPurchase Invoice`.company=%(company)s"),
- ("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
- ("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
- ("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
- ("to_date", " and `tabPurchase Invoice`.posting_date<=%(to_date)s"),
- ("mode_of_payment", " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"),
- ("item_group", " and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s"),
- ):
- if filters.get(opts[0]):
- conditions += opts[1]
+ if filters.get("from_date"):
+ query = query.where(pi.posting_date >= filters.get("from_date"))
+
+ if filters.get("to_date"):
+ query = query.where(pi.posting_date <= filters.get("to_date"))
+
+ if filters.get("item_code"):
+ query = query.where(pii.item_code == filters.get("item_code"))
+
+ if filters.get("item_group"):
+ query = query.where(pii.item_group == filters.get("item_group"))
if not filters.get("group_by"):
- conditions += (
- "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc"
- )
+ query = query.orderby(pi.posting_date, order=Order.desc)
+ query = query.orderby(pii.item_group, order=Order.desc)
else:
- conditions += get_group_by_conditions(filters, "Purchase Invoice")
+ query = apply_group_by_conditions(filters, "Purchase Invoice")
- return conditions
+ return query
-def get_items(filters, additional_query_columns):
- conditions = get_conditions(filters)
- if additional_query_columns:
- additional_query_columns = "," + ",".join(additional_query_columns)
- return frappe.db.sql(
- f"""
- select
- `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
- `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
- `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
- `tabPurchase Invoice`.unrealized_profit_loss_account,
- `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
- `tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
- `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
- `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
- `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
- `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
- `tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`,
- `tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {additional_query_columns}
- from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
- where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
- `tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
- `tabPurchase Invoice`.docstatus = 1 {conditions}
- """,
- filters,
- as_dict=1,
+def get_items(filters, additional_table_columns):
+ pi = frappe.qb.DocType("Purchase Invoice")
+ pii = frappe.qb.DocType("Purchase Invoice Item")
+ Item = frappe.qb.DocType("Item")
+ query = (
+ frappe.qb.from_(pi)
+ .join(pii)
+ .on(pi.name == pii.parent)
+ .left_join(Item)
+ .on(pii.item_code == Item.name)
+ .select(
+ pii.name,
+ pii.parent,
+ pi.posting_date,
+ pi.credit_to,
+ pi.company,
+ pi.supplier,
+ pi.remarks,
+ pi.base_net_total,
+ pi.unrealized_profit_loss_account,
+ pii.item_code,
+ pii.description,
+ pii.item_group,
+ pii.item_name.as_("pi_item_name"),
+ pii.item_group.as_("pi_item_group"),
+ Item.item_name.as_("i_item_name"),
+ Item.item_group.as_("i_item_group"),
+ pii.project,
+ pii.purchase_order,
+ pii.purchase_receipt,
+ pii.po_detail,
+ pii.expense_account,
+ pii.stock_qty,
+ pii.stock_uom,
+ pii.base_net_amount,
+ pi.supplier_name,
+ pi.mode_of_payment,
+ )
+ .where(pi.docstatus == 1)
)
+ if filters.get("supplier"):
+ query = query.where(pi.supplier == filters["supplier"])
+ if filters.get("company"):
+ query = query.where(pi.company == filters["company"])
+
+ if additional_table_columns:
+ for column in additional_table_columns:
+ if column.get("_doctype"):
+ table = frappe.qb.DocType(column.get("_doctype"))
+ query = query.select(table[column.get("fieldname")])
+ else:
+ query = query.select(pi[column.get("fieldname")])
+
+ query = apply_conditions(query, pi, pii, filters)
+
+ return query.run(as_dict=True)
+
def get_aii_accounts():
return dict(frappe.db.sql("select name, stock_received_but_not_billed from tabCompany"))
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
index 7ed2ebd89ab..16a97733393 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js
@@ -41,6 +41,12 @@ frappe.query_reports["Item-wise Sales Register"] = {
label: __("Warehouse"),
fieldtype: "Link",
options: "Warehouse",
+ get_query: function () {
+ const company = frappe.query_report.get_filter_value("company");
+ return {
+ filters: { company: company },
+ };
+ },
},
{
fieldname: "brand",
@@ -48,6 +54,12 @@ frappe.query_reports["Item-wise Sales Register"] = {
fieldtype: "Link",
options: "Brand",
},
+ {
+ fieldname: "item_code",
+ label: __("Item"),
+ fieldtype: "Link",
+ options: "Item",
+ },
{
fieldname: "item_group",
label: __("Item Group"),
diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
index c7819731930..cf08e45c537 100644
--- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
+++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py
@@ -7,6 +7,7 @@ from frappe import _
from frappe.model.meta import get_field_precision
from frappe.utils import cstr, flt
from frappe.utils.xlsxutils import handle_html
+from pypika import Order
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
@@ -26,7 +27,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
- item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions)
+ item_list = get_items(filters, additional_table_columns, additional_conditions)
if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
@@ -83,9 +84,7 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
"company": d.company,
"sales_order": d.sales_order,
"delivery_note": d.delivery_note,
- "income_account": d.unrealized_profit_loss_account
- if d.is_internal_customer == 1
- else d.income_account,
+ "income_account": get_income_account(d),
"cost_center": d.cost_center,
"stock_qty": d.stock_qty,
"stock_uom": d.stock_uom,
@@ -150,6 +149,15 @@ def _execute(filters=None, additional_table_columns=None, additional_conditions=
return columns, data, None, None, None, skip_total_row
+def get_income_account(row):
+ if row.enable_deferred_revenue:
+ return row.deferred_revenue_account
+ elif row.is_internal_customer == 1:
+ return row.unrealized_profit_loss_account
+ else:
+ return row.income_account
+
+
def get_columns(additional_table_columns, filters):
columns = []
@@ -333,93 +341,143 @@ def get_columns(additional_table_columns, filters):
return columns
-def get_conditions(filters, additional_conditions=None):
- conditions = ""
+def apply_conditions(query, si, sii, filters, additional_conditions=None):
+ for opts in ("company", "customer"):
+ if filters.get(opts):
+ query = query.where(si[opts] == filters[opts])
- for opts in (
- ("company", " and `tabSales Invoice`.company=%(company)s"),
- ("customer", " and `tabSales Invoice`.customer = %(customer)s"),
- ("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
- ("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
- ("to_date", " and `tabSales Invoice`.posting_date<=%(to_date)s"),
- ):
- if filters.get(opts[0]):
- conditions += opts[1]
+ if filters.get("from_date"):
+ query = query.where(si.posting_date >= filters.get("from_date"))
- if additional_conditions:
- conditions += additional_conditions
+ if filters.get("to_date"):
+ query = query.where(si.posting_date <= filters.get("to_date"))
if filters.get("mode_of_payment"):
- conditions += """ and exists(select name from `tabSales Invoice Payment`
- where parent=`tabSales Invoice`.name
- and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
+ sales_invoice = frappe.db.get_all(
+ "Sales Invoice Payment", {"mode_of_payment": filters.get("mode_of_payment")}, pluck="parent"
+ )
+ query = query.where(si.name.isin(sales_invoice))
if filters.get("warehouse"):
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
lft, rgt = frappe.db.get_all(
"Warehouse", filters={"name": filters.get("warehouse")}, fields=["lft", "rgt"], as_list=True
)[0]
- conditions += f"and ifnull(`tabSales Invoice Item`.warehouse, '') in (select name from `tabWarehouse` where lft > {lft} and rgt < {rgt}) "
+ warehouses = frappe.db.get_all("Warehouse", {"lft": (">", lft), "rgt": ("<", rgt)}, pluck="name")
+ query = query.where(sii.warehouse.isin(warehouses))
else:
- conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
+ query = query.where(sii.warehouse == filters.get("warehouse"))
if filters.get("brand"):
- conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
+ query = query.where(sii.brand == filters.get("brand"))
+
+ if filters.get("item_code"):
+ query = query.where(sii.item_code == filters.get("item_code"))
if filters.get("item_group"):
- conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
+ query = query.where(sii.item_group == filters.get("item_group"))
+
+ if filters.get("income_account"):
+ query = query.where(
+ (sii.income_account == filters.get("income_account"))
+ | (sii.deferred_revenue_account == filters.get("income_account"))
+ | (si.unrealized_profit_loss_account == filters.get("income_account"))
+ )
if not filters.get("group_by"):
- conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
+ query = query.orderby(si.posting_date, order=Order.desc)
+ query = query.orderby(sii.item_group, order=Order.desc)
else:
- conditions += get_group_by_conditions(filters, "Sales Invoice")
+ query = apply_group_by_conditions(query, si, sii, filters)
- return conditions
+ for key, value in (additional_conditions or {}).items():
+ query = query.where(si[key] == value)
+
+ return query
-def get_group_by_conditions(filters, doctype):
+def apply_group_by_conditions(query, si, ii, filters):
if filters.get("group_by") == "Invoice":
- return f"ORDER BY `tab{doctype} Item`.parent desc"
+ query = query.orderby(ii.parent, order=Order.desc)
elif filters.get("group_by") == "Item":
- return f"ORDER BY `tab{doctype} Item`.`item_code`"
+ query = query.orderby(ii.item_code)
elif filters.get("group_by") == "Item Group":
- return "ORDER BY `tab{} Item`.{}".format(doctype, frappe.scrub(filters.get("group_by")))
+ query = query.orderby(ii.item_group)
elif filters.get("group_by") in ("Customer", "Customer Group", "Territory", "Supplier"):
- return "ORDER BY `tab{}`.{}".format(doctype, frappe.scrub(filters.get("group_by")))
+ query = query.orderby(si[frappe.scrub(filters.get("group_by"))])
+
+ return query
def get_items(filters, additional_query_columns, additional_conditions=None):
- conditions = get_conditions(filters, additional_conditions)
+ si = frappe.qb.DocType("Sales Invoice")
+ sii = frappe.qb.DocType("Sales Invoice Item")
+ item = frappe.qb.DocType("Item")
+
+ query = (
+ frappe.qb.from_(si)
+ .join(sii)
+ .on(si.name == sii.parent)
+ .left_join(item)
+ .on(sii.item_code == item.name)
+ .select(
+ sii.name,
+ sii.parent,
+ si.posting_date,
+ si.debit_to,
+ si.unrealized_profit_loss_account,
+ si.is_internal_customer,
+ si.customer,
+ si.remarks,
+ si.territory,
+ si.company,
+ si.base_net_total,
+ sii.project,
+ sii.item_code,
+ sii.description,
+ sii.item_name,
+ sii.item_group,
+ sii.item_name.as_("si_item_name"),
+ sii.item_group.as_("si_item_group"),
+ item.item_name.as_("i_item_name"),
+ item.item_group.as_("i_item_group"),
+ sii.sales_order,
+ sii.delivery_note,
+ sii.income_account,
+ sii.cost_center,
+ sii.enable_deferred_revenue,
+ sii.deferred_revenue_account,
+ sii.stock_qty,
+ sii.stock_uom,
+ sii.base_net_rate,
+ sii.base_net_amount,
+ si.customer_name,
+ si.customer_group,
+ sii.so_detail,
+ si.update_stock,
+ sii.uom,
+ sii.qty,
+ )
+ .where(si.docstatus == 1)
+ )
+
if additional_query_columns:
- additional_query_columns = "," + ",".join(additional_query_columns)
- return frappe.db.sql(
- """
- select
- `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
- `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
- `tabSales Invoice`.unrealized_profit_loss_account,
- `tabSales Invoice`.is_internal_customer,
- `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
- `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
- `tabSales Invoice Item`.project,
- `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
- `tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
- `tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group,
- `tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
- `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
- `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
- `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
- `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
- `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
- `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {}
- from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem`
- where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and
- `tabItem`.name = `tabSales Invoice Item`.`item_code` and
- `tabSales Invoice`.docstatus = 1 {}
- """.format(additional_query_columns, conditions),
- filters,
- as_dict=1,
- ) # nosec
+ for column in additional_query_columns:
+ if column.get("_doctype"):
+ table = frappe.qb.DocType(column.get("_doctype"))
+ query = query.select(table[column.get("fieldname")])
+ else:
+ query = query.select(si[column.get("fieldname")])
+
+ if filters.get("customer"):
+ query = query.where(si.customer == filters["customer"])
+
+ if filters.get("customer_group"):
+ query = query.where(si.customer_group == filters["customer_group"])
+
+ query = apply_conditions(query, si, sii, filters, additional_conditions)
+
+ return query.run(as_dict=True)
def get_delivery_notes_against_sales_order(item_list):
@@ -427,16 +485,14 @@ def get_delivery_notes_against_sales_order(item_list):
so_item_rows = list(set([d.so_detail for d in item_list]))
if so_item_rows:
- delivery_notes = frappe.db.sql(
- """
- select parent, so_detail
- from `tabDelivery Note Item`
- where docstatus=1 and so_detail in (%s)
- group by so_detail, parent
- """
- % (", ".join(["%s"] * len(so_item_rows))),
- tuple(so_item_rows),
- as_dict=1,
+ dn_item = frappe.qb.DocType("Delivery Note Item")
+ delivery_notes = (
+ frappe.qb.from_(dn_item)
+ .select(dn_item.parent, dn_item.so_detail)
+ .where(dn_item.docstatus == 1)
+ .where(dn_item.so_detail.isin(so_item_rows))
+ .groupby(dn_item.so_detail, dn_item.parent)
+ .run(as_dict=True)
)
for dn in delivery_notes:
@@ -446,15 +502,16 @@ def get_delivery_notes_against_sales_order(item_list):
def get_grand_total(filters, doctype):
- return frappe.db.sql(
- f""" SELECT
- SUM(`tab{doctype}`.base_grand_total)
- FROM `tab{doctype}`
- WHERE `tab{doctype}`.docstatus = 1
- and posting_date between %s and %s
- """,
- (filters.get("from_date"), filters.get("to_date")),
- )[0][0] # nosec
+ return flt(
+ frappe.db.get_value(
+ doctype,
+ {
+ "docstatus": 1,
+ "posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
+ },
+ "sum(base_grand_total)",
+ )
+ )
def get_tax_accounts(
diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py
index f27569531b1..6c0bf91e3f8 100644
--- a/erpnext/accounts/report/sales_register/sales_register.py
+++ b/erpnext/accounts/report/sales_register/sales_register.py
@@ -80,6 +80,7 @@ def _execute(filters, additional_table_columns=None):
delivery_note = list(set(invoice_so_dn_map.get(inv.name, {}).get("delivery_note", [])))
cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", [])))
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
+ inv_customer_details = customer_details.get(inv.customer, {})
row = {
"voucher_type": inv.doctype,
@@ -88,9 +89,9 @@ def _execute(filters, additional_table_columns=None):
"customer": inv.customer,
"customer_name": inv.customer_name,
**get_values_for_columns(additional_table_columns, inv),
- "customer_group": customer_details.get(inv.customer).get("customer_group"),
- "territory": customer_details.get(inv.customer).get("territory"),
- "tax_id": customer_details.get(inv.customer).get("tax_id"),
+ "customer_group": inv_customer_details.get("customer_group"),
+ "territory": inv_customer_details.get("territory"),
+ "tax_id": inv_customer_details.get("tax_id"),
"receivable_account": inv.debit_to,
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
"project": inv.project,
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 1ad24c46603..2558e976bea 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -10,7 +10,7 @@ import frappe.defaults
from frappe import _, qb, throw
from frappe.model.meta import get_field_precision
from frappe.query_builder import AliasedQuery, Criterion, Table
-from frappe.query_builder.functions import Sum
+from frappe.query_builder.functions import Count, Sum
from frappe.query_builder.utils import DocType
from frappe.utils import (
add_days,
@@ -1492,24 +1492,39 @@ def get_stock_accounts(company, voucher_type=None, voucher_no=None):
)
]
- return stock_accounts
+ return list(set(stock_accounts))
def get_stock_and_account_balance(account=None, posting_date=None, company=None):
if not posting_date:
posting_date = nowdate()
- warehouse_account = get_warehouse_account_map(company)
-
account_balance = get_balance_on(
account, posting_date, in_account_currency=False, ignore_account_permission=True
)
- related_warehouses = [
- wh
- for wh, wh_details in warehouse_account.items()
- if wh_details.account == account and not wh_details.is_group
- ]
+ account_table = frappe.qb.DocType("Account")
+ query = (
+ frappe.qb.from_(account_table)
+ .select(Count(account_table.name))
+ .where(
+ (account_table.account_type == "Stock")
+ & (account_table.company == company)
+ & (account_table.is_group == 0)
+ )
+ )
+
+ no_of_stock_accounts = cint(query.run()[0][0])
+
+ related_warehouses = []
+ if no_of_stock_accounts > 1:
+ warehouse_account = get_warehouse_account_map(company)
+
+ related_warehouses = [
+ wh
+ for wh, wh_details in warehouse_account.items()
+ if wh_details.account == account and not wh_details.is_group
+ ]
total_stock_value = get_stock_value_on(related_warehouses, posting_date)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index 3a2a942bdf2..99a430cbb40 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -45,7 +45,7 @@
"calculate_depreciation",
"column_break_33",
"opening_accumulated_depreciation",
- "number_of_depreciations_booked",
+ "opening_number_of_booked_depreciations",
"is_fully_depreciated",
"section_break_36",
"finance_books",
@@ -257,12 +257,6 @@
"label": "Opening Accumulated Depreciation",
"options": "Company:company:default_currency"
},
- {
- "depends_on": "eval:(doc.is_existing_asset)",
- "fieldname": "number_of_depreciations_booked",
- "fieldtype": "Int",
- "label": "Number of Depreciations Booked"
- },
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.calculate_depreciation || doc.is_existing_asset",
@@ -546,6 +540,12 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
+ },
+ {
+ "depends_on": "eval:(doc.is_existing_asset)",
+ "fieldname": "opening_number_of_booked_depreciations",
+ "fieldtype": "Int",
+ "label": "Opening Number of Booked Depreciations"
}
],
"idx": 72,
@@ -589,7 +589,7 @@
"link_fieldname": "target_asset"
}
],
- "modified": "2024-04-18 16:45:47.306032",
+ "modified": "2024-05-21 13:46:21.066483",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 4edfcd11c89..8641bb33fad 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -89,8 +89,8 @@ class Asset(AccountsController):
maintenance_required: DF.Check
naming_series: DF.Literal["ACC-ASS-.YYYY.-"]
next_depreciation_date: DF.Date | None
- number_of_depreciations_booked: DF.Int
opening_accumulated_depreciation: DF.Currency
+ opening_number_of_booked_depreciations: DF.Int
policy_number: DF.Data | None
purchase_amount: DF.Currency
purchase_date: DF.Date | None
@@ -145,7 +145,7 @@ class Asset(AccountsController):
"Asset Depreciation Schedules created:
{0}
Please check, edit if needed, and submit the Asset."
).format(asset_depr_schedules_links)
)
-
+ self.set_total_booked_depreciations()
self.total_asset_cost = self.gross_purchase_amount
self.status = self.get_status()
@@ -417,7 +417,7 @@ class Asset(AccountsController):
if not self.is_existing_asset:
self.opening_accumulated_depreciation = 0
- self.number_of_depreciations_booked = 0
+ self.opening_number_of_booked_depreciations = 0
else:
depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
@@ -428,15 +428,15 @@ class Asset(AccountsController):
)
if self.opening_accumulated_depreciation:
- if not self.number_of_depreciations_booked:
- frappe.throw(_("Please set Number of Depreciations Booked"))
+ if not self.opening_number_of_booked_depreciations:
+ frappe.throw(_("Please set Opening Number of Booked Depreciations"))
else:
- self.number_of_depreciations_booked = 0
+ self.opening_number_of_booked_depreciations = 0
- if flt(row.total_number_of_depreciations) <= cint(self.number_of_depreciations_booked):
+ if flt(row.total_number_of_depreciations) <= cint(self.opening_number_of_booked_depreciations):
frappe.throw(
_(
- "Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked"
+ "Row {0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations"
).format(row.idx),
title=_("Invalid Schedule"),
)
@@ -457,6 +457,17 @@ class Asset(AccountsController):
).format(row.idx)
)
+ def set_total_booked_depreciations(self):
+ # set value of total number of booked depreciations field
+ for fb_row in self.get("finance_books"):
+ total_number_of_booked_depreciations = self.opening_number_of_booked_depreciations
+ depr_schedule = get_depr_schedule(self.name, "Active", fb_row.finance_book)
+ if depr_schedule:
+ for je in depr_schedule:
+ if je.journal_entry:
+ total_number_of_booked_depreciations += 1
+ fb_row.db_set("total_number_of_booked_depreciations", total_number_of_booked_depreciations)
+
def validate_expected_value_after_useful_life(self):
for row in self.get("finance_books"):
depr_schedule = get_depr_schedule(self.name, "Draft", row.finance_book)
@@ -1140,6 +1151,8 @@ def create_new_asset_after_split(asset, split_qty):
for row in new_asset.get("finance_books"):
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
+ if not current_asset_depr_schedule_doc:
+ continue
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row)
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 2c54f55f25c..cc8defc5fe6 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -323,6 +323,7 @@ def _make_journal_entry_for_depreciation(
if not je.meta.get_workflow():
je.submit()
+ asset.reload()
idx = cint(asset_depr_schedule_doc.finance_book_id)
row = asset.get("finance_books")[idx - 1]
row.value_after_depreciation -= depr_schedule.depreciation_amount
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 7e0c3ad6888..742cc3eaa49 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -355,7 +355,7 @@ class TestAsset(AssetSetup):
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
- number_of_depreciations_booked=2,
+ opening_number_of_booked_depreciations=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
@@ -453,7 +453,7 @@ class TestAsset(AssetSetup):
purchase_date="2020-01-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=6,
- number_of_depreciations_booked=1,
+ opening_number_of_booked_depreciations=1,
frequency_of_depreciation=10,
depreciation_start_date="2021-01-01",
opening_accumulated_depreciation=20000,
@@ -739,7 +739,7 @@ class TestDepreciationMethods(AssetSetup):
calculate_depreciation=1,
available_for_use_date="2030-06-06",
is_existing_asset=1,
- number_of_depreciations_booked=2,
+ opening_number_of_booked_depreciations=2,
opening_accumulated_depreciation=47095.89,
expected_value_after_useful_life=10000,
depreciation_start_date="2032-12-31",
@@ -789,7 +789,7 @@ class TestDepreciationMethods(AssetSetup):
available_for_use_date="2030-01-01",
is_existing_asset=1,
depreciation_method="Double Declining Balance",
- number_of_depreciations_booked=1,
+ opening_number_of_booked_depreciations=1,
opening_accumulated_depreciation=50000,
expected_value_after_useful_life=10000,
depreciation_start_date="2031-12-31",
@@ -1123,8 +1123,8 @@ class TestDepreciationBasics(AssetSetup):
self.assertRaises(frappe.ValidationError, asset.save)
- def test_number_of_depreciations_booked(self):
- """Tests if an error is raised when number_of_depreciations_booked is not specified when opening_accumulated_depreciation is."""
+ def test_opening_booked_depreciations(self):
+ """Tests if an error is raised when opening_number_of_booked_depreciations is not specified when opening_accumulated_depreciation is."""
asset = create_asset(
item_code="Macbook Pro",
@@ -1140,9 +1140,9 @@ class TestDepreciationBasics(AssetSetup):
self.assertRaises(frappe.ValidationError, asset.save)
def test_number_of_depreciations(self):
- """Tests if an error is raised when number_of_depreciations_booked >= total_number_of_depreciations."""
+ """Tests if an error is raised when opening_number_of_booked_depreciations >= total_number_of_depreciations."""
- # number_of_depreciations_booked > total_number_of_depreciations
+ # opening_number_of_booked_depreciations > total_number_of_depreciations
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
@@ -1151,13 +1151,13 @@ class TestDepreciationBasics(AssetSetup):
expected_value_after_useful_life=10000,
depreciation_start_date="2020-07-01",
opening_accumulated_depreciation=10000,
- number_of_depreciations_booked=5,
+ opening_number_of_booked_depreciations=5,
do_not_save=1,
)
self.assertRaises(frappe.ValidationError, asset.save)
- # number_of_depreciations_booked = total_number_of_depreciations
+ # opening_number_of_booked_depreciations = total_number_of_depreciations
asset_2 = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
@@ -1166,7 +1166,7 @@ class TestDepreciationBasics(AssetSetup):
expected_value_after_useful_life=10000,
depreciation_start_date="2020-07-01",
opening_accumulated_depreciation=10000,
- number_of_depreciations_booked=5,
+ opening_number_of_booked_depreciations=5,
do_not_save=1,
)
@@ -1501,19 +1501,17 @@ class TestDepreciationBasics(AssetSetup):
"""
asset = create_asset(calculate_depreciation=1)
- asset.opening_accumulated_depreciation = 2000
- asset.number_of_depreciations_booked = 1
asset.finance_books[0].expected_value_after_useful_life = 100
asset.save()
asset.reload()
- self.assertEqual(asset.finance_books[0].value_after_depreciation, 98000.0)
+ self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0)
# changing expected_value_after_useful_life shouldn't affect value_after_depreciation
asset.finance_books[0].expected_value_after_useful_life = 200
asset.save()
asset.reload()
- self.assertEqual(asset.finance_books[0].value_after_depreciation, 98000.0)
+ self.assertEqual(asset.finance_books[0].value_after_depreciation, 100000.0)
def test_asset_cost_center(self):
asset = create_asset(is_existing_asset=1, do_not_save=1)
@@ -1696,7 +1694,7 @@ def create_asset(**args):
"purchase_date": args.purchase_date or "2015-01-01",
"calculate_depreciation": args.calculate_depreciation or 0,
"opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0,
- "number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
+ "opening_number_of_booked_depreciations": args.opening_number_of_booked_depreciations or 0,
"gross_purchase_amount": args.gross_purchase_amount or 100000,
"purchase_amount": args.purchase_amount or 100000,
"maintenance_required": args.maintenance_required or 0,
diff --git a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
index c838f8b0f26..da05e930eab 100644
--- a/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
+++ b/erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json
@@ -108,7 +108,6 @@
"depends_on": "eval:doc.use_serial_batch_fields === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
- "hidden": 1,
"label": "Serial No",
"print_hide": 1
},
@@ -178,7 +177,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2024-03-05 11:22:57.346889",
+ "modified": "2024-06-26 17:06:22.564438",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Capitalization Stock Item",
@@ -188,4 +187,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 73838163d3a..36ee7402576 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -13,7 +13,7 @@
"column_break_2",
"gross_purchase_amount",
"opening_accumulated_depreciation",
- "number_of_depreciations_booked",
+ "opening_number_of_booked_depreciations",
"finance_book",
"finance_book_id",
"depreciation_details_section",
@@ -171,10 +171,10 @@
"read_only": 1
},
{
- "fieldname": "number_of_depreciations_booked",
+ "fieldname": "opening_number_of_booked_depreciations",
"fieldtype": "Int",
"hidden": 1,
- "label": "Number of Depreciations Booked",
+ "label": "Opening Number of Booked Depreciations",
"print_hide": 1,
"read_only": 1
},
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index f64e9123dc0..bd67a173343 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -50,7 +50,7 @@ class AssetDepreciationSchedule(Document):
gross_purchase_amount: DF.Currency
naming_series: DF.Literal["ACC-ADS-.YYYY.-"]
notes: DF.SmallText | None
- number_of_depreciations_booked: DF.Int
+ opening_number_of_booked_depreciations: DF.Int
opening_accumulated_depreciation: DF.Currency
rate_of_depreciation: DF.Percent
shift_based: DF.Check
@@ -161,7 +161,7 @@ class AssetDepreciationSchedule(Document):
return (
asset_doc.gross_purchase_amount != self.gross_purchase_amount
or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
- or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
+ or asset_doc.opening_number_of_booked_depreciations != self.opening_number_of_booked_depreciations
)
def not_manual_depr_or_have_manual_depr_details_been_modified(self, row):
@@ -194,7 +194,7 @@ class AssetDepreciationSchedule(Document):
self.finance_book = row.finance_book
self.finance_book_id = row.idx
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0
- self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked or 0
+ self.opening_number_of_booked_depreciations = asset_doc.opening_number_of_booked_depreciations or 0
self.gross_purchase_amount = asset_doc.gross_purchase_amount
self.depreciation_method = row.depreciation_method
self.total_number_of_depreciations = row.total_number_of_depreciations
@@ -263,7 +263,7 @@ class AssetDepreciationSchedule(Document):
row.db_update()
final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint(
- self.number_of_depreciations_booked
+ self.opening_number_of_booked_depreciations
)
has_pro_rata = _check_is_pro_rata(asset_doc, row)
@@ -328,7 +328,7 @@ class AssetDepreciationSchedule(Document):
if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal):
from_date = add_months(
getdate(asset_doc.available_for_use_date),
- (asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
+ (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
)
if self.depreciation_schedule:
from_date = self.depreciation_schedule[-1].schedule_date
@@ -378,13 +378,16 @@ class AssetDepreciationSchedule(Document):
from_date = get_last_day(
add_months(
getdate(asset_doc.available_for_use_date),
- ((self.number_of_depreciations_booked - 1) * row.frequency_of_depreciation),
+ (
+ (self.opening_number_of_booked_depreciations - 1)
+ * row.frequency_of_depreciation
+ ),
)
)
else:
from_date = add_months(
getdate(add_days(asset_doc.available_for_use_date, -1)),
- (self.number_of_depreciations_booked * row.frequency_of_depreciation),
+ (self.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
)
depreciation_amount, days, months = _get_pro_rata_amt(
row,
@@ -400,7 +403,8 @@ class AssetDepreciationSchedule(Document):
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
asset_doc.to_date = add_months(
asset_doc.available_for_use_date,
- (n + self.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
+ (n + self.opening_number_of_booked_depreciations)
+ * cint(row.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
@@ -546,34 +550,47 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
has_pro_rata = False
# if not existing asset, from_date = available_for_use_date
- # otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
+ # otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
# from_date = 01/01/2022
- from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly)
+ from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False)
days = date_diff(row.depreciation_start_date, from_date) + 1
-
- if wdv_or_dd_non_yearly:
- total_days = get_total_days(row.depreciation_start_date, 12)
- else:
- # if frequency_of_depreciation is 12 months, total_days = 365
- total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
-
+ total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
+ if days <= 0:
+ frappe.throw(
+ _(
+ """Error: This asset already has {0} depreciation periods booked.
+ The `depreciation start` date must be at least {1} periods after the `available for use` date.
+ Please correct the dates accordingly."""
+ ).format(
+ asset_doc.opening_number_of_booked_depreciations,
+ asset_doc.opening_number_of_booked_depreciations,
+ )
+ )
if days < total_days:
has_pro_rata = True
-
return has_pro_rata
def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False):
- if wdv_or_dd_non_yearly:
- return add_months(
+ """
+ if Asset has opening booked depreciations = 9,
+ available for use date = 17-07-2023,
+ depreciation start date = 30-04-2024
+ then from date should be 01-04-2024
+ """
+ if asset_doc.opening_number_of_booked_depreciations > 0:
+ from_date = add_months(
asset_doc.available_for_use_date,
- (asset_doc.number_of_depreciations_booked * 12),
+ (asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation) - 1,
)
+ if is_last_day_of_the_month(row.depreciation_start_date):
+ return add_days(get_last_day(from_date), 1)
+
+ # get from date when depreciation start date is not last day of the month
+ months_difference = month_diff(row.depreciation_start_date, from_date) - 1
+ return add_days(add_months(row.depreciation_start_date, -1 * months_difference), 1)
else:
- return add_months(
- asset_doc.available_for_use_date,
- (asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
- )
+ return asset_doc.available_for_use_date
def _get_pro_rata_amt(
@@ -678,7 +695,7 @@ def get_straight_line_or_manual_depr_amount(
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
- ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
+ ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
def get_daily_prorata_based_straight_line_depr(
@@ -704,7 +721,7 @@ def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
- ) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
+ ) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
asset_shift_factors_map = get_asset_shift_factors_map()
shift = (
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py
index 6e4966ac6cf..c359715571e 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/test_asset_depreciation_schedule.py
@@ -3,8 +3,11 @@
import frappe
from frappe.tests.utils import FrappeTestCase
-from frappe.utils import cstr
+from frappe.utils import cstr, flt
+from erpnext.assets.doctype.asset.depreciation import (
+ post_depreciation_entries,
+)
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
@@ -28,7 +31,7 @@ class TestAssetDepreciationSchedule(FrappeTestCase):
self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert)
- def test_daily_prorata_based_depr_on_sl_methond(self):
+ def test_daily_prorata_based_depr_on_sl_method(self):
asset = create_asset(
calculate_depreciation=1,
depreciation_method="Straight Line",
@@ -160,3 +163,69 @@ class TestAssetDepreciationSchedule(FrappeTestCase):
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
+
+ def test_update_total_number_of_booked_depreciations(self):
+ # check if updates total number of booked depreciations when depreciation gets booked
+ asset = create_asset(
+ item_code="Macbook Pro",
+ calculate_depreciation=1,
+ opening_accumulated_depreciation=2000,
+ opening_number_of_booked_depreciations=2,
+ depreciation_method="Straight Line",
+ available_for_use_date="2020-01-01",
+ depreciation_start_date="2020-03-31",
+ frequency_of_depreciation=1,
+ total_number_of_depreciations=24,
+ submit=1,
+ )
+
+ post_depreciation_entries(date="2021-03-31")
+ asset.reload()
+ """
+ opening_number_of_booked_depreciations = 2
+ number_of_booked_depreciations till 2021-03-31 = 13
+ total_number_of_booked_depreciations = 15
+ """
+ self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 15)
+
+ # cancel depreciation entry
+ depr_entry = get_depr_schedule(asset.name, "Active")[0].journal_entry
+
+ frappe.get_doc("Journal Entry", depr_entry).cancel()
+ asset.reload()
+
+ self.assertEqual(asset.finance_books[0].total_number_of_booked_depreciations, 14)
+
+ def test_schedule_for_wdv_method_for_existing_asset(self):
+ asset = create_asset(
+ calculate_depreciation=1,
+ depreciation_method="Written Down Value",
+ available_for_use_date="2020-07-17",
+ is_existing_asset=1,
+ opening_number_of_booked_depreciations=2,
+ opening_accumulated_depreciation=11666.67,
+ depreciation_start_date="2021-04-30",
+ total_number_of_depreciations=12,
+ frequency_of_depreciation=3,
+ gross_purchase_amount=50000,
+ rate_of_depreciation=40,
+ )
+
+ self.assertEqual(asset.status, "Draft")
+ expected_schedules = [
+ ["2021-04-30", 3833.33, 15500.0],
+ ["2021-07-31", 3833.33, 19333.33],
+ ["2021-10-31", 3833.33, 23166.66],
+ ["2022-01-31", 3833.33, 26999.99],
+ ["2022-04-30", 2300.0, 29299.99],
+ ["2022-07-31", 2300.0, 31599.99],
+ ["2022-10-31", 2300.0, 33899.99],
+ ["2023-01-31", 2300.0, 36199.99],
+ ["2023-04-30", 1380.0, 37579.99],
+ ["2023-07-31", 12420.01, 50000.0],
+ ]
+ schedules = [
+ [cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
+ for d in get_depr_schedule(asset.name, "Draft")
+ ]
+ self.assertEqual(schedules, expected_schedules)
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
index ba5b5f87826..c269948b742 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json
@@ -8,6 +8,7 @@
"finance_book",
"depreciation_method",
"total_number_of_depreciations",
+ "total_number_of_booked_depreciations",
"daily_prorata_based",
"shift_based",
"column_break_5",
@@ -104,12 +105,19 @@
"fieldname": "shift_based",
"fieldtype": "Check",
"label": "Depreciate based on shifts"
+ },
+ {
+ "default": "0",
+ "fieldname": "total_number_of_booked_depreciations",
+ "fieldtype": "Int",
+ "label": "Total Number of Booked Depreciations ",
+ "read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-12-29 08:49:39.876439",
+ "modified": "2024-05-21 15:48:20.907250",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",
diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py
index f812a0816dd..d06d6355ec3 100644
--- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py
+++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py
@@ -28,6 +28,7 @@ class AssetFinanceBook(Document):
rate_of_depreciation: DF.Percent
salvage_value_percentage: DF.Percent
shift_based: DF.Check
+ total_number_of_booked_depreciations: DF.Int
total_number_of_depreciations: DF.Int
value_after_depreciation: DF.Currency
# end: auto-generated types
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 570c9751a57..b44164f2dae 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -18,9 +18,7 @@ class AssetMaintenance(Document):
if TYPE_CHECKING:
from frappe.types import DF
- from erpnext.assets.doctype.asset_maintenance_task.asset_maintenance_task import (
- AssetMaintenanceTask,
- )
+ from erpnext.assets.doctype.asset_maintenance_task.asset_maintenance_task import AssetMaintenanceTask
asset_category: DF.ReadOnly | None
asset_maintenance_tasks: DF.Table[AssetMaintenanceTask]
@@ -47,6 +45,11 @@ class AssetMaintenance(Document):
assign_tasks(self.name, task.assign_to, task.maintenance_task, task.next_due_date)
self.sync_maintenance_tasks()
+ def after_delete(self):
+ asset = frappe.get_doc("Asset", self.asset_name)
+ if asset.status == "In Maintenance":
+ asset.set_status()
+
def sync_maintenance_tasks(self):
tasks_names = []
for task in self.get("asset_maintenance_tasks"):
diff --git a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
index 009bcc3e69a..95d02714c5b 100644
--- a/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
+++ b/erpnext/assets/doctype/asset_maintenance_log/asset_maintenance_log.py
@@ -5,7 +5,8 @@
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import getdate, nowdate
+from frappe.query_builder import DocType
+from frappe.utils import getdate, nowdate, today
from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate_next_due_date
@@ -75,6 +76,17 @@ class AssetMaintenanceLog(Document):
asset_maintenance_doc.save()
+def update_asset_maintenance_log_status():
+ AssetMaintenanceLog = DocType("Asset Maintenance Log")
+ (
+ frappe.qb.update(AssetMaintenanceLog)
+ .set(AssetMaintenanceLog.maintenance_status, "Overdue")
+ .where(
+ (AssetMaintenanceLog.maintenance_status == "Planned") & (AssetMaintenanceLog.due_date < today())
+ )
+ ).run()
+
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index 27a4eb6e995..489fbaca6b2 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -20,14 +20,14 @@ frappe.ui.form.on("Asset Repair", {
};
};
- frm.fields_dict.warehouse.get_query = function (doc) {
+ frm.set_query("warehouse", "stock_items", function () {
return {
filters: {
is_group: 0,
- company: doc.company,
+ company: frm.doc.company,
},
};
- };
+ });
frm.set_query("serial_and_batch_bundle", "stock_items", (doc, cdt, cdn) => {
let row = locals[cdt][cdn];
@@ -79,7 +79,7 @@ frappe.ui.form.on("Asset Repair", {
});
}
- if (frm.doc.repair_status == "Completed") {
+ if (frm.doc.repair_status == "Completed" && !frm.doc.completion_date) {
frm.set_value("completion_date", frappe.datetime.now_datetime());
}
},
@@ -87,15 +87,48 @@ frappe.ui.form.on("Asset Repair", {
stock_items_on_form_rendered() {
erpnext.setup_serial_or_batch_no();
},
+
+ stock_consumption: function (frm) {
+ if (!frm.doc.stock_consumption) {
+ frm.clear_table("stock_items");
+ frm.refresh_field("stock_items");
+ }
+ },
+
+ purchase_invoice: function (frm) {
+ if (frm.doc.purchase_invoice) {
+ frappe.call({
+ method: "frappe.client.get_value",
+ args: {
+ doctype: "Purchase Invoice",
+ fieldname: "base_net_total",
+ filters: { name: frm.doc.purchase_invoice },
+ },
+ callback: function (r) {
+ if (r.message) {
+ frm.set_value("repair_cost", r.message.base_net_total);
+ }
+ },
+ });
+ } else {
+ frm.set_value("repair_cost", 0);
+ }
+ },
});
frappe.ui.form.on("Asset Repair Consumed Item", {
- item_code: function (frm, cdt, cdn) {
+ warehouse: function (frm, cdt, cdn) {
var item = locals[cdt][cdn];
+ if (!item.item_code) {
+ frappe.msgprint(__("Please select an item code before setting the warehouse."));
+ frappe.model.set_value(cdt, cdn, "warehouse", "");
+ return;
+ }
+
let item_args = {
item_code: item.item_code,
- warehouse: frm.doc.warehouse,
+ warehouse: item.warehouse,
qty: item.consumed_quantity,
serial_no: item.serial_no,
company: frm.doc.company,
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.json b/erpnext/assets/doctype/asset_repair/asset_repair.json
index accb5bf54b5..c98f5a8d7f4 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.json
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.json
@@ -22,16 +22,14 @@
"column_break_14",
"project",
"accounting_details",
- "repair_cost",
+ "purchase_invoice",
"capitalize_repair_cost",
"stock_consumption",
"column_break_8",
- "purchase_invoice",
+ "repair_cost",
"stock_consumption_details_section",
- "warehouse",
"stock_items",
"total_repair_cost",
- "stock_entry",
"asset_depreciation_details_section",
"increase_in_asset_life",
"section_break_9",
@@ -122,7 +120,8 @@
"default": "0",
"fieldname": "repair_cost",
"fieldtype": "Currency",
- "label": "Repair Cost"
+ "label": "Repair Cost",
+ "read_only": 1
},
{
"fieldname": "amended_from",
@@ -218,13 +217,6 @@
"label": "Total Repair Cost",
"read_only": 1
},
- {
- "depends_on": "stock_consumption",
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "label": "Warehouse",
- "options": "Warehouse"
- },
{
"depends_on": "capitalize_repair_cost",
"fieldname": "asset_depreciation_details_section",
@@ -251,20 +243,12 @@
"fieldtype": "Link",
"label": "Company",
"options": "Company"
- },
- {
- "fieldname": "stock_entry",
- "fieldtype": "Link",
- "label": "Stock Entry",
- "no_copy": 1,
- "options": "Stock Entry",
- "read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-08-16 15:55:25.023471",
+ "modified": "2024-06-13 16:14:14.398356",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 27542bc6de8..903e68e32e0 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -47,20 +47,25 @@ class AssetRepair(AccountsController):
repair_cost: DF.Currency
repair_status: DF.Literal["Pending", "Completed", "Cancelled"]
stock_consumption: DF.Check
- stock_entry: DF.Link | None
stock_items: DF.Table[AssetRepairConsumedItem]
total_repair_cost: DF.Currency
- warehouse: DF.Link | None
# end: auto-generated types
def validate(self):
self.asset_doc = frappe.get_doc("Asset", self.asset)
+ self.validate_dates()
self.update_status()
if self.get("stock_items"):
self.set_stock_items_cost()
self.calculate_total_repair_cost()
+ def validate_dates(self):
+ if self.completion_date and (self.failure_date > self.completion_date):
+ frappe.throw(
+ _("Completion Date can not be before Failure Date. Please adjust the dates accordingly.")
+ )
+
def update_status(self):
if self.repair_status == "Pending" and self.asset_doc.status != "Out of Order":
frappe.db.set_value("Asset", self.asset, "status", "Out of Order")
@@ -105,22 +110,22 @@ class AssetRepair(AccountsController):
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.modify_depreciation_schedule()
- notes = _(
- "This schedule was created when Asset {0} was repaired through Asset Repair {1}."
- ).format(
- get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
- get_link_to_form(self.doctype, self.name),
- )
- self.asset_doc.flags.ignore_validate_update_after_submit = True
- make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
- self.asset_doc.save()
+ notes = _(
+ "This schedule was created when Asset {0} was repaired through Asset Repair {1}."
+ ).format(
+ get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+ get_link_to_form(self.doctype, self.name),
+ )
+ self.asset_doc.flags.ignore_validate_update_after_submit = True
+ make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
+ self.asset_doc.save()
- add_asset_activity(
- self.asset,
- _("Asset updated after completion of Asset Repair {0}").format(
- get_link_to_form("Asset Repair", self.name)
- ),
- )
+ add_asset_activity(
+ self.asset,
+ _("Asset updated after completion of Asset Repair {0}").format(
+ get_link_to_form("Asset Repair", self.name)
+ ),
+ )
def before_cancel(self):
self.asset_doc = frappe.get_doc("Asset", self.asset)
@@ -136,29 +141,28 @@ class AssetRepair(AccountsController):
self.asset_doc.total_asset_cost -= self.repair_cost
self.asset_doc.additional_asset_cost -= self.repair_cost
- if self.get("stock_consumption"):
- self.increase_stock_quantity()
if self.get("capitalize_repair_cost"):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.make_gl_entries(cancel=True)
- self.db_set("stock_entry", None)
if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
self.revert_depreciation_schedule_on_cancellation()
- notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
- get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
- get_link_to_form(self.doctype, self.name),
- )
- self.asset_doc.flags.ignore_validate_update_after_submit = True
- make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
- self.asset_doc.save()
+ notes = _(
+ "This schedule was created when Asset {0}'s Asset Repair {1} was cancelled."
+ ).format(
+ get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+ get_link_to_form(self.doctype, self.name),
+ )
+ self.asset_doc.flags.ignore_validate_update_after_submit = True
+ make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
+ self.asset_doc.save()
- add_asset_activity(
- self.asset,
- _("Asset updated after cancellation of Asset Repair {0}").format(
- get_link_to_form("Asset Repair", self.name)
- ),
- )
+ add_asset_activity(
+ self.asset,
+ _("Asset updated after cancellation of Asset Repair {0}").format(
+ get_link_to_form("Asset Repair", self.name)
+ ),
+ )
def after_delete(self):
frappe.get_doc("Asset", self.asset).set_status()
@@ -170,11 +174,6 @@ class AssetRepair(AccountsController):
def check_for_stock_items_and_warehouse(self):
if not self.get("stock_items"):
frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items"))
- if not self.warehouse:
- frappe.throw(
- _("Please enter Warehouse from which Stock Items consumed during the Repair were taken."),
- title=_("Missing Warehouse"),
- )
def increase_asset_value(self):
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
@@ -208,6 +207,7 @@ class AssetRepair(AccountsController):
stock_entry = frappe.get_doc(
{"doctype": "Stock Entry", "stock_entry_type": "Material Issue", "company": self.company}
)
+ stock_entry.asset_repair = self.name
for stock_item in self.get("stock_items"):
self.validate_serial_no(stock_item)
@@ -215,7 +215,7 @@ class AssetRepair(AccountsController):
stock_entry.append(
"items",
{
- "s_warehouse": self.warehouse,
+ "s_warehouse": stock_item.warehouse,
"item_code": stock_item.item_code,
"qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate,
@@ -228,8 +228,6 @@ class AssetRepair(AccountsController):
stock_entry.insert()
stock_entry.submit()
- self.db_set("stock_entry", stock_entry.name)
-
def validate_serial_no(self, stock_item):
if not stock_item.serial_and_batch_bundle and frappe.get_cached_value(
"Item", stock_item.item_code, "has_serial_no"
@@ -247,12 +245,6 @@ class AssetRepair(AccountsController):
"Serial and Batch Bundle", stock_item.serial_and_batch_bundle, values_to_update
)
- def increase_stock_quantity(self):
- if self.stock_entry:
- stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
- stock_entry.flags.ignore_links = True
- stock_entry.cancel()
-
def make_gl_entries(self, cancel=False):
if flt(self.total_repair_cost) > 0:
gl_entries = self.get_gl_entries()
@@ -316,7 +308,7 @@ class AssetRepair(AccountsController):
return
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
- stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
+ stock_entry = frappe.get_doc("Stock Entry", {"asset_repair": self.name})
default_expense_account = None
if not erpnext.is_perpetual_inventory_enabled(self.company):
@@ -357,7 +349,7 @@ class AssetRepair(AccountsController):
"cost_center": self.cost_center,
"posting_date": getdate(),
"against_voucher_type": "Stock Entry",
- "against_voucher": self.stock_entry,
+ "against_voucher": stock_entry.name,
"company": self.company,
},
item=self,
@@ -377,7 +369,7 @@ class AssetRepair(AccountsController):
def calculate_last_schedule_date(self, asset, row, extra_months):
asset.flags.increase_in_asset_life = True
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
- asset.number_of_depreciations_booked
+ asset.opening_number_of_booked_depreciations
)
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
@@ -410,7 +402,7 @@ class AssetRepair(AccountsController):
def calculate_last_schedule_date_before_modification(self, asset, row, extra_months):
asset.flags.increase_in_asset_life = True
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
- asset.number_of_depreciations_booked
+ asset.opening_number_of_booked_depreciations
)
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.py b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
index 278da1b08bf..44d08869a63 100644
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/test_asset_repair.py
@@ -76,14 +76,14 @@ class TestAssetRepair(unittest.TestCase):
def test_warehouse(self):
asset_repair = create_asset_repair(stock_consumption=1)
self.assertTrue(asset_repair.stock_consumption)
- self.assertTrue(asset_repair.warehouse)
+ self.assertTrue(asset_repair.stock_items[0].warehouse)
def test_decrease_stock_quantity(self):
asset_repair = create_asset_repair(stock_consumption=1, submit=1)
stock_entry = frappe.get_last_doc("Stock Entry")
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
- self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
+ self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.stock_items[0].warehouse)
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code)
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
@@ -114,14 +114,14 @@ class TestAssetRepair(unittest.TestCase):
asset_repair.repair_status = "Completed"
self.assertRaises(frappe.ValidationError, asset_repair.submit)
- def test_increase_in_asset_value_due_to_stock_consumption(self):
+ def test_no_increase_in_asset_value_when_not_capitalized(self):
asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value_after_depreciation(asset.name)
- asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
+ create_asset_repair(asset=asset, stock_consumption=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
- self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
+ self.assertEqual(increase_in_asset_value, 0)
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
asset = create_asset(calculate_depreciation=1, submit=1)
@@ -185,7 +185,7 @@ class TestAssetRepair(unittest.TestCase):
frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account
)
stock_entry_expense_account = (
- frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account
+ frappe.get_doc("Stock Entry", {"asset_repair": asset_repair.name}).get("items")[0].expense_account
)
expected_values = {
@@ -260,6 +260,12 @@ class TestAssetRepair(unittest.TestCase):
asset.finance_books[0].value_after_depreciation,
)
+ def test_asset_repiar_link_in_stock_entry(self):
+ asset = create_asset(calculate_depreciation=1, submit=1)
+ asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
+ stock_entry = frappe.get_last_doc("Stock Entry")
+ self.assertEqual(stock_entry.asset_repair, asset_repair.name)
+
def num_of_depreciations(asset):
return asset.finance_books[0].total_number_of_depreciations
@@ -289,7 +295,7 @@ def create_asset_repair(**args):
if args.stock_consumption:
asset_repair.stock_consumption = 1
- asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company=asset.company)
+ warehouse = args.warehouse or create_warehouse("Test Warehouse", company=asset.company)
bundle = None
if args.serial_no:
@@ -297,8 +303,8 @@ def create_asset_repair(**args):
frappe._dict(
{
"item_code": args.item_code,
- "warehouse": asset_repair.warehouse,
- "company": frappe.get_cached_value("Warehouse", asset_repair.warehouse, "company"),
+ "warehouse": warehouse,
+ "company": frappe.get_cached_value("Warehouse", warehouse, "company"),
"qty": (flt(args.stock_qty) or 1) * -1,
"voucher_type": "Asset Repair",
"type_of_transaction": "Asset Repair",
@@ -314,6 +320,7 @@ def create_asset_repair(**args):
"stock_items",
{
"item_code": args.item_code or "_Test Stock Item",
+ "warehouse": warehouse,
"valuation_rate": args.rate if args.get("rate") is not None else 100,
"consumed_quantity": args.qty or 1,
"serial_and_batch_bundle": bundle,
@@ -333,7 +340,7 @@ def create_asset_repair(**args):
stock_entry.append(
"items",
{
- "t_warehouse": asset_repair.warehouse,
+ "t_warehouse": asset_repair.stock_items[0].warehouse,
"item_code": asset_repair.stock_items[0].item_code,
"qty": asset_repair.stock_items[0].consumed_quantity,
"basic_rate": args.rate if args.get("rate") is not None else 100,
@@ -351,7 +358,7 @@ def create_asset_repair(**args):
company=asset.company,
expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"),
cost_center=asset_repair.cost_center,
- warehouse=asset_repair.warehouse,
+ warehouse=args.warehouse or create_warehouse("Test Warehouse", company=asset.company),
)
asset_repair.purchase_invoice = pi.name
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
index 6910c2eebf6..c4c13ce413b 100644
--- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"item_code",
+ "warehouse",
"valuation_rate",
"consumed_quantity",
"total_value",
@@ -44,19 +45,28 @@
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
- "options": "Item"
+ "options": "Item",
+ "reqd": 1
},
{
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
"options": "Serial and Batch Bundle"
+ },
+ {
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Warehouse",
+ "options": "Warehouse",
+ "reqd": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-04-06 02:24:20.375870",
+ "modified": "2024-06-13 12:01:47.147333",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair Consumed Item",
diff --git a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py
index ab43cfe62aa..4d41397a0b1 100644
--- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.py
@@ -15,7 +15,7 @@ class AssetRepairConsumedItem(Document):
from frappe.types import DF
consumed_quantity: DF.Data | None
- item_code: DF.Link | None
+ item_code: DF.Link
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
@@ -23,6 +23,7 @@ class AssetRepairConsumedItem(Document):
serial_no: DF.SmallText | None
total_value: DF.Currency
valuation_rate: DF.Currency
+ warehouse: DF.Link
# end: auto-generated types
pass
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index 4ff04c80434..8ebf9d6d389 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -125,9 +125,10 @@ def get_data(filters):
if assets_linked_to_fb and asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
continue
- asset_value = get_asset_value_after_depreciation(
- asset.asset_id, finance_book
- ) or get_asset_value_after_depreciation(asset.asset_id)
+ depreciation_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
+ asset_value = (
+ asset.gross_purchase_amount - asset.opening_accumulated_depreciation - depreciation_amount
+ )
row = {
"asset_id": asset.asset_id,
@@ -139,7 +140,7 @@ def get_data(filters):
or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
- "depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
+ "depreciated_amount": depreciation_amount,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
@@ -159,8 +160,9 @@ def prepare_chart_data(data, filters):
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
filters_filter_based_on = "Date Range"
date_field = "purchase_date"
- filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
- filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
+ filtered_data = [d for d in data if d.get(date_field)]
+ filters_from_date = min(filtered_data, key=lambda a: a.get(date_field)).get(date_field)
+ filters_to_date = max(filtered_data, key=lambda a: a.get(date_field)).get(date_field)
else:
filters_filter_based_on = filters.filter_based_on
date_field = frappe.scrub(filters.date_based_on)
@@ -184,11 +186,12 @@ def prepare_chart_data(data, filters):
)
for d in data:
- date = d.get(date_field)
- belongs_to_month = formatdate(date, "MMM YYYY")
+ if d.get(date_field):
+ date = d.get(date_field)
+ belongs_to_month = formatdate(date, "MMM YYYY")
- labels_values_map[belongs_to_month].asset_value += d.get("asset_value")
- labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount")
+ labels_values_map[belongs_to_month].asset_value += d.get("asset_value")
+ labels_values_map[belongs_to_month].depreciated_amount += d.get("depreciated_amount")
return {
"data": {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index cf383021b06..bd92ebef3d7 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -12,6 +12,7 @@ erpnext.buying.setup_buying_controller();
frappe.ui.form.on("Purchase Order", {
setup: function (frm) {
+ frm.ignore_doctypes_on_cancel_all = ["Unreconcile Payment", "Unreconcile Payment Entries"];
if (frm.doc.is_old_subcontracting_flow) {
frm.set_query("reserve_warehouse", "supplied_items", function () {
return {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 5351ff6d791..0508483a0fc 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -484,7 +484,13 @@ class PurchaseOrder(BuyingController):
self.auto_create_subcontracting_order()
def on_cancel(self):
- self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry")
+ self.ignore_linked_doctypes = (
+ "GL Entry",
+ "Payment Ledger Entry",
+ "Unreconcile Payment",
+ "Unreconcile Payment Entries",
+ )
+
super().on_cancel()
if self.is_against_so():
@@ -794,6 +800,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"field_map": {
"name": "po_detail",
"parent": "purchase_order",
+ "material_request": "material_request",
+ "material_request_item": "material_request_item",
"wip_composite_asset": "wip_composite_asset",
},
"postprocess": update_item,
@@ -902,12 +910,12 @@ def get_mapped_subcontracting_order(source_name, target_doc=None):
)
target_doc.populate_items_table()
+ source_doc = frappe.get_doc("Purchase Order", source_name)
if target_doc.set_warehouse:
for item in target_doc.items:
item.warehouse = target_doc.set_warehouse
else:
- source_doc = frappe.get_doc("Purchase Order", source_name)
if source_doc.set_warehouse:
for item in target_doc.items:
item.warehouse = source_doc.set_warehouse
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 0df17fdd4b9..ff5385dd961 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -138,6 +138,7 @@ class Supplier(TransactionBase):
validate_party_accounts(self)
self.validate_internal_supplier()
self.add_role_for_user()
+ self.validate_currency_for_receivable_payable_and_advance_account()
@frappe.whitelist()
def get_supplier_group_details(self):
diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json
index 1aa63fb5ba0..1bb9899cc85 100644
--- a/erpnext/buying/doctype/supplier/test_records.json
+++ b/erpnext/buying/doctype/supplier/test_records.json
@@ -35,6 +35,7 @@
"doctype": "Supplier",
"supplier_name": "_Test Supplier USD",
"supplier_group": "_Test Supplier Group",
+ "default_currency": "USD",
"accounts": [{
"company": "_Test Company",
"account": "_Test Payable USD - _TC"
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 40ce8b6bc57..0cbf1b320ca 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1430,10 +1430,13 @@ class AccountsController(TransactionBase):
if d.exchange_gain_loss and (
(d.reference_doctype, d.reference_name, str(d.idx)) not in booked
):
- if self.payment_type == "Receive":
- party_account = self.paid_from
- elif self.payment_type == "Pay":
- party_account = self.paid_to
+ if self.book_advance_payments_in_separate_party_account:
+ party_account = d.account
+ else:
+ if self.payment_type == "Receive":
+ party_account = self.paid_from
+ elif self.payment_type == "Pay":
+ party_account = self.paid_to
dr_or_cr = "debit" if d.exchange_gain_loss > 0 else "credit"
@@ -1761,8 +1764,8 @@ class AccountsController(TransactionBase):
item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None
- role_allowed_to_over_bill = frappe.db.get_single_value(
- "Accounts Settings", "role_allowed_to_over_bill"
+ role_allowed_to_over_bill = frappe.get_cached_value(
+ "Accounts Settings", None, "role_allowed_to_over_bill"
)
user_roles = frappe.get_roles()
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 14bf8ad7436..30a5f38400e 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -8,6 +8,7 @@ from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, flt, getdate
from frappe.utils.data import nowtime
+import erpnext
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
from erpnext.accounts.party import get_party_details
from erpnext.buying.utils import update_last_purchase_rate, validate_for_items
@@ -332,6 +333,8 @@ class BuyingController(SubcontractingController):
else:
item.valuation_rate = 0.0
+ update_regional_item_valuation_rate(self)
+
def set_incoming_rate(self):
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
return
@@ -656,10 +659,7 @@ class BuyingController(SubcontractingController):
return
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
- field = "purchase_invoice" if self.doctype == "Purchase Invoice" else "purchase_receipt"
-
self.process_fixed_asset()
- self.update_fixed_asset(field)
if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value(
"Buying Settings", "disable_last_purchase_rate"
@@ -769,7 +769,7 @@ class BuyingController(SubcontractingController):
if not row.asset_location:
frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
- item_data = frappe.db.get_value(
+ item_data = frappe.get_cached_value(
"Item", row.item_code, ["asset_naming_series", "asset_category"], as_dict=1
)
asset_quantity = row.qty if is_grouped_asset else 1
@@ -798,7 +798,7 @@ class BuyingController(SubcontractingController):
asset.flags.ignore_validate = True
asset.flags.ignore_mandatory = True
asset.set_missing_values()
- asset.insert()
+ asset.db_insert()
return asset.name
@@ -824,11 +824,7 @@ class BuyingController(SubcontractingController):
frappe.delete_doc("Asset", asset.name, force=1)
continue
- if self.docstatus in [0, 1] and not asset.get(field):
- asset.set(field, self.name)
- asset.purchase_date = self.posting_date
- asset.supplier = self.supplier
- elif self.docstatus == 2:
+ if self.docstatus == 2:
if asset.docstatus == 2:
continue
if asset.docstatus == 0:
@@ -935,3 +931,8 @@ def validate_item_type(doc, fieldname, message):
).format(items, message)
frappe.throw(error_message)
+
+
+@erpnext.allow_regional
+def update_regional_item_valuation_rate(doc):
+ pass
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index c57b2f3ddd6..a07a00dd03e 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -352,6 +352,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
doctype = "Batch"
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
+ page_len = 30
batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len)
batches.extend(get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len))
@@ -422,7 +423,7 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, p
& (stock_ledger_entry.batch_no.isnotnull())
)
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
- .having(Sum(stock_ledger_entry.actual_qty) > 0)
+ .having(Sum(stock_ledger_entry.actual_qty) != 0)
.offset(start)
.limit(page_len)
)
@@ -473,7 +474,7 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0
& (stock_ledger_entry.serial_and_batch_bundle.isnotnull())
)
.groupby(bundle.batch_no, bundle.warehouse)
- .having(Sum(bundle.qty) > 0)
+ .having(Sum(bundle.qty) != 0)
.offset(start)
.limit(page_len)
)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 96af12f2340..76d04c04d6a 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -146,7 +146,10 @@ def validate_returned_items(doc):
def validate_quantity(doc, args, ref, valid_items, already_returned_items):
fields = ["stock_qty"]
if doc.doctype in ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"]:
- fields.extend(["received_qty", "rejected_qty"])
+ if not args.get("return_qty_from_rejected_warehouse"):
+ fields.extend(["received_qty", "rejected_qty"])
+ else:
+ fields.extend(["received_qty"])
already_returned_data = already_returned_items.get(args.item_code) or {}
@@ -158,9 +161,12 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
for column in fields:
returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
- if column == "stock_qty":
+ if column == "stock_qty" and not args.get("return_qty_from_rejected_warehouse"):
reference_qty = ref.get(column)
current_stock_qty = args.get(column)
+ elif args.get("return_qty_from_rejected_warehouse"):
+ reference_qty = ref.get("rejected_qty") * ref.get("conversion_factor", 1.0)
+ current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
else:
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
@@ -883,6 +889,9 @@ def get_serial_batches_based_on_bundle(field, _bundle_ids):
if frappe.get_cached_value(row.voucher_type, row.voucher_no, "is_return"):
key = frappe.get_cached_value(row.voucher_type + " Item", row.voucher_detail_no, field)
+ if row.voucher_type in ["Sales Invoice", "Delivery Note"]:
+ row.qty = -1 * row.qty
+
if key not in available_dict:
available_dict[key] = frappe._dict(
{"qty": 0.0, "serial_nos": defaultdict(float), "batches": defaultdict(float)}
@@ -936,6 +945,7 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
if is_rejected:
fields.extend(["rejected_serial_and_batch_bundle", "return_qty_from_rejected_warehouse"])
+ del filters["rejected_serial_and_batch_bundle"]
data = frappe.get_all(
doctype,
fields=fields,
@@ -943,6 +953,9 @@ def get_serial_and_batch_bundle(field, doctype, reference_ids, is_rejected=False
)
for d in data:
+ if not d.get("serial_and_batch_bundle") and not d.get("rejected_serial_and_batch_bundle"):
+ continue
+
if is_rejected:
if d.get("return_qty_from_rejected_warehouse"):
_bundle_ids.append(d.get("serial_and_batch_bundle"))
@@ -1027,7 +1040,7 @@ def get_available_batch_qty(parent_doc, batch_no, warehouse):
)
-def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None):
+def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_field=None, qty_field=None):
from erpnext.stock.serial_batch_bundle import SerialBatchCreation
type_of_transaction = "Outward"
@@ -1037,11 +1050,21 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
if not warehouse_field:
warehouse_field = "warehouse"
+ if not qty_field:
+ qty_field = "qty"
+
warehouse = child_doc.get(warehouse_field)
if parent_doc.get("is_internal_customer"):
warehouse = child_doc.get("target_warehouse")
type_of_transaction = "Outward"
+ if not child_doc.get(qty_field):
+ frappe.throw(
+ _("For the {0}, the quantity is required to make the return entry").format(
+ frappe.bold(child_doc.item_code)
+ )
+ )
+
cls_obj = SerialBatchCreation(
{
"type_of_transaction": type_of_transaction,
@@ -1054,7 +1077,7 @@ def make_serial_batch_bundle_for_return(data, child_doc, parent_doc, warehouse_f
"voucher_type": parent_doc.doctype,
"voucher_no": parent_doc.name,
"voucher_detail_no": child_doc.name,
- "qty": child_doc.qty,
+ "qty": child_doc.get(qty_field),
"company": parent_doc.company,
"do_not_submit": True,
}
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 896413aaed8..492000d71d4 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -554,6 +554,7 @@ class StatusUpdater(Document):
ref_doc.set_status(update=True)
+@frappe.request_cache
def get_allowance_for(
item_code,
item_allowance=None,
@@ -583,20 +584,20 @@ def get_allowance_for(
global_amount_allowance,
)
- qty_allowance, over_billing_allowance = frappe.db.get_value(
+ qty_allowance, over_billing_allowance = frappe.get_cached_value(
"Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"]
)
if qty_or_amount == "qty" and not qty_allowance:
if global_qty_allowance is None:
global_qty_allowance = flt(
- frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
+ frappe.get_cached_value("Stock Settings", None, "over_delivery_receipt_allowance")
)
qty_allowance = global_qty_allowance
elif qty_or_amount == "amount" and not over_billing_allowance:
if global_amount_allowance is None:
global_amount_allowance = flt(
- frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
+ frappe.get_cached_value("Accounts Settings", None, "over_billing_allowance")
)
over_billing_allowance = global_amount_allowance
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index bde2f6bd099..212c9adf4af 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -97,7 +97,7 @@ class StockController(AccountsController):
)
)
- def make_gl_entries(self, gl_entries=None, from_repost=False):
+ def make_gl_entries(self, gl_entries=None, from_repost=False, via_landed_cost_voucher=False):
if self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@@ -118,7 +118,11 @@ class StockController(AccountsController):
if self.docstatus == 1:
if not gl_entries:
- gl_entries = self.get_gl_entries(warehouse_account)
+ gl_entries = (
+ self.get_gl_entries(warehouse_account, via_landed_cost_voucher)
+ if self.doctype == "Purchase Receipt"
+ else self.get_gl_entries(warehouse_account)
+ )
make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):
@@ -255,10 +259,17 @@ class StockController(AccountsController):
qty_field = "qty"
warehouse_field = "warehouse"
+ if not data.get("qty"):
+ frappe.throw(
+ _("For the {0}, no stock is available for the return in the warehouse {1}.").format(
+ frappe.bold(row.item_code), row.get(warehouse_field)
+ )
+ )
+
data = filter_serial_batches(
self, data, row, warehouse_field=warehouse_field, qty_field=qty_field
)
- bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field)
+ bundle = make_serial_batch_bundle_for_return(data, row, self, warehouse_field, qty_field)
if row.get("return_qty_from_rejected_warehouse"):
row.db_set(
{
@@ -715,6 +726,9 @@ class StockController(AccountsController):
row.db_set("rejected_serial_and_batch_bundle", None)
+ if row.get("current_serial_and_batch_bundle"):
+ row.db_set("current_serial_and_batch_bundle", None)
+
def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False):
if not table_name:
table_name = "items"
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index e0f1ed77140..d31ee258b27 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -294,16 +294,23 @@ class SubcontractingController(StockController):
receipt_items = {item.name: item.get(self.subcontract_data.order_field) for item in receipt_items}
consumed_materials = self.__get_consumed_items(doctype, receipt_items.keys())
- voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no]
- voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle(
- voucher_no=voucher_nos,
- is_outward=1,
- get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"),
- )
-
if return_consumed_items:
return (consumed_materials, receipt_items)
+ if not consumed_materials:
+ return
+
+ voucher_nos = [d.voucher_no for d in consumed_materials if d.voucher_no]
+ voucher_bundle_data = (
+ get_voucher_wise_serial_batch_from_bundle(
+ voucher_no=voucher_nos,
+ is_outward=1,
+ get_subcontracted_item=("Subcontracting Receipt Supplied Item", "main_item_code"),
+ )
+ if voucher_nos
+ else {}
+ )
+
for row in consumed_materials:
key = (row.rm_item_code, row.main_item_code, receipt_items.get(row.reference_name))
if not self.available_materials.get(key):
@@ -350,10 +357,14 @@ class SubcontractingController(StockController):
transferred_items = self.__get_transferred_items()
voucher_nos = [row.voucher_no for row in transferred_items]
- voucher_bundle_data = get_voucher_wise_serial_batch_from_bundle(
- voucher_no=voucher_nos,
- is_outward=0,
- get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"),
+ voucher_bundle_data = (
+ get_voucher_wise_serial_batch_from_bundle(
+ voucher_no=voucher_nos,
+ is_outward=0,
+ get_subcontracted_item=("Stock Entry Detail", "subcontracted_item"),
+ )
+ if voucher_nos
+ else {}
)
for row in transferred_items:
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index f91acb2e4ea..3f6830c2021 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -10,6 +10,7 @@ from frappe.utils import add_days, getdate, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.stock.doctype.item.test_item import create_item
@@ -55,6 +56,7 @@ class TestAccountsController(FrappeTestCase):
40 series - Company default Cost center is unset
50 series - Journals against Journals
60 series - Journals against Payment Entries
+ 70 series - Advances in Separate party account. Both Party and Advance account are in Foreign currency.
90 series - Dimension inheritence
"""
@@ -114,47 +116,102 @@ class TestAccountsController(FrappeTestCase):
self.supplier = make_supplier("_Test MC Supplier USD", "USD")
def create_account(self):
- account_name = "Debtors USD"
- if not frappe.db.get_value(
- "Account", filters={"account_name": account_name, "company": self.company}
- ):
- acc = frappe.new_doc("Account")
- acc.account_name = account_name
- acc.parent_account = "Accounts Receivable - " + self.company_abbr
- acc.company = self.company
- acc.account_currency = "USD"
- acc.account_type = "Receivable"
- acc.insert()
- else:
- name = frappe.db.get_value(
- "Account",
- filters={"account_name": account_name, "company": self.company},
- fieldname="name",
- pluck=True,
- )
- acc = frappe.get_doc("Account", name)
- self.debtors_usd = acc.name
+ accounts = [
+ frappe._dict(
+ {
+ "attribute_name": "debtors_usd",
+ "name": "Debtors USD",
+ "account_type": "Receivable",
+ "account_currency": "USD",
+ "parent_account": "Accounts Receivable - " + self.company_abbr,
+ }
+ ),
+ frappe._dict(
+ {
+ "attribute_name": "creditors_usd",
+ "name": "Creditors USD",
+ "account_type": "Payable",
+ "account_currency": "USD",
+ "parent_account": "Accounts Payable - " + self.company_abbr,
+ }
+ ),
+ # Advance accounts under Asset and Liability header
+ frappe._dict(
+ {
+ "attribute_name": "advance_received_usd",
+ "name": "Advance Received USD",
+ "account_type": "Receivable",
+ "account_currency": "USD",
+ "parent_account": "Current Liabilities - " + self.company_abbr,
+ }
+ ),
+ frappe._dict(
+ {
+ "attribute_name": "advance_paid_usd",
+ "name": "Advance Paid USD",
+ "account_type": "Payable",
+ "account_currency": "USD",
+ "parent_account": "Current Assets - " + self.company_abbr,
+ }
+ ),
+ ]
- account_name = "Creditors USD"
- if not frappe.db.get_value(
- "Account", filters={"account_name": account_name, "company": self.company}
- ):
- acc = frappe.new_doc("Account")
- acc.account_name = account_name
- acc.parent_account = "Accounts Payable - " + self.company_abbr
- acc.company = self.company
- acc.account_currency = "USD"
- acc.account_type = "Payable"
- acc.insert()
- else:
- name = frappe.db.get_value(
- "Account",
- filters={"account_name": account_name, "company": self.company},
- fieldname="name",
- pluck=True,
- )
- acc = frappe.get_doc("Account", name)
- self.creditors_usd = acc.name
+ for x in accounts:
+ if not frappe.db.get_value("Account", filters={"account_name": x.name, "company": self.company}):
+ acc = frappe.new_doc("Account")
+ acc.account_name = x.name
+ acc.parent_account = x.parent_account
+ acc.company = self.company
+ acc.account_currency = x.account_currency
+ acc.account_type = x.account_type
+ acc.insert()
+ else:
+ name = frappe.db.get_value(
+ "Account",
+ filters={"account_name": x.name, "company": self.company},
+ fieldname="name",
+ pluck=True,
+ )
+ acc = frappe.get_doc("Account", name)
+ setattr(self, x.attribute_name, acc.name)
+
+ def setup_advance_accounts_in_party_master(self):
+ company = frappe.get_doc("Company", self.company)
+ company.book_advance_payments_in_separate_party_account = 1
+ company.save()
+
+ customer = frappe.get_doc("Customer", self.customer)
+ customer.append(
+ "accounts",
+ {
+ "company": self.company,
+ "account": self.debtors_usd,
+ "advance_account": self.advance_received_usd,
+ },
+ )
+ customer.save()
+
+ supplier = frappe.get_doc("Supplier", self.supplier)
+ supplier.append(
+ "accounts",
+ {
+ "company": self.company,
+ "account": self.creditors_usd,
+ "advance_account": self.advance_paid_usd,
+ },
+ )
+ supplier.save()
+
+ def remove_advance_accounts_from_party_master(self):
+ company = frappe.get_doc("Company", self.company)
+ company.book_advance_payments_in_separate_party_account = 0
+ company.save()
+ customer = frappe.get_doc("Customer", self.customer)
+ customer.accounts = []
+ customer.save()
+ supplier = frappe.get_doc("Supplier", self.supplier)
+ supplier.accounts = []
+ supplier.save()
def create_sales_invoice(
self,
@@ -218,6 +275,48 @@ class TestAccountsController(FrappeTestCase):
payment.posting_date = posting_date
return payment
+ def create_purchase_invoice(
+ self,
+ qty=1,
+ rate=1,
+ conversion_rate=80,
+ posting_date=None,
+ do_not_save=False,
+ do_not_submit=False,
+ ):
+ """
+ Helper function to populate default values in purchase invoice
+ """
+ if posting_date is None:
+ posting_date = nowdate()
+
+ pinv = make_purchase_invoice(
+ posting_date=posting_date,
+ qty=qty,
+ rate=rate,
+ company=self.company,
+ supplier=self.supplier,
+ item_code=self.item,
+ item_name=self.item,
+ cost_center=self.cost_center,
+ warehouse=self.warehouse,
+ parent_cost_center=self.cost_center,
+ update_stock=0,
+ currency="USD",
+ conversion_rate=conversion_rate,
+ is_pos=0,
+ is_return=0,
+ income_account=self.income_account,
+ expense_account=self.expense_account,
+ do_not_save=True,
+ )
+ pinv.credit_to = self.creditors_usd
+ if not do_not_save:
+ pinv.save()
+ if not do_not_submit:
+ pinv.submit()
+ return pinv
+
def clear_old_entries(self):
doctype_list = [
"GL Entry",
@@ -1698,3 +1797,123 @@ class TestAccountsController(FrappeTestCase):
# Exchange Gain/Loss Journal should've been cancelled
exc_je_for_je1 = self.get_journals_for(je1.doctype, je1.name)
self.assertEqual(exc_je_for_je1, [])
+
+ def test_70_advance_payment_against_sales_invoice_in_foreign_currency(self):
+ """
+ Customer advance booked under Liability
+ """
+ self.setup_advance_accounts_in_party_master()
+
+ adv = self.create_payment_entry(amount=1, source_exc_rate=83)
+ adv.save() # explicit 'save' is needed to trigger set_liability_account()
+ self.assertEqual(adv.paid_from, self.advance_received_usd)
+ adv.submit()
+
+ si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1, do_not_submit=True)
+ si.debit_to = self.debtors_usd
+ si.save().submit()
+ self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
+
+ pr = self.create_payment_reconciliation()
+ pr.receivable_payable_account = self.debtors_usd
+ pr.default_advance_account = self.advance_received_usd
+ pr.get_unreconciled_entries()
+ self.assertEqual(pr.invoices[0].invoice_number, si.name)
+ self.assertEqual(pr.payments[0].reference_name, adv.name)
+
+ # Allocate and Reconcile
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+ self.assert_ledger_outstanding(si.doctype, si.name, 0.0, 0.0)
+
+ # Exc Gain/Loss journal should've been creatad
+ exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_si), 1)
+ self.assertEqual(len(exc_je_for_adv), 1)
+ self.assertEqual(exc_je_for_si, exc_je_for_adv)
+
+ adv.reload()
+ adv.cancel()
+ si.reload()
+ self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
+ # Exc Gain/Loss journal should've been cancelled
+ exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_si), 0)
+ self.assertEqual(len(exc_je_for_adv), 0)
+
+ self.remove_advance_accounts_from_party_master()
+
+ def test_71_advance_payment_against_purchase_invoice_in_foreign_currency(self):
+ """
+ Supplier advance booked under Asset
+ """
+ self.setup_advance_accounts_in_party_master()
+
+ usd_amount = 1
+ inr_amount = 85
+ exc_rate = 85
+ adv = create_payment_entry(
+ company=self.company,
+ payment_type="Pay",
+ party_type="Supplier",
+ party=self.supplier,
+ paid_from=self.cash,
+ paid_to=self.advance_paid_usd,
+ paid_amount=inr_amount,
+ )
+ adv.source_exchange_rate = 1
+ adv.target_exchange_rate = exc_rate
+ adv.received_amount = usd_amount
+ adv.paid_amount = exc_rate * usd_amount
+ adv.posting_date = nowdate()
+ adv.save()
+ # Make sure that advance account is still set
+ self.assertEqual(adv.paid_to, self.advance_paid_usd)
+ adv.submit()
+
+ pi = self.create_purchase_invoice(qty=1, conversion_rate=83, rate=1)
+ self.assertEqual(pi.credit_to, self.creditors_usd)
+ self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
+
+ pr = self.create_payment_reconciliation()
+ pr.party_type = "Supplier"
+ pr.party = self.supplier
+ pr.receivable_payable_account = self.creditors_usd
+ pr.default_advance_account = self.advance_paid_usd
+ pr.get_unreconciled_entries()
+ self.assertEqual(pr.invoices[0].invoice_number, pi.name)
+ self.assertEqual(pr.payments[0].reference_name, adv.name)
+
+ # Allocate and Reconcile
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [x.as_dict() for x in pr.payments]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+ self.assert_ledger_outstanding(pi.doctype, pi.name, 0.0, 0.0)
+
+ # Exc Gain/Loss journal should've been creatad
+ exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_pi), 1)
+ self.assertEqual(len(exc_je_for_adv), 1)
+ self.assertEqual(exc_je_for_pi, exc_je_for_adv)
+
+ adv.reload()
+ adv.cancel()
+ pi.reload()
+ self.assert_ledger_outstanding(pi.doctype, pi.name, 83.0, 1.0)
+ # Exc Gain/Loss journal should've been cancelled
+ exc_je_for_pi = self.get_journals_for(pi.doctype, pi.name)
+ exc_je_for_adv = self.get_journals_for(adv.doctype, adv.name)
+ self.assertEqual(len(exc_je_for_pi), 0)
+ self.assertEqual(len(exc_je_for_adv), 0)
+
+ self.remove_advance_accounts_from_party_master()
diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js
index 97415251a93..beef2c934dc 100644
--- a/erpnext/crm/doctype/lead/lead_list.js
+++ b/erpnext/crm/doctype/lead/lead_list.js
@@ -1,4 +1,8 @@
frappe.listview_settings["Lead"] = {
+ get_indicator: function (doc) {
+ var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
+ return indicator;
+ },
onload: function (listview) {
if (frappe.boot.user.can_create.includes("Prospect")) {
listview.page.add_action_item(__("Create Prospect"), function () {
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index aeaebafabe9..db9d31b53bb 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -118,6 +118,8 @@ class Opportunity(TransactionBase, CRMNote):
self.title = self.customer_name
self.calculate_totals()
+
+ def on_update(self):
self.update_prospect()
def map_fields(self):
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 64cbc297d20..028acc9cb11 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -442,6 +442,7 @@ scheduler_events = {
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_daily",
"erpnext.accounts.utils.run_ledger_health_checks",
+ "erpnext.assets.doctype.asset_maintenance_log.asset_maintenance_log.update_asset_maintenance_log_status",
],
"weekly": [
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_weekly",
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 4ce8342af6e..58ce7c50583 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -591,13 +591,13 @@
"default": "0",
"fieldname": "fg_based_operating_cost",
"fieldtype": "Check",
- "label": "FG based Operating Cost"
+ "label": "Finished Goods based Operating Cost"
},
{
"depends_on": "fg_based_operating_cost",
"fieldname": "fg_based_section_section",
"fieldtype": "Section Break",
- "label": "FG Based Operating Cost Section"
+ "label": "Finished Goods Based Operating Cost"
},
{
"depends_on": "fg_based_operating_cost",
@@ -637,7 +637,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2024-04-02 16:22:47.518411",
+ "modified": "2024-06-03 16:24:47.518411",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 20b9845ef6a..009320c7a18 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -1259,12 +1259,18 @@ def get_children(parent=None, is_root=False, **filters):
def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
stock_entry.additional_costs = []
- expenses_included_in_valuation = frappe.get_cached_value(
- "Company", work_order.company, "expenses_included_in_valuation"
+ company_account = frappe.db.get_value(
+ "Company",
+ work_order.company,
+ ["expenses_included_in_valuation", "default_operating_cost_account"],
+ as_dict=1,
)
- add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation)
- add_operations_cost(stock_entry, work_order, expenses_included_in_valuation)
+ expense_account = (
+ company_account.default_operating_cost_account or company_account.expenses_included_in_valuation
+ )
+ add_non_stock_items_cost(stock_entry, work_order, expense_account)
+ add_operations_cost(stock_entry, work_order, expense_account)
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js
index 32231aa4949..34d0fc7131b 100644
--- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js
@@ -212,7 +212,6 @@ erpnext.bom.BomConfigurator = class BomConfigurator extends erpnext.TransactionC
item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));
refresh_field("stock_qty", item.name, item.parentfield);
this.toggle_conversion_factor(item);
- this.frm.events.update_cost(this.frm);
}
}
};
diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json
index 56acd8a1a67..1726f898751 100644
--- a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json
+++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json
@@ -70,7 +70,7 @@
"fieldname": "fg_item",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "FG Item",
+ "label": "Finished Goods Item",
"options": "Item",
"reqd": 1
},
@@ -203,7 +203,7 @@
{
"fieldname": "fg_reference_id",
"fieldtype": "Data",
- "label": "FG Reference",
+ "label": "Finished Goods Reference",
"no_copy": 1,
"read_only": 1
},
@@ -230,7 +230,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2023-11-16 13:34:06.321061",
+ "modified": "2024-06-03 18:45:24.339532",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Creator Item",
diff --git a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
index 5a734d8684f..037a3fe0e81 100644
--- a/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
+++ b/erpnext/manufacturing/doctype/bom_operation/bom_operation.json
@@ -77,7 +77,7 @@
"fieldname": "time_in_mins",
"fieldtype": "Float",
"in_list_view": 1,
- "label": "Operation Time ",
+ "label": "Operation Time",
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"reqd": 1
@@ -203,4 +203,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 6db901c71a4..5b4ef233926 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -400,13 +400,20 @@ frappe.ui.form.on("Production Plan", {
},
download_materials_required(frm) {
+ const warehouses_data = [];
+
+ if (frm.doc.for_warehouse) {
+ warehouses_data.push({ warehouse: frm.doc.for_warehouse });
+ }
+
const fields = [
{
fieldname: "warehouses",
fieldtype: "Table MultiSelect",
label: __("Warehouses"),
- default: frm.doc.from_warehouse,
+ default: warehouses_data,
options: "Production Plan Material Request Warehouse",
+ reqd: 1,
get_query: function () {
return {
filters: {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index a378d8ae606..7d3aa000c87 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -452,6 +452,10 @@ class ProductionPlan(Document):
{"sales_order": data.parent, "sales_order_item": data.name, "qty": data.pending_qty}
)
+ bom_no = data.bom_no or item_details and item_details.bom_no or ""
+ if not bom_no:
+ continue
+
pi = self.append(
"po_items",
{
@@ -459,7 +463,7 @@ class ProductionPlan(Document):
"item_code": data.item_code,
"description": data.description or item_details.description,
"stock_uom": item_details and item_details.stock_uom or "",
- "bom_no": data.bom_no or item_details and item_details.bom_no or "",
+ "bom_no": bom_no,
"planned_qty": data.pending_qty,
"pending_qty": data.pending_qty,
"planned_start_date": now_datetime(),
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 8957c11d3f0..71d9b59c677 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -328,6 +328,28 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(pln2.po_items[0].bom_no, bom2.name)
+ def test_production_plan_with_non_active_bom_item(self):
+ item = make_item("Test Production Item 1 for Non Active BOM", {"is_stock_item": 1}).name
+
+ so1 = make_sales_order(item_code=item, qty=1)
+
+ pln = frappe.new_doc("Production Plan")
+ pln.company = so1.company
+ pln.get_items_from = "Sales Order"
+ pln.append(
+ "sales_orders",
+ {
+ "sales_order": so1.name,
+ "sales_order_date": so1.transaction_date,
+ "customer": so1.customer,
+ "grand_total": so1.grand_total,
+ },
+ )
+
+ pln.get_items()
+
+ self.assertFalse(pln.po_items)
+
def test_production_plan_combine_items(self):
"Test combining FG items in Production Plan."
item = "Test Production Item 1"
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index 78a389760a7..089d7396b60 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -85,7 +85,7 @@
"fieldname": "warehouse",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "FG Warehouse",
+ "label": "Finished Goods Warehouse",
"options": "Warehouse"
},
{
@@ -220,7 +220,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2024-02-27 13:24:43.571844",
+ "modified": "2024-06-03 13:10:20.252166",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item",
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 14a56f21afb..d1f2cdbd1aa 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1748,6 +1748,81 @@ class TestWorkOrder(FrappeTestCase):
job_card2.time_logs = []
job_card2.save()
+ def test_operating_cost_account(self):
+ operating_cost_account = "Test Operating Cost Account - _TC"
+ company = "_Test Company"
+ if not frappe.db.exists("Account", operating_cost_account):
+ frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_name": "Test Operating Cost Account",
+ "account_type": "Expense Account",
+ "company": company,
+ "parent_account": "Expenses - _TC",
+ "root_type": "Expense",
+ }
+ ).insert()
+
+ frappe.db.set_value("Company", company, "default_operating_cost_account", operating_cost_account)
+
+ for item in ["TEST RM OP COST Item 1", "TEST FG OP COST Item"]:
+ if not frappe.db.exists("Item", item):
+ make_item(item_code=item, properties={"is_stock_item": 1})
+
+ fg_item = "TEST FG OP COST Item"
+ bom_doc = make_bom(
+ item=fg_item,
+ raw_materials=["TEST RM OP COST Item 1"],
+ rate=150,
+ with_operations=1,
+ do_not_save=True,
+ )
+
+ workstation = "Test Workstation For Capacity Planning 1"
+ if not frappe.db.exists("Workstation", workstation):
+ make_workstation(workstation=workstation, production_capacity=1)
+
+ operation = "Test Operation For Capacity Planning 1"
+ if not frappe.db.exists("Operation", operation):
+ make_operation(operation=operation, workstation=workstation)
+
+ bom_doc.append(
+ "operations",
+ {"operation": operation, "time_in_mins": 60, "hour_rate": 100, "workstation": workstation},
+ )
+
+ bom_doc.save()
+ bom_doc.submit()
+
+ wo = make_wo_order_test_record(
+ production_item=fg_item,
+ bom_no=bom_doc.name,
+ qty=1,
+ skip_transfer=1,
+ )
+
+ job_cards = frappe.get_all("Job Card", filters={"work_order": wo.name})
+ for job_card in job_cards:
+ job_card_doc = frappe.get_doc("Job Card", job_card.name)
+ job_card_doc.time_logs = []
+ job_card_doc.append(
+ "time_logs",
+ {
+ "from_time": now(),
+ "to_time": add_to_date(now(), minutes=60),
+ "time_in_mins": 60,
+ "completed_qty": 1,
+ },
+ )
+
+ job_card_doc.submit()
+
+ se_doc = frappe.get_doc(make_stock_entry(wo.name, "Manufacture", 1))
+ se_doc.save()
+
+ for row in se_doc.additional_costs:
+ self.assertEqual(row.expense_account, operating_cost_account)
+
def test_op_cost_and_scrap_based_on_sub_assemblies(self):
# Make Sub Assembly BOM 1
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.json b/erpnext/manufacturing/doctype/workstation/workstation.json
index 5912714052b..4758e5d3588 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.json
+++ b/erpnext/manufacturing/doctype/workstation/workstation.json
@@ -17,8 +17,6 @@
"column_break_3",
"production_capacity",
"warehouse",
- "production_capacity_section",
- "parts_per_hour",
"workstation_status_tab",
"status",
"column_break_glcv",
@@ -210,16 +208,6 @@
"label": "Warehouse",
"options": "Warehouse"
},
- {
- "fieldname": "production_capacity_section",
- "fieldtype": "Section Break",
- "label": "Production Capacity"
- },
- {
- "fieldname": "parts_per_hour",
- "fieldtype": "Float",
- "label": "Parts Per Hour"
- },
{
"fieldname": "total_working_hours",
"fieldtype": "Float",
@@ -252,7 +240,7 @@
"idx": 1,
"image_field": "on_status_image",
"links": [],
- "modified": "2023-11-30 12:43:35.808845",
+ "modified": "2024-06-20 14:17:13.806609",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Workstation",
@@ -277,4 +265,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py
index 0966d26b781..326a8b37efc 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/workstation.py
@@ -55,6 +55,9 @@ class Workstation(Document):
hour_rate_electricity: DF.Currency
hour_rate_labour: DF.Currency
hour_rate_rent: DF.Currency
+ off_status_image: DF.AttachImage | None
+ on_status_image: DF.AttachImage | None
+ plant_floor: DF.Link | None
production_capacity: DF.Int
working_hours: DF.Table[WorkstationWorkingHour]
workstation_name: DF.Data
diff --git a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
index 51efc6e655f..73560dd939b 100644
--- a/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
+++ b/erpnext/manufacturing/report/process_loss_report/process_loss_report.py
@@ -102,7 +102,12 @@ def get_columns() -> Columns:
"fieldtype": "Float",
"width": "150",
},
- {"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"},
+ {
+ "label": _("Finished Goods Value"),
+ "fieldname": "total_fg_value",
+ "fieldtype": "Float",
+ "width": "150",
+ },
{
"label": _("Raw Material Value"),
"fieldname": "total_rm_value",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2522077e9c3..66e98dde5b4 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -365,3 +365,7 @@ erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency
erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1
+erpnext.patches.v15_0.rename_number_of_depreciations_booked_to_opening_booked_depreciations
+erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_doctype
+erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry
+erpnext.patches.v15_0.update_total_number_of_booked_depreciations
diff --git a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py
index ec72527552c..d740a5b9c8e 100644
--- a/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py
+++ b/erpnext/patches/v14_0/migrate_existing_lead_notes_as_per_the_new_format.py
@@ -9,15 +9,13 @@ def execute():
dt = frappe.qb.DocType(doctype)
records = (
- frappe.qb.from_(dt)
- .select(dt.name, dt.notes, dt.modified_by, dt.modified)
- .where(dt.notes.isnotnull() & dt.notes != "")
+ frappe.qb.from_(dt).select(dt.name, dt.notes).where(dt.notes.isnotnull() & dt.notes != "")
).run(as_dict=True)
for d in records:
if strip_html(cstr(d.notes)).strip():
doc = frappe.get_doc(doctype, d.name)
- doc.append("notes", {"note": d.notes, "added_by": d.modified_by, "added_on": d.modified})
+ doc.append("notes", {"note": d.notes})
doc.update_child_table("notes")
frappe.db.sql_ddl(f"alter table `tab{doctype}` drop column `notes`")
diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
index c31d754d2cd..523b559d734 100644
--- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
+++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
@@ -4,6 +4,7 @@ import frappe
def execute():
frappe.reload_doc("assets", "doctype", "Asset Depreciation Schedule")
frappe.reload_doc("assets", "doctype", "Asset Finance Book")
+ frappe.reload_doc("assets", "doctype", "Asset")
assets = get_details_of_draft_or_submitted_depreciable_assets()
@@ -43,7 +44,7 @@ def get_details_of_draft_or_submitted_depreciable_assets():
asset.name,
asset.opening_accumulated_depreciation,
asset.gross_purchase_amount,
- asset.number_of_depreciations_booked,
+ asset.opening_number_of_booked_depreciations,
asset.docstatus,
)
.where(asset.calculate_depreciation == 1)
diff --git a/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py b/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py
new file mode 100644
index 00000000000..18183374554
--- /dev/null
+++ b/erpnext/patches/v15_0/rename_number_of_depreciations_booked_to_opening_booked_depreciations.py
@@ -0,0 +1,7 @@
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+
+def execute():
+ if frappe.db.has_column("Asset", "number_of_depreciations_booked"):
+ rename_field("Asset", "number_of_depreciations_booked", "opening_number_of_booked_depreciations")
diff --git a/erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py b/erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py
new file mode 100644
index 00000000000..cc0668d9caa
--- /dev/null
+++ b/erpnext/patches/v15_0/update_asset_repair_field_in_stock_entry.py
@@ -0,0 +1,15 @@
+import frappe
+from frappe.query_builder import DocType
+
+
+def execute():
+ if frappe.db.has_column("Asset Repair", "stock_entry"):
+ AssetRepair = DocType("Asset Repair")
+ StockEntry = DocType("Stock Entry")
+
+ (
+ frappe.qb.update(StockEntry)
+ .join(AssetRepair)
+ .on(StockEntry.name == AssetRepair.stock_entry)
+ .set(StockEntry.asset_repair, AssetRepair.name)
+ ).run()
diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
index afb59e0f6f5..f8cb3e48e7a 100644
--- a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
+++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
@@ -3,18 +3,19 @@ import frappe
def execute():
# not using frappe.qb because https://github.com/frappe/frappe/issues/20292
+ # nosemgrep
frappe.db.sql(
"""UPDATE `tabAsset Depreciation Schedule`
JOIN `tabAsset`
ON `tabAsset Depreciation Schedule`.`asset`=`tabAsset`.`name`
SET
`tabAsset Depreciation Schedule`.`gross_purchase_amount`=`tabAsset`.`gross_purchase_amount`,
- `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`=`tabAsset`.`number_of_depreciations_booked`
+ `tabAsset Depreciation Schedule`.`opening_number_of_booked_depreciations`=`tabAsset`.`opening_number_of_booked_depreciations`
WHERE
(
`tabAsset Depreciation Schedule`.`gross_purchase_amount`<>`tabAsset`.`gross_purchase_amount`
OR
- `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`<>`tabAsset`.`number_of_depreciations_booked`
+ `tabAsset Depreciation Schedule`.`opening_number_of_booked_depreciations`<>`tabAsset`.`opening_number_of_booked_depreciations`
)
AND `tabAsset Depreciation Schedule`.`docstatus`<2"""
)
diff --git a/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py
new file mode 100644
index 00000000000..82f1c88903d
--- /dev/null
+++ b/erpnext/patches/v15_0/update_total_number_of_booked_depreciations.py
@@ -0,0 +1,30 @@
+import frappe
+
+from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+ get_depr_schedule,
+)
+
+
+def execute():
+ if frappe.db.has_column("Asset Finance Book", "total_number_of_booked_depreciations"):
+ assets = frappe.get_all(
+ "Asset", filters={"docstatus": 1}, fields=["name", "opening_number_of_booked_depreciations"]
+ )
+
+ for asset in assets:
+ asset_doc = frappe.get_doc("Asset", asset.name)
+
+ for fb_row in asset_doc.get("finance_books"):
+ depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book)
+ total_number_of_booked_depreciations = asset.opening_number_of_booked_depreciations or 0
+
+ if depr_schedule:
+ for je in depr_schedule:
+ if je.journal_entry:
+ total_number_of_booked_depreciations += 1
+ frappe.db.set_value(
+ "Asset Finance Book",
+ fb_row.name,
+ "total_number_of_booked_depreciations",
+ total_number_of_booked_depreciations,
+ )
diff --git a/erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py b/erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py
new file mode 100644
index 00000000000..e0291826930
--- /dev/null
+++ b/erpnext/patches/v15_0/update_warehouse_field_in_asset_repair_consumed_item_doctype.py
@@ -0,0 +1,14 @@
+import frappe
+
+
+# not able to use frappe.qb because of this bug https://github.com/frappe/frappe/issues/20292
+def execute():
+ if frappe.db.has_column("Asset Repair", "warehouse"):
+ # nosemgrep
+ frappe.db.sql(
+ """UPDATE `tabAsset Repair Consumed Item` ar_item
+ JOIN `tabAsset Repair` ar
+ ON ar.name = ar_item.parent
+ SET ar_item.warehouse = ar.warehouse
+ WHERE ifnull(ar.warehouse, '') != ''"""
+ )
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index cf6b34c9604..adf05ffb154 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -83,7 +83,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
}
- this.frm.refresh_field("taxes");
+ this.frm.refresh_fields();
}
calculate_discount_amount() {
@@ -841,7 +841,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
});
}
- this.frm.refresh_field("taxes");
+ this.frm.refresh_fields();
}
set_default_payment(total_amount_to_pay, update_paid_amount) {
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 937b19d9abb..dfb0ded8139 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -325,7 +325,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
const me = this;
- if (!this.frm.is_new() && this.frm.doc.docstatus === 0) {
+ if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) {
this.frm.add_custom_button(__("Quality Inspection(s)"), () => {
me.make_quality_inspection();
}, __("Create"));
@@ -1246,8 +1246,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
qty(doc, cdt, cdn) {
- if (!this.frm.doc.__onload?.load_after_mapping) {
- let item = frappe.get_doc(cdt, cdn);
+ let item = frappe.get_doc(cdt, cdn);
+ if (!this.is_a_mapped_document(item)) {
// item.pricing_rules = ''
frappe.run_serially([
() => this.remove_pricing_rule_for_item(item),
@@ -1733,6 +1733,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
me.frm.doc.items.forEach(d => {
if (in_list(JSON.parse(data.apply_rule_on_other_items), d[data.apply_rule_on])) {
for(var k in data) {
+ if (data.pricing_rule_for == "Discount Percentage" && data.apply_rule_on_other_items && k == "discount_amount") {
+ continue;
+ }
+
if (in_list(fields, k) && data[k] && (data.price_or_product_discount === 'Price' || k === 'pricing_rules')) {
frappe.model.set_value(d.doctype, d.name, k, data[k]);
}
@@ -1746,12 +1750,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
apply_product_discount(args) {
const items = this.frm.doc.items.filter(d => (d.is_free_item)) || [];
- const exist_items = items.map(row => (row.item_code, row.pricing_rules));
+ const exist_items = items.map(row => { return {item_code: row.item_code, pricing_rules: row.pricing_rules};});
args.free_item_data.forEach(pr_row => {
let row_to_modify = {};
- if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) {
+ // If there are no free items, or if the current free item doesn't exist in the table, add it
+ if (!items || !exist_items.filter(e_row => {
+ return e_row.item_code == pr_row.item_code && e_row.pricing_rules == pr_row.pricing_rules;
+ }).length) {
row_to_modify = frappe.model.add_child(this.frm.doc,
this.frm.doc.doctype + ' Item', 'items');
diff --git a/erpnext/public/js/templates/crm_notes.html b/erpnext/public/js/templates/crm_notes.html
index 53df9330784..a20e6c2723c 100644
--- a/erpnext/public/js/templates/crm_notes.html
+++ b/erpnext/public/js/templates/crm_notes.html
@@ -12,6 +12,7 @@
{% for(var i=0, l=notes.length; i