Compare commits

..

83 Commits

Author SHA1 Message Date
Frappe PR Bot
d5c26efb8c chore(release): Bumped to Version 15.10.2
## [15.10.2](https://github.com/frappe/erpnext/compare/v15.10.1...v15.10.2) (2024-01-17)

### Bug Fixes

* added indexing to improve performance ([50fe419](50fe4191d3))
* added item group in stock reco ([eec6c25](eec6c25f9e))
* Asset module tests ([9e33216](9e33216c24))
* asset WDV depreciation calc according to IT act ([2448ba6](2448ba6bc4))
* batches not coming correctly in the batch selector ([1394a1c](1394a1c5e9))
* broken dimension filters in Sales/Purchase register ([04037b7](04037b7e38))
* Cancel asset capitalisation record on cancellation of asset and vice-versa ([011c5a6](011c5a69f0))
* circular dependency error while deleting QC ([7ca8e49](7ca8e49b38))
* composite asset capitalization using asset components ([8bc8bc1](8bc8bc1822))
* consider all years in holiday list ([21eed78](21eed78fa1))
* consistency in display reserved_stock checkbox on Sales Order Item according global settings and item.is_stock_item (backport [#38322](https://github.com/frappe/erpnext/issues/38322)) ([#39417](https://github.com/frappe/erpnext/issues/39417)) ([53f61f1](53f61f1ad1))
* date in master document for dictionary condition ([9aae439](9aae439b1f))
* fetch name for fy ([53208df](53208dfb3e))
* Ignore default payment terms template for opening invoices ([5dc2e80](5dc2e80987))
* incorrect percentage received in purchase invoice ([758b9b8](758b9b8a81))
* incorrect sql error if account name has '%' ([b43f703](b43f70325c))
* modified date ([cd870ee](cd870ee30c))
* modified date ([f048017](f0480173fb))
* modified date was not set ([2dc8686](2dc8686a81))
* modified date was not updated ([61595c7](61595c7ede))
* performance issue related to stock entry (backport [#39301](https://github.com/frappe/erpnext/issues/39301)) ([#39303](https://github.com/frappe/erpnext/issues/39303)) ([d882305](d882305c72))
* permission issue for the BIN ([8816b27](8816b2740a))
* possible typerror in utils.js ([80956b7](80956b7956))
* project filters on Delivery Note and Sales Order ([a51f956](a51f956f3e))
* project query controller logic ([07e2901](07e2901e4b))
* reset default after test ([e6f3a14](e6f3a14289))
* SBB Total Qty validation for SE ([ca18853](ca18853785))
* test for asset depreciation ([ff1647a](ff1647a1d2))
* **test:** test case for project query ([1b8f572](1b8f572e80))
* unreconcile Bank Transaction on cancel of payment voucher ([16860c2](16860c228d))
* Update subscription period ([e219042](e219042304))
* use child table values instead of global min max ([04c96dd](04c96ddc6c))
* WDV as per IT Act: calculate yearly amount first and then split it based on months ([e3cd35b](e3cd35b959))
2024-01-17 17:06:29 +00:00
Deepesh Garg
e00533ff4e Merge pull request #39405 from frappe/version-15-hotfix
chore: release v15
2024-01-17 22:34:57 +05:30
Nabin Hait
6509548474 Merge pull request #39438 from frappe/mergify/bp/version-15-hotfix/pr-39429
fix: composite asset capitalization using asset components (backport #39429)
2024-01-17 22:13:50 +05:30
mergify[bot]
c8b3478fe1 Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-39429 2024-01-17 16:26:04 +00:00
Nabin Hait
f453aaeab0 Merge pull request #39439 from nabinhait/test-case-fix-asset-depr
fix: test for asset depreciation
2024-01-17 21:54:55 +05:30
Nabin Hait
ff1647a1d2 fix: test for asset depreciation 2024-01-17 21:53:00 +05:30
Nabin Hait
8bc8bc1822 fix: composite asset capitalization using asset components
(cherry picked from commit 5df40661d2)
2024-01-17 15:36:04 +00:00
Nabin Hait
9f86b92eb7 Merge pull request #39431 from frappe/mergify/bp/version-15-hotfix/pr-39427
fix: Asset module tests (backport #39427)
2024-01-17 17:38:46 +05:30
Nabin Hait
04c8e9b355 Merge pull request #39422 from frappe/mergify/bp/version-15-hotfix/pr-39386
fix: Cancel asset capitalisation record on cancellation of asset and vice-versa (backport #39386)
2024-01-17 17:33:21 +05:30
Nabin Hait
fbfd0a6db0 Merge pull request #39420 from frappe/mergify/bp/version-15-hotfix/pr-39385
fix: WDV as per IT Act: calculate yearly amount first and then split it based on months (backport #39385)
2024-01-17 17:29:12 +05:30
Nabin Hait
9e33216c24 fix: Asset module tests
(cherry picked from commit 97f69986ff)
2024-01-17 11:56:41 +00:00
Deepesh Garg
62f7d984be Merge pull request #39425 from frappe/mergify/bp/version-15-hotfix/pr-39423
fix: Update subscription period (#39423)
2024-01-17 15:03:51 +05:30
Deepesh Garg
e219042304 fix: Update subscription period
(cherry picked from commit 7eefedfb11)
2024-01-17 07:28:57 +00:00
Nabin Hait
011c5a69f0 fix: Cancel asset capitalisation record on cancellation of asset and vice-versa
(cherry picked from commit efe9f6656f)
2024-01-17 06:45:39 +00:00
Nabin Hait
e3cd35b959 fix: WDV as per IT Act: calculate yearly amount first and then split it based on months
(cherry picked from commit 22bd6a54b2)
2024-01-17 06:44:26 +00:00
mergify[bot]
53f61f1ad1 fix: consistency in display reserved_stock checkbox on Sales Order Item according global settings and item.is_stock_item (backport #38322) (#39417)
fix: consistency in display reserved_stock checkbox on Sales Order Item according global settings and item.is_stock_item (#38322)

* fix: consistency in display reserved_stock checkbox on Sales Order Item according global settings and item.is_stock_item

* fix: evaluate depends_on for fdata visibility in grid

* fix: evaluate depends_on for fdata visibility in grid

* chore: change after review

* chore: change for review

(cherry picked from commit af80d253db)

Co-authored-by: HENRY Florian <florian.henry@open-concept.pro>
2024-01-17 10:45:57 +05:30
rohitwaghchaure
c939aa5cf8 Merge pull request #39409 from frappe/mergify/bp/version-15-hotfix/pr-39406
fix: permission issue for the Bin (backport #39406)
2024-01-16 16:55:50 +05:30
ruthra kumar
93a861fac5 Merge pull request #39412 from frappe/mergify/bp/version-15-hotfix/pr-39402
fix: project query controller logic (backport #39402)
2024-01-16 16:49:56 +05:30
ruthra kumar
5a65a10dba refactor: better ordering of query result
(cherry picked from commit bfe42fdccb)
2024-01-16 10:55:36 +00:00
ruthra kumar
1b8f572e80 fix(test): test case for project query
(cherry picked from commit 3349dde5e2)
2024-01-16 10:55:36 +00:00
ruthra kumar
07e2901e4b fix: project query controller logic
(cherry picked from commit 4eefb445a7)
2024-01-16 10:55:35 +00:00
Rohit Waghchaure
8816b2740a fix: permission issue for the BIN
(cherry picked from commit 6e4d4a55cd)
2024-01-16 10:22:22 +00:00
ruthra kumar
bcf8053fd9 Merge pull request #39383 from frappe/mergify/bp/version-15-hotfix/pr-39332
refactor: prevent foreign currency subscription for a party (backport #39332)
2024-01-16 11:19:37 +05:30
ruthra kumar
cd1820f680 refactor(test): set default currency for party 2024-01-16 10:47:51 +05:30
ruthra kumar
235e3893a0 Merge pull request #39393 from frappe/mergify/bp/version-15-hotfix/pr-39391
fix: possible typerror in utils.js (backport #39391)
2024-01-15 20:49:48 +05:30
ruthra kumar
80956b7956 fix: possible typerror in utils.js
and remove unwanted debugging statements

(cherry picked from commit 60b26ad8b2)
2024-01-15 15:16:51 +00:00
Nabin Hait
9897c26ae6 Merge pull request #39285 from frappe/mergify/bp/version-15-hotfix/pr-39052
fix: asset WDV depreciation calc according to IT act [dev] (backport #39052)
2024-01-15 16:58:45 +05:30
ruthra kumar
32afe7de31 refactor(test): supply default currency for subscription plans
(cherry picked from commit 1387b0ba7f)
2024-01-15 09:36:50 +00:00
ruthra kumar
c288db0356 refactor: making currency mandatory for subcscription plans
(cherry picked from commit 19975dcb7b)
2024-01-15 09:36:50 +00:00
ruthra kumar
61f073f8b1 refactor: prevent foreign currency subscription for a party
(cherry picked from commit 6b5fa2c673)
2024-01-15 09:36:49 +00:00
mergify[bot]
e282ba78c1 ci: bump node in release workflow (backport #39377) (#39378)
* ci: bump node in release workflow

(cherry picked from commit aef87cced7)

# Conflicts:
#	.github/workflows/release.yml

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-01-15 12:08:44 +05:30
ruthra kumar
1b8b92b8d7 Merge pull request #39376 from frappe/mergify/bp/version-15-hotfix/pr-39336
refactor: disallow bank transactions on different currencies (backport #39336)
2024-01-15 10:41:26 +05:30
ruthra kumar
f2e19ca6fd Merge pull request #39375 from frappe/mergify/bp/version-15-hotfix/pr-39371
fix: incorrect sql error if account name has '%' (backport #39371)
2024-01-15 10:40:54 +05:30
ruthra kumar
32c6111728 refactor(test): supply default currency to Bank Transaction
(cherry picked from commit a27a4db3de)
2024-01-15 04:32:46 +00:00
ruthra kumar
f609b8ae5d refactor: better error message
(cherry picked from commit b4354cbc8d)
2024-01-15 04:32:46 +00:00
ruthra kumar
dbbba046ab refactor: disallow bank transactions on different currencies
(cherry picked from commit cdd0acc672)
2024-01-15 04:32:45 +00:00
ruthra kumar
b43f70325c fix: incorrect sql error if account name has '%'
(cherry picked from commit 641c3de0ca)
2024-01-15 04:10:40 +00:00
rohitwaghchaure
3f5b1bcd05 Merge pull request #39373 from frappe/mergify/bp/version-15-hotfix/pr-39372
fix: batches not coming correctly in the batch selector (backport #39372)
2024-01-15 06:34:11 +05:30
Rohit Waghchaure
1394a1c5e9 fix: batches not coming correctly in the batch selector
(cherry picked from commit 114f2b4326)
2024-01-14 18:19:30 +00:00
Deepesh Garg
7ca9ffbaa7 Merge pull request #39329 from frappe/mergify/bp/version-15-hotfix/pr-38974
fix: unreconciled Bank Transaction on cancel of payment voucher (#38974)
2024-01-14 17:55:32 +05:30
rohitwaghchaure
137ce76a35 Merge pull request #39369 from frappe/mergify/bp/version-15-hotfix/pr-39224
Update purchase_taxes_and_charges.json label Rate to Tax Rate (backport #39224)
2024-01-14 11:59:41 +05:30
rohitwaghchaure
cd870ee30c fix: modified date
(cherry picked from commit 6827edb2c5)
2024-01-14 05:57:45 +00:00
rohitwaghchaure
2dc8686a81 fix: modified date was not set
(cherry picked from commit 566876ae7a)
2024-01-14 05:57:44 +00:00
mahsem
6bdf944ecf Update purchase_taxes_and_charges.json label Rate to Tax Rate
Change Rate label to existing Tax Rate label so it can be correctly translated in other languages

(cherry picked from commit bd464197c4)
2024-01-14 05:57:44 +00:00
rohitwaghchaure
9842fb5b64 Merge pull request #39361 from frappe/mergify/bp/version-15-hotfix/pr-39225
Update sales_taxes_and_charges.json (backport #39225)
2024-01-14 11:24:20 +05:30
rohitwaghchaure
c548c57207 Merge pull request #39365 from frappe/mergify/bp/version-15-hotfix/pr-39359
fix: added item group in stock reco (backport #39359)
2024-01-14 11:24:00 +05:30
rohitwaghchaure
cb326ac0db Merge pull request #39362 from rohitwaghchaure/fixed-closing-stock-balance-field-issue
chore: removed extra field
2024-01-14 10:44:47 +05:30
rohitwaghchaure
f0480173fb fix: modified date 2024-01-14 10:43:34 +05:30
Rohit Waghchaure
eec6c25f9e fix: added item group in stock reco
(cherry picked from commit 116ff8241c)
2024-01-14 04:58:42 +00:00
Rohit Waghchaure
bf4e514cde chore: removed extra field 2024-01-14 10:21:38 +05:30
rohitwaghchaure
61595c7ede fix: modified date was not updated
(cherry picked from commit f567af49a6)
2024-01-14 04:46:29 +00:00
mahsem
d510b46f13 Update sales_taxes_and_charges.json
Change Rate label to existing Tax Rate label so it can be correctly translated in other languages

(cherry picked from commit 2b93be1139)
2024-01-14 04:46:29 +00:00
rohitwaghchaure
15e116375b Merge pull request #39325 from frappe/mergify/bp/version-15-hotfix/pr-39305
fix: naming series variable parsing for FY (backport #39305)
2024-01-14 09:39:06 +05:30
rohitwaghchaure
832aa5f5ab Merge pull request #39348 from frappe/mergify/bp/version-15-hotfix/pr-39335
fix: SBB Total Qty validation for SE (backport #39335)
2024-01-12 21:19:26 +05:30
s-aga-r
ca18853785 fix: SBB Total Qty validation for SE
(cherry picked from commit c20241fcb5)
2024-01-12 15:30:36 +00:00
rohitwaghchaure
049864e7a2 Merge pull request #39342 from frappe/mergify/bp/version-15-hotfix/pr-38970
fix: use local attribute range in multiple item variant dialog (backport #38970)
2024-01-12 20:57:14 +05:30
rohitwaghchaure
62db0d77ca Merge pull request #39345 from frappe/mergify/bp/version-15-hotfix/pr-39333
fix: added indexing to improve performance (backport #39333)
2024-01-12 20:55:57 +05:30
Rohit Waghchaure
50fe4191d3 fix: added indexing to improve performance
(cherry picked from commit ac81323fec)
2024-01-12 13:10:47 +00:00
Gursheen Anand
04c96ddc6c fix: use child table values instead of global min max
(cherry picked from commit 43fed29514)
2024-01-12 13:08:31 +00:00
mergify[bot]
d7840559a0 chore: remove share, print and email permissions from Buying Settings (backport #39337) (#39339)
chore: remove share, print and email permissions from Buying Settings

(cherry picked from commit 3c46abca6c)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2024-01-12 16:49:00 +05:30
Raffael Meyer
6f06313023 Merge pull request #39327 from frappe/mergify/bp/version-15-hotfix/pr-39117
fix: consider all years in holiday list (backport #39117)
2024-01-11 15:04:29 +01:00
barredterra
6b2e3503d9 chore: resolve merge confilcts 2024-01-11 14:56:20 +01:00
barredterra
b2fc5e4988 test: cancel voucher linked to Bank Transaction
(cherry picked from commit 517bedeb7e)
2024-01-11 13:37:48 +00:00
barredterra
16860c228d fix: unreconcile Bank Transaction on cancel of payment voucher
(cherry picked from commit 0a95b38166)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.py
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
#	erpnext/accounts/doctype/sales_invoice/sales_invoice.js
2024-01-11 13:37:48 +00:00
barredterra
8a84f8a465 test: improve test for local holidays
(cherry picked from commit 60329ade9e)
2024-01-11 13:29:01 +00:00
barredterra
21eed78fa1 fix: consider all years in holiday list
(cherry picked from commit 300aaa39fe)
2024-01-11 13:29:00 +00:00
Gursheen Anand
e6f3a14289 fix: reset default after test
(cherry picked from commit 813b7a96fb)
2024-01-11 13:27:13 +00:00
Gursheen Anand
2beb3f8718 test: naming series variable parsing
(cherry picked from commit bbdf98a8f0)
2024-01-11 13:27:06 +00:00
Gursheen Anand
9aae439b1f fix: date in master document for dictionary condition
(cherry picked from commit d96a777edd)
2024-01-11 13:27:02 +00:00
Nabin Hait
b748095589 Merge pull request #39220 from frappe/mergify/bp/version-15-hotfix/pr-39202
fix: parse naming series with FY name (backport #39202)
2024-01-11 18:24:34 +05:30
ruthra kumar
7bd2acdef1 Merge pull request #39323 from frappe/mergify/bp/version-15-hotfix/pr-39212
fix: project filters on Delivery Note and Sales Order (backport #39212)
2024-01-11 18:07:12 +05:30
ruthra kumar
a51f956f3e fix: project filters on Delivery Note and Sales Order
(cherry picked from commit 9ba6ff67d5)
2024-01-11 12:34:43 +00:00
ruthra kumar
494c26e48a Merge pull request #39319 from frappe/mergify/bp/version-15-hotfix/pr-39317
fix: broken dimension filters in Sales/Purchase register (backport #39317)
2024-01-11 17:12:35 +05:30
Deepesh Garg
da7b2febe0 Merge pull request #39295 from frappe/mergify/bp/version-15-hotfix/pr-39256
fix: Ignore default payment terms template for opening invoices (#39256)
2024-01-11 17:06:53 +05:30
ruthra kumar
04037b7e38 fix: broken dimension filters in Sales/Purchase register
(cherry picked from commit 7b3f9386d7)
2024-01-11 11:20:43 +00:00
rohitwaghchaure
484db65797 Merge pull request #39315 from frappe/mergify/bp/version-15-hotfix/pr-39215
fix: incorrect percentage received in purchase invoice (backport #39215)
2024-01-11 15:14:44 +05:30
rohitwaghchaure
3f4cf15def Merge pull request #39312 from frappe/mergify/bp/version-15-hotfix/pr-39299
fix: circular dependency error on deletion of QC and Stock Entry (backport #39299)
2024-01-11 15:14:34 +05:30
Rohit Waghchaure
758b9b8a81 fix: incorrect percentage received in purchase invoice
(cherry picked from commit 8d2c78867e)
2024-01-11 09:21:41 +00:00
Rohit Waghchaure
7ca8e49b38 fix: circular dependency error while deleting QC
(cherry picked from commit 7cc324e31e)
2024-01-11 09:20:42 +00:00
mergify[bot]
d882305c72 fix: performance issue related to stock entry (backport #39301) (#39303)
fix: performance issue related to stock entry (#39301)

(cherry picked from commit c67b0a3a64)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-01-10 22:18:16 +05:30
Deepesh Garg
5dc2e80987 fix: Ignore default payment terms template for opening invoices
(cherry picked from commit 53bf44d2b8)
2024-01-10 10:37:45 +00:00
anandbaburajan
2448ba6bc4 fix: asset WDV depreciation calc according to IT act
(cherry picked from commit 026824880d)
2024-01-10 06:48:06 +00:00
Gursheen Anand
53208dfb3e fix: fetch name for fy
(cherry picked from commit d0ea598cdf)
2024-01-08 16:22:58 +00:00
53 changed files with 559 additions and 204 deletions

View File

@@ -3,7 +3,7 @@ import inspect
import frappe
__version__ = "15.10.1"
__version__ = "15.10.2"
def get_default_company(user=None):

View File

@@ -76,6 +76,7 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
"deposit": 100,
"bank_account": self.bank_account,
"reference_number": "123",
"currency": "INR",
}
)
.save()

View File

@@ -3,6 +3,7 @@
import frappe
from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document
from frappe.utils import flt
@@ -48,6 +49,24 @@ class BankTransaction(Document):
def validate(self):
self.validate_duplicate_references()
self.validate_currency()
def validate_currency(self):
"""
Bank Transaction should be on the same currency as the Bank Account.
"""
if self.currency and self.bank_account:
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
account_currency = frappe.get_cached_value("Account", account, "account_currency")
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
)
)
def set_status(self):
if self.docstatus == 2:
@@ -415,3 +434,21 @@ def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt)
return docname
def remove_from_bank_transaction(doctype, docname):
"""Remove a (cancelled) voucher from all Bank Transactions."""
for bt_name in get_reconciled_bank_transactions(doctype, docname):
bt = frappe.get_doc("Bank Transaction", bt_name)
if bt.docstatus == DocStatus.cancelled():
continue
modified = False
for pe in bt.payment_entries:
if pe.payment_document == doctype and pe.payment_entry == docname:
bt.remove(pe)
modified = True
if modified:
bt.save()

View File

@@ -2,10 +2,10 @@
# See license.txt
import json
import unittest
import frappe
from frappe import utils
from frappe.model.docstatus import DocStatus
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
@@ -81,6 +81,29 @@ class TestBankTransaction(FrappeTestCase):
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertFalse(clearance_date)
def test_cancel_voucher(self):
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers)
payment.reload()
payment.cancel()
bank_transaction.reload()
self.assertEqual(bank_transaction.docstatus, DocStatus.submitted())
self.assertEqual(bank_transaction.unallocated_amount, 1700)
self.assertEqual(bank_transaction.payment_entries, [])
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self):
bank_transaction = frappe.get_doc(

View File

@@ -39,7 +39,7 @@ def test_record_generator():
]
start = 2012
end = now_datetime().year + 5
end = now_datetime().year + 25
for year in range(start, end):
test_records.append(
{

View File

@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Bank Transaction"];
},
refresh: function(frm) {

View File

@@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
frappe.ui.form.on('Payment Entry', {
onload: function(frm) {
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries', "Bank Transaction"];
if(frm.doc.__islocal) {
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);

View File

@@ -35,7 +35,17 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
super.onload();
// Ignore linked advances
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle"];
this.frm.ignore_doctypes_on_cancel_all = [
"Journal Entry",
"Payment Entry",
"Purchase Invoice",
"Repost Payment Ledger",
"Repost Accounting Ledger",
"Unreconcile Payment",
"Unreconcile Payment Entries",
"Serial and Batch Bundle",
"Bank Transaction",
];
if(!this.frm.doc.__islocal) {
// show credit_to in print format

View File

@@ -296,6 +296,18 @@ class PurchaseInvoice(BuyingController):
self.reset_default_field_value("set_warehouse", "items", "warehouse")
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
self.set_percentage_received()
def set_percentage_received(self):
total_billed_qty = 0.0
total_received_qty = 0.0
for row in self.items:
if row.purchase_receipt and row.pr_detail and row.received_qty:
total_billed_qty += row.qty
total_received_qty += row.received_qty
if total_billed_qty and total_received_qty:
self.per_received = total_received_qty / total_billed_qty * 100
def validate_release_date(self):
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):

View File

@@ -126,7 +126,7 @@
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"label": "Tax Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
@@ -230,7 +230,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-08-05 20:04:36.618240",
"modified": "2024-01-14 10:04:36.618240",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",
@@ -239,4 +239,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -36,9 +36,19 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
var me = this;
super.onload();
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries",
'Serial and Batch Bundle'
this.frm.ignore_doctypes_on_cancel_all = [
"POS Invoice",
"Timesheet",
"POS Invoice Merge Log",
"POS Closing Entry",
"Journal Entry",
"Payment Entry",
"Repost Payment Ledger",
"Repost Accounting Ledger",
"Unreconcile Payment",
"Unreconcile Payment Entries",
"Serial and Batch Bundle",
"Bank Transaction",
];
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {

View File

@@ -108,7 +108,7 @@
"fieldname": "rate",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Rate",
"label": "Tax Rate",
"oldfieldname": "rate",
"oldfieldtype": "Currency"
},
@@ -218,7 +218,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-10-17 13:08:17.776528",
"modified": "2024-01-14 10:08:17.776528",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",
@@ -227,4 +227,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"states": []
}
}

View File

@@ -16,6 +16,7 @@ from frappe.utils.data import (
date_diff,
flt,
get_last_day,
get_link_to_form,
getdate,
nowdate,
)
@@ -317,6 +318,37 @@ class Subscription(Document):
if self.is_new():
self.set_subscription_status()
self.validate_party_billing_currency()
def validate_party_billing_currency(self):
"""
Subscription should be of the same currency as the Party's default billing currency or company default.
"""
if self.party:
party_billing_currency = frappe.get_cached_value(
self.party_type, self.party, "default_currency"
) or frappe.get_cached_value("Company", self.company, "default_currency")
plans = [x.plan for x in self.plans]
subscription_plan_currencies = frappe.db.get_all(
"Subscription Plan", filters={"name": ("in", plans)}, fields=["name", "currency"]
)
unsupported_plans = []
for x in subscription_plan_currencies:
if x.currency != party_billing_currency:
unsupported_plans.append("{0}".format(get_link_to_form("Subscription Plan", x.name)))
if unsupported_plans:
unsupported_plans = [
_(
"Below Subscription Plans are of different currency to the party default billing currency/Company currency: {0}"
).format(frappe.bold(party_billing_currency))
] + unsupported_plans
frappe.throw(
unsupported_plans, frappe.ValidationError, "Unsupported Subscription Plans", as_list=True
)
def validate_trial_period(self) -> None:
"""
Runs sanity checks on trial period dates for the `Subscription`
@@ -563,6 +595,8 @@ class Subscription(Document):
) and self.can_generate_new_invoice(posting_date):
self.generate_invoice(posting_date=posting_date)
self.update_subscription_period(add_days(self.current_invoice_end, 1))
elif posting_date and getdate(posting_date) > getdate(self.current_invoice_end):
self.update_subscription_period()
if self.cancel_at_period_end and (
getdate(posting_date) >= getdate(self.current_invoice_end)

View File

@@ -460,11 +460,13 @@ class TestSubscription(FrappeTestCase):
self.assertEqual(len(subscription.invoices), 1)
def test_multi_currency_subscription(self):
party = "_Test Subscription Customer"
frappe.db.set_value("Customer", party, "default_currency", "USD")
subscription = create_subscription(
start_date="2018-01-01",
generate_invoice_at="Beginning of the current subscription period",
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1}],
party="_Test Subscription Customer",
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1, "currency": "USD"}],
party=party,
)
subscription.process()
@@ -528,13 +530,21 @@ class TestSubscription(FrappeTestCase):
def make_plans():
create_plan(plan_name="_Test Plan Name", cost=900)
create_plan(plan_name="_Test Plan Name 2", cost=1999)
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
create_plan(plan_name="_Test Plan Name 2", cost=1999, currency="INR")
create_plan(
plan_name="_Test Plan Name 3", cost=1999, billing_interval="Day", billing_interval_count=14
plan_name="_Test Plan Name 3",
cost=1999,
billing_interval="Day",
billing_interval_count=14,
currency="INR",
)
create_plan(
plan_name="_Test Plan Name 4", cost=20000, billing_interval="Month", billing_interval_count=3
plan_name="_Test Plan Name 4",
cost=20000,
billing_interval="Month",
billing_interval_count=3,
currency="INR",
)
create_plan(
plan_name="_Test Plan Multicurrency", cost=50, billing_interval="Month", currency="USD"

View File

@@ -41,7 +41,8 @@
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
"options": "Currency",
"reqd": 1
},
{
"fieldname": "column_break_3",
@@ -148,10 +149,11 @@
}
],
"links": [],
"modified": "2021-12-10 15:24:15.794477",
"modified": "2024-01-14 17:59:34.687977",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -193,5 +195,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -24,7 +24,7 @@ class SubscriptionPlan(Document):
billing_interval_count: DF.Int
cost: DF.Currency
cost_center: DF.Link | None
currency: DF.Link | None
currency: DF.Link
item: DF.Link
payment_gateway: DF.Link | None
plan_name: DF.Data

View File

@@ -84,10 +84,6 @@ function get_filters() {
options: budget_against_options,
default: "Cost Center",
reqd: 1,
get_data: function() {
console.log(this.options);
return ["Emacs", "Rocks"];
},
on_change: function() {
frappe.query_report.set_filter_value("budget_against_filter", []);
frappe.query_report.refresh();

View File

@@ -376,6 +376,10 @@ class PartyLedgerSummaryReport(object):
if not income_or_expense_accounts:
# prevent empty 'in' condition
income_or_expense_accounts.append("")
else:
# escape '%' in account name
# ignoring frappe.db.escape as it replaces single quotes with double quotes
income_or_expense_accounts = [x.replace("%", "%%") for x in income_or_expense_accounts]
accounts_query = (
qb.from_(gl)

View File

@@ -368,7 +368,7 @@ def filter_invoices_based_on_dimensions(filters, query, parent_doc):
dimension.document_type, filters.get(dimension.fieldname)
)
fieldname = dimension.fieldname
query = query.where(parent_doc[fieldname] == filters.fieldname)
query = query.where(parent_doc[fieldname].isin(filters[fieldname]))
return query

View File

@@ -23,6 +23,10 @@ class TestUtils(unittest.TestCase):
super(TestUtils, cls).setUpClass()
make_test_objects("Address", ADDRESS_RECORDS)
@classmethod
def tearDownClass(cls):
frappe.db.rollback()
def test_get_party_shipping_address(self):
address = get_party_shipping_address("Customer", "_Test Customer 1")
self.assertEqual(address, "_Test Billing Address 2 Title-Billing")
@@ -126,6 +130,38 @@ class TestUtils(unittest.TestCase):
self.assertEqual(len(payment_entry.references), 1)
self.assertEqual(payment_entry.difference_amount, 0)
def test_naming_series_variable_parsing(self):
"""
Tests parsing utility used by Naming Series Variable hook for FY
"""
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.utils import nowdate
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
# Configure Supplier Naming in Buying Settings
frappe.db.set_default("supp_master_name", "Auto Name")
# Configure Autoname in Supplier DocType
make_property_setter(
"Supplier", None, "naming_rule", "Expression", "Data", for_doctype="Doctype"
)
make_property_setter(
"Supplier", None, "autoname", "SUP-.FY.-.#####", "Data", for_doctype="Doctype"
)
fiscal_year = get_fiscal_year(nowdate())[0]
# Create Supplier
supplier = create_supplier()
# Check Naming Series in generated Supplier ID
doc_name = supplier.name.split("-")
self.assertEqual(len(doc_name), 3)
self.assertSequenceEqual(doc_name[0:2], ("SUP", fiscal_year))
frappe.db.set_default("supp_master_name", "Supplier Name")
ADDRESS_RECORDS = [
{

View File

@@ -1263,7 +1263,7 @@ def get_autoname_with_number(number_value, doc_title, company):
def parse_naming_series_variable(doc, variable):
if variable == "FY":
if doc:
date = doc.get("posting_date") or doc.get("transaction_date")
date = doc.get("posting_date") or doc.get("transaction_date") or getdate()
company = doc.get("company")
else:
date = getdate()

View File

@@ -202,8 +202,7 @@
"fieldname": "purchase_date",
"fieldtype": "Date",
"label": "Purchase Date",
"mandatory_depends_on": "eval:!doc.is_existing_asset",
"read_only": 1,
"mandatory_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
},
{
@@ -590,7 +589,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-01-05 17:36:53.131512",
"modified": "2024-01-15 17:35:49.226603",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -162,6 +162,7 @@ class Asset(AccountsController):
def on_cancel(self):
self.validate_cancellation()
self.cancel_movement_entries()
self.cancel_capitalization()
self.delete_depreciation_entries()
cancel_asset_depr_schedules(self)
self.set_status()
@@ -517,6 +518,16 @@ class Asset(AccountsController):
movement = frappe.get_doc("Asset Movement", movement.get("name"))
movement.cancel()
def cancel_capitalization(self):
asset_capitalization = frappe.db.get_value(
"Asset Capitalization",
{"target_asset": self.name, "docstatus": 1, "entry_type": "Capitalization"},
)
if asset_capitalization:
asset_capitalization = frappe.get_doc("Asset Capitalization", asset_capitalization)
asset_capitalization.cancel()
def delete_depreciation_entries(self):
if self.calculate_depreciation:
for row in self.get("finance_books"):
@@ -1027,6 +1038,8 @@ def is_cwip_accounting_enabled(asset_category):
@frappe.whitelist()
def get_asset_value_after_depreciation(asset_name, finance_book=None):
asset = frappe.get_doc("Asset", asset_name)
if not asset.calculate_depreciation:
return flt(asset.value_after_depreciation)
return asset.get_value_after_depreciation(finance_book)

View File

@@ -19,6 +19,7 @@ from frappe.utils import (
)
from frappe.utils.user import get_users_with_role
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
@@ -522,6 +523,13 @@ def depreciate_asset(asset_doc, date, notes):
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
cancel_depreciation_entries(asset_doc, date)
@erpnext.allow_regional
def cancel_depreciation_entries(asset_doc, date):
pass
def reset_depreciation_schedule(asset_doc, date, notes):
if not asset_doc.calculate_depreciation:

View File

@@ -891,7 +891,7 @@ class TestDepreciationMethods(AssetSetup):
["2030-12-31", 28630.14, 28630.14],
["2031-12-31", 35684.93, 64315.07],
["2032-12-31", 17842.46, 82157.53],
["2033-06-06", 5342.47, 87500.0],
["2033-06-06", 5342.46, 87499.99],
]
schedules = [
@@ -1003,7 +1003,7 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
depreciation_amount = get_depreciation_amount(
asset_depr_schedule_doc, asset, 100000, asset.finance_books[0]
asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0]
)
self.assertEqual(depreciation_amount, 30000)

View File

@@ -21,10 +21,10 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
this.show_stock_ledger();
}
if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
this.get_target_asset_details();
}
// if (this.frm.doc.stock_items && !this.frm.doc.stock_items.length && this.frm.doc.target_asset && this.frm.doc.capitalization_method === "Choose a WIP composite asset") {
// this.set_consumed_stock_items_tagged_to_wip_composite_asset(this.frm.doc.target_asset);
// this.get_target_asset_details();
// }
}
setup_queries() {
@@ -143,13 +143,20 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
},
callback: function (r) {
if (!r.exc && r.message) {
me.frm.clear_table("stock_items");
for (let item of r.message) {
me.frm.add_child("stock_items", item);
if(r.message[0] && r.message[0].length) {
me.frm.clear_table("stock_items");
for (let item of r.message[0]) {
me.frm.add_child("stock_items", item);
}
refresh_field("stock_items");
}
if (r.message[1] && r.message[1].length) {
me.frm.clear_table("asset_items");
for (let item of r.message[1]) {
me.frm.add_child("asset_items", item);
}
me.frm.refresh_field("asset_items");
}
refresh_field("stock_items");
me.calculate_totals();
}

View File

@@ -136,11 +136,19 @@ class AssetCapitalization(StockController):
"Stock Ledger Entry",
"Repost Item Valuation",
"Serial and Batch Bundle",
"Asset",
)
self.cancel_target_asset()
self.update_stock_ledger()
self.make_gl_entries()
self.restore_consumed_asset_items()
def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset)
if asset_doc.docstatus == 1:
asset_doc.cancel()
def set_title(self):
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
@@ -881,7 +889,6 @@ def get_consumed_asset_details(args):
out.cost_center = get_default_cost_center(
args, item_defaults, item_group_defaults, brand_defaults
)
return out
@@ -929,10 +936,27 @@ def get_items_tagged_to_wip_composite_asset(asset):
"qty",
"valuation_rate",
"amount",
"is_fixed_asset",
"parent",
]
pr_items = frappe.get_all(
"Purchase Receipt Item", filters={"wip_composite_asset": asset}, fields=fields
"Purchase Receipt Item", filters={"wip_composite_asset": asset, "docstatus": 1}, fields=fields
)
return pr_items
stock_items = []
asset_items = []
for d in pr_items:
if not d.is_fixed_asset:
stock_items.append(frappe._dict(d))
else:
asset_details = frappe.db.get_value(
"Asset",
{"item_code": d.item_code, "purchase_receipt": d.parent},
["name as asset", "asset_name"],
as_dict=1,
)
d.update(asset_details)
asset_items.append(frappe._dict(d))
return stock_items, asset_items

View File

@@ -7,6 +7,7 @@ from frappe.model.document import Document
from frappe.utils import (
add_days,
add_months,
add_years,
cint,
date_diff,
flt,
@@ -18,6 +19,7 @@ from frappe.utils import (
)
import erpnext
from erpnext.accounts.utils import get_fiscal_year
class AssetDepreciationSchedule(Document):
@@ -283,12 +285,20 @@ class AssetDepreciationSchedule(Document):
depreciation_amount = 0
number_of_pending_depreciations = final_number_of_depreciations - start
yearly_opening_wdv = value_after_depreciation
current_fiscal_year_end_date = None
for n in range(start, final_number_of_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
if not current_fiscal_year_end_date:
current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
elif getdate(schedule_date) > getdate(current_fiscal_year_end_date):
current_fiscal_year_end_date = add_years(current_fiscal_year_end_date, 1)
yearly_opening_wdv = value_after_depreciation
if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
else:
@@ -298,6 +308,7 @@ class AssetDepreciationSchedule(Document):
self,
asset_doc,
value_after_depreciation,
yearly_opening_wdv,
row,
n,
prev_depreciation_amount,
@@ -341,10 +352,7 @@ class AssetDepreciationSchedule(Document):
n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
and get_updated_rate_of_depreciation_for_wdv_and_dd(
asset_doc, value_after_depreciation, row, False
)
== row.rate_of_depreciation
and not self.flags.wdv_it_act_applied
):
from_date = add_days(
asset_doc.available_for_use_date, -1
@@ -404,8 +412,9 @@ class AssetDepreciationSchedule(Document):
if not depreciation_amount:
continue
value_after_depreciation -= flt(
depreciation_amount, asset_doc.precision("gross_purchase_amount")
value_after_depreciation = flt(
value_after_depreciation - flt(depreciation_amount),
asset_doc.precision("gross_purchase_amount"),
)
# Adjust depreciation amount in the last period based on the expected value after useful life
@@ -585,6 +594,7 @@ def get_depreciation_amount(
asset_depr_schedule,
asset,
depreciable_value,
yearly_opening_wdv,
fb_row,
schedule_idx=0,
prev_depreciation_amount=0,
@@ -596,26 +606,18 @@ def get_depreciation_amount(
asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
)
else:
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row
)
return get_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
rate_of_depreciation,
fb_row.frequency_of_depreciation,
yearly_opening_wdv,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
)
@erpnext.allow_regional
def get_updated_rate_of_depreciation_for_wdv_and_dd(
asset, depreciable_value, fb_row, show_msg=True
):
return fb_row.rate_of_depreciation
def get_straight_line_or_manual_depr_amount(
asset_depr_schedule, asset, row, schedule_idx, number_of_pending_depreciations
):
@@ -751,30 +753,57 @@ def get_asset_shift_factors_map():
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
@erpnext.allow_regional
def get_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
rate_of_depreciation,
frequency_of_depreciation,
yearly_opening_wdv,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
):
if cint(frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
return get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
)
def get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
):
if cint(fb_row.frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
else:
if has_wdv_or_dd_non_yearly_pro_rata:
if schedule_idx == 0:
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
flt(depreciable_value)
* flt(fb_row.frequency_of_depreciation)
* (flt(fb_row.rate_of_depreciation) / 1200)
)
else:
return prev_depreciation_amount
else:
if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
return (
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
flt(depreciable_value)
* flt(fb_row.frequency_of_depreciation)
* (flt(fb_row.rate_of_depreciation) / 1200)
)
else:
return prev_depreciation_amount

View File

@@ -94,7 +94,6 @@
},
{
"default": "0",
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
"fieldname": "daily_prorata_based",
"fieldtype": "Check",
"label": "Depreciate based on daily pro-rata"
@@ -110,7 +109,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-11-29 00:57:07.579777",
"modified": "2023-12-29 08:49:39.876439",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",

View File

@@ -214,7 +214,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-01-05 15:26:02.320942",
"modified": "2024-01-12 16:42:01.894346",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",
@@ -240,39 +240,24 @@
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts User",
"share": 1
"role": "Accounts User"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1
"role": "Accounts Manager"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Stock Manager",
"share": 1
"role": "Stock Manager"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Stock User",
"share": 1
"role": "Stock User"
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Purchase User",
"share": 1
"role": "Purchase User"
}
],
"sort_field": "modified",

View File

@@ -1414,11 +1414,16 @@ class AccountsController(TransactionBase):
reconcile_against_document(lst)
def on_cancel(self):
from erpnext.accounts.doctype.bank_transaction.bank_transaction import (
remove_from_bank_transaction,
)
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
unlink_ref_doc_from_payment_entries,
)
remove_from_bank_transaction(self.doctype, self.name)
if self.doctype in ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]:
# Cancel Exchange Gain/Loss Journal before unlinking
cancel_exchange_gain_loss_journal(self)
@@ -1947,7 +1952,7 @@ class AccountsController(TransactionBase):
self.remove(item)
def set_payment_schedule(self):
if self.doctype == "Sales Invoice" and self.is_pos:
if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
self.payment_terms_template = ""
return
@@ -2130,7 +2135,7 @@ class AccountsController(TransactionBase):
)
def validate_payment_schedule_amount(self):
if self.doctype == "Sales Invoice" and self.is_pos:
if (self.doctype == "Sales Invoice" and self.is_pos) or self.get("is_opening") == "Yes":
return
party_account_currency = self.get("party_account_currency")

View File

@@ -6,10 +6,12 @@ import json
from collections import OrderedDict, defaultdict
import frappe
from frappe import scrub
from frappe import qb, scrub
from frappe.desk.reportview import get_filters_cond, get_match_cond
from frappe.query_builder.functions import Concat, Sum
from frappe.query_builder import Criterion, CustomFunction
from frappe.query_builder.functions import Concat, Locate, Sum
from frappe.utils import nowdate, today, unique
from pypika import Order
import erpnext
from erpnext.stock.get_item_details import _get_item_tax_template
@@ -339,37 +341,46 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
doctype = "Project"
cond = ""
proj = qb.DocType("Project")
qb_filter_and_conditions = []
qb_filter_or_conditions = []
ifelse = CustomFunction("IF", ["condition", "then", "else"])
if filters and filters.get("customer"):
cond = """(`tabProject`.customer = %s or
ifnull(`tabProject`.customer,"")="") and""" % (
frappe.db.escape(filters.get("customer"))
)
qb_filter_and_conditions.append(proj.customer == filters.get("customer"))
qb_filter_and_conditions.append(proj.status.notin(["Completed", "Cancelled"]))
q = qb.from_(proj)
fields = get_fields(doctype, ["name", "project_name"])
searchfields = frappe.get_meta(doctype).get_search_fields()
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
for x in fields:
q = q.select(proj[x])
return frappe.db.sql(
"""select {fields} from `tabProject`
where
`tabProject`.status not in ('Completed', 'Cancelled')
and {cond} {scond} {match_cond}
order by
(case when locate(%(_txt)s, `tabProject`.name) > 0 then locate(%(_txt)s, `tabProject`.name) else 99999 end),
`tabProject`.idx desc,
`tabProject`.name asc
limit {page_len} offset {start}""".format(
fields=", ".join(["`tabProject`.{0}".format(f) for f in fields]),
cond=cond,
scond=searchfields,
match_cond=get_match_cond(doctype),
start=start,
page_len=page_len,
),
{"txt": "%{0}%".format(txt), "_txt": txt.replace("%", "")},
)
# don't consider 'customer' and 'status' fields for pattern search, as they must be exactly matched
searchfields = [
x for x in frappe.get_meta(doctype).get_search_fields() if x not in ["customer", "status"]
]
# pattern search
if txt:
for x in searchfields:
qb_filter_or_conditions.append(proj[x].like(f"%{txt}%"))
q = q.where(Criterion.all(qb_filter_and_conditions)).where(Criterion.any(qb_filter_or_conditions))
# ordering
if txt:
# project_name containing search string 'txt' will be given higher precedence
q = q.orderby(ifelse(Locate(txt, proj.project_name) > 0, Locate(txt, proj.project_name), 99999))
q = q.orderby(proj.idx, order=Order.desc).orderby(proj.name)
if page_len:
q = q.limit(page_len)
if start:
q = q.offset(start)
return q.run()
@frappe.whitelist()
@@ -416,23 +427,14 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
meta = frappe.get_meta(doctype, cached=True)
searchfields = meta.get_search_fields()
query = get_batches_from_stock_ledger_entries(searchfields, txt, filters)
bundle_query = get_batches_from_serial_and_batch_bundle(searchfields, txt, filters)
data = (
frappe.qb.from_((query) + (bundle_query))
.select("batch_no", "qty", "manufacturing_date", "expiry_date")
.offset(start)
.limit(page_len)
batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len)
batches.extend(
get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)
)
for field in searchfields:
data = data.select(field)
filtered_batches = get_filterd_batches(batches)
data = data.run()
data = get_filterd_batches(data)
return data
return filtered_batches
def get_filterd_batches(data):
@@ -452,7 +454,7 @@ def get_filterd_batches(data):
return filterd_batch
def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, page_len=100):
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_table = frappe.qb.DocType("Batch")
@@ -474,6 +476,8 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
& (stock_ledger_entry.batch_no.isnotnull())
)
.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
.offset(start)
.limit(page_len)
)
query = query.select(
@@ -488,16 +492,16 @@ def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
query = query.select(batch_table[field])
if txt:
txt_condition = batch_table.name.like(txt)
txt_condition = batch_table.name.like("%{0}%".format(txt))
for field in searchfields + ["name"]:
txt_condition |= batch_table[field].like(txt)
txt_condition |= batch_table[field].like("%{0}%".format(txt))
query = query.where(txt_condition)
return query
return query.run(as_list=1) or []
def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0, page_len=100):
bundle = frappe.qb.DocType("Serial and Batch Entry")
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_table = frappe.qb.DocType("Batch")
@@ -522,6 +526,8 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
& (stock_ledger_entry.serial_and_batch_bundle.isnotnull())
)
.groupby(bundle.batch_no, bundle.warehouse)
.offset(start)
.limit(page_len)
)
bundle_query = bundle_query.select(
@@ -536,13 +542,13 @@ def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
bundle_query = bundle_query.select(batch_table[field])
if txt:
txt_condition = batch_table.name.like(txt)
txt_condition = batch_table.name.like("%{0}%".format(txt))
for field in searchfields + ["name"]:
txt_condition |= batch_table[field].like(txt)
txt_condition |= batch_table[field].like("%{0}%".format(txt))
bundle_query = bundle_query.where(txt_condition)
return bundle_query
return bundle_query.run(as_list=1)
@frappe.whitelist()

View File

@@ -68,7 +68,7 @@ class TestQueries(unittest.TestCase):
self.assertGreaterEqual(len(query(txt="_Test Item Home Desktop Manufactured")), 1)
def test_project_query(self):
query = add_default_params(queries.get_project_name, "BOM")
query = add_default_params(queries.get_project_name, "Project")
self.assertGreaterEqual(len(query(txt="_Test Project")), 1)

View File

@@ -173,7 +173,7 @@ frappe.ui.form.on('Production Plan', {
method: "set_status",
freeze: true,
doc: frm.doc,
args: {close : close},
args: {close : close, update_bin: true},
callback: function() {
frm.reload_doc();
}

View File

@@ -579,7 +579,7 @@ class ProductionPlan(Document):
frappe.delete_doc("Work Order", d.name)
@frappe.whitelist()
def set_status(self, close=None):
def set_status(self, close=None, update_bin=False):
self.status = {0: "Draft", 1: "Submitted", 2: "Cancelled"}.get(self.docstatus)
if close:
@@ -599,7 +599,7 @@ class ProductionPlan(Document):
if close is not None:
self.db_set("status", self.status)
if self.docstatus == 1 and self.status != "Completed":
if update_bin and self.docstatus == 1 and self.status != "Completed":
self.update_bin_qty()
def update_ordered_status(self):

View File

@@ -1486,14 +1486,14 @@ class TestProductionPlan(FrappeTestCase):
before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
pln.reload()
pln.set_status(close=True)
pln.set_status(close=True, update_bin=True)
bin_name = get_or_make_bin(rm_item, rm_warehouse)
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
self.assertAlmostEqual(after_qty, before_qty - 10)
pln.reload()
pln.set_status(close=False)
pln.set_status(close=False, update_bin=True)
bin_name = get_or_make_bin(rm_item, rm_warehouse)
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))

View File

@@ -21,7 +21,7 @@ $.extend(erpnext, {
},
toggle_naming_series: function() {
if(cur_frm.fields_dict.naming_series) {
if(cur_frm && cur_frm.fields_dict.naming_series) {
cur_frm.toggle_display("naming_series", cur_frm.doc.__islocal?true:false);
}
},

View File

@@ -16,6 +16,8 @@ erpnext.accounts.dimensions = {
},
callback: function(r) {
me.accounting_dimensions = r.message[0];
// Ignoring "Project" as it is already handled specifically in Sales Order and Delivery Note
me.accounting_dimensions = me.accounting_dimensions.filter(x=>{return x.document_type != "Project"});
me.default_dimensions = r.message[1];
me.setup_filters(frm, doctype);
}

View File

@@ -94,6 +94,9 @@ frappe.ui.form.on("Sales Order", {
frm.set_value("reserve_stock", 0);
frm.set_df_property("reserve_stock", "read_only", 1);
frm.set_df_property("reserve_stock", "hidden", 1);
frm.fields_dict.items.grid.update_docfield_property('reserve_stock', 'hidden', 1);
frm.fields_dict.items.grid.update_docfield_property('reserve_stock', 'default', 0);
frm.fields_dict.items.grid.update_docfield_property('reserve_stock', 'read_only', 1);
}
})
}

View File

@@ -200,6 +200,7 @@ class SalesOrder(SellingController):
self.validate_for_items()
self.validate_warehouse()
self.validate_drop_ship()
self.validate_reserved_stock()
self.validate_serial_no_based_delivery()
validate_against_blanket_order(self)
validate_inter_company_party(
@@ -660,6 +661,17 @@ class SalesOrder(SellingController):
).format(item.item_code)
)
def validate_reserved_stock(self):
"""Clean reserved stock flag for non-stock Item"""
enable_stock_reservation = frappe.db.get_single_value(
"Stock Settings", "enable_stock_reservation"
)
for item in self.items:
if item.reserve_stock and (not enable_stock_reservation or not cint(item.is_stock_item)):
item.reserve_stock = 0
def has_unreserved_stock(self) -> bool:
"""Returns True if there is any unreserved item in the Sales Order."""

View File

@@ -10,6 +10,7 @@
"item_code",
"customer_item_code",
"ensure_delivery_based_on_produced_serial_no",
"is_stock_item",
"reserve_stock",
"col_break1",
"delivery_date",
@@ -867,6 +868,7 @@
{
"allow_on_submit": 1,
"default": "1",
"depends_on": "eval:doc.is_stock_item",
"fieldname": "reserve_stock",
"fieldtype": "Check",
"label": "Reserve Stock",
@@ -891,6 +893,16 @@
"label": "Production Plan Qty",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"fetch_from": "item_code.is_stock_item",
"fieldname": "is_stock_item",
"fieldtype": "Check",
"hidden": 1,
"label": "Is Stock Item",
"print_hide": 1,
"report_hide": 1
}
],
"idx": 1,

View File

@@ -87,7 +87,7 @@ class HolidayList(Document):
for holiday_date, holiday_name in country_holidays(
self.country,
subdiv=self.subdivision,
years=[from_date.year, to_date.year],
years=list(range(from_date.year, to_date.year + 1)),
language=frappe.local.lang,
).items():
if holiday_date in existing_holidays:

View File

@@ -48,17 +48,58 @@ class TestHolidayList(unittest.TestCase):
def test_local_holidays(self):
holiday_list = frappe.new_doc("Holiday List")
holiday_list.from_date = "2023-04-01"
holiday_list.to_date = "2023-04-30"
holiday_list.from_date = "2022-01-01"
holiday_list.to_date = "2024-12-31"
holiday_list.country = "DE"
holiday_list.subdivision = "SN"
holiday_list.get_local_holidays()
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
self.assertNotIn(date(2023, 1, 1), holidays)
holidays = holiday_list.get_holidays()
self.assertIn(date(2022, 1, 1), holidays)
self.assertIn(date(2022, 4, 15), holidays)
self.assertIn(date(2022, 4, 18), holidays)
self.assertIn(date(2022, 5, 1), holidays)
self.assertIn(date(2022, 5, 26), holidays)
self.assertIn(date(2022, 6, 6), holidays)
self.assertIn(date(2022, 10, 3), holidays)
self.assertIn(date(2022, 10, 31), holidays)
self.assertIn(date(2022, 11, 16), holidays)
self.assertIn(date(2022, 12, 25), holidays)
self.assertIn(date(2022, 12, 26), holidays)
self.assertIn(date(2023, 1, 1), holidays)
self.assertIn(date(2023, 4, 7), holidays)
self.assertIn(date(2023, 4, 10), holidays)
self.assertNotIn(date(2023, 5, 1), holidays)
self.assertIn(date(2023, 5, 1), holidays)
self.assertIn(date(2023, 5, 18), holidays)
self.assertIn(date(2023, 5, 29), holidays)
self.assertIn(date(2023, 10, 3), holidays)
self.assertIn(date(2023, 10, 31), holidays)
self.assertIn(date(2023, 11, 22), holidays)
self.assertIn(date(2023, 12, 25), holidays)
self.assertIn(date(2023, 12, 26), holidays)
self.assertIn(date(2024, 1, 1), holidays)
self.assertIn(date(2024, 3, 29), holidays)
self.assertIn(date(2024, 4, 1), holidays)
self.assertIn(date(2024, 5, 1), holidays)
self.assertIn(date(2024, 5, 9), holidays)
self.assertIn(date(2024, 5, 20), holidays)
self.assertIn(date(2024, 10, 3), holidays)
self.assertIn(date(2024, 10, 31), holidays)
self.assertIn(date(2024, 11, 20), holidays)
self.assertIn(date(2024, 12, 25), holidays)
self.assertIn(date(2024, 12, 26), holidays)
# check some random dates that should not be local holidays
self.assertNotIn(date(2022, 1, 2), holidays)
self.assertNotIn(date(2023, 4, 16), holidays)
self.assertNotIn(date(2024, 4, 19), holidays)
self.assertNotIn(date(2022, 5, 2), holidays)
self.assertNotIn(date(2023, 5, 27), holidays)
self.assertNotIn(date(2024, 6, 7), holidays)
self.assertNotIn(date(2022, 10, 4), holidays)
self.assertNotIn(date(2023, 10, 30), holidays)
self.assertNotIn(date(2024, 11, 17), holidays)
self.assertNotIn(date(2022, 12, 24), holidays)
def test_localized_country_names(self):
lang = frappe.local.lang

View File

@@ -186,7 +186,7 @@
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2023-11-01 16:51:17.079107",
"modified": "2024-01-16 15:11:46.140323",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
@@ -213,6 +213,21 @@
"read": 1,
"report": 1,
"role": "Stock User"
},
{
"read": 1,
"report": 1,
"role": "Stock Manager"
},
{
"read": 1,
"report": 1,
"role": "Purchase Manager"
},
{
"read": 1,
"report": 1,
"role": "Sales Manager"
}
],
"quick_entry": 1,

View File

@@ -103,15 +103,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Closing Stock Balance",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "include_uom",
"fieldtype": "Link",
@@ -122,7 +113,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-05-17 11:46:04.448220",
"modified": "2023-05-18 11:46:04.448220",
"modified_by": "Administrator",
"module": "Stock",
"name": "Closing Stock Balance",

View File

@@ -600,26 +600,12 @@ $.extend(erpnext.item, {
}
});
} else {
frappe.call({
method: "frappe.client.get",
args: {
doctype: "Item Attribute",
name: d.attribute
}
}).then((r) => {
if(r.message) {
const from = r.message.from_range;
const to = r.message.to_range;
const increment = r.message.increment;
let values = [];
for(var i = from; i <= to; i = flt(i + increment, 6)) {
values.push(i);
}
attr_val_fields[d.attribute] = values;
resolve();
}
});
let values = [];
for(var i = d.from_range; i <= d.to_range; i = flt(i + d.increment, 6)) {
values.push(i);
}
attr_val_fields[d.attribute] = values;
resolve();
}
});

View File

@@ -1228,6 +1228,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
"field_map": {
"name": "pr_detail",
"parent": "purchase_receipt",
"qty": "received_qty",
"purchase_order_item": "po_detail",
"purchase_order": "purchase_order",
"is_fixed_asset": "is_fixed_asset",

View File

@@ -111,6 +111,9 @@ class QualityInspection(Document):
def on_cancel(self):
self.update_qc_reference()
def on_trash(self):
self.update_qc_reference()
def validate_readings_status_mandatory(self):
for reading in self.readings:
if not reading.status:

View File

@@ -250,6 +250,33 @@ class TestQualityInspection(FrappeTestCase):
qa.delete()
dn.delete()
def test_delete_quality_inspection_linked_with_stock_entry(self):
item_code = create_item("_Test Cicuular Dependecy Item with QA").name
se = make_stock_entry(
item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100, do_not_submit=True
)
se.inspection_required = 1
se.save()
qa = create_quality_inspection(
item_code=item_code, reference_type="Stock Entry", reference_name=se.name, do_not_submit=True
)
se.reload()
se.items[0].quality_inspection = qa.name
se.save()
qa.delete()
se.reload()
qc = se.items[0].quality_inspection
self.assertFalse(qc)
se.delete()
def create_quality_inspection(**args):
args = frappe._dict(args)

View File

@@ -104,7 +104,8 @@
"in_standard_filter": 1,
"label": "Stock Entry Type",
"options": "Stock Entry Type",
"reqd": 1
"reqd": 1,
"search_index": 1
},
{
"depends_on": "eval:doc.purpose == 'Material Transfer'",
@@ -546,7 +547,8 @@
"label": "Job Card",
"options": "Job Card",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "amended_from",
@@ -679,7 +681,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-06-19 18:23:40.748114",
"modified": "2024-01-12 11:56:58.644882",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@@ -561,7 +561,8 @@
"label": "Job Card Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"default": "0",
@@ -589,7 +590,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-05-09 12:41:18.210864",
"modified": "2024-01-12 11:56:04.626103",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry Detail",

View File

@@ -20,7 +20,6 @@ class StockEntryDetail(Document):
allow_alternative_item: DF.Check
allow_zero_valuation_rate: DF.Check
amount: DF.Currency
attach_something_here: DF.Attach | None
barcode: DF.Data | None
basic_amount: DF.Currency
basic_rate: DF.Currency

View File

@@ -10,8 +10,9 @@
"has_item_scanned",
"item_code",
"item_name",
"warehouse",
"item_group",
"column_break_6",
"warehouse",
"qty",
"valuation_rate",
"amount",
@@ -52,6 +53,7 @@
"reqd": 1
},
{
"fetch_from": "item_code.item_name",
"fieldname": "item_name",
"fieldtype": "Data",
"in_global_search": 1,
@@ -213,11 +215,18 @@
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
},
{
"fetch_from": "item_code.item_group",
"fieldname": "item_group",
"fieldtype": "Link",
"label": "Item Group",
"options": "Item Group"
}
],
"istable": 1,
"links": [],
"modified": "2023-11-02 15:47:07.929550",
"modified": "2024-01-14 10:04:23.599951",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",