mirror of
https://github.com/frappe/erpnext.git
synced 2026-02-18 17:15:04 +00:00
chore: release v15 (#42024)
* fix(Sales Order): only show permitted actions (cherry picked from commitc29d955371) * fix(Delivery Note): only show permitted actions (cherry picked from commit418bdc1dcc) * fix: do not show zero balance stock items in stock balance report (backport #41958) (#41961) fix: do not show zero balance stock in stock balance (cherry picked from commit7f7b363d48) Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com> * fix: add string for translation (backport #41903) (#41963) fix: add string for translation (#41903) fix: add string for translation (cherry picked from commitf28c692dca) Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> * refactor: remove use of can_create for Payment Request (#41647) (cherry picked from commit47bc5691a1) * fix: move condition for shipment * fix: incorrect discount on other item When discount is applied on other item, don't update `discount_amount` as the amount is calculated for current item (cherry picked from commit654764e398) * fix: incorrect against_account upon reposting (cherry picked from commit20c4098399) * fix: decimal issue in pick list (backport #41972) (#41982) fix: decimal issue in pick list (#41972) (cherry picked from commit21adc7b63e) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * refactor: renamed number of depreciations booked to opening booked de… (#41515) * refactor: renamed number of depreciations booked to opening booked depreciations * feat: introduced new field for showing total number of booked depreciations * fix: reload asset when creating asset depreciation * chore: added nosemgrep for security checks * feat: default account head for operating cost (backport #41985) (#41987) * feat: default account head for operating cost (#41985) (cherry picked from commitfd7666a029) # Conflicts: # erpnext/manufacturing/doctype/bom/bom.py # erpnext/setup/doctype/company/company.json * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * perf: dont run queries unnecessarily, improved filters (#41993) * perf: dont run queries unnecessarily, improved filters * perf: dont run query if `in` filter is empty (cherry picked from commitac6d85aed6) * chore: remove validation on payment entry (cherry picked from commite7740033ca) * refactor: convert amount to base currency for advances (cherry picked from commitc9ede1ffbe) * refactor: for advances uses the party account in references table (cherry picked from commit7dce6e03c7) * refactor(test): simpler create_account helper method (cherry picked from commit475e0ddeee) * test: exc gain/loss booking on advances under asset/liability (cherry picked from commit827d67d02f) * test: advance against purchase invoice (cherry picked from commit90c84822d0) * refactor: validation to force accounts to be on same currency (cherry picked from commit0f0b4d88bc) * refactor: validation in customer group (cherry picked from commit4f9a228175) * refactor: validation in Supplier Group (cherry picked from commit107b614518) * refactor: better error messages (cherry picked from commit83ff94b9b8) * chore: fix test data (cherry picked from commit07d59443b7) * chore: remove dead code (cherry picked from commit7e318c0132) * fix(test): incorrect field for customer default billing currency (cherry picked from commitc696d13a5e) * refactor(test): enfore use of customer/supplier master While using advance accounts in foreign currency, always use Customer/Supplier master to maintain them (cherry picked from commit64e63887be) * refactor(test): make and use a different party for subscription (cherry picked from commit3fabf4aaa4) * fix: pricing rule with and without 'apply multiple' and priority Either all of the pricing rules identified for an item should have 'apply multiple' enabled. If not, Priority is applied and only the highest priority is applied (cherry picked from commit5e875b238c) * test: priority takes effect on with and without apply multiple (cherry picked from commitefebc3662e) * feat: accounting dimension filters in gp report (cherry picked from commitd165638bbb) * feat(gp): group by cost center (cherry picked from commite26bc17c75) * fix: Wrong Delete Batch on Purchase Receipt (backport #42007) (#42012) fix: Wrong Delete Batch on Purchase Receipt (#42007) (cherry picked from commitd50487ce53) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: incorrect Difference Amount (backport #42008) (#42013) fix: incorrect Difference Amount (#42008) (cherry picked from commit7d91c6cbd5) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: valuation rate for the legacy batches (backport #42011) (#42020) fix: valuation rate for the legacy batches (#42011) (cherry picked from commit9ab333d105) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: timeout while cancelling LCV (backport #42030) (backport #42031) (#42032) fix: timeout while cancelling LCV (backport #42030) (#42031) fix: timeout while cancelling LCV (#42030) fix: timeout while canelling LCV (cherry picked from commit21bf7fd1f8) Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> (cherry picked from commit2e76b9f9db) Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * fix: Stock Reservation Entry was not getting created (backport #42033) (#42035) fix: Stock Reservation Entry was not getting created (#42033) (cherry picked from commit1a9899b32b) Co-authored-by: Poorvi-R-Bhat <poorvi.r.bhat@gmail.com> * fix: manufacturing date issue in the batch (backport #42034) (#42037) * fix: manufacturing date issue in the batch (#42034) (cherry picked from commiteca3e02f8d) # Conflicts: # erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py * chore: fix conflicts --------- Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com> * fix: fixed asset value in Fixed Asset Register (backport #41930) (#42027) fix: fixed asset value in Fixed Asset Register (#41930) (cherry picked from commit1c643a0ead) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * feat: Turkish Chart Of Accounts (backport #41756) (#42028) * feat: Create Turkish Chart Of Accounts (cherry picked from commit5c8ea86a3f) * feat: Create Turkish Chart Of Accounts (cherry picked from commitb401ba2c26) --------- Co-authored-by: fzozyurt <fzozyurt@outlook.com> * perf: code optimization to handle large asset creation (backport #42018) (#42025) perf: code optimization to handle large asset creation (#42018) (cherry picked from commit5738d93f95) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * fix: incorrect time period in asset depreciation schedule (backport #41805) (#42043) fix: incorrect time period in asset depreciation schedule (#41805) * fix(wip): depreciation calculation for existing asset * fix(wip): added validation for incorrect depreciation period * fix: depreciation schedule time period issue for existing asset * chore: run pre-commit checks and apply fixes * style: apply formatting changes * style: made some necessary changes * chore: modified test (cherry picked from commit625f16dee0) Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * chore: patch to enable total number of booked depreciations field (backport #41940) (#42042) * chore: patch to enable total number of booked depreciations field (#41940) * chore: patch to enable total number of booked depreciations field * fix: conflict resolved * refactor: replaced fb_row.db_set with set_value (cherry picked from commit5fdd1d3278) # Conflicts: # erpnext/patches.txt * fix: resolved conflicts * fix: removed unmerged patches --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> * fix: lead status filter (backport #41816) (#42046) fix: lead status filter (#41816) (cherry picked from commit8ae2b8ff8c) Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com> * fix: unhide serial no field (backport #42045) (#42047) * fix: unhide serial no field (#42045) (cherry picked from commit80c6981cfa) # Conflicts: # erpnext/assets/doctype/asset_capitalization_stock_item/asset_capitalization_stock_item.json * fix: resolved conflicts --------- Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> --------- Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com> Co-authored-by: mahsem <137205921+mahsem@users.noreply.github.com> Co-authored-by: ruthra kumar <ruthra@erpnext.com> Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com> Co-authored-by: Sagar Vora <sagar@resilient.tech> Co-authored-by: Dany Robert <danyrt@wahni.com> Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com> Co-authored-by: Poorvi-R-Bhat <poorvi.r.bhat@gmail.com> Co-authored-by: fzozyurt <fzozyurt@outlook.com> Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com>
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"):
|
||||
|
||||
@@ -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()
|
||||
@@ -163,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",
|
||||
@@ -1266,7 +1249,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(
|
||||
{
|
||||
@@ -1285,7 +1268,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(
|
||||
{
|
||||
|
||||
@@ -1237,6 +1237,68 @@ 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()
|
||||
|
||||
|
||||
test_dependencies = ["Campaign"]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -593,7 +593,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):
|
||||
@@ -601,6 +601,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(
|
||||
|
||||
@@ -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"]:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:<br>{0}<br><br>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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -377,7 +377,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 +410,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)
|
||||
|
||||
@@ -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,
|
||||
@@ -185,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": {
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -659,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"
|
||||
@@ -772,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
|
||||
@@ -801,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
|
||||
|
||||
@@ -827,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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -365,3 +365,5 @@ 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_total_number_of_booked_depreciations
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
@@ -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"""
|
||||
)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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
|
||||
|
||||
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,
|
||||
)
|
||||
@@ -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"));
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -635,6 +635,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
|
||||
set_data(data) {
|
||||
data.forEach((d) => {
|
||||
d.qty = Math.abs(d.qty);
|
||||
d.name = d.child_row || d.name;
|
||||
this.dialog.fields_dict.entries.df.data.push(d);
|
||||
});
|
||||
|
||||
|
||||
@@ -144,6 +144,7 @@ class Customer(TransactionBase):
|
||||
self.validate_default_bank_account()
|
||||
self.validate_internal_customer()
|
||||
self.add_role_for_user()
|
||||
self.validate_currency_for_receivable_payable_and_advance_account()
|
||||
|
||||
# set loyalty program tier
|
||||
if frappe.db.exists("Customer", self.name):
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"customer_type": "Individual",
|
||||
"doctype": "Customer",
|
||||
"territory": "_Test Territory",
|
||||
"default_currency": "USD",
|
||||
"accounts": [{
|
||||
"company": "_Test Company",
|
||||
"account": "_Test Receivable USD - _TC"
|
||||
|
||||
@@ -58,7 +58,8 @@ frappe.ui.form.on("Sales Order", {
|
||||
if (
|
||||
frm.doc.status !== "Closed" &&
|
||||
flt(frm.doc.per_delivered, 2) < 100 &&
|
||||
flt(frm.doc.per_billed, 2) < 100
|
||||
flt(frm.doc.per_billed, 2) < 100 &&
|
||||
frm.has_perm("write")
|
||||
) {
|
||||
frm.add_custom_button(__("Update Items"), () => {
|
||||
erpnext.utils.update_child_items({
|
||||
@@ -85,7 +86,11 @@ frappe.ui.form.on("Sales Order", {
|
||||
}
|
||||
|
||||
// Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock.
|
||||
if (frm.doc.__onload && frm.doc.__onload.has_reserved_stock) {
|
||||
if (
|
||||
frm.doc.__onload &&
|
||||
frm.doc.__onload.has_reserved_stock &&
|
||||
frappe.model.can_cancel("Stock Reservation Entry")
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Unreserve"),
|
||||
() => frm.events.cancel_stock_reservation_entries(frm),
|
||||
@@ -94,7 +99,7 @@ frappe.ui.form.on("Sales Order", {
|
||||
}
|
||||
|
||||
frm.doc.items.forEach((item) => {
|
||||
if (flt(item.stock_reserved_qty) > 0) {
|
||||
if (flt(item.stock_reserved_qty) > 0 && frappe.model.can_read("Stock Reservation Entry")) {
|
||||
frm.add_custom_button(
|
||||
__("Reserved Stock"),
|
||||
() => frm.events.show_reserved_stock(frm),
|
||||
@@ -142,6 +147,10 @@ frappe.ui.form.on("Sales Order", {
|
||||
},
|
||||
|
||||
get_items_from_internal_purchase_order(frm) {
|
||||
if (!frappe.model.can_read("Purchase Order")) {
|
||||
return;
|
||||
}
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Purchase Order"),
|
||||
() => {
|
||||
@@ -634,15 +643,17 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
}
|
||||
|
||||
if (!doc.__onload || !doc.__onload.has_reserved_stock) {
|
||||
// Don't show the `Reserve` button if the Sales Order has Picked Items.
|
||||
if (flt(doc.per_picked, 2) < 100 && flt(doc.per_delivered, 2) < 100) {
|
||||
this.frm.add_custom_button(
|
||||
__("Pick List"),
|
||||
() => this.create_pick_list(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
if (
|
||||
(!doc.__onload || !doc.__onload.has_reserved_stock) &&
|
||||
flt(doc.per_picked, 2) < 100 &&
|
||||
flt(doc.per_delivered, 2) < 100 &&
|
||||
frappe.model.can_create("Pick List")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Pick List"),
|
||||
() => this.create_pick_list(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1;
|
||||
@@ -657,20 +668,25 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
(order_is_a_sale || order_is_a_custom_sale) &&
|
||||
allow_delivery
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Delivery Note"),
|
||||
() => this.make_delivery_note_based_on_delivery_date(true),
|
||||
__("Create")
|
||||
);
|
||||
this.frm.add_custom_button(
|
||||
__("Work Order"),
|
||||
() => this.make_work_order(),
|
||||
__("Create")
|
||||
);
|
||||
if (frappe.model.can_create("Delivery Note")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Delivery Note"),
|
||||
() => this.make_delivery_note_based_on_delivery_date(true),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
if (frappe.model.can_create("Work Order")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Work Order"),
|
||||
() => this.make_work_order(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// sales invoice
|
||||
if (flt(doc.per_billed, 2) < 100) {
|
||||
if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Invoice"),
|
||||
() => me.make_sales_invoice(),
|
||||
@@ -680,8 +696,10 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
|
||||
// material request
|
||||
if (
|
||||
!doc.order_type ||
|
||||
((order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 2) < 100)
|
||||
(!doc.order_type ||
|
||||
((order_is_a_sale || order_is_a_custom_sale) &&
|
||||
flt(doc.per_delivered, 2) < 100)) &&
|
||||
frappe.model.can_create("Material Request")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Material Request"),
|
||||
@@ -696,7 +714,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
|
||||
// Make Purchase Order
|
||||
if (!this.frm.doc.is_internal_customer) {
|
||||
if (!this.frm.doc.is_internal_customer && frappe.model.can_create("Purchase Order")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Purchase Order"),
|
||||
() => this.make_purchase_order(),
|
||||
@@ -706,24 +724,32 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
|
||||
// maintenance
|
||||
if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Maintenance Visit"),
|
||||
() => this.make_maintenance_visit(),
|
||||
__("Create")
|
||||
);
|
||||
this.frm.add_custom_button(
|
||||
__("Maintenance Schedule"),
|
||||
() => this.make_maintenance_schedule(),
|
||||
__("Create")
|
||||
);
|
||||
if (frappe.model.can_create("Maintenance Visit")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Maintenance Visit"),
|
||||
() => this.make_maintenance_visit(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
if (frappe.model.can_create("Maintenance Schedule")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Maintenance Schedule"),
|
||||
() => this.make_maintenance_schedule(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// project
|
||||
if (flt(doc.per_delivered, 2) < 100) {
|
||||
if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) {
|
||||
this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create"));
|
||||
}
|
||||
|
||||
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
||||
if (
|
||||
doc.docstatus === 1 &&
|
||||
!doc.inter_company_order_reference &&
|
||||
frappe.model.can_create("Purchase Order")
|
||||
) {
|
||||
let me = this;
|
||||
let internal = me.frm.doc.is_internal_customer;
|
||||
if (internal) {
|
||||
@@ -752,13 +778,20 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
() => this.make_payment_request(),
|
||||
__("Create")
|
||||
);
|
||||
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
|
||||
|
||||
if (frappe.model.can_create("Payment Entry")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment"),
|
||||
() => this.make_payment_entry(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
}
|
||||
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.frm.doc.docstatus === 0) {
|
||||
if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Quotation")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Quotation"),
|
||||
function () {
|
||||
|
||||
@@ -38,6 +38,53 @@ class CustomerGroup(NestedSet):
|
||||
def validate(self):
|
||||
if not self.parent_customer_group:
|
||||
self.parent_customer_group = get_root_of("Customer Group")
|
||||
self.validate_currency_for_receivable_and_advance_account()
|
||||
|
||||
def validate_currency_for_receivable_and_advance_account(self):
|
||||
for x in self.accounts:
|
||||
company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency")
|
||||
receivable_account_currency = None
|
||||
advance_account_currency = None
|
||||
|
||||
if x.account:
|
||||
receivable_account_currency = frappe.get_cached_value(
|
||||
"Account", x.account, "account_currency"
|
||||
)
|
||||
|
||||
if x.advance_account:
|
||||
advance_account_currency = frappe.get_cached_value(
|
||||
"Account", x.advance_account, "account_currency"
|
||||
)
|
||||
|
||||
if receivable_account_currency and receivable_account_currency != company_default_currency:
|
||||
frappe.throw(
|
||||
_("Receivable Account: {0} must be in Company default currency: {1}").format(
|
||||
frappe.bold(x.account),
|
||||
frappe.bold(company_default_currency),
|
||||
)
|
||||
)
|
||||
|
||||
if advance_account_currency and advance_account_currency != company_default_currency:
|
||||
frappe.throw(
|
||||
_("Advance Account: {0} must be in Company default currency: {1}").format(
|
||||
frappe.bold(x.advance_account), frappe.bold(company_default_currency)
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
receivable_account_currency
|
||||
and advance_account_currency
|
||||
and receivable_account_currency != advance_account_currency
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Both Receivable Account: {0} and Advance Account: {1} must be of same currency for company: {2}"
|
||||
).format(
|
||||
frappe.bold(x.account),
|
||||
frappe.bold(x.advance_account),
|
||||
frappe.bold(x.company),
|
||||
)
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
self.validate_name_with_customer()
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils.nestedset import NestedSet, get_root_of
|
||||
|
||||
|
||||
@@ -32,6 +33,51 @@ class SupplierGroup(NestedSet):
|
||||
def validate(self):
|
||||
if not self.parent_supplier_group:
|
||||
self.parent_supplier_group = get_root_of("Supplier Group")
|
||||
self.validate_currency_for_payable_and_advance_account()
|
||||
|
||||
def validate_currency_for_payable_and_advance_account(self):
|
||||
for x in self.accounts:
|
||||
company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency")
|
||||
payable_account_currency = None
|
||||
advance_account_currency = None
|
||||
|
||||
if x.account:
|
||||
payable_account_currency = frappe.get_cached_value("Account", x.account, "account_currency")
|
||||
|
||||
if x.advance_account:
|
||||
advance_account_currency = frappe.get_cached_value(
|
||||
"Account", x.advance_account, "account_currency"
|
||||
)
|
||||
|
||||
if payable_account_currency and payable_account_currency != company_default_currency:
|
||||
frappe.throw(
|
||||
_("Payable Account: {0} must be in Company default currency: {1}").format(
|
||||
frappe.bold(x.account),
|
||||
frappe.bold(company_default_currency),
|
||||
)
|
||||
)
|
||||
|
||||
if advance_account_currency and advance_account_currency != company_default_currency:
|
||||
frappe.throw(
|
||||
_("Advance Account: {0} must be in Company default currency: {1}").format(
|
||||
frappe.bold(x.advance_account), frappe.bold(company_default_currency)
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
payable_account_currency
|
||||
and advance_account_currency
|
||||
and payable_account_currency != advance_account_currency
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Both Payable Account: {0} and Advance Account: {1} must be of same currency for company: {2}"
|
||||
).format(
|
||||
frappe.bold(x.account),
|
||||
frappe.bold(x.advance_account),
|
||||
frappe.bold(x.company),
|
||||
)
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
NestedSet.on_update(self)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import datetime
|
||||
from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||
from frappe.utils import flt
|
||||
@@ -8,12 +11,7 @@ from pypika import Order
|
||||
class DeprecatedSerialNoValuation:
|
||||
@deprecated
|
||||
def calculate_stock_value_from_deprecarated_ledgers(self):
|
||||
if not frappe.db.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["name"],
|
||||
filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": self.sle.item_code},
|
||||
limit=1,
|
||||
):
|
||||
if not has_sle_for_serial_nos(self.sle.item_code):
|
||||
return
|
||||
|
||||
serial_nos = self.get_filterd_serial_nos()
|
||||
@@ -82,6 +80,20 @@ class DeprecatedSerialNoValuation:
|
||||
return incoming_values
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def has_sle_for_serial_nos(item_code):
|
||||
serial_nos = frappe.db.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["name"],
|
||||
filters={"serial_no": ("is", "set"), "is_cancelled": 0, "item_code": item_code},
|
||||
limit=1,
|
||||
)
|
||||
if serial_nos:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class DeprecatedBatchNoValuation:
|
||||
@deprecated
|
||||
def calculate_avg_rate_from_deprecarated_ledgers(self):
|
||||
@@ -92,19 +104,25 @@ class DeprecatedBatchNoValuation:
|
||||
|
||||
@deprecated
|
||||
def get_sle_for_batches(self):
|
||||
from erpnext.stock.utils import get_combine_datetime
|
||||
|
||||
if not self.batchwise_valuation_batches:
|
||||
return []
|
||||
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
|
||||
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
|
||||
self.sle.posting_date, self.sle.posting_time
|
||||
)
|
||||
if self.sle.creation:
|
||||
timestamp_condition |= (
|
||||
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||
== CombineDatetime(self.sle.posting_date, self.sle.posting_time)
|
||||
) & (sle.creation < self.sle.creation)
|
||||
timestamp_condition = None
|
||||
if self.sle.posting_date and self.sle.posting_time:
|
||||
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
||||
if not self.sle.creation:
|
||||
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
||||
|
||||
timestamp_condition = sle.posting_datetime < posting_datetime
|
||||
|
||||
if self.sle.creation:
|
||||
timestamp_condition |= (sle.posting_datetime == posting_datetime) & (
|
||||
sle.creation < self.sle.creation
|
||||
)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
@@ -120,10 +138,12 @@ class DeprecatedBatchNoValuation:
|
||||
& (sle.batch_no.isnotnull())
|
||||
& (sle.is_cancelled == 0)
|
||||
)
|
||||
.where(timestamp_condition)
|
||||
.groupby(sle.batch_no)
|
||||
)
|
||||
|
||||
if timestamp_condition:
|
||||
query = query.where(timestamp_condition)
|
||||
|
||||
if self.sle.name:
|
||||
query = query.where(sle.name != self.sle.name)
|
||||
|
||||
@@ -134,8 +154,8 @@ class DeprecatedBatchNoValuation:
|
||||
if not self.non_batchwise_valuation_batches:
|
||||
return
|
||||
|
||||
self.non_batchwise_balance_value = 0.0
|
||||
self.non_batchwise_balance_qty = 0.0
|
||||
self.non_batchwise_balance_value = defaultdict(float)
|
||||
self.non_batchwise_balance_qty = defaultdict(float)
|
||||
|
||||
self.set_balance_value_for_non_batchwise_valuation_batches()
|
||||
|
||||
@@ -146,12 +166,12 @@ class DeprecatedBatchNoValuation:
|
||||
if not self.non_batchwise_balance_qty:
|
||||
continue
|
||||
|
||||
if self.non_batchwise_balance_value == 0:
|
||||
if self.non_batchwise_balance_qty.get(batch_no) == 0:
|
||||
self.batch_avg_rate[batch_no] = 0.0
|
||||
self.stock_value_differece[batch_no] = 0.0
|
||||
else:
|
||||
self.batch_avg_rate[batch_no] = (
|
||||
self.non_batchwise_balance_value / self.non_batchwise_balance_qty
|
||||
self.non_batchwise_balance_value[batch_no] / self.non_batchwise_balance_qty[batch_no]
|
||||
)
|
||||
self.stock_value_differece[batch_no] = self.non_batchwise_balance_value
|
||||
|
||||
@@ -174,17 +194,21 @@ class DeprecatedBatchNoValuation:
|
||||
|
||||
@deprecated
|
||||
def set_balance_value_from_sl_entries(self) -> None:
|
||||
from erpnext.stock.utils import get_combine_datetime
|
||||
|
||||
sle = frappe.qb.DocType("Stock Ledger Entry")
|
||||
batch = frappe.qb.DocType("Batch")
|
||||
|
||||
timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
|
||||
self.sle.posting_date, self.sle.posting_time
|
||||
)
|
||||
posting_datetime = get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
|
||||
if not self.sle.creation:
|
||||
posting_datetime = posting_datetime + datetime.timedelta(milliseconds=1)
|
||||
|
||||
timestamp_condition = sle.posting_datetime < posting_datetime
|
||||
|
||||
if self.sle.creation:
|
||||
timestamp_condition |= (
|
||||
CombineDatetime(sle.posting_date, sle.posting_time)
|
||||
== CombineDatetime(self.sle.posting_date, self.sle.posting_time)
|
||||
) & (sle.creation < self.sle.creation)
|
||||
timestamp_condition |= (sle.posting_datetime == posting_datetime) & (
|
||||
sle.creation < self.sle.creation
|
||||
)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(sle)
|
||||
@@ -201,6 +225,7 @@ class DeprecatedBatchNoValuation:
|
||||
& (sle.batch_no.isnotnull())
|
||||
& (batch.use_batchwise_valuation == 0)
|
||||
& (sle.is_cancelled == 0)
|
||||
& (sle.batch_no.isin(self.non_batchwise_valuation_batches))
|
||||
)
|
||||
.where(timestamp_condition)
|
||||
.groupby(sle.batch_no)
|
||||
@@ -210,8 +235,8 @@ class DeprecatedBatchNoValuation:
|
||||
query = query.where(sle.name != self.sle.name)
|
||||
|
||||
for d in query.run(as_dict=True):
|
||||
self.non_batchwise_balance_value += flt(d.batch_value)
|
||||
self.non_batchwise_balance_qty += flt(d.batch_qty)
|
||||
self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value)
|
||||
self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty)
|
||||
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
||||
|
||||
@deprecated
|
||||
@@ -249,6 +274,7 @@ class DeprecatedBatchNoValuation:
|
||||
& (bundle.is_cancelled == 0)
|
||||
& (bundle.docstatus == 1)
|
||||
& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
|
||||
& (bundle_child.batch_no.isin(self.non_batchwise_valuation_batches))
|
||||
)
|
||||
.where(timestamp_condition)
|
||||
.groupby(bundle_child.batch_no)
|
||||
@@ -260,6 +286,6 @@ class DeprecatedBatchNoValuation:
|
||||
query = query.where(bundle.voucher_type != "Pick List")
|
||||
|
||||
for d in query.run(as_dict=True):
|
||||
self.non_batchwise_balance_value += flt(d.batch_value)
|
||||
self.non_batchwise_balance_qty += flt(d.batch_qty)
|
||||
self.non_batchwise_balance_value[d.batch_no] += flt(d.batch_value)
|
||||
self.non_batchwise_balance_qty[d.batch_no] += flt(d.batch_qty)
|
||||
self.available_qty[d.batch_no] += flt(d.batch_qty)
|
||||
|
||||
@@ -161,11 +161,25 @@ class Batch(Document):
|
||||
self.use_batchwise_valuation = 1
|
||||
|
||||
def before_save(self):
|
||||
self.set_expiry_date()
|
||||
|
||||
def set_expiry_date(self):
|
||||
has_expiry_date, shelf_life_in_days = frappe.db.get_value(
|
||||
"Item", self.item, ["has_expiry_date", "shelf_life_in_days"]
|
||||
)
|
||||
|
||||
if not self.expiry_date and has_expiry_date and shelf_life_in_days:
|
||||
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
|
||||
if (
|
||||
not self.manufacturing_date
|
||||
and self.reference_doctype in ["Stock Entry", "Purchase Receipt", "Purchase Invoice"]
|
||||
and self.reference_name
|
||||
):
|
||||
self.manufacturing_date = frappe.db.get_value(
|
||||
self.reference_doctype, self.reference_name, "posting_date"
|
||||
)
|
||||
|
||||
if self.manufacturing_date:
|
||||
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
|
||||
|
||||
if has_expiry_date and not self.expiry_date:
|
||||
frappe.throw(
|
||||
|
||||
@@ -79,7 +79,12 @@ frappe.ui.form.on("Delivery Note", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.docstatus === 1 && frm.doc.is_return === 1 && frm.doc.per_billed !== 100) {
|
||||
if (
|
||||
frm.doc.docstatus === 1 &&
|
||||
frm.doc.is_return === 1 &&
|
||||
frm.doc.per_billed !== 100 &&
|
||||
frappe.model.can_create("Sales Invoice")
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Credit Note"),
|
||||
function () {
|
||||
@@ -93,7 +98,11 @@ frappe.ui.form.on("Delivery Note", {
|
||||
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && !frm.doc.inter_company_reference) {
|
||||
if (
|
||||
frm.doc.docstatus == 1 &&
|
||||
!frm.doc.inter_company_reference &&
|
||||
frappe.model.can_create("Purchase Receipt")
|
||||
) {
|
||||
let internal = frm.doc.is_internal_customer;
|
||||
if (internal) {
|
||||
let button_label =
|
||||
@@ -140,43 +149,47 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
refresh(doc, dt, dn) {
|
||||
var me = this;
|
||||
super.refresh();
|
||||
if (!doc.is_return && (doc.status != "Closed" || this.frm.is_new())) {
|
||||
if (this.frm.doc.docstatus === 0) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Order"),
|
||||
function () {
|
||||
if (!me.frm.doc.customer) {
|
||||
frappe.throw({
|
||||
title: __("Mandatory"),
|
||||
message: __("Please Select a Customer"),
|
||||
});
|
||||
}
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
||||
args: {
|
||||
for_reserved_stock: 1,
|
||||
},
|
||||
source_doctype: "Sales Order",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
customer: me.frm.doc.customer,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_delivered: ["<", 99.99],
|
||||
company: me.frm.doc.company,
|
||||
project: me.frm.doc.project || undefined,
|
||||
},
|
||||
if (
|
||||
!doc.is_return &&
|
||||
(doc.status != "Closed" || this.frm.is_new()) &&
|
||||
this.frm.has_perm("write") &&
|
||||
frappe.model.can_read("Sales Order") &&
|
||||
this.frm.doc.docstatus === 0
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Order"),
|
||||
function () {
|
||||
if (!me.frm.doc.customer) {
|
||||
frappe.throw({
|
||||
title: __("Mandatory"),
|
||||
message: __("Please Select a Customer"),
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
);
|
||||
}
|
||||
}
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
|
||||
args: {
|
||||
for_reserved_stock: 1,
|
||||
},
|
||||
source_doctype: "Sales Order",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
customer: me.frm.doc.customer,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Closed", "On Hold"]],
|
||||
per_delivered: ["<", 99.99],
|
||||
company: me.frm.doc.company,
|
||||
project: me.frm.doc.project || undefined,
|
||||
},
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
);
|
||||
}
|
||||
|
||||
if (!doc.is_return && doc.status != "Closed") {
|
||||
if (doc.docstatus == 1) {
|
||||
if (doc.docstatus == 1 && frappe.model.can_create("Shipment")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Shipment"),
|
||||
function () {
|
||||
@@ -186,7 +199,11 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
);
|
||||
}
|
||||
|
||||
if (flt(doc.per_installed, 2) < 100 && doc.docstatus == 1)
|
||||
if (
|
||||
flt(doc.per_installed, 2) < 100 &&
|
||||
doc.docstatus == 1 &&
|
||||
frappe.model.can_create("Installation Note")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Installation Note"),
|
||||
function () {
|
||||
@@ -194,8 +211,9 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1) {
|
||||
if (doc.docstatus == 1 && this.frm.has_perm("create")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Sales Return"),
|
||||
function () {
|
||||
@@ -205,7 +223,7 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
);
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1) {
|
||||
if (doc.docstatus == 1 && frappe.model.can_create("Delivery Trip")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Delivery Trip"),
|
||||
function () {
|
||||
@@ -215,19 +233,23 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
);
|
||||
}
|
||||
|
||||
if (doc.docstatus == 0 && !doc.__islocal) {
|
||||
if (doc.__onload && doc.__onload.has_unpacked_items) {
|
||||
this.frm.add_custom_button(
|
||||
__("Packing Slip"),
|
||||
function () {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip",
|
||||
frm: me.frm,
|
||||
});
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
if (
|
||||
doc.docstatus == 0 &&
|
||||
!doc.__islocal &&
|
||||
doc.__onload &&
|
||||
doc.__onload.has_unpacked_items &&
|
||||
frappe.model.can_create("Packing Slip")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Packing Slip"),
|
||||
function () {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip",
|
||||
frm: me.frm,
|
||||
});
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
if (!doc.__islocal && doc.docstatus == 1) {
|
||||
@@ -254,7 +276,13 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1 && !doc.is_return && doc.status != "Closed" && flt(doc.per_billed) < 100) {
|
||||
if (
|
||||
doc.docstatus == 1 &&
|
||||
!doc.is_return &&
|
||||
doc.status != "Closed" &&
|
||||
flt(doc.per_billed) < 100 &&
|
||||
frappe.model.can_create("Sales Invoice")
|
||||
) {
|
||||
// show Make Invoice button only if Delivery Note is not created from Sales Invoice
|
||||
var from_sales_invoice = false;
|
||||
from_sales_invoice = me.frm.doc.items.some(function (item) {
|
||||
|
||||
@@ -693,7 +693,8 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
|
||||
# if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock.
|
||||
remaining_stock_qty = item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
|
||||
|
||||
while flt(remaining_stock_qty) > 0 and available_locations:
|
||||
precision = frappe.get_precision("Pick List Item", "qty")
|
||||
while flt(remaining_stock_qty, precision) > 0 and available_locations:
|
||||
item_location = available_locations.pop(0)
|
||||
item_location = frappe._dict(item_location)
|
||||
|
||||
@@ -838,6 +839,7 @@ def validate_picked_materials(item_code, required_qty, locations, picked_item_de
|
||||
|
||||
def filter_locations_by_picked_materials(locations, picked_item_details) -> list[dict]:
|
||||
filterd_locations = []
|
||||
precision = frappe.get_precision("Pick List Item", "qty")
|
||||
for row in locations:
|
||||
key = row.warehouse
|
||||
if row.batch_no:
|
||||
@@ -856,7 +858,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list
|
||||
if row.serial_nos:
|
||||
row.serial_nos = list(set(row.serial_nos) - set(picked_item_details[key].get("serial_no")))
|
||||
|
||||
if row.qty > 0:
|
||||
if flt(row.qty, precision) > 0:
|
||||
filterd_locations.append(row)
|
||||
|
||||
return filterd_locations
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, cint, cstr, flt, nowtime, today
|
||||
from frappe.utils import add_days, cint, cstr, flt, getdate, nowtime, today
|
||||
from pypika import functions as fn
|
||||
|
||||
import erpnext
|
||||
@@ -2961,6 +2961,35 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
self.assertSequenceEqual(expected_gle, gl_entries)
|
||||
frappe.local.enable_perpetual_inventory["_Test Company"] = old_perpetual_inventory
|
||||
|
||||
def test_manufacturing_and_expiry_date_for_batch(self):
|
||||
item = make_item(
|
||||
"_Test Manufacturing and Expiry Date For Batch",
|
||||
{
|
||||
"is_purchase_item": 1,
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "B-MEBATCH.#####",
|
||||
"has_expiry_date": 1,
|
||||
"shelf_life_in_days": 5,
|
||||
},
|
||||
)
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
qty=10,
|
||||
rate=100,
|
||||
item_code=item.name,
|
||||
posting_date=today(),
|
||||
)
|
||||
|
||||
pr.reload()
|
||||
self.assertTrue(pr.items[0].serial_and_batch_bundle)
|
||||
|
||||
batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
|
||||
batch = frappe.get_doc("Batch", batch_no)
|
||||
self.assertEqual(batch.manufacturing_date, getdate(today()))
|
||||
self.assertEqual(batch.expiry_date, getdate(add_days(today(), 5)))
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -1220,6 +1220,7 @@ def get_serial_batch_ledgers(item_code=None, docstatus=None, voucher_no=None, na
|
||||
"`tabSerial and Batch Entry`.`warehouse`",
|
||||
"`tabSerial and Batch Entry`.`batch_no`",
|
||||
"`tabSerial and Batch Entry`.`serial_no`",
|
||||
"`tabSerial and Batch Entry`.`name` as `child_row`",
|
||||
]
|
||||
|
||||
if not child_row:
|
||||
@@ -2104,10 +2105,13 @@ def get_ledgers_from_serial_batch_bundle(**kwargs) -> list[frappe._dict]:
|
||||
)
|
||||
|
||||
for key, val in kwargs.items():
|
||||
if not val:
|
||||
if val is None:
|
||||
continue
|
||||
|
||||
if key in ["get_subcontracted_item"]:
|
||||
if not val and isinstance(val, list):
|
||||
return []
|
||||
|
||||
if key == "get_subcontracted_item":
|
||||
continue
|
||||
|
||||
if key in ["name", "item_code", "warehouse", "voucher_no", "company", "voucher_detail_no"]:
|
||||
|
||||
@@ -404,7 +404,9 @@ class StockReconciliation(StockController):
|
||||
fields=["total_qty as qty", "avg_rate as rate"],
|
||||
)[0]
|
||||
|
||||
bundle_data.qty = abs(bundle_data.qty)
|
||||
self.calculate_difference_amount(item, bundle_data)
|
||||
|
||||
return True
|
||||
|
||||
inventory_dimensions_dict = {}
|
||||
@@ -464,11 +466,16 @@ class StockReconciliation(StockController):
|
||||
frappe.msgprint(_("Removed items with no change in quantity or value."))
|
||||
|
||||
def calculate_difference_amount(self, item, item_dict):
|
||||
self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
|
||||
item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")
|
||||
) - flt(item_dict.get("qty"), item.precision("qty")) * flt(
|
||||
item_dict.get("rate"), item.precision("valuation_rate")
|
||||
)
|
||||
qty_precision = item.precision("qty")
|
||||
val_precision = item.precision("valuation_rate")
|
||||
|
||||
new_qty = flt(item.qty, qty_precision)
|
||||
new_valuation_rate = flt(item.valuation_rate or item_dict.get("rate"), val_precision)
|
||||
|
||||
current_qty = flt(item_dict.get("qty"), qty_precision)
|
||||
current_valuation_rate = flt(item_dict.get("rate"), val_precision)
|
||||
|
||||
self.difference_amount += (new_qty * new_valuation_rate) - (current_qty * current_valuation_rate)
|
||||
|
||||
def validate_data(self):
|
||||
def _get_msg(row_num, msg):
|
||||
|
||||
@@ -647,7 +647,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
"has_serial_no": 1,
|
||||
"has_batch_no": 1,
|
||||
"serial_no_series": "SRS9.####",
|
||||
"batch_number_series": "BNS9.####",
|
||||
"batch_number_series": "BNS90.####",
|
||||
"create_new_batch": 1,
|
||||
},
|
||||
)
|
||||
@@ -680,7 +680,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"batch_number_series": "BNS9.####",
|
||||
"batch_number_series": "BNS91.####",
|
||||
"create_new_batch": 1,
|
||||
},
|
||||
).name
|
||||
@@ -1109,6 +1109,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
)
|
||||
|
||||
sr.reload()
|
||||
self.assertEqual(sr.difference_amount, 98900.0)
|
||||
|
||||
self.assertTrue(sr.items[0].current_valuation_rate)
|
||||
current_sabb = sr.items[0].current_serial_and_batch_bundle
|
||||
|
||||
@@ -41,7 +41,7 @@ frappe.ui.form.on("Stock Settings", {
|
||||
msg += " ";
|
||||
msg += __("This is considered dangerous from accounting point of view.");
|
||||
msg += "<br>";
|
||||
msg += "Do you still want to enable negative inventory?";
|
||||
msg += __("Do you still want to enable negative inventory?");
|
||||
|
||||
frappe.confirm(
|
||||
msg,
|
||||
|
||||
@@ -137,6 +137,10 @@ class StockBalanceReport:
|
||||
report_data.update(
|
||||
{"reserved_stock": sre_details.get((report_data.item_code, report_data.warehouse), 0.0)}
|
||||
)
|
||||
|
||||
if report_data and report_data.bal_qty == 0 and report_data.bal_val == 0:
|
||||
continue
|
||||
|
||||
self.data.append(report_data)
|
||||
|
||||
def get_item_warehouse_map(self):
|
||||
|
||||
@@ -1794,6 +1794,7 @@ def get_next_stock_reco(kwargs):
|
||||
sle.actual_qty,
|
||||
sle.has_batch_no,
|
||||
)
|
||||
.force_index("item_warehouse")
|
||||
.where(
|
||||
(sle.item_code == kwargs.get("item_code"))
|
||||
& (sle.warehouse == kwargs.get("warehouse"))
|
||||
|
||||
@@ -276,11 +276,7 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
|
||||
sn_obj = SerialNoValuation(sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code"))
|
||||
|
||||
return sn_obj.get_incoming_rate()
|
||||
elif (
|
||||
args.get("batch_no")
|
||||
and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
|
||||
and not args.get("serial_and_batch_bundle")
|
||||
):
|
||||
elif args.get("batch_no") and not args.get("serial_and_batch_bundle"):
|
||||
args.actual_qty = args.qty
|
||||
args.batch_nos = frappe._dict({args.batch_no: args})
|
||||
|
||||
|
||||
@@ -623,8 +623,8 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
"has_batch_no": 1,
|
||||
"has_serial_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BNGS-.####",
|
||||
"serial_no_series": "BNSS-.####",
|
||||
"batch_number_series": "BNGS0-.####",
|
||||
"serial_no_series": "BNSS90-.####",
|
||||
}
|
||||
).name
|
||||
|
||||
@@ -715,8 +715,8 @@ class TestSubcontractingReceipt(FrappeTestCase):
|
||||
"has_batch_no": 1,
|
||||
"has_serial_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "BNGS-.####",
|
||||
"serial_no_series": "BNSS-.####",
|
||||
"batch_number_series": "BNGS91-.####",
|
||||
"serial_no_series": "BNSS91-.####",
|
||||
}
|
||||
).name
|
||||
|
||||
|
||||
@@ -168,6 +168,69 @@ class TransactionBase(StatusUpdater):
|
||||
if len(child_table_values) > 1:
|
||||
self.set(default_field, None)
|
||||
|
||||
def validate_currency_for_receivable_payable_and_advance_account(self):
|
||||
if self.doctype in ["Customer", "Supplier"]:
|
||||
account_type = "Receivable" if self.doctype == "Customer" else "Payable"
|
||||
for x in self.accounts:
|
||||
company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency")
|
||||
receivable_payable_account_currency = None
|
||||
advance_account_currency = None
|
||||
|
||||
if x.account:
|
||||
receivable_payable_account_currency = frappe.get_cached_value(
|
||||
"Account", x.account, "account_currency"
|
||||
)
|
||||
|
||||
if x.advance_account:
|
||||
advance_account_currency = frappe.get_cached_value(
|
||||
"Account", x.advance_account, "account_currency"
|
||||
)
|
||||
if receivable_payable_account_currency and (
|
||||
receivable_payable_account_currency != self.default_currency
|
||||
and receivable_payable_account_currency != company_default_currency
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"{0} Account: {1} ({2}) must be in either customer billing currency: {3} or Company default currency: {4}"
|
||||
).format(
|
||||
account_type,
|
||||
frappe.bold(x.account),
|
||||
frappe.bold(receivable_payable_account_currency),
|
||||
frappe.bold(self.default_currency),
|
||||
frappe.bold(company_default_currency),
|
||||
)
|
||||
)
|
||||
|
||||
if advance_account_currency and (
|
||||
advance_account_currency != self.default_currency
|
||||
and advance_account_currency != company_default_currency
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Advance Account: {0} must be in either customer billing currency: {1} or Company default currency: {2}"
|
||||
).format(
|
||||
frappe.bold(x.advance_account),
|
||||
frappe.bold(self.default_currency),
|
||||
frappe.bold(company_default_currency),
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
receivable_payable_account_currency
|
||||
and advance_account_currency
|
||||
and receivable_payable_account_currency != advance_account_currency
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Both {0} Account: {1} and Advance Account: {2} must be of same currency for company: {3}"
|
||||
).format(
|
||||
account_type,
|
||||
frappe.bold(x.account),
|
||||
frappe.bold(x.advance_account),
|
||||
frappe.bold(x.company),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def delete_events(ref_type, ref_name):
|
||||
events = (
|
||||
|
||||
Reference in New Issue
Block a user