Compare commits

..

84 Commits

Author SHA1 Message Date
Frappe PR Bot
4ef07f97c9 chore(release): Bumped to Version 15.85.1
## [15.85.1](https://github.com/frappe/erpnext/compare/v15.85.0...v15.85.1) (2025-10-30)

### Bug Fixes

* **pos:** order pos invoices by timestamp ([8bd1e8f](8bd1e8ff74))
2025-10-30 15:42:14 +00:00
rohitwaghchaure
670579bde3 Merge pull request #50278 from frappe/mergify/bp/version-15/pr-50277
fix(pos): order pos invoices by timestamp (backport #50277)
2025-10-30 21:10:53 +05:30
Kavin
8bd1e8ff74 fix(pos): order pos invoices by timestamp
(cherry picked from commit 12903b11ed)
2025-10-30 13:20:53 +00:00
Frappe PR Bot
49f66a8a51 chore(release): Bumped to Version 15.85.0
# [15.85.0](https://github.com/frappe/erpnext/compare/v15.84.0...v15.85.0) (2025-10-28)

### Bug Fixes

* **accounts:** update payment mode account retrieval to use namespaced function ([2300660](23006601b2))
* add message for missing purchase orders in production plan ([5798409](5798409f69))
* **asset depreciations and balances:** showing opening entries ([81f19b9](81f19b950d))
* avoid group columns mutation ([158e1b2](158e1b28a9))
* **Bank Transaction:** make transaction ID non-unique ([b52b04a](b52b04a10c))
* fiscal year overlap validation for company-specific years ([482a796](482a796212))
* get valuation rate based of previous SLEs for material receipt ([e9f4a34](e9f4a34d8d))
* **gross profit:** remove customer name from columns ([a767253](a7672530f4))
* **journal-entry:** allow copy account currency when duplicating JV ([1b227b8](1b227b8b4f))
* optimized the slow query to get the batchwise available qty ([85bf936](85bf9366b0))
* Pass uom field name to update existing item qty ([93b2786](93b2786865))
* provision to find and fix incorrect serial and batch bundles ([2276741](22767410d5))
* recalculate amount based on allocated amount ([2a90bff](2a90bffb5f))
* resolve conflicts ([97147a4](97147a484d))
* sabb missed in the incorrect serial no valuation report ([8a995f2](8a995f28c9))
* set default value for as zero for additional asset cost ([ee5e4ec](ee5e4eccec))
* set status to Draft for auto-created assets from Purchase Receipt ([d8eddbf](d8eddbfd03))
* stock difference value for adjustment entry ([6c0694f](6c0694ff17))
* **Task:** make Timesheet-dependent fields no_copy (backport [#50130](https://github.com/frappe/erpnext/issues/50130)) ([#50196](https://github.com/frappe/erpnext/issues/50196)) ([4988ff8](4988ff84df))
* use correct field name ([a82fa8c](a82fa8c26b))

### Features

* Add posting date param for reverse GL entries ([1a7092d](1a7092d7b6))
* add project filter to Delayed Tasks Summary report ([82ca729](82ca729e2b))
2025-10-28 13:48:43 +00:00
Diptanil Saha
0a09bb8d17 Merge pull request #50249 from frappe/version-15-hotfix
chore: release v15
2025-10-28 19:17:10 +05:30
rohitwaghchaure
ba700bce90 Merge pull request #50252 from frappe/mergify/bp/version-15-hotfix/pr-50247
fix: provision to find and fix incorrect serial and batch bundles (backport #50247)
2025-10-28 18:21:46 +05:30
Rohit Waghchaure
22767410d5 fix: provision to find and fix incorrect serial and batch bundles
(cherry picked from commit 10ad56060c)
2025-10-28 12:15:11 +00:00
Diptanil Saha
89e7a260fd Merge pull request #50251 from frappe/mergify/bp/version-15-hotfix/pr-50137
fix(payment-reco): recalculate amount based on allocated amount (backport #50137)
2025-10-28 17:34:10 +05:30
l0gesh29
2a90bffb5f fix: recalculate amount based on allocated amount
(cherry picked from commit 5a7a6a9bd5)
2025-10-28 11:58:10 +00:00
Diptanil Saha
7434522ea1 Merge pull request #50250 from frappe/mergify/bp/version-15-hotfix/pr-50220
fix(gross profit): remove customer name from columns (backport #50220)
2025-10-28 15:37:53 +05:30
ravibharathi656
158e1b28a9 fix: avoid group columns mutation
(cherry picked from commit 7ce81127d2)
2025-10-28 09:51:23 +00:00
ravibharathi656
a7672530f4 fix(gross profit): remove customer name from columns
(cherry picked from commit 0009925af0)
2025-10-28 09:51:23 +00:00
Diptanil Saha
ad33735541 Merge pull request #50245 from frappe/mergify/bp/version-15-hotfix/pr-50213
fix(Bank Transaction)!: make transaction ID non-unique (backport #50213)
2025-10-28 10:20:49 +05:30
Diptanil Saha
977f385006 chore: resolve conflict 2025-10-28 09:07:14 +05:30
barredterra
b52b04a10c fix(Bank Transaction): make transaction ID non-unique
(cherry picked from commit a2a41a0eaa)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.json
2025-10-28 02:45:52 +00:00
Diptanil Saha
05b89b4121 Merge pull request #50243 from frappe/mergify/bp/version-15-hotfix/pr-49599
fix(accounts): reference get_payment_mode_account correctly in Sales Invoice (backport #49599)
2025-10-28 08:08:36 +05:30
Diptanil Saha
36983c97cb chore: resolve conflict 2025-10-28 08:06:10 +05:30
KerollesFathy
23006601b2 fix(accounts): update payment mode account retrieval to use namespaced function
(cherry picked from commit 0af74aef00)

# Conflicts:
#	erpnext/public/js/controllers/accounts.js
2025-10-27 16:50:10 +00:00
rohitwaghchaure
37e9461ece Merge pull request #50239 from frappe/mergify/bp/version-15-hotfix/pr-50233
fix: optimized the slow query to get the batch-wise available qty (backport #50233)
2025-10-27 17:31:15 +05:30
Mihir Kandoi
126d79dcd2 Merge pull request #50241 from frappe/mergify/bp/version-15-hotfix/pr-50221
fix: Pass uom field name to update existing item qty (backport #50221)
2025-10-27 17:29:29 +05:30
Kavin
93b2786865 fix: Pass uom field name to update existing item qty
(cherry picked from commit 23d69389ec)
2025-10-27 11:48:44 +00:00
rohitwaghchaure
2680f85430 chore: fix linters issue 2025-10-27 17:06:45 +05:30
rohitwaghchaure
2d7fde024d chore: fix conflicts 2025-10-27 16:57:00 +05:30
Rohit Waghchaure
85bf9366b0 fix: optimized the slow query to get the batchwise available qty
(cherry picked from commit 9c21567309)

# Conflicts:
#	erpnext/stock/serial_batch_bundle.py
2025-10-27 11:19:24 +00:00
Khushi Rawat
86f507bb6e Merge pull request #50238 from frappe/mergify/bp/version-15-hotfix/pr-50222
fix(asset depreciations and balances): showing opening entries (backport #50222)
2025-10-27 16:35:46 +05:30
ravibharathi656
81f19b950d fix(asset depreciations and balances): showing opening entries
(cherry picked from commit d3afa67be3)
2025-10-27 10:48:36 +00:00
Mihir Kandoi
7b4eac2294 Merge pull request #50236 from Abdeali099/fix-msg
fix: add message for missing purchase orders in production plan
2025-10-27 15:54:09 +05:30
Diptanil Saha
aff4b62258 Merge pull request #50234 from frappe/mergify/bp/version-15-hotfix/pr-50039
fix(journal-entry): allow copy account currency when duplicating JV (backport #50039)
2025-10-27 15:36:40 +05:30
Abdeali Chharchhoda
5798409f69 fix: add message for missing purchase orders in production plan 2025-10-27 15:09:22 +05:30
Bhavan23
1b227b8b4f fix(journal-entry): allow copy account currency when duplicating JV
(cherry picked from commit 76748e4573)
2025-10-27 08:45:01 +00:00
Diptanil Saha
4eca7f0524 Merge pull request #50230 from frappe/mergify/bp/version-15-hotfix/pr-50103
fix: fiscal year overlap validation for company-specific years (backport #50103)
2025-10-27 11:01:57 +05:30
rehansari26
4bb66179ab test: replace get_doc with new_doc in fiscal year tests
(cherry picked from commit d59e55fb08)
2025-10-27 05:08:36 +00:00
Rehan Ansari
d84a29e1a1 test: fiscal year overlap validation for company-specific years
(cherry picked from commit 94ae098854)
2025-10-27 05:08:36 +00:00
Rehan Ansari
482a796212 fix: fiscal year overlap validation for company-specific years
(cherry picked from commit 57aaf34d3e)
2025-10-27 05:08:36 +00:00
Diptanil Saha
41694d3751 Merge pull request #50223 from frappe/mergify/bp/version-15-hotfix/pr-49889
feat: add project filter to Delayed Tasks Summary report (backport #49889)
2025-10-24 22:27:54 +05:30
Rehan Ansari
82ca729e2b feat: add project filter to Delayed Tasks Summary report
(cherry picked from commit 88097e78d2)
2025-10-24 16:42:15 +00:00
rohitwaghchaure
e67361257e Merge pull request #50216 from frappe/mergify/bp/version-15-hotfix/pr-50215
fix: get valuation rate based of previous SLEs for material receipt (backport #50215)
2025-10-24 00:05:12 +05:30
Rohit Waghchaure
e9f4a34d8d fix: get valuation rate based of previous SLEs for material receipt
(cherry picked from commit fa9ef6708f)
2025-10-23 18:30:20 +00:00
Khushi Rawat
c6c4e31b32 Merge pull request #50211 from frappe/mergify/bp/version-15-hotfix/pr-50209
fix: set status to Draft for auto-created assets from Purchase Receipt (backport #50209)
2025-10-23 23:33:42 +05:30
khushi8112
ee5e4eccec fix: set default value for as zero for additional asset cost 2025-10-23 22:51:30 +05:30
khushi8112
a82fa8c26b fix: use correct field name 2025-10-23 20:45:08 +05:30
khushi8112
97147a484d fix: resolve conflicts 2025-10-23 19:09:30 +05:30
khushi8112
d8eddbfd03 fix: set status to Draft for auto-created assets from Purchase Receipt
(cherry picked from commit 20c2cb40d1)

# Conflicts:
#	erpnext/assets/doctype/asset/asset.py
#	erpnext/patches.txt
2025-10-23 13:03:17 +00:00
Deepesh Garg
977464e6a7 Merge pull request #50206 from frappe/mergify/bp/version-15-hotfix/pr-50204
feat: Add posting date param for reverse GL entries (#50204)
2025-10-23 16:02:07 +05:30
Deepesh Garg
db84df0cbd chore: Linting issues
(cherry picked from commit 05e1a737f1)
2025-10-23 08:12:31 +00:00
Deepesh Garg
1a7092d7b6 feat: Add posting date param for reverse GL entries
(cherry picked from commit 38988bf797)
2025-10-23 08:12:30 +00:00
rohitwaghchaure
fcd05bb7e4 Merge pull request #50202 from frappe/mergify/bp/version-15-hotfix/pr-50200
fix: stock difference value for adjustment entry (backport #50200)
2025-10-23 10:31:00 +05:30
Rohit Waghchaure
6c0694ff17 fix: stock difference value for adjustment entry
(cherry picked from commit fb4c7de86c)
2025-10-22 17:03:12 +00:00
mergify[bot]
4988ff84df fix(Task): make Timesheet-dependent fields no_copy (backport #50130) (#50196)
* fix(Task): make Timesheet-dependent fields no_copy

(cherry picked from commit 2a2e4b5423)

# Conflicts:
#	erpnext/projects/doctype/task/task.json

* chore: resolve conflicts

---------

Co-authored-by: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-10-22 08:34:11 +00:00
Mihir Kandoi
e5d0116924 Merge pull request #50194 from frappe/mergify/bp/version-15-hotfix/pr-50151
chore: remove print statement (backport #50151)
2025-10-22 14:01:04 +05:30
Mihir Kandoi
38d3b17dfe Merge pull request #50195 from frappe/mergify/bp/version-15-hotfix/pr-50153
fix: Address title error when create new transporter on driver  (backport #50153)
2025-10-22 13:48:31 +05:30
Kerolles Fathy
89d305ac9f Merge pull request #50153 from KerollesFathy/fix-address-error-when-create-transporter
fix: Address title error when create new transporter on driver
(cherry picked from commit 8efcf6cb38)
2025-10-22 08:14:25 +00:00
Abdeali Chharchhoda
8b2be43897 chore: remove print statement
(cherry picked from commit 1ad4dc9066)
2025-10-22 08:14:24 +00:00
rohitwaghchaure
0bd3b5aac2 Merge pull request #50192 from frappe/mergify/bp/version-15-hotfix/pr-50191
fix: sabb missed in the incorrect serial no valuation report (backport #50191)
2025-10-22 12:17:53 +05:30
Rohit Waghchaure
8a995f28c9 fix: sabb missed in the incorrect serial no valuation report
(cherry picked from commit b50bac6788)
2025-10-22 06:26:01 +00:00
Frappe PR Bot
e714b82802 chore(release): Bumped to Version 15.84.0
# [15.84.0](https://github.com/frappe/erpnext/compare/v15.83.2...v15.84.0) (2025-10-21)

### Bug Fixes

* added exception handling on service level agreement apply hook ([#50096](https://github.com/frappe/erpnext/issues/50096)) ([a3a6d39](a3a6d39436))
* adjustment entry ([76cfa28](76cfa28a42))
* correct monthly sales history (backport [#50056](https://github.com/frappe/erpnext/issues/50056)) ([#50179](https://github.com/frappe/erpnext/issues/50179)) ([afc2d95](afc2d95736))
* handle flt conversion for prev_ordered_qty ([3d3e116](3d3e116797))
* internal transfer entry with serial/batch ([d67a439](d67a439051))
* **point-of-sale:** render payment methods only payment component is visible ([2b0281c](2b0281c510))
* preview stock ledger for manual serial and batch values ([c64dcf3](c64dcf3423))
* **stock:** remove duplicate fields ([58a1383](58a1383380))
* validation for negative batch ([b9dd05f](b9dd05f292))

### Features

* set options for IBAN fields (backport [#49377](https://github.com/frappe/erpnext/issues/49377)) ([#49413](https://github.com/frappe/erpnext/issues/49413)) ([bd3a132](bd3a132868))

### Performance Improvements

* Add index for faster queries ([#50175](https://github.com/frappe/erpnext/issues/50175)) ([0571ed0](0571ed0735))
2025-10-21 12:44:58 +00:00
Diptanil Saha
7ec6ef3139 Merge pull request #50182 from frappe/version-15-hotfix
chore: release v15
2025-10-21 18:13:27 +05:30
mergify[bot]
afc2d95736 fix: correct monthly sales history (backport #50056) (#50179)
fix: correct monthly sales history (#50056)

* fix: correct monthly sales history

* fix: correct monthly sales history calculation

* chore: remove unrelated file accidentally committed

---------


(cherry picked from commit f51ed30c23)

Co-authored-by: Ahmed AbuKhatwa <82771130+AhmedAbokhatwa@users.noreply.github.com>
Co-authored-by: AhmedAbukhatwa <Ahmedabukhatwa1@gmail.com>
2025-10-21 11:06:02 +05:30
Deepesh Garg
1e9ffe351b Merge pull request #50178 from frappe/mergify/bp/version-15-hotfix/pr-50175
perf: Add index for faster queries
2025-10-21 09:56:35 +05:30
Deepesh Garg
0571ed0735 perf: Add index for faster queries (#50175)
(cherry picked from commit 7a91ec3e33)
2025-10-20 18:43:48 +00:00
mergify[bot]
43941ecd5e refactor: simplify expression (backport #50168) (#50170)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-10-19 23:59:27 +02:00
Frappe PR Bot
8b9788ca74 chore(release): Bumped to Version 15.83.2
## [15.83.2](https://github.com/frappe/erpnext/compare/v15.83.1...v15.83.2) (2025-10-18)

### Bug Fixes

* internal transfer entry with serial/batch ([97cdac1](97cdac10d7))
* **stock:** remove duplicate fields ([5e8e6ef](5e8e6ef2f3))
2025-10-18 04:42:12 +00:00
rohitwaghchaure
eb38bcdab7 Merge pull request #50164 from frappe/mergify/bp/version-15/pr-50158
fix: internal transfer entry with serial/batch (backport #50156) (backport #50158)
2025-10-18 10:10:35 +05:30
rohitwaghchaure
9876d1c7e6 Merge pull request #50165 from frappe/mergify/bp/version-15/pr-50157
fix(stock-settings): remove duplicate fields (backport #50157)
2025-10-18 10:10:20 +05:30
Kavin
5e8e6ef2f3 fix(stock): remove duplicate fields
(cherry picked from commit 58a1383380)
2025-10-18 03:49:50 +00:00
rohitwaghchaure
2055c4e8d3 Merge pull request #50157 from aerele/support-51015
fix(stock-settings): remove duplicate fields
2025-10-18 09:19:21 +05:30
Rohit Waghchaure
97cdac10d7 fix: internal transfer entry with serial/batch
(cherry picked from commit 9b4e62a758)
(cherry picked from commit d67a439051)
2025-10-18 03:47:47 +00:00
rohitwaghchaure
df22902035 Merge pull request #50158 from frappe/mergify/bp/version-15-hotfix/pr-50156
fix: internal transfer entry with serial/batch (backport #50156)
2025-10-17 21:32:16 +05:30
Rohit Waghchaure
d67a439051 fix: internal transfer entry with serial/batch
(cherry picked from commit 9b4e62a758)
2025-10-17 15:34:30 +00:00
Kavin
58a1383380 fix(stock): remove duplicate fields 2025-10-17 18:35:53 +05:30
rohitwaghchaure
f5af5dd8c0 Merge pull request #50145 from frappe/mergify/bp/version-15-hotfix/pr-50123
fix: validation for negative batch (backport #50123)
2025-10-17 12:34:15 +05:30
Rohit Waghchaure
b9dd05f292 fix: validation for negative batch
(cherry picked from commit f9c8f27586)
2025-10-17 06:07:37 +00:00
Diptanil Saha
44f39e0b43 Merge pull request #50142 from frappe/mergify/bp/version-15-hotfix/pr-50141
fix(point-of-sale): render payment methods only when payment component is visible (backport #50141)
2025-10-17 01:46:05 +05:30
diptanilsaha
2b0281c510 fix(point-of-sale): render payment methods only payment component is visible
(cherry picked from commit 7dc4306640)
2025-10-16 20:13:33 +00:00
rohitwaghchaure
03766e8980 Merge pull request #50117 from frappe/mergify/bp/version-15-hotfix/pr-50113
fix: adjustment entry (backport #50113)
2025-10-15 21:31:32 +05:30
Diptanil Saha
e7b7377b7e Merge pull request #50120 from frappe/mergify/bp/version-15-hotfix/pr-50096
fix: added exception handling on service level agreement apply hook (backport #50096)
2025-10-15 19:51:23 +05:30
Diptanil Saha
a3a6d39436 fix: added exception handling on service level agreement apply hook (#50096)
* fix: added exception handling on service level agreement apply hook

* Revert "fix: added exception handling on service level agreement apply hook"

This reverts commit dae93aa96f.

* fix: Ignore missing SLA table during install/uninstall

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit 182c9fd966)
2025-10-15 14:04:17 +00:00
Rohit Waghchaure
76cfa28a42 fix: adjustment entry
(cherry picked from commit c0851abaee)
2025-10-15 10:30:33 +00:00
Diptanil Saha
afa7d2b595 Merge pull request #50109 from frappe/mergify/bp/version-15-hotfix/pr-50095
fix: preview stock ledger for manual serial and batch values (backport #50095)
2025-10-15 13:14:13 +05:30
Kavin
c64dcf3423 fix: preview stock ledger for manual serial and batch values
(cherry picked from commit c5f68d0b27)
2025-10-15 05:47:41 +00:00
Mihir Kandoi
3fa38d0b4e Merge pull request #50106 from frappe/mergify/bp/version-15-hotfix/pr-50078
fix: handle flt conversion for prev_ordered_qty (backport #50078)
2025-10-15 00:02:09 +05:30
mergify[bot]
abb210bd18 refactor: add supplier filter in buying (backport #50013) (#50107)
* refactor: add supplier filter in buying

(cherry picked from commit 108b108d64)

# Conflicts:
#	erpnext/public/js/controllers/buying.js

* chore: resolve conflicts

---------

Co-authored-by: manikandan-s-18 <manikandansundar18@gmail.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-10-14 18:22:20 +00:00
Kavin
3d3e116797 fix: handle flt conversion for prev_ordered_qty
(cherry picked from commit 77c35ef47f)
2025-10-14 18:10:30 +00:00
mergify[bot]
bd3a132868 feat: set options for IBAN fields (backport #49377) (#49413)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-10-14 14:42:08 +00:00
45 changed files with 391 additions and 175 deletions

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.83.1"
__version__ = "15.85.1"
def get_default_company(user=None):

View File

@@ -132,7 +132,8 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "IBAN",
"length": 30
"length": 34,
"options": "IBAN"
},
{
"fieldname": "column_break_12",
@@ -208,6 +209,7 @@
"label": "Disabled"
}
],
"grid_page_length": 50,
"links": [
{
"group": "Transactions",
@@ -250,7 +252,7 @@
"link_fieldname": "default_bank_account"
}
],
"modified": "2024-10-30 09:41:14.113414",
"modified": "2025-08-29 12:32:01.081687",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",
@@ -282,6 +284,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "bank,account",
"sort_field": "modified",
"sort_order": "DESC",

View File

@@ -9,7 +9,8 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact,
)
from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
from frappe.utils import comma_and, get_link_to_form, validate_iban
from frappe.utils.deprecations import deprecated
class BankAccount(Document):
@@ -52,7 +53,6 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
self.validate_account()
self.update_default_bank_account()
@@ -72,34 +72,10 @@ class BankAccount(Document):
if self.is_company_account and not self.company:
frappe.throw(_("Company is manadatory for company account"))
@deprecated
def validate_iban(self):
"""
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
"""
# IBAN field is optional
if not self.iban:
return
def encode_char(c):
# Position in the alphabet (A=1, B=2, ...) plus nine
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
iban = "".join(self.iban.split(" ")).upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
try:
to_check = int("".join(encoded))
except ValueError:
frappe.throw(_("IBAN is not valid"))
if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid"))
"""Kept for backward compatibility, will be removed in v16."""
validate_iban(self.iban, throw=True)
def update_default_bank_account(self):
if self.is_default and not self.disabled:

View File

@@ -3,45 +3,8 @@
import unittest
import frappe
from frappe import ValidationError
# test_records = frappe.get_test_records('Bank Account')
class TestBankAccount(unittest.TestCase):
def test_validate_iban(self):
valid_ibans = [
"GB82 WEST 1234 5698 7654 32",
"DE91 1000 0000 0123 4567 89",
"FR76 3000 6000 0112 3456 7890 189",
]
invalid_ibans = [
# wrong checksum (3rd place)
"GB72 WEST 1234 5698 7654 32",
"DE81 1000 0000 0123 4567 89",
"FR66 3000 6000 0112 3456 7890 189",
]
bank_account = frappe.get_doc({"doctype": "Bank Account"})
try:
bank_account.validate_iban()
except AttributeError:
msg = "BankAccount.validate_iban() failed for empty IBAN"
self.fail(msg=msg)
for iban in valid_ibans:
bank_account.iban = iban
try:
bank_account.validate_iban()
except ValidationError:
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()
pass

View File

@@ -146,6 +146,7 @@
"fieldname": "iban",
"fieldtype": "Data",
"label": "IBAN",
"options": "IBAN",
"read_only": 1
},
{
@@ -216,7 +217,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2025-09-25 23:44:12.473583",
"modified": "2025-09-26 00:38:17.584694",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Guarantee",

View File

@@ -123,8 +123,7 @@
"fieldname": "transaction_id",
"fieldtype": "Data",
"label": "Transaction ID",
"read_only": 1,
"unique": 1
"read_only": 1
},
{
"allow_on_submit": 1,
@@ -223,7 +222,8 @@
{
"fieldname": "bank_party_iban",
"fieldtype": "Data",
"label": "Party IBAN (Bank Statement)"
"label": "Party IBAN (Bank Statement)",
"options": "IBAN"
},
{
"fieldname": "bank_party_account_number",
@@ -238,7 +238,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-09-26 17:06:29.207673",
"modified": "2025-10-23 17:32:58.514807",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -99,7 +99,7 @@ class FiscalYear(Document):
)
overlap = False
if not self.get("companies") or not company_for_existing:
if not self.get("companies") and not company_for_existing:
overlap = True
for d in self.get("companies"):

View File

@@ -26,6 +26,27 @@ class TestFiscalYear(unittest.TestCase):
self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
def test_company_fiscal_year_overlap(self):
for name in ["_Test Global FY 2001", "_Test Company FY 2001"]:
if frappe.db.exists("Fiscal Year", name):
frappe.delete_doc("Fiscal Year", name)
global_fy = frappe.new_doc("Fiscal Year")
global_fy.year = "_Test Global FY 2001"
global_fy.year_start_date = "2001-04-01"
global_fy.year_end_date = "2002-03-31"
global_fy.insert()
company_fy = frappe.new_doc("Fiscal Year")
company_fy.year = "_Test Company FY 2001"
company_fy.year_start_date = "2001-01-01"
company_fy.year_end_date = "2001-12-31"
company_fy.append("companies", {"company": "_Test Company"})
company_fy.insert()
self.assertTrue(frappe.db.exists("Fiscal Year", global_fy.name))
self.assertTrue(frappe.db.exists("Fiscal Year", company_fy.name))
def test_record_generator():
test_records = [

View File

@@ -106,7 +106,6 @@
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"no_copy": 1,
"options": "Currency",
"print_hide": 1,
"read_only": 1
@@ -272,7 +271,8 @@
"label": "Advance Voucher Type",
"no_copy": 1,
"options": "DocType",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "advance_voucher_no",
@@ -280,13 +280,14 @@
"label": "Advance Voucher No",
"no_copy": 1,
"options": "advance_voucher_type",
"read_only": 1
"read_only": 1,
"search_index": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-09-29 13:01:48.916517",
"modified": "2025-10-27 13:48:32.805100",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -385,6 +385,16 @@ frappe.ui.form.on("Payment Reconciliation Allocation", {
// filter payment
let payment = frm.doc.payments.filter((x) => x.reference_name == row.reference_name);
let amount = payment[0].amount;
for (const d of frm.doc.allocation) {
if (row.reference_name == d.reference_name && amount) {
if (d.allocated_amount <= amount) {
d.amount = amount;
amount -= d.allocated_amount;
}
}
}
frm.call({
doc: frm.doc,
method: "calculate_difference_on_allocation_change",

View File

@@ -226,7 +226,8 @@
"fetch_from": "bank_account.iban",
"fieldname": "iban",
"fieldtype": "Read Only",
"label": "IBAN"
"label": "IBAN",
"options": "IBAN"
},
{
"fetch_from": "bank_account.branch_code",
@@ -443,11 +444,12 @@
"label": "Phone Number"
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-12-27 21:29:10.361894",
"modified": "2025-08-29 11:52:48.555415",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -482,8 +484,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_preview_popup": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -161,6 +161,8 @@ def get_pos_invoices(start, end, pos_profile, user):
`tabPOS Invoice`
where
owner = %s and docstatus = 1 and pos_profile = %s and ifnull(consolidated_invoice,'') = ''
order by
timestamp
""",
(user, pos_profile),
as_dict=1,

View File

@@ -2411,6 +2411,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
target.purchase_order = source.purchase_order
target.po_detail = source.purchase_order_item
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
target.use_serial_batch_fields = 1
item_field_map = {
"doctype": target_doctype + " Item",
"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],

View File

@@ -648,6 +648,7 @@ def make_reverse_gl_entries(
adv_adj=False,
update_outstanding="Yes",
partial_cancel=False,
posting_date=None,
):
"""
Get original gl entries of the voucher
@@ -745,6 +746,8 @@ def make_reverse_gl_entries(
if immutable_ledger_enabled:
new_gle["is_cancelled"] = 0
new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate()
elif posting_date:
new_gle["posting_date"] = posting_date
if new_gle["debit"] or new_gle["credit"]:
make_entry(new_gle, adv_adj, "Yes")

View File

@@ -273,6 +273,7 @@ def get_asset_value_adjustment_map_by_category(filters):
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
AND gle.is_opening = 'No'
GROUP BY a.asset_category
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
@@ -543,6 +544,7 @@ def get_asset_value_adjustment_map(filters):
AND a.company = %(company)s
AND a.purchase_date <= %(to_date)s
AND gle.account = aca.fixed_asset_account
AND gle.is_opening = 'No'
GROUP BY a.name
""",
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},

View File

@@ -231,6 +231,15 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
group_columns = group_wise_columns.get(scrub(filters.group_by))
# removing customer_name from group columns
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
if "customer_name" in group_columns and (
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
):
group_columns = [col for col in group_columns if col != "customer_name"]
for src in gross_profit_data.grouped_data:
total_base_amount += src.base_amount or 0.00
total_buying_amount += src.buying_amount or 0.00

View File

@@ -518,6 +518,7 @@
"read_only": 1
},
{
"default": "0",
"depends_on": "eval:doc.docstatus > 0",
"fieldname": "additional_asset_cost",
"fieldtype": "Currency",
@@ -596,7 +597,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2025-05-20 00:44:06.229177",
"modified": "2025-10-23 22:43:33.634452",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -641,4 +642,4 @@
"states": [],
"title_field": "asset_name",
"track_changes": 1
}
}

View File

@@ -69,6 +69,7 @@ class Asset(AccountsController):
default_finance_book: DF.Link | None
department: DF.Link | None
depr_entry_posting_status: DF.Literal["", "Successful", "Failed"]
depreciation_completed: DF.Check
depreciation_method: DF.Literal["", "Straight Line", "Double Declining Balance", "Manual"]
disposal_date: DF.Date | None
finance_books: DF.Table[AssetFinanceBook]
@@ -152,7 +153,9 @@ class Asset(AccountsController):
)
self.validate_expected_value_after_useful_life()
self.set_total_booked_depreciations()
self.total_asset_cost = self.gross_purchase_amount
def before_save(self):
self.total_asset_cost = self.gross_purchase_amount + self.additional_asset_cost
self.status = self.get_status()
def on_submit(self):

View File

@@ -644,9 +644,8 @@ class PurchaseOrder(BuyingController):
if not self.is_against_so():
return
for item in removed_items:
prev_ordered_qty = (
prev_ordered_qty = flt(
frappe.get_cached_value("Sales Order Item", item.get("sales_order_item"), "ordered_qty")
or 0.0
)
frappe.db.set_value(

View File

@@ -3073,9 +3073,7 @@ def set_balance_in_account_currency(
_("Account: {0} with currency: {1} can not be selected").format(gl_dict.account, account_currency)
)
gl_dict["account_currency"] = (
company_currency if account_currency == company_currency else account_currency
)
gl_dict["account_currency"] = account_currency
# set debit/credit in account currency if not provided
if flt(gl_dict.debit) and not flt(gl_dict.debit_in_account_currency):

View File

@@ -878,6 +878,7 @@ class BuyingController(SubcontractingController):
"asset_category": item_data.get("asset_category"),
"location": row.asset_location,
"company": self.company,
"status": "Draft",
"supplier": self.supplier,
"purchase_date": self.posting_date,
"calculate_depreciation": 0,

View File

@@ -253,6 +253,14 @@ class StockController(AccountsController):
"do_not_submit": True if not via_landed_cost_voucher else False,
}
if self.is_internal_transfer() and row.get("from_warehouse") and not self.is_return:
self.update_bundle_details(bundle_details, table_name, row)
bundle_details["type_of_transaction"] = "Outward"
bundle_details["warehouse"] = row.get("from_warehouse")
bundle_details["qty"] = row.get("stock_qty") or row.get("qty")
self.create_serial_batch_bundle(bundle_details, row)
continue
if row.get("qty") or row.get("consumed_qty") or row.get("stock_qty"):
self.update_bundle_details(bundle_details, table_name, row, parent_details=parent_details)
self.create_serial_batch_bundle(bundle_details, row)
@@ -1544,7 +1552,9 @@ def get_stock_ledger_preview(doc, filters):
if doc.get("update_stock") or doc.doctype in ("Purchase Receipt", "Delivery Note"):
doc.docstatus = 1
doc.make_bundle_using_old_serial_batch_fields()
doc.update_stock_ledger()
columns = get_sl_columns(filters)
sl_entries = get_sl_entries_for_preview(doc.doctype, doc.name, fields)

View File

@@ -724,6 +724,9 @@ class ProductionPlan(Document):
if not wo_list:
frappe.msgprint(_("No Work Orders were created"))
if not po_list:
frappe.msgprint(_("No Purchase Orders were created"))
def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
items_data = self.get_production_items()

View File

@@ -26,7 +26,6 @@ def get_exploded_items(bom, data, indent=0, qty=1):
)
for item in exploded_items:
print(item.bom_no, indent)
item["indent"] = indent
data.append(
{

View File

@@ -422,3 +422,4 @@ execute:frappe.db.set_single_value("Accounts Settings", "fetch_valuation_rate_fo
erpnext.patches.v15_0.add_company_payment_gateway_account
erpnext.patches.v15_0.update_uae_zero_rated_fetch
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
erpnext.patches.v15_0.set_asset_status_if_not_already_set

View File

@@ -0,0 +1,13 @@
import frappe
from frappe.query_builder import DocType
def execute():
Asset = DocType("Asset")
query = (
frappe.qb.update(Asset)
.set(Asset.status, "Draft")
.where((Asset.docstatus == 0) & ((Asset.status.isnull()) | (Asset.status == "")))
)
query.run()

View File

@@ -245,6 +245,7 @@
"fieldname": "act_start_date",
"fieldtype": "Date",
"label": "Actual Start Date (via Timesheet)",
"no_copy": 1,
"oldfieldname": "act_start_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -253,6 +254,7 @@
"fieldname": "actual_time",
"fieldtype": "Float",
"label": "Actual Time in Hours (via Timesheet)",
"no_copy": 1,
"read_only": 1
},
{
@@ -263,6 +265,7 @@
"fieldname": "act_end_date",
"fieldtype": "Date",
"label": "Actual End Date (via Timesheet)",
"no_copy": 1,
"oldfieldname": "act_end_date",
"oldfieldtype": "Date",
"read_only": 1
@@ -277,6 +280,7 @@
"fieldname": "total_costing_amount",
"fieldtype": "Currency",
"label": "Total Costing Amount (via Timesheet)",
"no_copy": 1,
"oldfieldname": "actual_budget",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -290,6 +294,7 @@
"fieldname": "total_billing_amount",
"fieldtype": "Currency",
"label": "Total Billable Amount (via Timesheet)",
"no_copy": 1,
"read_only": 1
},
{
@@ -399,7 +404,7 @@
"is_tree": 1,
"links": [],
"max_attachments": 5,
"modified": "2024-01-08 16:00:41.296203",
"modified": "2025-10-16 08:39:12.214577",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
@@ -429,4 +434,4 @@
"timeline_field": "project",
"title_field": "subject",
"track_seen": 1
}
}

View File

@@ -3,6 +3,12 @@
frappe.query_reports["Delayed Tasks Summary"] = {
filters: [
{
fieldname: "project",
label: __("Project"),
fieldtype: "Link",
options: "Project",
},
{
fieldname: "from_date",
label: __("From Date"),

View File

@@ -23,6 +23,7 @@ def get_data(filters):
fields=[
"name",
"subject",
"project",
"exp_start_date",
"exp_end_date",
"status",
@@ -56,7 +57,7 @@ def get_data(filters):
def get_conditions(filters):
conditions = frappe._dict()
keys = ["priority", "status"]
keys = ["priority", "status", "project"]
for key in keys:
if filters.get(key):
conditions[key] = filters.get(key)
@@ -89,6 +90,13 @@ def get_columns():
columns = [
{"fieldname": "name", "fieldtype": "Link", "label": _("Task"), "options": "Task", "width": 150},
{"fieldname": "subject", "fieldtype": "Data", "label": _("Subject"), "width": 200},
{
"fieldname": "project",
"fieldtype": "Link",
"label": _("Project"),
"options": "Project",
"width": 150,
},
{"fieldname": "status", "fieldtype": "Data", "label": _("Status"), "width": 100},
{"fieldname": "priority", "fieldtype": "Data", "label": _("Priority"), "width": 80},
{"fieldname": "progress", "fieldtype": "Data", "label": _("Progress (%)"), "width": 120},

View File

@@ -237,10 +237,10 @@ erpnext.accounts.pos = {
frappe.ui.form.on(doctype, {
mode_of_payment: function(frm, cdt, cdn) {
var d = locals[cdt][cdn];
get_payment_mode_account(frm, d.mode_of_payment, function(account){
frappe.model.set_value(cdt, cdn, 'account', account)
})
}
erpnext.accounts.pos.get_payment_mode_account(frm, d.mode_of_payment, function (account) {
frappe.model.set_value(cdt, cdn, "account", account);
});
},
});
},

View File

@@ -17,7 +17,17 @@ erpnext.buying = {
this.setup_queries(doc, cdt, cdn);
super.onload();
this.frm.set_query('shipping_rule', function() {
if (["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype)) {
this.frm.set_query("supplier", function () {
return {
filters: {
is_transporter: 0,
},
};
});
}
this.frm.set_query("shipping_rule", function () {
return {
filters: {
"shipping_rule_type": "Buying"

View File

@@ -394,6 +394,10 @@ erpnext.PointOfSale.Payment = class {
const payments = doc.payments;
const currency = doc.currency;
if (!this.$payment_modes.is(":visible")) {
return;
}
this.$payment_modes.html(
`${payments
.map((p, i) => {
@@ -612,6 +616,10 @@ erpnext.PointOfSale.Payment = class {
const currency = doc.currency;
const label = __("Change Amount");
if (!this.$totals.is(":visible")) {
return;
}
this.$totals.html(
`<div class="col">
<div class="total-label">${__("Grand Total")}</div>

View File

@@ -763,13 +763,11 @@ def update_company_current_month_sales(company):
def update_company_monthly_sales(company):
"""Cache past year monthly sales of every company based on sales invoices"""
import json
from frappe.utils.goal import get_monthly_results
filter_str = f"company = {frappe.db.escape(company)} and status != 'Draft' and docstatus=1"
filter_dict = {"company": company, "status": ["!=", "Draft"], "docstatus": 1}
month_to_value_dict = get_monthly_results(
"Sales Invoice", "base_grand_total", "posting_date", filter_str, "sum"
"Sales Invoice", "base_grand_total", "posting_date", filter_dict, "sum"
)
frappe.db.set_value("Company", company, "sales_monthly_history", json.dumps(month_to_value_dict))

View File

@@ -24,6 +24,7 @@ frappe.ui.form.on("Driver", {
transporter: function (frm, cdt, cdn) {
// this assumes that supplier's address has same title as supplier's name
if (!frm.doc.transporter) return;
frappe.db
.get_doc("Address", null, { address_title: frm.doc.transporter })
.then((r) => {

View File

@@ -814,7 +814,8 @@
"depends_on": "eval:doc.salary_mode == 'Bank'",
"fieldname": "iban",
"fieldtype": "Data",
"label": "IBAN"
"label": "IBAN",
"options": "IBAN"
}
],
"icon": "fa fa-user",
@@ -822,7 +823,7 @@
"image_field": "image",
"is_tree": 1,
"links": [],
"modified": "2025-07-04 08:29:34.347269",
"modified": "2025-08-29 11:52:12.819878",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
@@ -864,6 +865,7 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "employee_name",
"show_name_in_global_search": 1,
"sort_field": "modified",
@@ -871,4 +873,4 @@
"states": [],
"title_field": "employee_name",
"track_changes": 1
}
}

View File

@@ -1310,6 +1310,9 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
if source.get("use_serial_batch_fields"):
target.set("use_serial_batch_fields", 1)
if (source.get("serial_no") or source.get("batch_no")) and not source.get("serial_and_batch_bundle"):
target.set("use_serial_batch_fields", 1)
doclist = get_mapped_doc(
doctype,
source_name,

View File

@@ -539,7 +539,7 @@ frappe.ui.form.on("Stock Entry", {
const item = locals[cdt][cdn];
item.transfer_qty = flt(item.qty) * flt(item.conversion_factor);
const args = {
let args = {
item_code: item.item_code,
posting_date: frm.doc.posting_date,
posting_time: frm.doc.posting_time,
@@ -553,6 +553,10 @@ frappe.ui.form.on("Stock Entry", {
allow_zero_valuation: 1,
};
if (item.batch_no && frm.doc.purpose == "Material Receipt") {
args.qty = Math.abs(args.qty) * -1;
}
if (item.item_code || item.serial_no) {
frappe.call({
method: "erpnext.stock.utils.get_incoming_rate",

View File

@@ -7,7 +7,10 @@ frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Stock Reconciliation", {
setup(frm) {
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
frm.barcode_scanner = new erpnext.utils.BarcodeScanner({ frm });
frm.barcode_scanner = new erpnext.utils.BarcodeScanner({
frm: frm,
uom_field: "stock_uom",
});
},
onload: function (frm) {

View File

@@ -36,17 +36,6 @@
"show_barcode_field",
"clean_description_html",
"allow_internal_transfer_at_arms_length_price",
"quality_inspection_settings_section",
"action_if_quality_inspection_is_not_submitted",
"column_break_23",
"action_if_quality_inspection_is_rejected",
"stock_reservation_tab",
"enable_stock_reservation",
"column_break_rx3e",
"allow_partial_reservation",
"auto_reserve_stock_for_sales_order_on_purchase",
"serial_and_batch_reservation_section",
"auto_reserve_serial_and_batch",
"serial_and_batch_item_settings_tab",
"section_break_7",
"allow_existing_serial_no",
@@ -548,8 +537,8 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-05-06 02:39:24.284587",
"modified_by": "Administrator",
"modified": "2025-10-17 18:32:35.829395",
"modified_by": "hello@aerele.in",
"module": "Stock",
"name": "Stock Settings",
"owner": "Administrator",
@@ -569,8 +558,9 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -24,24 +24,26 @@ frappe.query_reports["Incorrect Serial and Batch Bundle"] = {
},
onload(report) {
report.page.add_inner_button(__("Remove SABB Entry"), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
report.page
.add_inner_button(__("Fix SABB Entry"), () => {
let indexes = frappe.query_report.datatable.rowmanager.getCheckedRows();
let selected_rows = indexes.map((i) => frappe.query_report.data[i]);
if (!selected_rows.length) {
frappe.throw(__("Please select a row to create a Reposting Entry"));
} else {
frappe.call({
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.remove_sabb_entry",
freeze: true,
args: {
selected_rows: selected_rows,
},
callback: function (r) {
frappe.query_report.refresh();
},
});
}
});
if (!selected_rows.length) {
frappe.throw(__("Please select at least one row to fix"));
} else {
frappe.call({
method: "erpnext.stock.report.incorrect_serial_and_batch_bundle.incorrect_serial_and_batch_bundle.fix_sabb_entries",
freeze: true,
args: {
selected_rows: selected_rows,
},
callback: function (r) {
frappe.query_report.refresh();
},
});
}
})
.addClass("btn-primary");
},
};

View File

@@ -13,7 +13,10 @@ def execute(filters: dict | None = None):
every time the report is refreshed or a filter is updated.
"""
columns = get_columns()
data = get_data(filters)
unlinked_bundles = get_unlinked_serial_batch_bundles(filters) or []
linked_cancelled_bundles = get_linked_cancelled_sabb(filters) or []
data = unlinked_bundles + linked_cancelled_bundles
return columns, data
@@ -50,14 +53,17 @@ def get_columns() -> list[dict]:
"fieldtype": "Data",
"width": 200,
},
{
"label": _("Is Cancelled"),
"fieldname": "is_cancelled",
"fieldtype": "Check",
"width": 200,
},
]
def get_data(filters) -> list[list]:
"""Return data for the report.
The report data is a list of rows, with each row being a list of cell values.
"""
def get_unlinked_serial_batch_bundles(filters) -> list[list]:
# SABB has not been linked to any SLE
SABB = frappe.qb.DocType("Serial and Batch Bundle")
SLE = frappe.qb.DocType("Stock Ledger Entry")
@@ -77,6 +83,7 @@ def get_data(filters) -> list[list]:
SABB.voucher_type,
SABB.voucher_no,
SABB.voucher_detail_no,
SABB.is_cancelled,
)
.where(
(SLE.serial_and_batch_bundle.isnull())
@@ -94,14 +101,63 @@ def get_data(filters) -> list[list]:
return data
def get_linked_cancelled_sabb(filters):
# SABB has cancelled but voucher is not cancelled
SABB = frappe.qb.DocType("Serial and Batch Bundle")
SLE = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(SABB)
.inner_join(SLE)
.on(SABB.name == SLE.serial_and_batch_bundle)
.select(
SABB.name,
SABB.voucher_type,
SABB.voucher_no,
SABB.voucher_detail_no,
SABB.is_cancelled,
)
.where(
(SLE.serial_and_batch_bundle.isnotnull())
& (SABB.docstatus == 2)
& (SABB.is_cancelled == 1)
& (SLE.is_cancelled == 0)
)
)
for field in filters:
query = query.where(SABB[field] == filters[field])
data = query.run(as_dict=1)
return data
@frappe.whitelist()
def remove_sabb_entry(selected_rows):
def fix_sabb_entries(selected_rows):
if isinstance(selected_rows, str):
selected_rows = frappe.parse_json(selected_rows)
for row in selected_rows:
doc = frappe.get_doc("Serial and Batch Bundle", row.get("name"))
doc.cancel()
doc.delete()
if doc.is_cancelled == 0 and not frappe.db.get_value(
"Stock Ledger Entry",
{"serial_and_batch_bundle": doc.name, "is_cancelled": 0},
"name",
):
doc.db_set({"is_cancelled": 1, "docstatus": 2})
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been removed."))
for row in doc.entries:
row.db_set("docstatus", 2)
elif doc.is_cancelled == 1 and frappe.db.get_value(
"Stock Ledger Entry",
{"serial_and_batch_bundle": doc.name, "is_cancelled": 0},
"name",
):
doc.db_set({"is_cancelled": 0, "docstatus": 1})
for row in doc.entries:
row.db_set("docstatus", 1)
frappe.msgprint(_("Selected Serial and Batch Bundle entries have been fixed."))

View File

@@ -18,16 +18,41 @@ def execute(filters=None):
def get_data(filters):
data = get_stock_ledger_entries(filters)
serial_nos_data = prepare_serial_nos(data)
bundles = get_bundles(data)
serial_nos_data = prepare_serial_nos(data, bundles)
data = get_incorrect_serial_nos(serial_nos_data)
return data
def prepare_serial_nos(data):
def get_bundles(data):
bundles = [d.serial_and_batch_bundle for d in data if d.serial_and_batch_bundle]
bundle_dict = frappe._dict()
serial_nos_data = frappe.get_all(
"Serial and Batch Entry",
fields=["parent", "serial_no", "incoming_rate", "qty"],
filters={"parent": ("in", bundles), "serial_no": ("is", "set")},
)
for entry in serial_nos_data:
bundle_dict.setdefault(entry.parent, []).append(entry)
return bundle_dict
def prepare_serial_nos(data, bundles):
serial_no_wise_data = {}
for row in data:
if not row.serial_nos:
if not row.serial_nos and not row.serial_and_batch_bundle:
continue
if row.serial_and_batch_bundle:
for bundle in bundles.get(row.serial_and_batch_bundle, []):
sle = copy.deepcopy(row)
sle.serial_no = bundle.serial_no
sle.qty = bundle.qty
sle.valuation_rate = bundle.incoming_rate * (1 if sle.qty > 0 else -1)
serial_no_wise_data.setdefault(bundle.serial_no, []).append(sle)
continue
for serial_no in get_serial_nos(row.serial_nos):
@@ -54,6 +79,9 @@ def get_incorrect_serial_nos(serial_nos_data):
total_value.qty += total_dict.qty
total_value.valuation_rate += total_dict.valuation_rate
if total_dict.qty == 0 and abs(total_dict.valuation_rate) == 0:
continue
result.append(total_dict)
result.append({})
@@ -81,6 +109,7 @@ def get_stock_ledger_entries(report_filters):
"voucher_no",
"item_code",
"serial_no as serial_nos",
"serial_and_batch_bundle",
"actual_qty",
"posting_date",
"posting_time",
@@ -89,7 +118,8 @@ def get_stock_ledger_entries(report_filters):
"(stock_value_difference / actual_qty) as valuation_rate",
]
filters = {"serial_no": ("is", "set"), "is_cancelled": 0}
filters = {"is_cancelled": 0}
or_filters = {"serial_no": ("is", "set"), "serial_and_batch_bundle": ("is", "set")}
if report_filters.get("item_code"):
filters["item_code"] = report_filters.get("item_code")
@@ -104,6 +134,7 @@ def get_stock_ledger_entries(report_filters):
"Stock Ledger Entry",
fields=fields,
filters=filters,
or_filters=or_filters,
order_by="posting_date asc, posting_time asc, creation asc",
)

View File

@@ -731,13 +731,55 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
for ledger in entries:
self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate)
self.available_qty[ledger.batch_no] += flt(ledger.qty)
self.total_qty[ledger.batch_no] += flt(ledger.total_qty)
entries = self.get_batch_wise_total_available_qty()
for row in entries:
self.total_qty[row.batch_no] += flt(row.total_qty)
self.calculate_avg_rate_from_deprecarated_ledgers()
self.calculate_avg_rate_for_non_batchwise_valuation()
self.set_stock_value_difference()
def get_batch_wise_total_available_qty(self) -> list[dict]:
# Get total qty of each batch no from Serial and Batch Bundle without checking time condition
if not self.batchwise_valuation_batches:
return []
parent = frappe.qb.DocType("Serial and Batch Bundle")
child = frappe.qb.DocType("Serial and Batch Entry")
query = (
frappe.qb.from_(parent)
.inner_join(child)
.on(parent.name == child.parent)
.select(
child.batch_no,
Sum(child.qty).as_("total_qty"),
)
.where(
(parent.warehouse == self.sle.warehouse)
& (parent.item_code == self.sle.item_code)
& (child.batch_no.isin(self.batchwise_valuation_batches))
& (parent.docstatus == 1)
& (parent.is_cancelled == 0)
& (parent.type_of_transaction.isin(["Inward", "Outward"]))
)
.for_update()
.groupby(child.batch_no)
)
# Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference
if self.sle.voucher_detail_no:
query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no)
elif self.sle.voucher_no:
query = query.where(parent.voucher_no != self.sle.voucher_no)
query = query.where(parent.voucher_type != "Pick List")
return query.run(as_dict=True)
def get_batch_no_ledgers(self) -> list[dict]:
# Get batch wise stock value difference from Serial and Batch Bundle considering time condition
if not self.batchwise_valuation_batches:
return []
@@ -765,11 +807,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
.on(parent.name == child.parent)
.select(
child.batch_no,
Sum(Case().when(timestamp_condition, child.stock_value_difference).else_(0)).as_(
"incoming_rate"
),
Sum(Case().when(timestamp_condition, child.qty).else_(0)).as_("qty"),
Sum(child.qty).as_("total_qty"),
Sum(child.stock_value_difference).as_("incoming_rate"),
Sum(child.qty).as_("qty"),
)
.where(
(parent.warehouse == self.sle.warehouse)
@@ -790,6 +829,8 @@ class BatchNoValuation(DeprecatedBatchNoValuation):
query = query.where(parent.voucher_no != self.sle.voucher_no)
query = query.where(parent.voucher_type != "Pick List")
if timestamp_condition:
query = query.where(timestamp_condition)
return query.run(as_dict=True)

View File

@@ -893,10 +893,19 @@ class update_entries_after:
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
sle.stock_value_difference = stock_value_difference
if sle.is_adjustment_entry and flt(sle.qty_after_transaction, self.flt_precision) == 0:
if (
sle.is_adjustment_entry
and flt(sle.qty_after_transaction, self.flt_precision) == 0
and flt(sle.stock_value, self.currency_precision) != 0
):
sle.stock_value_difference = (
get_stock_value_difference(
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
sle.item_code,
sle.warehouse,
sle.posting_date,
sle.posting_time,
voucher_detail_no=sle.voucher_detail_no,
creation=sle.creation,
)
* -1
)
@@ -2353,24 +2362,32 @@ def is_internal_transfer(sle):
return True
def get_stock_value_difference(item_code, warehouse, posting_date, posting_time, voucher_no=None):
def get_stock_value_difference(
item_code, warehouse, posting_date, posting_time, voucher_no=None, voucher_detail_no=None, creation=None
):
table = frappe.qb.DocType("Stock Ledger Entry")
posting_datetime = get_combine_datetime(posting_date, posting_time)
query = (
frappe.qb.from_(table)
.select(Sum(table.stock_value_difference).as_("value"))
.where(
(table.is_cancelled == 0)
& (table.item_code == item_code)
& (table.warehouse == warehouse)
& (table.posting_datetime <= posting_datetime)
)
.where((table.is_cancelled == 0) & (table.item_code == item_code) & (table.warehouse == warehouse))
)
if voucher_no:
if voucher_detail_no:
query = query.where(table.voucher_detail_no != voucher_detail_no)
elif voucher_no:
query = query.where(table.voucher_no != voucher_no)
if creation:
query = query.where(
(table.posting_datetime < posting_datetime)
| ((table.posting_datetime == posting_datetime) & (table.creation < creation))
)
else:
query = query.where(table.posting_datetime <= posting_datetime)
difference_amount = query.run()
return flt(difference_amount[0][0]) if difference_amount else 0

View File

@@ -484,10 +484,16 @@ def get_documents_with_active_service_level_agreement():
def set_documents_with_active_service_level_agreement():
active = frozenset(
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
)
frappe.cache.set_value("doctypes_with_active_sla", active)
try:
active = frozenset(
sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
)
frappe.cache.set_value("doctypes_with_active_sla", active)
except (frappe.DoesNotExistError, frappe.db.TableMissingError):
# This happens during install / uninstall when wildcard hook for SLA intercepts some doc action.
# In both cases, the error can be safely ignored.
active = frozenset()
return active