Compare commits

..

220 Commits

Author SHA1 Message Date
Frappe PR Bot
616448f9c9 chore(release): Bumped to Version 14.92.2
## [14.92.2](https://github.com/frappe/erpnext/compare/v14.92.1...v14.92.2) (2025-10-21)

### Bug Fixes

* do reposting of first transfer entry based on item-wh combination ([85c509e](85c509ea69))
* handle flt conversion for prev_ordered_qty ([9460568](94605687c7))
2025-10-21 12:45:22 +00:00
Diptanil Saha
70a8c64ea4 Merge pull request #50181 from frappe/version-14-hotfix
chore: release v14
2025-10-21 18:13:40 +05:30
mergify[bot]
00a75d4984 refactor: simplify expression (backport #50168) (#50169)
Co-authored-by: barredterra <14891507+barredterra@users.noreply.github.com>
2025-10-19 23:59:31 +02:00
rohitwaghchaure
8b826c2369 Merge pull request #50121 from frappe/mergify/bp/version-14-hotfix/pr-50061
fix: do reposting of first transfer entry based on item-wh combination (backport #50061)
2025-10-16 15:02:52 +05:30
rohitwaghchaure
210786119d chore: fix conflicts 2025-10-16 00:17:50 +05:30
Rohit Waghchaure
85c509ea69 fix: do reposting of first transfer entry based on item-wh combination
(cherry picked from commit 2f25b445ab)

# Conflicts:
#	erpnext/stock/stock_ledger.py
2025-10-15 18:44:46 +00:00
Mihir Kandoi
6e35b6b6b3 Merge pull request #50105 from frappe/mergify/bp/version-14-hotfix/pr-50078
fix: handle flt conversion for prev_ordered_qty (backport #50078)
2025-10-15 00:15:14 +05:30
Kavin
94605687c7 fix: handle flt conversion for prev_ordered_qty
(cherry picked from commit 77c35ef47f)
2025-10-14 18:10:27 +00:00
Frappe PR Bot
e27518b2d4 chore(release): Bumped to Version 14.92.1
## [14.92.1](https://github.com/frappe/erpnext/compare/v14.92.0...v14.92.1) (2025-10-14)

### Bug Fixes

* sanitize projects field in tasks webform ([#50089](https://github.com/frappe/erpnext/issues/50089)) ([aadc83d](aadc83d0d9))
2025-10-14 14:18:29 +00:00
Diptanil Saha
cf71178af6 Merge pull request #50099 from frappe/version-14-hotfix
chore: release v14
2025-10-14 19:46:52 +05:30
ruthra kumar
002575244f Merge pull request #50097 from frappe/mergify/bp/version-14-hotfix/pr-50089
fix: sanitize projects field in tasks webform (backport #50089)
2025-10-14 19:22:52 +05:30
Akhil Narang
aadc83d0d9 fix: sanitize projects field in tasks webform (#50089)
Signed-off-by: Akhil Narang <me@akhilnarang.dev>
(cherry picked from commit f8b50d3ffa)
2025-10-14 13:08:25 +00:00
Frappe PR Bot
66f1e2a076 chore(release): Bumped to Version 14.92.0
# [14.92.0](https://github.com/frappe/erpnext/compare/v14.91.3...v14.92.0) (2025-10-07)

### Bug Fixes

* **Common Code:** fetch canonical URI from Code List (backport [#49882](https://github.com/frappe/erpnext/issues/49882)) ([#49883](https://github.com/frappe/erpnext/issues/49883)) ([9b5183e](9b5183e0e5))
* do not fetch disabled item tax template ([dcae024](dcae024fd2))
* linter; dont change doc after DB update (backport [#49907](https://github.com/frappe/erpnext/issues/49907)) ([#49909](https://github.com/frappe/erpnext/issues/49909)) ([30a6fe5](30a6fe55ca))
* resolved conflict ([2c383a6](2c383a69f9))
* Set paid amount automatically only if return entry validated and has negative grand total ([#49829](https://github.com/frappe/erpnext/issues/49829)) ([b7d76d8](b7d76d8fad))

### Features

* dynamic due date in payment terms when fetched from order (backport [#48864](https://github.com/frappe/erpnext/issues/48864)) ([#49937](https://github.com/frappe/erpnext/issues/49937)) ([d82106c](d82106cb76))
2025-10-07 13:18:52 +00:00
ruthra kumar
1d5935a10f chore: release v14 (#49943)
* refactor(Supplier): custom buttons call make methods (backport #49840) (#49841)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* fix(Common Code): fetch canonical URI from Code List (backport #49882) (#49883)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Common Code): fetch canonical URI from Code List (#49882)

* fix: Set paid amount automatically only if return entry validated and has negative grand total (#49829)

(cherry picked from commit dcbcc596f2)

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

* fix: resolved conflict

* fix: linter; dont change doc after DB update (backport #49907) (#49909)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
fix: linter; dont change doc after DB update (#49907)

* fix: do not fetch disabled item tax template

(cherry picked from commit b10cf4a928)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
#	erpnext/stock/get_item_details.py

* chore: fix conflicts

* chore: fix conflicts

* chore: fix linters issue

* feat: dynamic due date in payment terms when fetched from order (backport #48864) (#49937)

* feat: dynamic due date in payment terms when fetched from order (#48864)

* fix: dynamic due date when payment terms are fetched from order

* fix(test): use change_settings decorator for settings enable and disable

* fix(test): compare schedule for due_date dynamically

* fix: save conditions for due date at invoice level

* fix: make fields read only and on change of date unset the date condition fields

* fix: remove fetch_form

* fix: correct field assingment

* fix: revert unwanted changes

* refactor: streamline payment term field assignments and enhance discount date handling

* refactor: remove payment_term from fields_to_copy and optimize currency handling in transaction callback

* refactor: ensure default values for payment schedule and discount validity fields

(cherry picked from commit 3c70cbbaf8)

# Conflicts:
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.json
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.py
#	erpnext/public/js/controllers/transaction.js
#	erpnext/selling/doctype/sales_order/test_sales_order.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: resolve conflicts

---------

Co-authored-by: Lakshit Jain <ljain112@gmail.com>

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
Co-authored-by: ljain112 <ljain112@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-10-07 18:47:23 +05:30
mergify[bot]
d82106cb76 feat: dynamic due date in payment terms when fetched from order (backport #48864) (#49937)
* feat: dynamic due date in payment terms when fetched from order (#48864)

* fix: dynamic due date when payment terms are fetched from order

* fix(test): use change_settings decorator for settings enable and disable

* fix(test): compare schedule for due_date dynamically

* fix: save conditions for due date at invoice level

* fix: make fields read only and on change of date unset the date condition fields

* fix: remove fetch_form

* fix: correct field assingment

* fix: revert unwanted changes

* refactor: streamline payment term field assignments and enhance discount date handling

* refactor: remove payment_term from fields_to_copy and optimize currency handling in transaction callback

* refactor: ensure default values for payment schedule and discount validity fields

(cherry picked from commit 3c70cbbaf8)

# Conflicts:
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.json
#	erpnext/accounts/doctype/payment_schedule/payment_schedule.py
#	erpnext/public/js/controllers/transaction.js
#	erpnext/selling/doctype/sales_order/test_sales_order.py
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: resolve conflicts

---------

Co-authored-by: Lakshit Jain <ljain112@gmail.com>
2025-10-07 14:10:46 +05:30
rohitwaghchaure
03aeac6c9b Merge pull request #49931 from frappe/mergify/bp/version-14-hotfix/pr-49702
fix: do not fetch disabled item tax template (backport #49702)
2025-10-07 11:54:19 +05:30
rohitwaghchaure
014f5bff5c chore: fix linters issue 2025-10-07 11:24:49 +05:30
rohitwaghchaure
819667ab24 chore: fix conflicts 2025-10-07 11:00:45 +05:30
rohitwaghchaure
aa20e224fb chore: fix conflicts 2025-10-07 11:00:02 +05:30
ljain112
dcae024fd2 fix: do not fetch disabled item tax template
(cherry picked from commit b10cf4a928)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
#	erpnext/stock/get_item_details.py
2025-10-07 05:26:09 +00:00
mergify[bot]
30a6fe55ca fix: linter; dont change doc after DB update (backport #49907) (#49909)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
fix: linter; dont change doc after DB update (#49907)
2025-10-06 13:34:24 +02:00
ruthra kumar
57f9894f41 Merge pull request #49892 from frappe/mergify/bp/version-14-hotfix/pr-49829
fix: Set paid amount automatically only if return entry validated and has negative grand total (backport #49829)
2025-10-06 12:58:55 +05:30
Nabin Hait
2c383a69f9 fix: resolved conflict 2025-10-06 12:19:22 +05:30
Nabin Hait
b7d76d8fad fix: Set paid amount automatically only if return entry validated and has negative grand total (#49829)
(cherry picked from commit dcbcc596f2)

# Conflicts:
#	erpnext/public/js/controllers/taxes_and_totals.js
2025-10-06 05:58:16 +00:00
mergify[bot]
9b5183e0e5 fix(Common Code): fetch canonical URI from Code List (backport #49882) (#49883)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Common Code): fetch canonical URI from Code List (#49882)
2025-10-04 19:22:04 +02:00
mergify[bot]
0b43d518af refactor(Supplier): custom buttons call make methods (backport #49840) (#49841)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-10-02 00:16:20 +02:00
Frappe PR Bot
a6b490e4b3 chore(release): Bumped to Version 14.91.3
## [14.91.3](https://github.com/frappe/erpnext/compare/v14.91.2...v14.91.3) (2025-09-30)

### Performance Improvements

* reposting for backdated transactions ([3d51c1b](3d51c1b363))
2025-09-30 13:07:25 +00:00
ruthra kumar
a5d33d5665 Merge pull request #49802 from frappe/version-14-hotfix
chore: release v14
2025-09-30 18:36:03 +05:30
Frappe PR Bot
48c3c80383 chore(release): Bumped to Version 14.91.2
## [14.91.2](https://github.com/frappe/erpnext/compare/v14.91.1...v14.91.2) (2025-09-25)

### Performance Improvements

* reposting for backdated transactions ([f3da431](f3da431f1f))
2025-09-25 14:45:52 +00:00
rohitwaghchaure
a9a68f2523 Merge pull request #49726 from frappe/mergify/bp/version-14/pr-49723
perf: reposting for backdated transactions (backport #49720) (backport #49723)
2025-09-25 20:14:23 +05:30
Rohit Waghchaure
f3da431f1f perf: reposting for backdated transactions
(cherry picked from commit 1b0fc0541b)
(cherry picked from commit 3d51c1b363)
2025-09-25 13:22:15 +00:00
rohitwaghchaure
14e92d589f Merge pull request #49723 from frappe/mergify/bp/version-14-hotfix/pr-49720
perf: reposting for backdated transactions (backport #49720)
2025-09-25 18:50:18 +05:30
Rohit Waghchaure
3d51c1b363 perf: reposting for backdated transactions
(cherry picked from commit 1b0fc0541b)
2025-09-25 12:16:23 +00:00
Frappe PR Bot
825ef415ef chore(release): Bumped to Version 14.91.1
## [14.91.1](https://github.com/frappe/erpnext/compare/v14.91.0...v14.91.1) (2025-09-23)

### Bug Fixes

* add condition for name ([0c7911d](0c7911d0fe))
2025-09-23 13:35:10 +00:00
ruthra kumar
f6148c4352 chore: release v14 (#49696)
* fix: add condition for name

(cherry picked from commit cf5a2d6351)

* test: add test to validate user permission in qb

(cherry picked from commit a5b881ea74)

# Conflicts:
#	erpnext/setup/doctype/employee/test_employee.py

---------

Co-authored-by: venkat102 <venkatesharunachalam659@gmail.com>
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-09-23 19:03:40 +05:30
ruthra kumar
3c408ff66b Merge pull request #49616 from frappe/mergify/bp/version-14-hotfix/pr-49467
fix: add condition for name (backport #49467)
2025-09-22 10:49:01 +05:30
venkat102
f6bc9fc505 test: add test to validate user permission in qb
(cherry picked from commit a5b881ea74)

# Conflicts:
#	erpnext/setup/doctype/employee/test_employee.py
2025-09-18 16:47:10 +05:30
venkat102
0c7911d0fe fix: add condition for name
(cherry picked from commit cf5a2d6351)
2025-09-18 11:12:13 +00:00
Frappe PR Bot
b8ecc4274f chore(release): Bumped to Version 14.91.0
# [14.91.0](https://github.com/frappe/erpnext/compare/v14.90.1...v14.91.0) (2025-09-16)

### Bug Fixes

* duplicate items being created when fetching items from warehouse in stock reco ([646de0b](646de0bec1))
* remove ignore_permissions ([180f406](180f406917))

### Features

* add permission check for custom button ([42b38b7](42b38b7998))
2025-09-16 14:41:37 +00:00
ruthra kumar
a9b7bc1385 Merge pull request #49565 from frappe/version-14-hotfix
chore: release v14
2025-09-16 20:08:58 +05:30
Mihir Kandoi
d062e50f35 Merge pull request #49515 from frappe/mergify/bp/version-14-hotfix/pr-48456 2025-09-10 10:06:48 +05:30
Mihir Kandoi
646de0bec1 fix: duplicate items being created when fetching items from warehouse in stock reco
(cherry picked from commit 73f6c29559)
2025-09-10 03:01:06 +00:00
Raffael Meyer
02de47c673 Merge pull request #49434 from frappe/mergify/bp/version-14-hotfix/pr-49374
fix: improve permission handling for party link creation (backport #49374)
2025-09-03 17:12:27 +02:00
Marc-Constantin Enke
42b38b7998 feat: add permission check for custom button
(cherry picked from commit 00fd1d2f26)
2025-09-02 17:20:31 +00:00
Marc-Constantin Enke
180f406917 fix: remove ignore_permissions
(cherry picked from commit 7f55f421ab)
2025-09-02 17:20:31 +00:00
Frappe PR Bot
90803b852e chore(release): Bumped to Version 14.90.1
## [14.90.1](https://github.com/frappe/erpnext/compare/v14.90.0...v14.90.1) (2025-09-02)

### Bug Fixes

* add is_cancelled in condition ([b9f9be3](b9f9be3d88))
* ignore cancelled gl and add company filter ([6b29c06](6b29c06511))
* set missing due date in Purchase Invoice and POS Invoice ([#49232](https://github.com/frappe/erpnext/issues/49232)) ([2b6b0b3](2b6b0b32a7))
2025-09-02 13:48:57 +00:00
ruthra kumar
4cbde47ac4 Merge pull request #49424 from frappe/version-14-hotfix
chore: release v14
2025-09-02 19:17:38 +05:30
ruthra kumar
0b3215cd42 Merge pull request #49416 from frappe/mergify/bp/version-14-hotfix/pr-49379
fix: add is_cancelled in condition (backport #49379)
2025-09-02 11:16:44 +05:30
l0gesh29
b9f9be3d88 fix: add is_cancelled in condition
(cherry picked from commit 77a9cf6398)
2025-09-02 05:27:23 +00:00
ruthra kumar
faeedb01a8 Merge pull request #49411 from frappe/mergify/bp/version-14-hotfix/pr-49366
fix(trial-balance-simple): ignore cancelled gl and add company filter (backport #49366)
2025-09-02 10:42:36 +05:30
Raffael Meyer
a2402ddf52 Merge pull request #49233 from frappe/mergify/bp/version-14-hotfix/pr-49232
fix: set missing due date in Purchase Invoice and POS Invoice (backport #49232)
2025-09-02 00:49:37 +02:00
l0gesh29
6b29c06511 fix: ignore cancelled gl and add company filter
(cherry picked from commit afb067ce50)
2025-09-01 11:16:22 +00:00
Frappe PR Bot
2cedd455d9 chore(release): Bumped to Version 14.90.0
# [14.90.0](https://github.com/frappe/erpnext/compare/v14.89.2...v14.90.0) (2025-08-26)

### Bug Fixes

* **dunning:** include accounting dimension upon gl creation ([4fccef0](4fccef0636))
* **payment_entry:** clear party_type when switching to Internal Transfer to avoid 'Supplier None not found' ([e27dd64](e27dd64044))
* **Supplier:** add make_method for Pricing Rule (backport [#49282](https://github.com/frappe/erpnext/issues/49282)) ([#49283](https://github.com/frappe/erpnext/issues/49283)) ([7986e69](7986e69839))

### Features

* add make methods for Bank Account (backport [#49000](https://github.com/frappe/erpnext/issues/49000)) ([#49274](https://github.com/frappe/erpnext/issues/49274)) ([836545b](836545bdb4))
2025-08-26 11:35:26 +00:00
ruthra kumar
ba31f685f8 Merge pull request #49327 from frappe/version-14-hotfix
chore: release v14
2025-08-26 17:01:27 +05:30
ruthra kumar
507d27d509 Merge pull request #49309 from niyati34/fix/payment-entry-clear-party-type-v14hotfix
Fix/payment entry clear party type v14hotfix
2025-08-26 13:27:38 +05:30
niyati34
e27dd64044 fix(payment_entry): clear party_type when switching to Internal Transfer to avoid 'Supplier None not found' 2025-08-25 19:49:05 +05:30
ruthra kumar
7f617a54d1 Merge pull request #49278 from aerele/dunning-accouting-dimension
fix(dunning): include accounting dimension upon gl creation
2025-08-25 16:56:35 +05:30
mergify[bot]
7986e69839 fix(Supplier): add make_method for Pricing Rule (backport #49282) (#49283)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Supplier): add make_method for Pricing Rule (#49282)
2025-08-22 19:26:10 +02:00
mergify[bot]
836545bdb4 feat: add make methods for Bank Account (backport #49000) (#49274)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2025-08-22 19:17:44 +02:00
ravibharathi656
4fccef0636 fix(dunning): include accounting dimension upon gl creation 2025-08-22 12:34:39 +05:30
Raffael Meyer
2b6b0b32a7 fix: set missing due date in Purchase Invoice and POS Invoice (#49232)
(cherry picked from commit 77478303fe)
2025-08-19 12:30:46 +00:00
Frappe PR Bot
dac192a210 chore(release): Bumped to Version 14.89.2
## [14.89.2](https://github.com/frappe/erpnext/compare/v14.89.1...v14.89.2) (2025-08-19)

### Bug Fixes

* formatted string for disabled filter in get_income_account ([1c24bed](1c24bedaf6))
* handle empty loyalty point details ([0d6be69](0d6be69ce5))
* include missing cint import ([ea454ef](ea454ef770))
* sanitize column name for inventory_dimensions in get_stock_balance ([c3ebc39](c3ebc39f3b))
* use query builder instead of raw SQL in get_blanket_orders ([9d06e09](9d06e093a6))
* use query builder instead of raw SQL in get_loyalty_details ([64e6d65](64e6d65c00))
* use query builder instead of raw SQL in get_material_requests_based_on_supplier ([5a0e690](5a0e690307))
* use query builder instead of raw SQL in get_rfq_containing_supplier ([4b643fe](4b643feb92))
* use query builder instead of raw SQL in get_timesheet_detail_rate ([22d7bbb](22d7bbb081))
* use query builder instead of raw SQL in unset_existing_data ([06f04ca](06f04cad3e))
2025-08-19 11:53:42 +00:00
ruthra kumar
8421e09a95 Merge pull request #49226 from frappe/version-14-hotfix
chore: release v14
2025-08-19 17:22:16 +05:30
Diptanil Saha
43a723546d Merge pull request #49220 from frappe/mergify/bp/version-14-hotfix/pr-49192
fix: improve queries with query builder and input sanitization (backport #49192)
2025-08-19 11:51:32 +05:30
diptanilsaha
ea454ef770 fix: include missing cint import 2025-08-19 11:27:55 +05:30
diptanilsaha
0d6be69ce5 fix: handle empty loyalty point details
(cherry picked from commit 1231ca17c9)
2025-08-19 05:25:23 +00:00
diptanilsaha
22d7bbb081 fix: use query builder instead of raw SQL in get_timesheet_detail_rate
(cherry picked from commit e563ed0c75)
2025-08-19 05:25:23 +00:00
diptanilsaha
4b643feb92 fix: use query builder instead of raw SQL in get_rfq_containing_supplier
(cherry picked from commit 7f2a52ff71)
2025-08-19 05:25:22 +00:00
diptanilsaha
06f04cad3e fix: use query builder instead of raw SQL in unset_existing_data
(cherry picked from commit 7fa4ed6139)
2025-08-19 05:25:22 +00:00
diptanilsaha
c3ebc39f3b fix: sanitize column name for inventory_dimensions in get_stock_balance
(cherry picked from commit eb22794f14)
2025-08-19 05:25:22 +00:00
diptanilsaha
9d06e093a6 fix: use query builder instead of raw SQL in get_blanket_orders
(cherry picked from commit 1db135262d)
2025-08-19 05:25:22 +00:00
diptanilsaha
1c24bedaf6 fix: formatted string for disabled filter in get_income_account
(cherry picked from commit 6320f7290f)
2025-08-19 05:25:21 +00:00
diptanilsaha
5a0e690307 fix: use query builder instead of raw SQL in get_material_requests_based_on_supplier
(cherry picked from commit de919568b4)
2025-08-19 05:25:21 +00:00
diptanilsaha
64e6d65c00 fix: use query builder instead of raw SQL in get_loyalty_details
(cherry picked from commit 8696ba2f5d)
2025-08-19 05:25:21 +00:00
Frappe PR Bot
e7915b31ce chore(release): Bumped to Version 14.89.1
## [14.89.1](https://github.com/frappe/erpnext/compare/v14.89.0...v14.89.1) (2025-08-12)

### Bug Fixes

* add condition to fetch active accounts ([8fc8aa2](8fc8aa2dfd))
* nonetype error on applying presentation_currency filter on financial statements and trial balance report ([89d00ee](89d00ee4a2))
* **purchase invoice:** filter only enabled account ([e0d9a47](e0d9a47ff7))
* **tax withholding details:** avoid voucher duplication ([92ee871](92ee871b79))
* timeout while submitting purchase receipt ([06c3839](06c3839abc))
2025-08-12 12:02:50 +00:00
ruthra kumar
b4200c5453 Merge pull request #49117 from frappe/version-14-hotfix
chore: release v14
2025-08-12 17:31:29 +05:30
ruthra kumar
2f4bfa77b2 Merge pull request #48998 from frappe/mergify/bp/version-14-hotfix/pr-48761
fix: prevent gain or loss entry cancellation upon reposting (backport #48761)
2025-08-12 16:44:09 +05:30
Vignesh S
5eeaaf4757 chore: fix text case failure casued by mergify 2025-08-12 16:25:59 +05:30
ruthra kumar
8faabb8110 Merge pull request #49115 from frappe/mergify/bp/version-14-hotfix/pr-48909
fix(tax withholding details): avoid voucher duplication (backport #48909)
2025-08-12 16:07:00 +05:30
ravibharathi656
92ee871b79 fix(tax withholding details): avoid voucher duplication
(cherry picked from commit 8837016243)
2025-08-12 09:31:36 +00:00
MochaMind
67dad765dc chore: release v14 (#49057)
* fix: timeout while submitting purchase receipt

(cherry picked from commit c433943c46)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.json

* chore: fix conflicts

* fix: nonetype error on applying presentation_currency filter on financial statements and trial balance report

(cherry picked from commit d7e22de44c)

* fix(purchase invoice): filter only enabled account

(cherry picked from commit c3111db6e2)

* fix: add condition to fetch active accounts

(cherry picked from commit 7c8dd86a35)

* chore: add back filter

(cherry picked from commit 23308f6d10)

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
Co-authored-by: diptanilsaha <diptanil@frappe.io>
Co-authored-by: mithili <mithili15602@gamil.com>
Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-08-11 11:50:29 +05:30
Mihir Kandoi
25ff9e0a4d Merge pull request #49048 from frappe/mergify/bp/version-14-hotfix/pr-48813
fix(purchase invoice): filter only enabled account (backport #48813)
2025-08-08 12:00:55 +05:30
mithili
a230a0d94c chore: add back filter
(cherry picked from commit 23308f6d10)
2025-08-08 05:25:01 +00:00
mithili
8fc8aa2dfd fix: add condition to fetch active accounts
(cherry picked from commit 7c8dd86a35)
2025-08-08 05:25:01 +00:00
mithili
e0d9a47ff7 fix(purchase invoice): filter only enabled account
(cherry picked from commit c3111db6e2)
2025-08-08 05:25:01 +00:00
rohitwaghchaure
9d0034f0a4 Merge pull request #49018 from frappe/mergify/bp/version-14-hotfix/pr-49010
fix: timeout while submitting purchase receipt (backport #49010)
2025-08-07 12:28:30 +05:30
Diptanil Saha
f3d780c5bc Merge pull request #49024 from frappe/mergify/bp/version-14-hotfix/pr-49023
fix: NoneType error on applying presentation_currency filter on financial statements and trial balance report (backport #49023)
2025-08-07 02:31:03 +05:30
diptanilsaha
89d00ee4a2 fix: nonetype error on applying presentation_currency filter on financial statements and trial balance report
(cherry picked from commit d7e22de44c)
2025-08-06 20:41:33 +00:00
rohitwaghchaure
22e63099ee chore: fix conflicts 2025-08-06 22:24:45 +05:30
Rohit Waghchaure
06c3839abc fix: timeout while submitting purchase receipt
(cherry picked from commit c433943c46)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
2025-08-06 14:35:43 +00:00
Frappe PR Bot
a7d13d580c chore(release): Bumped to Version 14.89.0
# [14.89.0](https://github.com/frappe/erpnext/compare/v14.88.4...v14.89.0) (2025-08-06)

### Bug Fixes

* add doctype fieldname in condition ([c263e6d](c263e6d67e))
* add flt to prevent NoneType errors ([2c98081](2c98081be6))
* closing balance being added twice to fifo queue ([7abc4c4](7abc4c4022))
* do not split round off when there is a cost center allocation ([388b5fb](388b5fb8f6))
* payment ledger voucher seperator row currencies ([0de4477](0de4477ddf))
* provide company for outstanding record. ([235f271](235f271729))
* provide missing `company` in report records that require reference to `Company:company:default_currency` ([d38fd07](d38fd0779c))
* Use correct Attachments folder in code list import ([f415bc3](f415bc3d14))

### Features

* add show_amount_in_company_currency in gl report ([eb69122](eb691226d5))
2025-08-06 02:35:47 +00:00
ruthra kumar
dd9dc29fe5 Merge pull request #48980 from frappe/version-14-hotfix
chore: release v14
2025-08-06 08:04:22 +05:30
Logesh Periyasamy
b3035ec7d4 Merge pull request #48761 from aerele/exchange-gain-or-loss-on-repost
fix: prevent gain or loss entry cancellation upon reposting
(cherry picked from commit a8d17b7590)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2025-08-06 07:44:42 +05:30
ruthra kumar
0ecc056a3f Merge pull request #48996 from frappe/mergify/bp/version-14-hotfix/pr-48901
fix: do not split round off when there is a cost center allocation (backport #48901)
2025-08-05 20:55:34 +05:30
ravibharathi656
7a9150aac0 test: add test for cost center allocation commercial rounding
(cherry picked from commit dd24cce509)

# Conflicts:
#	erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
2025-08-05 20:35:21 +05:30
venkat102
388b5fb8f6 fix: do not split round off when there is a cost center allocation
(cherry picked from commit f0df41d521)
2025-08-05 20:35:18 +05:30
ruthra kumar
96a1a4a8b8 Merge pull request #48984 from frappe/mergify/bp/version-14-hotfix/pr-48798
feat: add show_amount_in_company_currency in gl report (backport #48798)
2025-08-05 17:44:27 +05:30
l0gesh29
eb691226d5 feat: add show_amount_in_company_currency in gl report
(cherry picked from commit 468e5e9b2e)

# Conflicts:
#	erpnext/accounts/report/general_ledger/general_ledger.js
2025-08-05 17:11:33 +05:30
Mihir Kandoi
00e61a531a Merge pull request #48986 from mihir-kandoi/st45290
fix: closing balance being added twice to fifo queue in stock ageing report
2025-08-05 15:48:38 +05:30
Mihir Kandoi
7abc4c4022 fix: closing balance being added twice to fifo queue 2025-08-05 15:25:25 +05:30
Mihir Kandoi
1ae3fd3e27 Merge pull request #48966 from frappe/mergify/bp/version-14-hotfix/pr-48693
fix: Use correct Attachments folder in code list import (backport #48693)
2025-08-05 14:29:16 +05:30
ruthra kumar
ade44fbdb4 Merge pull request #48970 from frappe/mergify/bp/version-14-hotfix/pr-48861
chore: correct description for `consider_party_ledger_amount` in Tax Withholding Category (backport #48861)
2025-08-05 14:26:25 +05:30
ljain112
82e8fb8a5e chore: correct description for consider_party_ledger_amount in Tax Withholding Category
(cherry picked from commit f619bca2d6)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
2025-08-05 14:24:38 +05:30
Corentin Forler
f415bc3d14 fix: Use correct Attachments folder in code list import
(cherry picked from commit bc2cb1737a)
2025-08-05 08:34:37 +00:00
ruthra kumar
5bc3273460 Merge pull request #48963 from frappe/mergify/bp/version-14-hotfix/pr-48860
fix: add doctype fieldname in condition (backport #48860)
2025-08-05 13:09:37 +05:30
l0gesh29
c263e6d67e fix: add doctype fieldname in condition
(cherry picked from commit e2d63e4c32)

# Conflicts:
#	erpnext/accounts/utils.py
2025-08-05 12:16:33 +05:30
ruthra kumar
11b5a81170 Merge pull request #48927 from pps190/backport/version-14-hotfix/48926
fix: provide missing company in report records that require reference to Company:company:default_currency (Backport #48926)
2025-08-04 10:29:49 +05:30
Mihir Kandoi
27485b9472 Merge pull request #48329 from pps190/backport/version-14-hotfix/34909
Get incoming rate v14 fix (Backport #34909)
2025-08-02 22:09:30 +05:30
Devin Slauenwhite
0de4477ddf fix: payment ledger voucher seperator row currencies 2025-08-02 12:29:52 -04:00
Devin Slauenwhite
235f271729 fix: provide company for outstanding record. 2025-08-02 12:23:32 -04:00
Devin Slauenwhite
d38fd0779c fix: provide missing company in report records that require reference to Company:company:default_currency 2025-08-02 12:03:40 -04:00
Mihir Kandoi
093c6bb17e Merge pull request #48907 from frappe/st45332
fix: add flt to prevent NoneType errors
2025-08-01 17:07:50 +05:30
Mihir Kandoi
2c98081be6 fix: add flt to prevent NoneType errors 2025-08-01 16:39:56 +05:30
Frappe PR Bot
e5c821a822 chore(release): Bumped to Version 14.88.4
## [14.88.4](https://github.com/frappe/erpnext/compare/v14.88.3...v14.88.4) (2025-07-29)

### Bug Fixes

* import get_link_to_form from utils ([b43fbf2](b43fbf2a7d))
* include empty values in user permission ([4d12ae0](4d12ae069e))
* incorrect GL entries ([493c8ce](493c8ce04e))
* post gl entry on completion date of asset repair ([80ae982](80ae9820f0))
* resolved conflicts ([6cdc781](6cdc7811b8))
* resolved conflicts ([4524c74](4524c74968))
2025-07-29 15:11:30 +00:00
ruthra kumar
500ffbab09 Merge pull request #48831 from frappe/version-14-hotfix
chore: release v14
2025-07-29 20:40:08 +05:30
ruthra kumar
451326fdeb Merge pull request #48846 from frappe/mergify/bp/version-14-hotfix/pr-48835
fix: include empty values in user permission (backport #48835)
2025-07-29 20:17:18 +05:30
l0gesh29
4d12ae069e fix: include empty values in user permission
(cherry picked from commit f13d98fc7c)
2025-07-29 14:26:33 +00:00
Khushi Rawat
4297c58e5e Merge pull request #48834 from frappe/mergify/bp/version-14-hotfix/pr-48833
fix: post gl entry on completion date of asset repair (backport #48833)
2025-07-29 16:57:02 +05:30
Khushi Rawat
b43fbf2a7d fix: import get_link_to_form from utils 2025-07-29 15:48:41 +05:30
Khushi Rawat
6cdc7811b8 fix: resolved conflicts 2025-07-29 15:42:00 +05:30
Khushi Rawat
59ac7aeb23 chore: resolved conflicts 2025-07-29 15:41:20 +05:30
Khushi Rawat
4524c74968 fix: resolved conflicts 2025-07-29 15:39:10 +05:30
khushi8112
80ae9820f0 fix: post gl entry on completion date of asset repair
(cherry picked from commit 5a82b723c2)

# Conflicts:
#	erpnext/assets/doctype/asset_repair/asset_repair.json
#	erpnext/assets/doctype/asset_repair/asset_repair.py
#	erpnext/assets/doctype/asset_repair/test_asset_repair.py
2025-07-29 10:07:22 +00:00
rohitwaghchaure
5d92f38bbe Merge pull request #48807 from frappe/mergify/bp/version-14-hotfix/pr-48801
fix: incorrect GL entries (backport #48801)
2025-07-28 17:54:00 +05:30
rohitwaghchaure
a944eaf054 chore: fix conflicts 2025-07-28 16:35:39 +05:30
Rohit Waghchaure
493c8ce04e fix: incorrect GL entries
(cherry picked from commit 4c273fcc99)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2025-07-28 10:58:37 +00:00
Frappe PR Bot
a711cd7a18 chore(release): Bumped to Version 14.88.3
## [14.88.3](https://github.com/frappe/erpnext/compare/v14.88.2...v14.88.3) (2025-07-23)

### Bug Fixes

* add validation for account key ([360f3b7](360f3b7c25))
* calculate due date based on payment term ([#46416](https://github.com/frappe/erpnext/issues/46416)) ([9e7a5bb](9e7a5bb1bb))
* performance issue while submitting the purchase invoice ([4fe09fd](4fe09fd7e8))
* **period closing voucher:** closing account head debit and debit in account currency should be equal ([76a233e](76a233e1b7))
* **production plan:** add company filter to sub assembly warehouse ([8934adf](8934adfef4))
* show amount for exchange gain or loss account ([581221f](581221f1ec))
2025-07-23 02:50:52 +00:00
ruthra kumar
942aca31f2 Merge pull request #48746 from frappe/version-14-hotfix
chore: release v14
2025-07-23 08:19:35 +05:30
ruthra kumar
77ea06dc7d Merge pull request #48740 from frappe/mergify/bp/version-14-hotfix/pr-48665
fix: show amount for exchange gain or loss account (backport #48665)
2025-07-22 12:56:31 +05:30
l0gesh29
360f3b7c25 fix: add validation for account key
(cherry picked from commit b6da350c20)
2025-07-22 06:44:03 +00:00
l0gesh29
581221f1ec fix: show amount for exchange gain or loss account
(cherry picked from commit 4f90f50eb2)
2025-07-22 06:44:03 +00:00
rohitwaghchaure
e0be83caa5 Merge pull request #48728 from frappe/mergify/bp/version-14-hotfix/pr-48704
fix(production plan): add company filter to sub assembly warehouse (backport #48704)
2025-07-21 20:04:07 +05:30
ravibharathi656
8934adfef4 fix(production plan): add company filter to sub assembly warehouse
(cherry picked from commit 1728a95111)
2025-07-21 14:27:40 +00:00
Frappe PR Bot
259251dfab chore(release): Bumped to Version 14.88.2
## [14.88.2](https://github.com/frappe/erpnext/compare/v14.88.1...v14.88.2) (2025-07-17)

### Bug Fixes

* calculate due date based on payment term ([#46416](https://github.com/frappe/erpnext/issues/46416)) ([9ce357f](9ce357f3bf))
2025-07-17 11:18:43 +00:00
rohitwaghchaure
f8c54f0a9b Merge pull request #48660 from frappe/mergify/bp/version-14/pr-48638
fix: calculate due date based on payment term (backport #46416) (backport #48638)
2025-07-17 16:47:21 +05:30
Sudharsanan Ashok
9ce357f3bf fix: calculate due date based on payment term (#46416)
(cherry picked from commit 9e808c832f)
(cherry picked from commit 9e7a5bb1bb)
2025-07-17 10:45:07 +00:00
Marica
2d6f4740be Merge pull request #48638 from frappe/mergify/bp/version-14-hotfix/pr-46416
fix: calculate due date based on payment term (backport #46416)
2025-07-16 17:18:04 +02:00
Frappe PR Bot
a3b38d9a91 chore(release): Bumped to Version 14.88.1
## [14.88.1](https://github.com/frappe/erpnext/compare/v14.88.0...v14.88.1) (2025-07-16)

### Bug Fixes

* **period closing voucher:** closing account head debit and debit in account currency should be equal ([18eea34](18eea3425c))
2025-07-16 15:09:51 +00:00
ruthra kumar
ed2e8c2cc6 Merge pull request #48640 from frappe/mergify/bp/version-14/pr-48615
fix(period closing voucher): closing account head debit and debit in account currency should be equal (backport #48615)
2025-07-16 20:38:16 +05:30
venkat102
18eea3425c fix(period closing voucher): closing account head debit and debit in account currency should be equal
(cherry picked from commit 76a233e1b7)
2025-07-16 14:46:15 +00:00
ruthra kumar
7aaea9f5b6 Merge pull request #48615 from aerele/backport-48612
fix(period closing voucher): closing account head debit and debit in account currency should be equal
2025-07-16 20:13:11 +05:30
Sudharsanan Ashok
9e7a5bb1bb fix: calculate due date based on payment term (#46416)
(cherry picked from commit 9e808c832f)
2025-07-16 14:40:15 +00:00
rohitwaghchaure
27105501fb Merge pull request #48636 from frappe/mergify/bp/version-14-hotfix/pr-48633
fix: performance issue while submitting the purchase invoice (backport #48633)
2025-07-16 17:10:28 +05:30
Rohit Waghchaure
4fe09fd7e8 fix: performance issue while submitting the purchase invoice
(cherry picked from commit 47979871de)
2025-07-16 11:16:37 +00:00
venkat102
76a233e1b7 fix(period closing voucher): closing account head debit and debit in account currency should be equal 2025-07-15 18:24:54 +05:30
Frappe PR Bot
bda447f20a chore(release): Bumped to Version 14.88.0
# [14.88.0](https://github.com/frappe/erpnext/compare/v14.87.0...v14.88.0) (2025-07-15)

### Bug Fixes

* **Employee:** add context to status in List View (backport [#48576](https://github.com/frappe/erpnext/issues/48576)) ([#48578](https://github.com/frappe/erpnext/issues/48578)) ([9dad082](9dad08274a))

### Features

* parent item group support in Stock Projected Qty report ([3b36be2](3b36be214f))
* update the modified date of the SLE after reposting ([8966c95](8966c956d5))
2025-07-15 12:50:14 +00:00
ruthra kumar
7db420cf9f Merge pull request #48601 from frappe/version-14-hotfix
chore: release v14
2025-07-15 18:18:44 +05:30
ruthra kumar
5ddfe3ce1d Merge pull request #48585 from frappe/mergify/bp/version-14-hotfix/pr-47892
refactor: use sql for building voucher balance in Receivable report (backport #47892)
2025-07-15 08:23:20 +05:30
ruthra kumar
ff998572e7 chore: resolve conflicts 2025-07-15 08:03:48 +05:30
ruthra kumar
f364a41490 chore: rename method
(cherry picked from commit fc8ca7d82c)

# Conflicts:
#	erpnext/accounts/report/accounts_receivable/accounts_receivable.py
2025-07-15 01:52:45 +00:00
ruthra kumar
804edad233 refactor: build and pass match conditions as qb criterion
(cherry picked from commit 7efeed54de)

# Conflicts:
#	erpnext/accounts/report/accounts_receivable/accounts_receivable.py
#	erpnext/accounts/utils.py
2025-07-15 01:52:45 +00:00
ruthra kumar
e533158a1c chore: drop unused utility method
(cherry picked from commit 52c0df24e3)
2025-07-15 01:52:45 +00:00
ruthra kumar
5bcf642dff refactor: dynamic DB field types
(cherry picked from commit 9d0ebe3427)

# Conflicts:
#	erpnext/accounts/report/accounts_receivable/accounts_receivable.py
2025-07-15 01:52:44 +00:00
ruthra kumar
d7822f493b refactor: better variable name
(cherry picked from commit 1a90c0d031)
2025-07-15 01:52:44 +00:00
ruthra kumar
5188745c53 refactor: prefix-ed names for easy distinction
(cherry picked from commit c5e35cc330)
2025-07-15 01:52:44 +00:00
ruthra kumar
006a8c71fd refactor: utility to drop existing procedures and include cost center
(cherry picked from commit da32bb5f51)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.js
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
2025-07-15 01:52:44 +00:00
ruthra kumar
ba25d334b1 refactor: order by posting date
(cherry picked from commit 7b7440d44a)
2025-07-15 01:52:43 +00:00
ruthra kumar
dd1020ccfb refactor: call procedures based on config
(cherry picked from commit e90c6a33bd)

# Conflicts:
#	erpnext/accounts/report/accounts_receivable/accounts_receivable.py
2025-07-15 01:52:43 +00:00
ruthra kumar
de2f80811f refactor: introduce sql option for data fetch
(cherry picked from commit 8cf8f6abad)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.py
2025-07-15 01:52:43 +00:00
ruthra kumar
12eaa64572 refactor: better readability
(cherry picked from commit 097e74979f)
2025-07-15 01:52:42 +00:00
ruthra kumar
da1dcb8b21 refactor: using sql procedures for AR report
- dynamic filters are passed

(cherry picked from commit e5920c57aa)

# Conflicts:
#	erpnext/accounts/report/accounts_receivable/accounts_receivable.py
2025-07-15 01:52:42 +00:00
mergify[bot]
9dad08274a fix(Employee): add context to status in List View (backport #48576) (#48578)
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
fix(Employee): add context to status in List View (#48576)
2025-07-14 15:53:42 +02:00
rohitwaghchaure
d8d53bc43e Merge pull request #48496 from frappe/mergify/bp/version-14-hotfix/pr-48490
feat: update the modified date of the SLE after reposting (backport #48490)
2025-07-09 19:45:26 +05:30
Rohit Waghchaure
8966c956d5 feat: update the modified date of the SLE after reposting
(cherry picked from commit c2cd4934e7)
2025-07-09 13:52:11 +00:00
rohitwaghchaure
a518fc080a Merge pull request #48491 from frappe/mergify/bp/version-14-hotfix/pr-48488
feat: parent item group support in Stock Projected Qty report (backport #48488)
2025-07-09 18:59:55 +05:30
Rohit Waghchaure
3b36be214f feat: parent item group support in Stock Projected Qty report
(cherry picked from commit 6e80d89d13)
2025-07-09 11:19:41 +00:00
Frappe PR Bot
20af226947 chore(release): Bumped to Version 14.87.0
# [14.87.0](https://github.com/frappe/erpnext/compare/v14.86.5...v14.87.0) (2025-07-08)

### Bug Fixes

* fetch from parent optional in inventory dimension ([bb901ca](bb901cab88))
* incorrect backport ([94a17b6](94a17b6ec2))

### Features

* Period-wise closing entries for TB (backport [#39712](https://github.com/frappe/erpnext/issues/39712)) ([#48386](https://github.com/frappe/erpnext/issues/48386)) ([711e06b](711e06b537))

### Reverts

* do not convert exchange gain/loss amount to foreign currency ([083402e](083402e9a8))
* Revert "fix: stock reco qty with inventory dimension ([#47918](https://github.com/frappe/erpnext/issues/47918))" ([9207d79](9207d79ce6))
2025-07-08 12:54:12 +00:00
ruthra kumar
7f26b1d5a2 Merge pull request #48461 from frappe/version-14-hotfix
chore: release v14
2025-07-08 18:22:45 +05:30
ruthra kumar
c332fcdeff Merge pull request #48458 from frappe/mergify/bp/version-14-hotfix/pr-48361
revert: do not convert exchange gain/loss amount to foreign currency (backport #48361)
2025-07-08 15:45:19 +05:30
mergify[bot]
51adea3b89 refactor: remove do_reposting_for_each_stock_transaction feature (backport #48444) (#48451)
* refactor: remove do_reposting_for_each_stock_transaction feature

Co-authored-by: Mihir Kandoi <kandoimihir@gmail.com>
2025-07-08 15:06:00 +05:30
venkat102
083402e9a8 revert: do not convert exchange gain/loss amount to foreign currency
(cherry picked from commit c17ae703c7)

# Conflicts:
#	erpnext/accounts/report/general_ledger/test_general_ledger.py
2025-07-08 14:26:24 +05:30
rohitwaghchaure
1116a0fc8e Merge pull request #48442 from frappe/mergify/bp/version-14-hotfix/pr-48441
Revert "fix: stock reco qty with inventory dimension" (backport #48441)
2025-07-08 12:30:58 +05:30
rohitwaghchaure
b15d7e573e chore: fix conflicts 2025-07-08 10:45:49 +05:30
rohitwaghchaure
9207d79ce6 Revert "fix: stock reco qty with inventory dimension (#47918)"
This reverts commit 342cebc778.

(cherry picked from commit 8ba66c9833)

# Conflicts:
#	erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
2025-07-08 05:07:53 +00:00
rohitwaghchaure
33eb72035b Merge pull request #48433 from frappe/mergify/bp/version-14-hotfix/pr-48432
fix: fetch from parent optional in inventory dimension (backport #48432)
2025-07-08 10:13:56 +05:30
rohitwaghchaure
2c6099e051 Merge pull request #48438 from rohitwaghchaure/fixed-sales-incoming-rate-issue
fix: incorrect backport
2025-07-08 09:55:06 +05:30
rohitwaghchaure
5f4aaed5a0 chore: fix conflicts 2025-07-08 08:34:16 +05:30
Rohit Waghchaure
94a17b6ec2 fix: incorrect backport 2025-07-08 08:32:12 +05:30
Rohit Waghchaure
bb901cab88 fix: fetch from parent optional in inventory dimension
(cherry picked from commit 8aac6a6b18)

# Conflicts:
#	erpnext/stock/doctype/inventory_dimension/inventory_dimension.json
2025-07-07 11:52:53 +00:00
Diptanil Saha
711e06b537 feat: Period-wise closing entries for TB (backport #39712) (#48386)
feat: Period-wise closing entries for TB

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2025-07-03 22:47:28 +05:30
Diptanil Saha
c54b65acc2 chore: fix flaky test in Tax Withholding Details (backport #48375) (#48387)
* fix(test): flaky budget test case

* chore(test): import get_accumulated_monthly_budget

* fix(test): tds_payable_monthly tests

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2025-07-03 22:09:19 +05:30
Frappe PR Bot
179b8c7c61 chore(release): Bumped to Version 14.86.5
## [14.86.5](https://github.com/frappe/erpnext/compare/v14.86.4...v14.86.5) (2025-07-01)

### Bug Fixes

* accounting entries for standalone credit notes ([15ae019](15ae0196c6))
* add validation for exchange gain/loss entries ([b98cce8](b98cce8b9a))
* do not allow backdated transactions against serial numbers. ([#48281](https://github.com/frappe/erpnext/issues/48281)) ([945bdab](945bdabebb))
2025-07-01 11:59:12 +00:00
ruthra kumar
b107387dda Merge pull request #48337 from frappe/version-14-hotfix
chore: release v14
2025-07-01 17:27:40 +05:30
ruthra kumar
9fcca18bf3 Merge pull request #48334 from frappe/mergify/bp/version-14-hotfix/pr-48162
fix: add validation for exchange gain/loss entries (backport #48162)
2025-07-01 16:20:45 +05:30
i-am-vimal
b98cce8b9a fix: add validation for exchange gain/loss entries
(cherry picked from commit 5c9eddd31e)

# Conflicts:
#	erpnext/accounts/report/utils.py
2025-07-01 15:36:51 +05:30
Hossein Yousefian
bd9655a781 get_incoming_rate_voucher_no_fix 2025-06-30 17:11:04 -04:00
Hossein Yousefian
1df3b608ec get_incoming_rate_zero_in_rate_fix 2025-06-30 17:11:04 -04:00
rohitwaghchaure
62b7a8d21a Merge pull request #48313 from frappe/mergify/bp/version-14-hotfix/pr-48310
fix: accounting entries for standalone credit notes (backport #48310)
2025-06-30 10:09:23 +05:30
rohitwaghchaure
6b31e54891 chore: fix conflicts 2025-06-30 09:42:13 +05:30
Rohit Waghchaure
15ae0196c6 fix: accounting entries for standalone credit notes
(cherry picked from commit 52177cffcd)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
2025-06-30 04:09:39 +00:00
Frappe PR Bot
a06d705d75 chore(release): Bumped to Version 14.86.4
## [14.86.4](https://github.com/frappe/erpnext/compare/v14.86.3...v14.86.4) (2025-06-27)

### Bug Fixes

* do not allow backdated transactions against serial numbers. ([#48281](https://github.com/frappe/erpnext/issues/48281)) ([1393100](1393100a62))
2025-06-27 06:27:09 +00:00
rohitwaghchaure
9e3e3182f2 Merge pull request #48290 from frappe/mergify/bp/version-14/pr-48281
fix: do not allow backdated transactions against serial numbers. (backport #48281)
2025-06-27 11:55:38 +05:30
rohitwaghchaure
1393100a62 fix: do not allow backdated transactions against serial numbers. (#48281)
(cherry picked from commit 945bdabebb)
2025-06-27 06:03:52 +00:00
rohitwaghchaure
945bdabebb fix: do not allow backdated transactions against serial numbers. (#48281) 2025-06-27 11:33:03 +05:30
Frappe PR Bot
f07594b863 chore(release): Bumped to Version 14.86.3
## [14.86.3](https://github.com/frappe/erpnext/compare/v14.86.2...v14.86.3) (2025-06-24)

### Bug Fixes

* get already billed amount from current doc instead of database ([#48079](https://github.com/frappe/erpnext/issues/48079)) ([0790db7](0790db79fd))
2025-06-24 14:08:14 +00:00
ruthra kumar
25a382ec24 Merge pull request #48241 from frappe/version-14-hotfix
chore: release v14
2025-06-24 19:36:37 +05:30
ruthra kumar
d7c47e8ea5 Merge pull request #48234 from frappe/mergify/bp/version-14-hotfix/pr-48079
fix: get already billed amount from current doc instead of database (backport #48079)
2025-06-24 17:36:05 +05:30
ljain112
2627fb10ce chore: resolve conflicts 2025-06-24 16:14:24 +05:30
Lakshit Jain
0790db79fd fix: get already billed amount from current doc instead of database (#48079)
* fix: get already billed amount from current doc instead of database

* fix: throw overbilling validation for all items in single call

* refactor: minor fixes

---------

Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
(cherry picked from commit 47c3c4808e)

# Conflicts:
#	erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
2025-06-24 10:11:43 +00:00
Frappe PR Bot
0dad2acfc8 chore(release): Bumped to Version 14.86.2
## [14.86.2](https://github.com/frappe/erpnext/compare/v14.86.1...v14.86.2) (2025-06-17)

### Bug Fixes

* incorrect warehouse set from SO to MR ([fbfc6a6](fbfc6a6f62))
2025-06-17 14:36:59 +00:00
ruthra kumar
193042ef07 Merge pull request #48099 from frappe/version-14-hotfix
chore: release v14
2025-06-17 20:05:31 +05:30
rohitwaghchaure
8208a9ec27 Merge pull request #48064 from frappe/mergify/bp/version-14-hotfix/pr-48061
fix: incorrect warehouse set from SO to MR (backport #48061)
2025-06-16 14:52:54 +05:30
Rohit Waghchaure
fbfc6a6f62 fix: incorrect warehouse set from SO to MR
(cherry picked from commit 0da8d9c869)
2025-06-16 04:42:26 +00:00
Frappe PR Bot
e9f9eaa2d0 chore(release): Bumped to Version 14.86.1
## [14.86.1](https://github.com/frappe/erpnext/compare/v14.86.0...v14.86.1) (2025-06-10)

### Bug Fixes

* available qty in BOM Stock Report ([186173a](186173a21e))
* incorrect warehouse in MR ([2cac05e](2cac05e56c))
* key-error for COGS By Item Group report (backport [#47914](https://github.com/frappe/erpnext/issues/47914)) ([#47916](https://github.com/frappe/erpnext/issues/47916)) ([f5ef376](f5ef376486))
* stock adjustment entry during reposting (backport [#47878](https://github.com/frappe/erpnext/issues/47878)) ([#47882](https://github.com/frappe/erpnext/issues/47882)) ([c9eeca2](c9eeca22f5))
* stock reco qty with inventory dimension (backport [#47918](https://github.com/frappe/erpnext/issues/47918)) ([#47921](https://github.com/frappe/erpnext/issues/47921)) ([6397c36](6397c366fd))
* throw permission error ([#47976](https://github.com/frappe/erpnext/issues/47976)) ([96c937b](96c937bf6a))
2025-06-10 14:31:53 +00:00
ruthra kumar
08d40ddb9b Merge pull request #47997 from frappe/version-14-hotfix
chore: release v14
2025-06-10 20:00:21 +05:30
rohitwaghchaure
d60ab92082 Merge pull request #48003 from frappe/mergify/bp/version-14-hotfix/pr-47998
fix: incorrect warehouse in MR (backport #47998)
2025-06-10 18:54:18 +05:30
rohitwaghchaure
85a0581145 chore: fix conflicts 2025-06-10 18:20:28 +05:30
Rohit Waghchaure
2cac05e56c fix: incorrect warehouse in MR
(cherry picked from commit 2b9ca79291)

# Conflicts:
#	erpnext/manufacturing/doctype/production_plan/production_plan.py
2025-06-10 12:41:16 +00:00
ruthra kumar
10e4e610db Merge pull request #47993 from frappe/mergify/bp/version-14-hotfix/pr-47981
refactor(Work Order): query_sales_order (backport #47981)
2025-06-10 15:01:22 +05:30
barredterra
9ab80cfd6c refactor(Work Order): query_sales_order
- Use `get_list` instead of `db.sql_list`

    The method is used for setting link options in the frontend and the Link field doesn't ignore permissions, so get_list should be fine here.

- Added type hints to enable argument validation

(cherry picked from commit 2dbdacf905)
2025-06-10 09:03:13 +00:00
rohitwaghchaure
8f2b83d434 Merge pull request #47986 from frappe/mergify/bp/version-14-hotfix/pr-47942
fix: available qty in BOM Stock Report (backport #47942)
2025-06-10 14:03:06 +05:30
Sagar Vora
7f0ebb37da Merge pull request #47977 from frappe/mergify/bp/version-14-hotfix/pr-47976
fix: throw permission error (backport #47976)
2025-06-10 07:39:53 +00:00
Sagar Vora
1d42c4a305 chore: fix conflicts 2025-06-10 13:08:39 +05:30
Rohit Waghchaure
186173a21e fix: available qty in BOM Stock Report
(cherry picked from commit ea689bbe3f)
2025-06-10 06:50:51 +00:00
Aayush Dalal
96c937bf6a fix: throw permission error (#47976)
Co-authored-by: Sagar Vora <16315650+sagarvora@users.noreply.github.com>
(cherry picked from commit 8b6a8d0c4f)

# Conflicts:
#	erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
#	erpnext/stock/utils.py
2025-06-09 17:29:42 +00:00
rohitwaghchaure
52aafc9410 Merge pull request #47932 from rohitwaghchaure/fixed-sabb-cond
chore: incorrect condition
2025-06-06 13:13:54 +05:30
Rohit Waghchaure
3232310c0f chore: incorrect condition 2025-06-06 12:30:03 +05:30
mergify[bot]
6397c366fd fix: stock reco qty with inventory dimension (backport #47918) (#47921)
fix: stock reco qty with inventory dimension (#47918)

(cherry picked from commit 342cebc778)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-06-06 09:50:20 +05:30
mergify[bot]
f5ef376486 fix: key-error for COGS By Item Group report (backport #47914) (#47916)
fix: key-error for COGS By Item Group report (#47914)

fix: keyerror for COGS By Item Group report
(cherry picked from commit 997ce4eaa7)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-06-05 17:18:22 +05:30
mergify[bot]
c9eeca22f5 fix: stock adjustment entry during reposting (backport #47878) (#47882)
fix: stock adjustment entry during reposting (#47878)

fix: stock adjustment entry
(cherry picked from commit cbcd580daa)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2025-06-04 12:51:22 +05:30
85 changed files with 1252 additions and 552 deletions

View File

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

View File

@@ -3,4 +3,14 @@
frappe.ui.form.on("Accounts Settings", {
refresh: function (frm) {},
drop_ar_procedures: function (frm) {
frm.call({
doc: frm.doc,
method: "drop_ar_sql_procedures",
callback: function (r) {
frappe.show_alert(__("Procedures dropped"), 5);
},
});
},
});

View File

@@ -77,6 +77,8 @@
"receivable_payable_remarks_length",
"accounts_receivable_payable_tuning_section",
"receivable_payable_fetch_method",
"column_break_ntmi",
"drop_ar_procedures",
"legacy_section",
"ignore_is_opening_check_for_reporting"
],
@@ -488,7 +490,7 @@
"fieldname": "receivable_payable_fetch_method",
"fieldtype": "Select",
"label": "Data Fetch Method",
"options": "Buffered Cursor\nUnBuffered Cursor"
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
},
{
"fieldname": "accounts_receivable_payable_tuning_section",
@@ -499,6 +501,17 @@
"fieldname": "legacy_section",
"fieldtype": "Section Break",
"label": "Legacy Fields"
},
{
"fieldname": "column_break_ntmi",
"fieldtype": "Column Break"
},
{
"depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"",
"description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report",
"fieldname": "drop_ar_procedures",
"fieldtype": "Button",
"label": "Drop Procedures"
}
],
"icon": "icon-cog",

View File

@@ -65,3 +65,11 @@ class AccountsSettings(Document):
def validate_pending_reposts(self):
if self.acc_frozen_upto:
check_pending_reposting(self.acc_frozen_upto)
@frappe.whitelist()
def drop_ar_sql_procedures(self):
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
frappe.db.sql(f"drop function if exists {InitSQLProceduresForAR.genkey_function_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")

View File

@@ -6,7 +6,11 @@ import unittest
import frappe
from frappe.utils import now_datetime, nowdate
from erpnext.accounts.doctype.budget.budget import BudgetError, get_actual_expense
from erpnext.accounts.doctype.budget.budget import (
BudgetError,
get_accumulated_monthly_budget,
get_actual_expense,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -96,6 +100,10 @@ class TestBudget(unittest.TestCase):
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
accumulated_limit = get_accumulated_monthly_budget(
budget.monthly_distribution, nowdate(), budget.fiscal_year, budget.accounts[0].budget_amount
)
mr = frappe.get_doc(
{
"doctype": "Material Request",
@@ -109,7 +117,7 @@ class TestBudget(unittest.TestCase):
"uom": "_Test UOM",
"warehouse": "_Test Warehouse - _TC",
"schedule_date": nowdate(),
"rate": 100000,
"rate": accumulated_limit + 1,
"expense_account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
}

View File

@@ -448,9 +448,8 @@ def unset_existing_data(company):
"Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template",
]:
frappe.db.sql(
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
)
dt = frappe.qb.DocType(doctype)
frappe.qb.from_(dt).where(dt.company == company).delete().run()
def set_default_accounts(company):

View File

@@ -4,6 +4,8 @@
import unittest
import frappe
from frappe.query_builder.functions import Sum
from frappe.tests.utils import change_settings
from frappe.utils import add_days, today
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
@@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase):
coa2.cancel()
jv.cancel()
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
cca = create_cost_center_allocation(
"_Test Company",
"Main Cost Center 1 - _TC",
{"Sub Cost Center 2 - _TC": 50, "Sub Cost Center 3 - _TC": 50},
)
si = create_sales_invoice(rate=145.65, cost_center="Main Cost Center 1 - _TC")
gl_entry = frappe.qb.DocType("GL Entry")
gl_entries = (
frappe.qb.from_(gl_entry)
.select(Sum(gl_entry.credit).as_("cr"), Sum(gl_entry.debit).as_("dr"))
.where(gl_entry.voucher_type == "Sales Invoice")
.where(gl_entry.voucher_no == si.name)
).run(as_dict=1)
self.assertEqual(gl_entries[0].cr, gl_entries[0].dr)
si.cancel()
cca.cancel()
def create_cost_center_allocation(
company,

View File

@@ -55,46 +55,46 @@ class Dunning(AccountsController):
"conversion_rate",
"cost_center",
]
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
accounting_dimensions = get_accounting_dimensions()
invoice_fields.extend(accounting_dimensions)
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
default_cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
gl_entries.append(
self.get_gl_dict(
{
"account": inv.debit_to,
"party_type": "Customer",
"party": self.customer,
"due_date": self.due_date,
"against": self.income_account,
"debit": dunning_in_company_currency,
"debit_in_account_currency": self.dunning_amount,
"against_voucher": self.name,
"against_voucher_type": "Dunning",
"cost_center": inv.cost_center or default_cost_center,
"project": inv.project,
},
inv.party_account_currency,
item=inv,
)
)
gl_entries.append(
self.get_gl_dict(
{
"account": self.income_account,
"against": self.customer,
"credit": dunning_in_company_currency,
"cost_center": inv.cost_center or default_cost_center,
"credit_in_account_currency": self.dunning_amount,
"project": inv.project,
},
item=inv,
)
)
debit = {
"account": inv.debit_to,
"party_type": "Customer",
"party": self.customer,
"due_date": self.due_date,
"against": self.income_account,
"debit": dunning_in_company_currency,
"debit_in_account_currency": self.dunning_amount,
"against_voucher": self.name,
"against_voucher_type": "Dunning",
"cost_center": inv.cost_center or default_cost_center,
"project": inv.project,
}
credit = {
"account": self.income_account,
"against": self.customer,
"credit": dunning_in_company_currency,
"credit_in_account_currency": self.dunning_amount,
"cost_center": inv.cost_center or default_cost_center,
"project": inv.project,
}
for dimension in accounting_dimensions:
if val := inv.get(dimension):
debit[dimension] = credit[dimension] = val
gl_entries = [
self.get_gl_dict(debit, inv.party_account_currency, item=inv),
self.get_gl_dict(credit, item=inv),
]
make_gl_entries(
gl_entries, cancel=(self.docstatus == 2), update_outstanding="No", merge_entries=False
)

View File

@@ -261,7 +261,7 @@ def validate_balance_type(account, adv_adj=False):
if balance_must_be:
balance = frappe.db.sql(
"""select sum(debit) - sum(credit)
from `tabGL Entry` where account = %s""",
from `tabGL Entry` where is_cancelled = 0 and account = %s""",
account,
)[0][0]

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import flt, today
@@ -18,22 +19,30 @@ def get_loyalty_details(
if not expiry_date:
expiry_date = today()
condition = ""
if company:
condition = " and company=%s " % frappe.db.escape(company)
if not include_expired_entry:
condition += " and expiry_date>='%s' " % expiry_date
LoyaltyPointEntry = frappe.qb.DocType("Loyalty Point Entry")
loyalty_point_details = frappe.db.sql(
f"""select sum(loyalty_points) as loyalty_points,
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
where customer=%s and loyalty_program=%s and posting_date <= %s
{condition}
group by customer""",
(customer, loyalty_program, expiry_date),
as_dict=1,
query = (
frappe.qb.from_(LoyaltyPointEntry)
.select(
Sum(LoyaltyPointEntry.loyalty_points).as_("loyalty_points"),
Sum(LoyaltyPointEntry.purchase_amount).as_("total_spent"),
)
.where(
(LoyaltyPointEntry.customer == customer)
& (LoyaltyPointEntry.loyalty_program == loyalty_program)
& (LoyaltyPointEntry.posting_date <= expiry_date)
)
.groupby(LoyaltyPointEntry.customer)
)
if company:
query = query.where(LoyaltyPointEntry.company == company)
if not include_expired_entry:
query = query.where(LoyaltyPointEntry.expiry_date >= expiry_date)
loyalty_point_details = query.run(as_dict=True)
if loyalty_point_details:
return loyalty_point_details[0]
else:

View File

@@ -60,6 +60,6 @@ def create_party_link(primary_role, primary_party, secondary_party):
party_link.secondary_role = "Customer" if primary_role == "Supplier" else "Supplier"
party_link.secondary_party = secondary_party
party_link.save(ignore_permissions=True)
party_link.save()
return party_link

View File

@@ -330,7 +330,7 @@ frappe.ui.form.on('Payment Entry', {
payment_type: function(frm) {
set_default_party_type(frm);
if(frm.doc.payment_type == "Internal Transfer") {
$.each(["party", "party_balance", "paid_from", "paid_to",
$.each(["party", "party_type", "party_balance", "paid_from", "paid_to",
"references", "total_allocated_amount"], function(i, field) {
frm.set_value(field, null);
});

View File

@@ -10,14 +10,19 @@
"description",
"section_break_4",
"due_date",
"invoice_portion",
"mode_of_payment",
"column_break_5",
"invoice_portion",
"due_date_based_on",
"credit_days",
"credit_months",
"section_break_6",
"discount_type",
"discount_date",
"column_break_9",
"discount",
"discount_type",
"column_break_9",
"discount_validity_based_on",
"discount_validity",
"section_break_9",
"payment_amount",
"outstanding",
@@ -155,12 +160,50 @@
"fieldtype": "Currency",
"label": "Payment Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fieldname": "due_date_based_on",
"fieldtype": "Select",
"label": "Due Date Based On",
"options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
"read_only": 1
},
{
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
"fieldname": "credit_days",
"fieldtype": "Int",
"label": "Credit Days",
"non_negative": 1,
"read_only": 1
},
{
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
"fieldname": "credit_months",
"fieldtype": "Int",
"label": "Credit Months",
"non_negative": 1,
"read_only": 1
},
{
"depends_on": "discount",
"fieldname": "discount_validity_based_on",
"fieldtype": "Select",
"label": "Discount Validity Based On",
"options": "\nDay(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
"read_only": 1
},
{
"depends_on": "discount_validity_based_on",
"fieldname": "discount_validity",
"fieldtype": "Int",
"label": "Discount Validity",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-16 13:57:06.382859",
"modified": "2025-07-31 08:38:25.820701",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
@@ -171,4 +214,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -161,4 +161,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -206,6 +206,9 @@ class PeriodClosingVoucher(AccountsController):
return gl_entry
def get_gle_for_closing_account(self, acc):
debit = abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
credit = abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0
gl_entry = self.get_gl_dict(
{
"company": self.company,
@@ -214,16 +217,10 @@ class PeriodClosingVoucher(AccountsController):
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) < 0
else 0,
"debit_in_account_currency": debit,
"debit": debit,
"credit_in_account_currency": credit,
"credit": credit,
"is_period_closing_voucher_entry": 1,
},
item=acc,

View File

@@ -560,7 +560,13 @@ class POSInvoice(SalesInvoice):
"Account", self.debit_to, "account_currency", cache=True
)
if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
self.due_date = get_due_date(
self.posting_date,
"Customer",
self.customer,
self.company,
template_name=self.payment_terms_template,
)
super(SalesInvoice, self).set_missing_values(for_validate)

View File

@@ -29,7 +29,10 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
this.frm.set_query("expense_account", "items", function () {
return {
query: "erpnext.controllers.queries.get_expense_account",
filters: { company: doc.company },
filters: {
company: doc.company,
disabled: 0,
},
};
});
}

View File

@@ -173,7 +173,12 @@ class PurchaseInvoice(BuyingController):
)
if not self.due_date:
self.due_date = get_due_date(
self.posting_date, "Supplier", self.supplier, self.company, self.bill_date
self.posting_date,
"Supplier",
self.supplier,
self.company,
self.bill_date,
template_name=self.payment_terms_template,
)
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
@@ -781,7 +786,7 @@ class PurchaseInvoice(BuyingController):
self.get_provisional_accounts()
for item in self.get("items"):
if flt(item.base_net_amount):
if flt(item.base_net_amount) or (self.get("update_stock") and item.valuation_rate):
account_currency = get_account_currency(item.expense_account)
if item.item_code:
frappe.get_cached_value("Item", item.item_code, "asset_category")
@@ -1016,6 +1021,9 @@ class PurchaseInvoice(BuyingController):
def get_provisional_accounts(self):
self.provisional_accounts = frappe._dict()
linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt])
if not linked_purchase_receipts:
return
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
@@ -1124,6 +1132,30 @@ class PurchaseInvoice(BuyingController):
warehouse_debit_amount = stock_amount
elif self.is_return and self.update_stock and (self.is_internal_supplier or not self.return_against):
net_rate = item.base_net_amount
stock_amount = net_rate + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_adjustment_amt = stock_amount - warehouse_debit_amount
gl_entries.append(
self.get_gl_dict(
{
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"debit_in_transaction_currency": stock_adjustment_amt / self.conversion_rate,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
return warehouse_debit_amount
def make_tax_gl_entries(self, gl_entries):

View File

@@ -1816,19 +1816,16 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
self.assertAlmostEqual(rate, 500)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_allocation_for_payment_terms(self):
from erpnext.buying.doctype.purchase_order.test_purchase_order import (
create_pr_against_po,
create_purchase_order,
)
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
make_purchase_invoice as make_pi_from_pr,
)
automatically_fetch_payment_terms()
frappe.db.set_value(
"Payment Terms Template",
"_Test Payment Term Template",
@@ -1854,7 +1851,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
pi = make_pi_from_pr(pr.name)
self.assertEqual(pi.payment_schedule[0].payment_amount, 1000)
automatically_fetch_payment_terms(enable=0)
frappe.db.set_value(
"Payment Terms Template",
"_Test Payment Term Template",

View File

@@ -151,7 +151,7 @@ def start_repost(account_repost_doc=str) -> None:
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
if not repost_doc.delete_cancelled_entries:
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.make_gl_entries_on_cancel(from_repost=True)
doc.docstatus = 1
if doc.doctype == "Sales Invoice":
@@ -163,7 +163,7 @@ def start_repost(account_repost_doc=str) -> None:
elif doc.doctype == "Purchase Receipt":
if not repost_doc.delete_cancelled_entries:
doc.docstatus = 2
doc.make_gl_entries_on_cancel()
doc.make_gl_entries_on_cancel(from_repost=True)
doc.docstatus = 1
doc.make_gl_entries(from_repost=True)

View File

@@ -498,7 +498,13 @@ class SalesInvoice(SellingController):
"Account", self.debit_to, "account_currency", cache=True
)
if not self.due_date and self.customer:
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
self.due_date = get_due_date(
self.posting_date,
"Customer",
self.customer,
self.company,
template_name=self.payment_terms_template,
)
super().set_missing_values(for_validate)
@@ -1019,7 +1025,6 @@ class SalesInvoice(SellingController):
self.make_exchange_gain_loss_journal()
elif self.docstatus == 2:
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
if update_outstanding == "No":

View File

@@ -3287,6 +3287,7 @@ class TestSalesInvoice(FrappeTestCase):
si.posting_date = getdate()
si.submit()
@change_settings("Accounts Settings", {"over_billing_allowance": 0})
def test_over_billing_case_against_delivery_note(self):
"""
Test a case where duplicating the item with qty = 1 in the invoice
@@ -3294,24 +3295,23 @@ class TestSalesInvoice(FrappeTestCase):
"""
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
dn = create_delivery_note()
dn.submit()
si = make_sales_invoice(dn.name)
# make a copy of first item and add it to invoice
item_copy = frappe.copy_doc(si.items[0])
si.save()
si.items = [] # Clear existing items
si.append("items", item_copy)
si.save()
si.append("items", item_copy)
with self.assertRaises(frappe.ValidationError) as err:
si.submit()
si.save()
self.assertTrue("cannot overbill" in str(err.exception).lower())
frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", over_billing_allowance)
dn.cancel()
@change_settings(
"Accounts Settings",
@@ -3889,6 +3889,60 @@ class TestSalesInvoice(FrappeTestCase):
self.assertEqual(invoice.outstanding_amount, 0)
def test_system_generated_exchange_gain_or_loss_je_after_repost(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
update_repost_settings,
)
update_repost_settings()
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=80,
)
pe = get_payment_entry("Sales Invoice", si.name)
pe.reference_no = "10"
pe.reference_date = nowdate()
pe.paid_from_account_currency = si.currency
pe.paid_to_account_currency = "INR"
pe.source_exchange_rate = 85
pe.target_exchange_rate = 1
pe.paid_amount = si.outstanding_amount
pe.received_amount = 8500
pe.insert()
pe.submit()
ral = frappe.new_doc("Repost Accounting Ledger")
ral.company = si.company
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
ral.save()
ral.submit()
je = frappe.qb.DocType("Journal Entry")
jea = frappe.qb.DocType("Journal Entry Account")
q = (
(
frappe.qb.from_(je)
.join(jea)
.on(je.name == jea.parent)
.select(je.docstatus)
.where(
(je.voucher_type == "Exchange Gain Or Loss")
& (jea.reference_name == si.name)
& (jea.reference_type == "Sales Invoice")
& (je.is_system_generated == 1)
)
)
.limit(1)
.run()
)
self.assertEqual(q[0][0], 1)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -75,7 +75,7 @@
},
{
"default": "0",
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
"description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
"fieldname": "consider_party_ledger_amount",
"fieldtype": "Check",
"label": "Consider Entire Party Ledger Amount",
@@ -102,10 +102,11 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2021-07-27 21:47:34.396071",
"modified": "2025-07-30 07:13:51.785735",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Category",
"naming_rule": "Set by user",
"owner": "Administrator",
"permissions": [
{
@@ -148,4 +149,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}
}

View File

@@ -179,6 +179,15 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
round_off_account, default_currency = frappe.get_cached_value(
"Company", gl_map[0].company, ["round_off_account", "default_currency"]
)
if not precision:
precision = get_field_precision(
frappe.get_meta("GL Entry").get_field("debit"),
currency=default_currency,
)
new_gl_map = []
for d in gl_map:
cost_center = d.get("cost_center")
@@ -192,6 +201,11 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
new_gl_map.append(d)
continue
if d.account == round_off_account:
d.cost_center = cost_center_allocation[0][0]
new_gl_map.append(d)
continue
for sub_cost_center, percentage in cost_center_allocation:
gle = copy.deepcopy(d)
gle.cost_center = sub_cost_center

View File

@@ -552,12 +552,13 @@ def validate_party_accounts(doc):
@frappe.whitelist()
def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
def get_due_date(posting_date, party_type, party, company=None, bill_date=None, template_name=None):
"""Get due date from `Payment Terms Template`"""
due_date = None
if (bill_date or posting_date) and party:
due_date = bill_date or posting_date
template_name = get_payment_terms_template(party, party_type, company)
if not template_name:
template_name = get_payment_terms_template(party, party_type, company)
if template_name:
due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d")

View File

@@ -6,6 +6,7 @@ from collections import OrderedDict
import frappe
from frappe import _, qb, query_builder, scrub
from frappe.database.schema import get_definition
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Date, Substring, Sum
from frappe.utils import cint, cstr, flt, getdate, nowdate
@@ -14,7 +15,10 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimension_with_children,
)
from erpnext.accounts.utils import get_currency_precision
from erpnext.accounts.utils import (
build_qb_match_conditions,
get_currency_precision,
)
# This report gives a summary of all Outstanding Invoices considering the following
@@ -91,9 +95,6 @@ class ReceivablePayableReport:
def get_data(self):
self.get_sales_invoices_or_customers_based_on_sales_person()
# Build delivery note map against all sales invoices
self.build_delivery_note_map()
# Get invoice details like bill_no, due_date etc for all invoices
self.get_invoice_details()
@@ -114,12 +115,16 @@ class ReceivablePayableReport:
self.fetch_ple_in_buffered_cursor()
elif self.ple_fetch_method == "UnBuffered Cursor":
self.fetch_ple_in_unbuffered_cursor()
elif self.ple_fetch_method == "Raw SQL":
self.fetch_ple_in_sql_procedures()
# Build delivery note map against all sales invoices
self.build_delivery_note_map()
self.build_data()
def fetch_ple_in_buffered_cursor(self):
query, param = self.ple_query.walk()
self.ple_entries = frappe.db.sql(query, param, as_dict=True)
self.ple_entries = self.ple_query.run(as_dict=True)
for ple in self.ple_entries:
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
@@ -132,9 +137,8 @@ class ReceivablePayableReport:
def fetch_ple_in_unbuffered_cursor(self):
self.ple_entries = []
query, param = self.ple_query.walk()
with frappe.db.unbuffered_cursor():
for ple in frappe.db.sql(query, param, as_dict=True, as_iterator=True):
for ple in self.ple_query.run(as_dict=True, as_iterator=True):
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
self.ple_entries.append(ple)
@@ -299,6 +303,79 @@ class ReceivablePayableReport:
row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency
def fetch_ple_in_sql_procedures(self):
self.proc = InitSQLProceduresForAR()
build_balance = f"""
begin not atomic
declare done boolean default false;
declare rec1 row type of `{self.proc._row_def_table_name}`;
declare ple cursor for {self.ple_query.get_sql()};
declare continue handler for not found set done = true;
open ple;
fetch ple into rec1;
while not done do
call {self.proc.init_procedure_name}(rec1);
fetch ple into rec1;
end while;
close ple;
set done = false;
open ple;
fetch ple into rec1;
while not done do
call {self.proc.allocate_procedure_name}(rec1);
fetch ple into rec1;
end while;
close ple;
end;
"""
frappe.db.sql(build_balance)
balances = frappe.db.sql(
f"""select
name,
voucher_type,
voucher_no,
party,
party_account `account`,
posting_date,
account_currency,
cost_center,
sum(invoiced) `invoiced`,
sum(paid) `paid`,
sum(credit_note) `credit_note`,
sum(invoiced) - sum(paid) - sum(credit_note) `outstanding`,
sum(invoiced_in_account_currency) `invoiced_in_account_currency`,
sum(paid_in_account_currency) `paid_in_account_currency`,
sum(credit_note_in_account_currency) `credit_note_in_account_currency`,
sum(invoiced_in_account_currency) - sum(paid_in_account_currency) - sum(credit_note_in_account_currency) `outstanding_in_account_currency`
from `{self.proc._voucher_balance_name}` group by name order by posting_date;""",
as_dict=True,
)
for x in balances:
if self.filters.get("ignore_accounts"):
key = (x.voucher_type, x.voucher_no, x.party)
else:
key = (x.account, x.voucher_type, x.voucher_no, x.party)
_d = self.build_voucher_dict(x)
for field in [
"invoiced",
"paid",
"credit_note",
"outstanding",
"invoiced_in_account_currency",
"paid_in_account_currency",
"credit_note_in_account_currency",
"outstanding_in_account_currency",
"cost_center",
]:
_d[field] = x.get(field)
self.voucher_balance[key] = _d
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
@@ -839,6 +916,9 @@ class ReceivablePayableReport:
else:
query = query.select(ple.remarks)
if match_conditions := build_qb_match_conditions("Payment Ledger Entry"):
query = query.where(Criterion.all(match_conditions))
if self.filters.get("group_by_party"):
query = query.orderby(self.ple.party, self.ple.posting_date)
else:
@@ -1231,3 +1311,134 @@ def get_customer_group_with_children(customer_groups):
frappe.throw(_("Customer Group: {0} does not exist").format(d))
return list(set(all_customer_groups))
class InitSQLProceduresForAR:
"""
Initialize SQL Procedures, Functions and Temporary tables to build Receivable / Payable report
"""
_varchar_type = get_definition("Data")
_currency_type = get_definition("Currency")
# Temporary Tables
_voucher_balance_name = "_ar_voucher_balance"
_voucher_balance_definition = f"""
create temporary table `{_voucher_balance_name}`(
name {_varchar_type},
voucher_type {_varchar_type},
voucher_no {_varchar_type},
party {_varchar_type},
party_account {_varchar_type},
posting_date date,
account_currency {_varchar_type},
cost_center {_varchar_type},
invoiced {_currency_type},
paid {_currency_type},
credit_note {_currency_type},
invoiced_in_account_currency {_currency_type},
paid_in_account_currency {_currency_type},
credit_note_in_account_currency {_currency_type}) engine=memory;
"""
_row_def_table_name = "_ar_ple_row"
_row_def_table_definition = f"""
create temporary table `{_row_def_table_name}`(
name {_varchar_type},
account {_varchar_type},
voucher_type {_varchar_type},
voucher_no {_varchar_type},
against_voucher_type {_varchar_type},
against_voucher_no {_varchar_type},
party_type {_varchar_type},
cost_center {_varchar_type},
party {_varchar_type},
posting_date date,
due_date date,
account_currency {_varchar_type},
amount {_currency_type},
amount_in_account_currency {_currency_type}) engine=memory;
"""
# Function
genkey_function_name = "ar_genkey"
genkey_function_sql = f"""
create function `{genkey_function_name}`(rec row type of `{_row_def_table_name}`, allocate bool) returns char(40)
begin
if allocate then
return sha1(concat_ws(',', rec.account, rec.against_voucher_type, rec.against_voucher_no, rec.party));
else
return sha1(concat_ws(',', rec.account, rec.voucher_type, rec.voucher_no, rec.party));
end if;
end
"""
# Procedures
init_procedure_name = "ar_init_tmp_table"
init_procedure_sql = f"""
create procedure ar_init_tmp_table(in ple row type of `{_row_def_table_name}`)
begin
if not exists (select name from `{_voucher_balance_name}` where name = `{genkey_function_name}`(ple, false))
then
insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, false), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, 0, 0, 0, 0, 0, 0);
end if;
end;
"""
allocate_procedure_name = "ar_allocate_to_tmp_table"
allocate_procedure_sql = f"""
create procedure ar_allocate_to_tmp_table(in ple row type of `{_row_def_table_name}`)
begin
declare invoiced {_currency_type} default 0;
declare invoiced_in_account_currency {_currency_type} default 0;
declare paid {_currency_type} default 0;
declare paid_in_account_currency {_currency_type} default 0;
declare credit_note {_currency_type} default 0;
declare credit_note_in_account_currency {_currency_type} default 0;
if ple.amount > 0 then
if (ple.voucher_type in ("Journal Entry", "Payment Entry") and (ple.voucher_no != ple.against_voucher_no)) then
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
else
set invoiced = ple.amount;
set invoiced_in_account_currency = ple.amount_in_account_currency;
end if;
else
if ple.voucher_type in ("Sales Invoice", "Purchase Invoice") then
if (ple.voucher_no = ple.against_voucher_no) then
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
else
set credit_note = -1 * ple.amount;
set credit_note_in_account_currency = -1 * ple.amount_in_account_currency;
end if;
else
set paid = -1 * ple.amount;
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
end if;
end if;
insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, true), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0);
end;
"""
def __init__(self):
existing_procedures = frappe.db.get_routines()
if self.genkey_function_name not in existing_procedures:
frappe.db.sql(self.genkey_function_sql)
if self.init_procedure_name not in existing_procedures:
frappe.db.sql(self.init_procedure_sql)
if self.allocate_procedure_name not in existing_procedures:
frappe.db.sql(self.allocate_procedure_sql)
frappe.db.sql(f"drop table if exists `{self._voucher_balance_name}`")
frappe.db.sql(self._voucher_balance_definition)
frappe.db.sql(f"drop table if exists `{self._row_def_table_name}`")
frappe.db.sql(self._row_def_table_definition)

View File

@@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = {
label: __("Show Net Values in Party Account"),
fieldtype: "Check",
},
{
fieldname: "show_amount_in_company_currency",
label: __("Show Credit / Debit in Company Currency"),
fieldtype: "Check",
},
{
fieldname: "show_remarks",
label: __("Show Remarks"),

View File

@@ -205,7 +205,7 @@ def get_gl_entries(filters, accounting_dimensions):
)
if filters.get("presentation_currency"):
return convert_to_presentation_currency(gl_entries, currency_map)
return convert_to_presentation_currency(gl_entries, currency_map, filters)
else:
return gl_entries
@@ -580,6 +580,19 @@ def get_columns(filters):
company = filters.get("company") or get_default_company()
filters["presentation_currency"] = currency = get_company_currency(company)
company_currency = get_company_currency(filters.get("company") or get_default_company())
if (
filters.get("show_amount_in_company_currency")
and filters["presentation_currency"] != company_currency
):
frappe.throw(
_("Presentation Currency cannot be {0} , When {1} is enabled.").format(
frappe.bold(filters["presentation_currency"]),
frappe.bold("Show Credit / Debit in Company Currency"),
)
)
columns = [
{
"label": _("GL Entry"),

View File

@@ -46,6 +46,7 @@ class PaymentLedger:
against_voucher_no=ple.against_voucher_no,
amount=ple.amount,
currency=ple.account_currency,
company=ple.company,
)
if self.filters.include_account_currency:
@@ -77,6 +78,7 @@ class PaymentLedger:
against_voucher_no="Outstanding:",
amount=total,
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
if self.filters.include_account_currency:
@@ -85,7 +87,12 @@ class PaymentLedger:
voucher_data.append(entry)
# empty row
voucher_data.append(frappe._dict())
voucher_data.append(
frappe._dict(
currency=voucher_data[0].currency,
company=voucher_data[0].company,
)
)
self.data.extend(voucher_data)
def build_conditions(self):

View File

@@ -45,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
gle_map = get_gle_map(tds_docs)
out = []
entries = {}
for name, details in gle_map.items():
for entry in details:
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
@@ -119,9 +120,14 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
"supplier_invoice_date": bill_date,
}
)
out.append(row)
out.sort(key=lambda x: x["section_code"])
key = entry.voucher_no
if key in entries:
entries[key]["tax_amount"] += tax_amount
else:
entries[key] = row
out = list(entries.values())
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
return out

View File

@@ -67,11 +67,12 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
mid_year = add_to_date(fiscal_year[1], months=6)
tds_doc = frappe.get_doc("Tax Withholding Category", "TDS - 3")
tds_doc.rates[0].to_date = mid_year
from_date = add_to_date(mid_year, days=1)
tds_doc.append(
"rates",
{
"tax_withholding_rate": 20,
"from_date": add_to_date(mid_year, days=1),
"from_date": from_date,
"to_date": fiscal_year[2],
"single_threshold": 1,
"cumulative_threshold": 1,
@@ -80,18 +81,19 @@ class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
tds_doc.save()
inv_1 = make_purchase_invoice(rate=1000, do_not_submit=True)
inv_1 = make_purchase_invoice(
rate=1000, posting_date=add_to_date(fiscal_year[1], days=1), do_not_save=True, do_not_submit=True
)
inv_1.set_posting_time = 1
inv_1.apply_tds = 1
inv_1.tax_withholding_category = "TDS - 3"
inv_1.tax_withholding_category = tds_doc.name
inv_1.save()
inv_1.submit()
inv_2 = make_purchase_invoice(
rate=1000, do_not_submit=True, posting_date=add_to_date(mid_year, days=1), do_not_save=True
)
inv_2 = make_purchase_invoice(rate=1000, posting_date=from_date, do_not_save=True, do_not_submit=True)
inv_2.set_posting_time = 1
inv_1.apply_tds = 1
inv_2.tax_withholding_category = "TDS - 3"
inv_2.apply_tds = 1
inv_2.tax_withholding_category = tds_doc.name
inv_2.save()
inv_2.submit()

View File

@@ -79,8 +79,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function () {
options: erpnext.get_presentation_currency_list(),
},
{
fieldname: "with_period_closing_entry",
label: __("Period Closing Entry"),
fieldname: "with_period_closing_entry_for_opening",
label: __("With Period Closing Entry For Opening Balances"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "with_period_closing_entry_for_current_period",
label: __("Period Closing Entry For Current Period"),
fieldtype: "Check",
default: 1,
},

View File

@@ -120,7 +120,7 @@ def get_data(filters):
max_rgt,
filters,
gl_entries_by_account,
ignore_closing_entries=not flt(filters.with_period_closing_entry),
ignore_closing_entries=not flt(filters.with_period_closing_entry_for_current_period),
ignore_opening_entries=True,
)
@@ -274,7 +274,7 @@ def get_opening_balance(
):
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
if not flt(filters.with_period_closing_entry):
if not flt(filters.with_period_closing_entry_for_opening):
if doctype == "Account Closing Balance":
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
else:

View File

@@ -4,15 +4,25 @@
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"mandatory": 1,
"options": "Company",
"wildcard_filter": 0
}
],
"idx": 0,
"is_standard": "Yes",
"modified": "2019-01-17 17:20:42.374958",
"modified": "2025-08-28 19:06:54.273322",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Trial Balance (Simple)",
"owner": "Administrator",
"prepared_report": 0,
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\nwhere is_cancelled = 0 and company = %(company)s\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
"ref_doctype": "GL Entry",
"report_name": "Trial Balance (Simple)",
"report_type": "Query Report",

View File

@@ -86,7 +86,7 @@ def get_rate_as_at(date, from_currency, to_currency):
return rate
def convert_to_presentation_currency(gl_entries, currency_info):
def convert_to_presentation_currency(gl_entries, currency_info, filters=None):
"""
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
in `currency_info`.
@@ -99,6 +99,13 @@ def convert_to_presentation_currency(gl_entries, currency_info):
company_currency = currency_info["company_currency"]
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
exchange_gain_or_loss = False
if filters and isinstance(filters.get("account"), list):
account_filter = filters.get("account")
gain_loss_account = frappe.db.get_value("Company", filters.company, "exchange_gain_loss_account")
exchange_gain_or_loss = len(account_filter) == 1 and account_filter[0] == gain_loss_account
for entry in gl_entries:
debit = flt(entry["debit"])
@@ -107,7 +114,11 @@ def convert_to_presentation_currency(gl_entries, currency_info):
credit_in_account_currency = flt(entry["credit_in_account_currency"])
account_currency = entry["account_currency"]
if len(account_currencies) == 1 and account_currency == presentation_currency:
if (
len(account_currencies) == 1
and account_currency == presentation_currency
and not exchange_gain_or_loss
) and not (filters and filters.get("show_amount_in_company_currency")):
entry["debit"] = debit_in_account_currency
entry["credit"] = credit_in_account_currency
else:

View File

@@ -2,12 +2,14 @@
# License: GNU General Public License v3. See license.txt
from collections import defaultdict
from json import loads
from typing import TYPE_CHECKING, Optional
import frappe
import frappe.defaults
from frappe import _, qb, throw
from frappe.desk.reportview import build_match_conditions
from frappe.model.meta import get_field_precision
from frappe.query_builder import AliasedQuery, Criterion, Table
from frappe.query_builder.functions import Round, Sum
@@ -26,6 +28,7 @@ from frappe.utils import (
nowdate,
)
from pypika import Order
from pypika.functions import Coalesce
from pypika.terms import ExistsCriterion
import erpnext
@@ -2191,3 +2194,44 @@ def run_ledger_health_checks():
doc.general_and_payment_ledger_mismatch = True
doc.checked_on = run_date
doc.save()
def get_link_fields_grouped_by_option(doctype):
meta = frappe.get_meta(doctype)
link_fields_map = defaultdict(list)
for df in meta.fields:
if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions:
link_fields_map[df.options].append(df.fieldname)
return link_fields_map
def build_qb_match_conditions(doctype, user=None) -> list:
match_filters = build_match_conditions(doctype, user, False)
link_fields_map = get_link_fields_grouped_by_option(doctype)
criterion = []
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
if match_filters:
_dt = qb.DocType(doctype)
for filter in match_filters:
for link_option, allowed_values in filter.items():
fieldnames = link_fields_map.get(link_option, [])
cond = None
if link_option == doctype:
cond = _dt["name"].isin(allowed_values)
for fieldname in fieldnames:
field = _dt[fieldname]
cond = field.isin(allowed_values)
if not apply_strict_user_permissions:
cond = (Coalesce(field, "") == "") | cond
if cond:
criterion.append(cond)
return criterion

View File

@@ -76,6 +76,7 @@
"fieldname": "completion_date",
"fieldtype": "Datetime",
"label": "Completion Date",
"mandatory_depends_on": "eval:doc.repair_status==\"Completed\"",
"no_copy": 1
},
{
@@ -264,7 +265,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-08-16 15:55:25.023471",
"modified": "2025-07-29 15:14:34.044564",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
@@ -302,10 +303,11 @@
"write": 1
}
],
"row_format": "Dynamic",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "asset_name",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -3,7 +3,7 @@
import frappe
from frappe import _
from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours
from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_diff_in_hours
import erpnext
from erpnext.accounts.general_ledger import make_gl_entries
@@ -20,6 +20,20 @@ class AssetRepair(AccountsController):
self.set_stock_items_cost()
self.calculate_total_repair_cost()
def validate_asset(self):
if self.asset_doc.status in ("Sold", "Fully Depreciated", "Scrapped"):
frappe.throw(
_("Asset {0} is in {1} status and cannot be repaired.").format(
get_link_to_form("Asset", self.asset), self.asset_doc.status
)
)
def validate_dates(self):
if self.completion_date and (getdate(self.failure_date) > getdate(self.completion_date)):
frappe.throw(
_("Completion Date can not be before Failure Date. Please adjust the dates accordingly.")
)
def update_status(self):
if self.repair_status == "Pending":
frappe.db.set_value("Asset", self.asset, "status", "Out of Order")
@@ -195,7 +209,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"against_voucher_type": "Purchase Invoice",
"against_voucher": self.purchase_invoice,
"company": self.company,
@@ -214,7 +228,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"company": self.company,
},
item=self,
@@ -248,7 +262,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"company": self.company,
},
item=self,
@@ -265,7 +279,7 @@ class AssetRepair(AccountsController):
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"posting_date": self.completion_date,
"against_voucher_type": "Stock Entry",
"against_voucher": self.stock_entry,
"company": self.company,

View File

@@ -4,7 +4,7 @@
import unittest
import frappe
from frappe.utils import flt, nowdate
from frappe.utils import add_days, flt, nowdate
from erpnext.assets.doctype.asset.asset import (
get_asset_account,
@@ -288,6 +288,7 @@ def create_asset_repair(**args):
if args.submit:
asset_repair.repair_status = "Completed"
asset_repair.completion_date = add_days(args.failure_date, 1)
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
if args.stock_consumption:

View File

@@ -456,9 +456,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

@@ -526,12 +526,8 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertRaises(frappe.ValidationError, pr.submit)
self.assertRaises(frappe.ValidationError, pi.submit)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_make_purchase_invoice_with_terms(self):
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
)
automatically_fetch_payment_terms()
po = create_purchase_order(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
@@ -555,7 +551,6 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0)
self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
automatically_fetch_payment_terms(enable=0)
def test_warehouse_company_validation(self):
from erpnext.stock.utils import InvalidWarehouseCompany
@@ -703,6 +698,7 @@ class TestPurchaseOrder(FrappeTestCase):
)
self.assertEqual(due_date, "2023-03-31")
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 0})
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
po = create_purchase_order(do_not_save=1)
po.payment_terms_template = "_Test Payment Term Template"
@@ -834,18 +830,16 @@ class TestPurchaseOrder(FrappeTestCase):
bo.load_from_db()
self.assertEqual(bo.items[0].ordered_qty, 5)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
)
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
compare_payment_schedules,
)
automatically_fetch_payment_terms()
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
po.payment_terms_template = "Test Receivable Template"
@@ -859,8 +853,6 @@ class TestPurchaseOrder(FrappeTestCase):
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
compare_payment_schedules(self, po, pi)
automatically_fetch_payment_terms(enable=0)
def test_internal_transfer_flow(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
make_inter_company_purchase_invoice,

View File

@@ -9,6 +9,7 @@ from frappe import _
from frappe.core.doctype.communication.email import make
from frappe.desk.form.load import get_attachments
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder import Order
from frappe.utils import get_url
from frappe.utils.print_format import download_pdf
from frappe.utils.user import get_user_fullname
@@ -502,35 +503,32 @@ def get_supplier_tag():
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if txt:
conditions += "and rfq.name like '%%" + txt + "%%' "
rfq = frappe.qb.DocType("Request for Quotation")
rfq_supplier = frappe.qb.DocType("Request for Quotation Supplier")
if filters.get("transaction_date"):
conditions += "and rfq.transaction_date = '{}'".format(filters.get("transaction_date"))
rfq_data = frappe.db.sql(
f"""
select
distinct rfq.name, rfq.transaction_date,
rfq.company
from
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
where
rfq.name = rfq_supplier.parent
and rfq_supplier.supplier = %(supplier)s
and rfq.docstatus = 1
and rfq.company = %(company)s
{conditions}
order by rfq.transaction_date ASC
limit %(page_len)s offset %(start)s """,
{
"page_len": page_len,
"start": start,
"company": filters.get("company"),
"supplier": filters.get("supplier"),
},
as_dict=1,
query = (
frappe.qb.from_(rfq)
.from_(rfq_supplier)
.select(rfq.name)
.distinct()
.select(rfq.transaction_date, rfq.company)
.where(
(rfq.name == rfq_supplier.parent)
& (rfq_supplier.supplier == filters.get("supplier"))
& (rfq.docstatus == 1)
& (rfq.company == filters.get("company"))
)
.orderby(rfq.transaction_date, order=Order.asc)
.limit(page_len)
.offset(start)
)
if txt:
query = query.where(rfq.name.like(f"%%{txt}%%"))
if filters.get("transaction_date"):
query = query.where(rfq.transaction_date == filters.get("transaction_date"))
rfq_data = query.run(as_dict=1)
return rfq_data

View File

@@ -42,6 +42,11 @@ frappe.ui.form.on("Supplier", {
},
};
});
frm.make_methods = {
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
"Pricing Rule": () => erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name),
};
},
refresh: function (frm) {
@@ -84,21 +89,9 @@ frappe.ui.form.on("Supplier", {
__("View")
);
frm.add_custom_button(
__("Bank Account"),
function () {
erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name);
},
__("Create")
);
frm.add_custom_button(__("Bank Account"), () => frm.make_methods["Bank Account"](), __("Create"));
frm.add_custom_button(
__("Pricing Rule"),
function () {
erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
},
__("Create")
);
frm.add_custom_button(__("Pricing Rule"), () => frm.make_methods["Pricing Rule"](), __("Create"));
frm.add_custom_button(
__("Get Supplier Group Details"),
@@ -108,7 +101,10 @@ frappe.ui.form.on("Supplier", {
__("Actions")
);
if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
if (
cint(frappe.defaults.get_default("enable_common_party_accounting")) &&
frappe.model.can_create("Party Link")
) {
frm.add_custom_button(
__("Link with Customer"),
function () {

View File

@@ -213,6 +213,11 @@ class AccountsController(TransactionBase):
self.validate_date_with_fiscal_year()
self.validate_party_accounts()
if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
if self.is_return:
self.validate_qty()
else:
self.validate_deferred_start_and_end_date()
self.validate_inter_company_reference()
@@ -258,11 +263,6 @@ class AccountsController(TransactionBase):
self.set_advance_gain_or_loss()
if self.is_return:
self.validate_qty()
else:
self.validate_deferred_start_and_end_date()
self.validate_deferred_income_expense_account()
self.set_inter_company_account()
@@ -1742,69 +1742,50 @@ class AccountsController(TransactionBase):
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on):
from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None
ref_wise_billed_amount = self.get_reference_wise_billed_amt(ref_dt, item_ref_dn, based_on)
role_allowed_to_over_bill = frappe.get_cached_value(
"Accounts Settings", None, "role_allowed_to_over_bill"
)
user_roles = frappe.get_roles()
if not ref_wise_billed_amount:
return
total_overbilled_amt = 0.0
overbilled_items = []
precision = self.precision(based_on, "items")
precision_allowance = 1 / (10**precision)
reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)]
reference_details = self.get_billing_reference_details(reference_names, ref_dt + " Item", based_on)
role_allowed_to_overbill = frappe.get_cached_value(
"Accounts Settings", None, "role_allowed_to_over_bill"
)
is_overbilling_allowed = role_allowed_to_overbill in frappe.get_roles()
for item in self.get("items"):
if not item.get(item_ref_dn):
continue
for row in ref_wise_billed_amount.values():
total_billed_amt = row.billed_amt
allowance = get_allowance_for(row.item_code, {}, None, None, "amount")[0]
ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
based_on_amt = flt(item.get(based_on))
if not ref_amt:
if based_on_amt: # Skip warning for free items
frappe.msgprint(
_(
"System will not check over billing since amount for Item {0} in {1} is zero"
).format(item.item_code, ref_dt),
title=_("Warning"),
indicator="orange",
)
continue
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
total_billed_amt = flt(flt(already_billed) + based_on_amt, self.precision(based_on, item))
allowance, item_allowance, global_qty_allowance, global_amount_allowance = get_allowance_for(
item.item_code, item_allowance, global_qty_allowance, global_amount_allowance, "amount"
)
max_allowed_amt = flt(ref_amt * (100 + allowance) / 100)
max_allowed_amt = flt(row.ref_amt * (100 + allowance) / 100)
if total_billed_amt < 0 and max_allowed_amt < 0:
# while making debit note against purchase return entry(purchase receipt) getting overbill error
total_billed_amt = abs(total_billed_amt)
max_allowed_amt = abs(max_allowed_amt)
total_billed_amt, max_allowed_amt = abs(total_billed_amt), abs(max_allowed_amt)
overbill_amt = total_billed_amt - max_allowed_amt
row["max_allowed_amt"] = max_allowed_amt
total_overbilled_amt += overbill_amt
if overbill_amt > 0.01 and role_allowed_to_over_bill not in user_roles:
if self.doctype != "Purchase Invoice":
self.throw_overbill_exception(item, max_allowed_amt)
elif not cint(
if overbill_amt > precision_allowance and not is_overbilling_allowed:
if self.doctype != "Purchase Invoice" or not cint(
frappe.db.get_single_value(
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
)
):
self.throw_overbill_exception(item, max_allowed_amt)
overbilled_items.append(row)
if role_allowed_to_over_bill in user_roles and total_overbilled_amt > 0.1:
if overbilled_items:
self.throw_overbill_exception(overbilled_items, precision)
if is_overbilling_allowed and total_overbilled_amt > 0.1:
frappe.msgprint(
_("Overbilling of {} ignored because you have {} role.").format(
total_overbilled_amt, role_allowed_to_over_bill
total_overbilled_amt, role_allowed_to_overbill
),
indicator="orange",
alert=True,
@@ -1820,55 +1801,88 @@ class AccountsController(TransactionBase):
)
)
def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
def get_reference_wise_billed_amt(self, ref_dt, item_ref_dn, based_on):
"""
Returns Sum of Amount of
Sales/Purchase Invoice Items
that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`)
that are submitted OR not submitted but are under current invoice
"""
reference_names = [d.get(item_ref_dn) for d in self.items if d.get(item_ref_dn)]
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Sum
if not reference_names:
return
item_doctype = frappe.qb.DocType(item.doctype)
ref_wise_billed_amount = {}
precision = self.precision(based_on, "items")
reference_details = self.get_billing_reference_details(reference_names, ref_dt + " Item", based_on)
already_billed = self.get_already_billed_amount(reference_names, item_ref_dn, based_on)
for item in self.items:
key = item.get(item_ref_dn)
if not key:
continue
ref_amt = flt(reference_details.get(key), precision)
current_amount = flt(item.get(based_on), precision)
if not ref_amt:
if current_amount: # Skip warning for free items
frappe.msgprint(
_(
"System will not check over billing since amount for Item {0} in {1} is zero"
).format(item.item_code, ref_dt),
title=_("Warning"),
indicator="orange",
)
continue
ref_wise_billed_amount.setdefault(
key,
frappe._dict(item_code=item.item_code, billed_amt=0.0, ref_amt=ref_amt, rows=[]),
)
ref_wise_billed_amount[key]["rows"].append(item.idx)
ref_wise_billed_amount[key]["ref_amt"] = ref_amt
ref_wise_billed_amount[key]["billed_amt"] += current_amount
if key in already_billed:
ref_wise_billed_amount[key]["billed_amt"] += flt(already_billed.pop(key, 0), precision)
return ref_wise_billed_amount
def get_already_billed_amount(self, reference_names, item_ref_dn, based_on):
item_doctype = frappe.qb.DocType(self.items[0].doctype)
based_on_field = frappe.qb.Field(based_on)
join_field = frappe.qb.Field(item_ref_dn)
result = (
frappe.qb.from_(item_doctype)
.select(Sum(based_on_field))
.where(join_field == item.get(item_ref_dn))
.where(
Criterion.any(
[ # select all items from other invoices OR current invoices
Criterion.all(
[ # for selecting items from other invoices
item_doctype.docstatus == 1,
item_doctype.parent != self.name,
]
),
Criterion.all(
[ # for selecting items from current invoice, that are linked to same reference
item_doctype.docstatus == 0,
item_doctype.parent == self.name,
item_doctype.name != item.name,
]
),
]
)
)
).run()
return result[0][0] if result else 0
def throw_overbill_exception(self, item, max_allowed_amt):
frappe.throw(
_(
"Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings"
).format(item.item_code, item.idx, max_allowed_amt)
return frappe._dict(
(
frappe.qb.from_(item_doctype)
.select(join_field, Sum(based_on_field))
.where(join_field.isin(reference_names))
.where((item_doctype.docstatus == 1) & (item_doctype.parent != self.name))
.groupby(join_field)
).run()
)
def throw_overbill_exception(self, overbilled_items, precision):
message = (
_("<p>Cannot overbill for the following Items:</p>")
+ "<ul>"
+ "".join(
_("<li>Item {0} in row(s) {1} billed more than {2}</li>").format(
frappe.bold(item.item_code),
", ".join(str(x) for x in item.rows),
frappe.bold(fmt_money(item.max_allowed_amt, precision=precision, currency=self.currency)),
)
for item in overbilled_items
)
+ "</ul>"
)
message += _("<p>To allow over-billing, please set allowance in Accounts Settings.</p>")
frappe.throw(_(message))
def get_company_default(self, fieldname, ignore_validation=False):
from erpnext.accounts.utils import get_company_default
@@ -2225,6 +2239,7 @@ class AccountsController(TransactionBase):
self.payment_schedule = []
self.payment_terms_template = po_or_so.payment_terms_template
posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
for schedule in po_or_so.payment_schedule:
payment_schedule = {
@@ -2237,6 +2252,17 @@ class AccountsController(TransactionBase):
}
if automatically_fetch_payment_terms:
if schedule.due_date_based_on:
payment_schedule["due_date"] = get_due_date(schedule, posting_date)
payment_schedule["due_date_based_on"] = schedule.due_date_based_on
payment_schedule["credit_days"] = cint(schedule.credit_days)
payment_schedule["credit_months"] = cint(schedule.credit_months)
if schedule.discount_validity_based_on:
payment_schedule["discount_date"] = get_discount_date(schedule, posting_date)
payment_schedule["discount_validity_based_on"] = schedule.discount_validity_based_on
payment_schedule["discount_validity"] = cint(schedule.discount_validity)
payment_schedule["payment_amount"] = flt(
grand_total * flt(payment_schedule["invoice_portion"]) / 100,
schedule.precision("payment_amount"),
@@ -2724,9 +2750,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):
@@ -2973,14 +2997,26 @@ def get_payment_term_details(
term = frappe.get_doc("Payment Term", term)
else:
term_details.payment_term = term.payment_term
term_details.description = term.description
term_details.invoice_portion = term.invoice_portion
fields_to_copy = [
"description",
"invoice_portion",
"discount_type",
"discount",
"mode_of_payment",
"due_date_based_on",
"credit_days",
"credit_months",
"discount_validity_based_on",
"discount_validity",
]
for field in fields_to_copy:
term_details[field] = term.get(field)
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
term_details.discount_type = term.discount_type
term_details.discount = term.discount
term_details.outstanding = term_details.payment_amount
term_details.mode_of_payment = term.mode_of_payment
if bill_date:
term_details.due_date = get_due_date(term, bill_date)
@@ -2999,11 +3035,11 @@ def get_due_date(term, posting_date=None, bill_date=None):
due_date = None
date = bill_date or posting_date
if term.due_date_based_on == "Day(s) after invoice date":
due_date = add_days(date, term.credit_days)
due_date = add_days(date, cint(term.credit_days))
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = add_days(get_last_day(date), term.credit_days)
due_date = add_days(get_last_day(date), cint(term.credit_days))
elif term.due_date_based_on == "Month(s) after the end of the invoice month":
due_date = get_last_day(add_months(date, term.credit_months))
due_date = get_last_day(add_months(date, cint(term.credit_months)))
return due_date
@@ -3011,11 +3047,11 @@ def get_discount_date(term, posting_date=None, bill_date=None):
discount_validity = None
date = bill_date or posting_date
if term.discount_validity_based_on == "Day(s) after invoice date":
discount_validity = add_days(date, term.discount_validity)
discount_validity = add_days(date, cint(term.discount_validity))
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
discount_validity = add_days(get_last_day(date), term.discount_validity)
discount_validity = add_days(get_last_day(date), cint(term.discount_validity))
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
discount_validity = get_last_day(add_months(date, term.discount_validity))
discount_validity = get_last_day(add_months(date, cint(term.discount_validity)))
return discount_validity

View File

@@ -6,7 +6,7 @@ import json
from collections import defaultdict
import frappe
from frappe import qb, scrub
from frappe import cint, qb, scrub
from frappe.desk.reportview import get_filters_cond, get_match_cond
from frappe.query_builder import Criterion, CustomFunction
from frappe.query_builder.functions import Locate
@@ -528,21 +528,27 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select distinct bo.name, bo.blanket_order_type, bo.to_date
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
where
boi.parent = bo.name
and boi.item_code = {item_code}
and bo.blanket_order_type = '{blanket_order_type}'
and bo.company = {company}
and bo.docstatus = 1""".format(
item_code=frappe.db.escape(filters.get("item")),
blanket_order_type=filters.get("blanket_order_type"),
company=frappe.db.escape(filters.get("company")),
bo = frappe.qb.DocType("Blanket Order")
bo_item = frappe.qb.DocType("Blanket Order Item")
blanket_orders = (
frappe.qb.from_(bo)
.from_(bo_item)
.select(bo.name)
.distinct()
.select(bo.blanket_order_type, bo.to_date)
.where(
(bo_item.parent == bo.name)
& (bo_item.item_code == filters.get("item"))
& (bo.blanket_order_type == filters.get("blanket_order_type"))
& (bo.company == filters.get("company"))
& (bo.docstatus == 1)
)
.run()
)
return blanket_orders
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -560,7 +566,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
if filters.get("company"):
condition += "and tabAccount.company = %(company)s"
condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}"
condition += " and tabAccount.disabled = %(disabled)s"
return frappe.db.sql(
f"""select tabAccount.name from `tabAccount`
@@ -570,7 +576,11 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
and tabAccount.`{searchfield}` LIKE %(txt)s
{condition} {get_match_cond(doctype)}
order by idx desc, name""",
{"txt": "%" + txt + "%", "company": filters.get("company", "")},
{
"txt": "%" + txt + "%",
"company": filters.get("company", ""),
"disabled": cint(filters.get("disabled", 0)),
},
)
@@ -646,7 +656,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
and tabAccount.is_group=0
and tabAccount.docstatus!=2
and tabAccount.disabled = 0
and tabAccount.{searchfield} LIKE %(txt)s
{condition} {get_match_cond(doctype)}""",
{"company": filters.get("company", ""), "txt": "%" + txt + "%"},

View File

@@ -520,8 +520,9 @@ class StockController(AccountsController):
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self):
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
def make_gl_entries_on_cancel(self, from_repost=False):
if not from_repost:
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
if frappe.db.sql(
"""select name from `tabGL Entry` where voucher_type=%s
and voucher_no=%s""",
@@ -1027,12 +1028,7 @@ def is_reposting_pending():
)
def future_sle_exists(args, sl_entries=None, allow_force_reposting=True):
if allow_force_reposting and frappe.db.get_single_value(
"Stock Reposting Settings", "do_reposting_for_each_stock_transaction"
):
return True
def future_sle_exists(args, sl_entries=None):
key = (args.voucher_type, args.voucher_no)
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}

View File

@@ -60,7 +60,7 @@ def import_genericode():
"doctype": "File",
"attached_to_doctype": "Code List",
"attached_to_name": code_list.name,
"folder": "Home/Attachments",
"folder": frappe.db.get_value("File", {"is_attachments_folder": 1}),
"file_name": frappe.local.uploaded_filename,
"file_url": frappe.local.uploaded_file_url,
"is_private": 1,

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"code_list",
"canonical_uri",
"title",
"common_code",
"description",
@@ -71,10 +72,17 @@
"in_list_view": 1,
"label": "Description",
"max_height": "60px"
},
{
"fetch_from": "code_list.canonical_uri",
"fieldname": "canonical_uri",
"fieldtype": "Data",
"label": "Canonical URI"
}
],
"grid_page_length": 50,
"links": [],
"modified": "2024-11-06 07:46:17.175687",
"modified": "2025-10-04 17:22:28.176155",
"modified_by": "Administrator",
"module": "EDI",
"name": "Common Code",
@@ -94,10 +102,11 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "common_code,description",
"show_title_field_in_link": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}
}

View File

@@ -22,6 +22,7 @@ class CommonCode(Document):
additional_data: DF.Code | None
applies_to: DF.Table[DynamicLink]
canonical_uri: DF.Data | None
code_list: DF.Link
common_code: DF.Data
description: DF.SmallText | None

View File

@@ -37,6 +37,14 @@ frappe.ui.form.on("Production Plan", {
};
});
frm.set_query("sub_assembly_warehouse", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
frm.set_query("material_request", "material_requests", function () {
return {
filters: {

View File

@@ -1048,6 +1048,7 @@ def get_exploded_items(item_details, company, bom_no, include_non_stock_items, p
item.purchase_uom,
item_uom.conversion_factor,
item.safety_stock,
bom.item.as_("main_bom_item"),
)
.where(
(bei.docstatus < 2)
@@ -1115,6 +1116,7 @@ def get_subitems(
item_default.default_warehouse,
item.purchase_uom,
item_uom.conversion_factor,
bom.item.as_("main_bom_item"),
)
.where(
(bom.name == bom_no)
@@ -1228,6 +1230,7 @@ def get_material_request_items(
"sales_order": sales_order,
"description": row.get("description"),
"uom": row.get("purchase_uom") or row.get("stock_uom"),
"main_bom_item": row.get("main_bom_item"),
}
@@ -1757,6 +1760,7 @@ def get_raw_materials_of_sub_assembly_items(
item.purchase_uom,
item_uom.conversion_factor,
item.safety_stock,
bom.item.as_("main_bom_item"),
)
.where(
(bei.docstatus == 1)

View File

@@ -1322,20 +1322,20 @@ def stop_unstop(work_order, status):
@frappe.whitelist()
def query_sales_order(production_item):
out = frappe.db.sql_list(
"""
select distinct so.name from `tabSales Order` so, `tabSales Order Item` so_item
where so_item.parent=so.name and so_item.item_code=%s and so.docstatus=1
union
select distinct so.name from `tabSales Order` so, `tabPacked Item` pi_item
where pi_item.parent=so.name and pi_item.item_code=%s and so.docstatus=1
""",
(production_item, production_item),
def query_sales_order(production_item: str) -> list[str]:
return frappe.get_list(
"Sales Order",
filters=[
["Sales Order", "docstatus", "=", 1],
],
or_filters=[
["Sales Order Item", "item_code", "=", production_item],
["Packed Item", "item_code", "=", production_item],
],
pluck="name",
distinct=True,
)
return out
@frappe.whitelist()
def make_job_card(work_order, operations):

View File

@@ -79,7 +79,7 @@ def get_bom_stock(filters):
BOM_ITEM.stock_qty,
BOM_ITEM.stock_uom,
BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity,
Sum(BIN.actual_qty).as_("actual_qty"),
BIN.actual_qty.as_("actual_qty"),
Sum(Floor(BIN.actual_qty / (BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity))),
)
.where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM"))

View File

@@ -292,12 +292,16 @@ def get_projectwise_timesheet_data(project=None, parent=None, from_time=None, to
@frappe.whitelist()
def get_timesheet_detail_rate(timelog, currency):
timelog_detail = frappe.db.sql(
f"""SELECT tsd.billing_amount as billing_amount,
ts.currency as currency FROM `tabTimesheet Detail` tsd
INNER JOIN `tabTimesheet` ts ON ts.name=tsd.parent
WHERE tsd.name = '{timelog}'""",
as_dict=1,
ts = frappe.qb.DocType("Timesheet")
ts_detail = frappe.qb.DocType("Timesheet Detail")
timelog_detail = (
frappe.qb.from_(ts_detail)
.inner_join(ts)
.on(ts.name == ts_detail.parent)
.select(ts_detail.billing_amount.as_("billing_amount"), ts.currency.as_("currency"))
.where(ts_detail.name == timelog)
.run(as_dict=1)
)[0]
if timelog_detail.currency:

View File

@@ -1,15 +1,17 @@
import urllib.parse
import frappe
def get_context(context):
if frappe.form_dict.project:
context.parents = [
{"title": frappe.form_dict.project, "route": "/projects?project=" + frappe.form_dict.project}
]
context.success_url = "/projects?project=" + frappe.form_dict.project
if project := frappe.form_dict.project:
title = frappe.utils.data.escape_html(project)
route = "/projects?" + urllib.parse.urlencode({"project": project})
context.parents = [{"title": title, "route": route}]
context.success_url = route
elif context.doc and context.doc.get("project"):
context.parents = [
{"title": context.doc.project, "route": "/projects?project=" + context.doc.project}
]
context.success_url = "/projects?project=" + context.doc.project
elif context.doc and (project := context.doc.get("project")):
title = frappe.utils.data.escape_html(project)
route = "/projects?" + urllib.parse.urlencode({"project": project})
context.parents = [{"title": title, "route": route}]
context.success_url = route

View File

@@ -76,9 +76,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
// Update paid amount on return/debit note creation
if (
this.frm.doc.doctype === "Purchase Invoice"
&& this.frm.doc.is_return
&& (this.frm.doc.grand_total > this.frm.doc.paid_amount)
this.frm.doc.doctype === "Purchase Invoice" &&
this.frm.doc.is_return &&
this.frm.doc.grand_total < 0 &&
this.frm.doc.grand_total > this.frm.doc.paid_amount
) {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
}

View File

@@ -878,6 +878,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
}
discount_date(doc, cdt, cdn) {
// Remove fields as discount_date is auto-managed by payment terms
const row = locals[cdt][cdn];
["discount_validity", "discount_validity_based_on"].forEach((field) => {
row[field] = "";
});
this.frm.refresh_field("payment_schedule");
}
due_date() {
// due_date is to be changed, payment terms template and/or payment schedule must
// be removed as due_date is automatically changed based on payment terms
@@ -2203,6 +2212,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
'item_code': item.item_code,
'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
'item_group': item.item_group,
'disabled': 0,
}
if (doc.tax_category)
@@ -2244,7 +2254,18 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
payment_term(doc, cdt, cdn) {
const me = this;
var row = locals[cdt][cdn];
if(row.payment_term) {
// empty date condition fields
[
"due_date_based_on",
"credit_days",
"credit_months",
"discount_validity",
"discount_validity_based_on",
].forEach(function (field) {
row[field] = "";
});
if (row.payment_term) {
frappe.call({
method: "erpnext.controllers.accounts_controller.get_payment_term_details",
args: {
@@ -2254,16 +2275,19 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total,
base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total
},
callback: function(r) {
if(r.message && !r.exc) {
for (var d in r.message) {
frappe.model.set_value(cdt, cdn, d, r.message[d]);
const company_currency = me.get_company_currency();
me.update_payment_schedule_grid_labels(company_currency);
callback: function (r) {
if (r.message && !r.exc) {
const company_currency = me.get_company_currency();
for (let d in r.message) {
row[d] = r.message[d];
}
me.update_payment_schedule_grid_labels(company_currency);
me.frm.refresh_field("payment_schedule");
}
}
})
},
});
} else {
me.frm.refresh_field("payment_schedule");
}
}

View File

@@ -215,17 +215,9 @@ $.extend(erpnext.utils, {
},
make_bank_account: function (doctype, docname) {
frappe.call({
method: "erpnext.accounts.doctype.bank_account.bank_account.make_bank_account",
args: {
doctype: doctype,
docname: docname,
},
freeze: true,
callback: function (r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
},
frappe.new_doc("Bank Account", {
party_type: doctype,
party: docname,
});
},

View File

@@ -14,6 +14,7 @@ frappe.ui.form.on("Customer", {
method: "erpnext.selling.doctype.customer.customer.make_opportunity",
frm: cur_frm,
}),
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
};
frm.add_fetch("lead_name", "company_name", "customer_name");
@@ -155,7 +156,10 @@ frappe.ui.form.on("Customer", {
__("Actions")
);
if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
if (
cint(frappe.defaults.get_default("enable_common_party_accounting")) &&
frappe.model.can_create("Party Link")
) {
frm.add_custom_button(
__("Link with Supplier"),
function () {

View File

@@ -1266,6 +1266,11 @@ def make_raw_material_request(items, company, sales_order, project=None):
items.update({"company": company, "sales_order": sales_order})
item_wh = {}
for item in items.get("items"):
if item.get("warehouse"):
item_wh[item.get("item_code")] = item.get("warehouse")
raw_materials = get_items_for_material_requests(items)
if not raw_materials:
frappe.msgprint(_("Material Request not created, as quantity for Raw Materials already available."))
@@ -1290,7 +1295,7 @@ def make_raw_material_request(items, company, sales_order, project=None):
"item_code": item.get("item_code"),
"qty": item.get("quantity"),
"schedule_date": schedule_date,
"warehouse": item.get("warehouse"),
"warehouse": item_wh.get(item.get("main_bom_item")) or item.get("warehouse"),
"sales_order": sales_order,
"project": project,
},

View File

@@ -10,7 +10,7 @@ from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.controllers.accounts_controller import update_child_qty_rate
from erpnext.controllers.accounts_controller import get_due_date, update_child_qty_rate
from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
make_maintenance_schedule,
)
@@ -1759,14 +1759,13 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
so.load_from_db()
self.assertRaises(frappe.LinkExistsError, so.cancel)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
automatically_fetch_payment_terms()
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
so.payment_terms_template = "Test Receivable Template"
@@ -1780,8 +1779,6 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertEqual(so.payment_terms_template, si.payment_terms_template)
compare_payment_schedules(self, so, si)
automatically_fetch_payment_terms(enable=0)
def test_zero_amount_sales_order_billing_status(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
@@ -2284,16 +2281,14 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
self.assertRaises(frappe.ValidationError, so1.update_status, "Draft")
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.automatically_fetch_payment_terms = enable
accounts_settings.save()
def compare_payment_schedules(doc, doc1, doc2):
for index, schedule in enumerate(doc1.get("payment_schedule")):
posting_date = doc1.get("bill_date") or doc1.get("posting_date") or doc1.get("transaction_date")
due_date = schedule.due_date
if schedule.due_date_based_on:
due_date = get_due_date(schedule, posting_date=posting_date)
doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term)
doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date)
doc.assertEqual(due_date, doc2.payment_schedule[index].due_date)
doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion)
doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount)

View File

@@ -37,15 +37,15 @@ class SellingSettings(Document):
)
def toggle_hide_tax_id(self):
self.hide_tax_id = cint(self.hide_tax_id)
_hide_tax_id = cint(self.hide_tax_id)
# Make property setters to hide tax_id fields
for doctype in ("Sales Order", "Sales Invoice", "Delivery Note"):
make_property_setter(
doctype, "tax_id", "hidden", self.hide_tax_id, "Check", validate_fields_for_doctype=False
doctype, "tax_id", "hidden", _hide_tax_id, "Check", validate_fields_for_doctype=False
)
make_property_setter(
doctype, "tax_id", "print_hide", self.hide_tax_id, "Check", validate_fields_for_doctype=False
doctype, "tax_id", "print_hide", _hide_tax_id, "Check", validate_fields_for_doctype=False
)
def toggle_editable_rate_for_bundle_items(self):

View File

@@ -21,6 +21,12 @@ erpnext.setup.EmployeeController = class EmployeeController extends frappe.ui.fo
};
frappe.ui.form.on("Employee", {
setup: function (frm) {
frm.make_methods = {
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
};
},
onload: function (frm) {
frm.set_query("department", function () {
return {

View File

@@ -2,8 +2,10 @@ frappe.listview_settings["Employee"] = {
add_fields: ["status", "branch", "department", "designation", "image"],
filters: [["status", "=", "Active"]],
get_indicator: function (doc) {
var indicator = [__(doc.status), frappe.utils.guess_colour(doc.status), "status,=," + doc.status];
indicator[1] = { Active: "green", Inactive: "red", Left: "gray", Suspended: "orange" }[doc.status];
return indicator;
return [
__(doc.status, null, "Employee"),
{ Active: "green", Inactive: "red", Left: "gray", Suspended: "orange" }[doc.status],
"status,=," + doc.status,
];
},
};

View File

@@ -5,8 +5,10 @@ import unittest
import frappe
import frappe.utils
from frappe.query_builder import Criterion
import erpnext
from erpnext.accounts.utils import build_qb_match_conditions
from erpnext.setup.doctype.employee.employee import InactiveEmployeeStatusError
test_records = frappe.get_test_records("Employee")
@@ -34,6 +36,32 @@ class TestEmployee(unittest.TestCase):
employee_doc.save()
self.assertTrue("Employee" not in frappe.get_roles(user))
def test_employee_user_permission(self):
employee1 = make_employee("employee_1_test@company.com", create_user_permission=1)
employee2 = make_employee("employee_2_test@company.com", create_user_permission=1)
make_employee("employee_3_test@company.com", create_user_permission=1)
employee1_doc = frappe.get_doc("Employee", employee1)
employee2_doc = frappe.get_doc("Employee", employee2)
employee2_doc.reload()
employee2_doc.reports_to = employee1_doc.name
employee2_doc.save()
frappe.set_user(employee1_doc.user_id)
Employee = frappe.qb.DocType("Employee")
qb_employee_list = (
frappe.qb.from_(Employee)
.select(Employee.name)
.where(Criterion.all(build_qb_match_conditions("Employee")))
.orderby(Employee.Name)
).run(pluck=Employee.name)
employee_list = frappe.db.get_list("Employee", pluck="name", order_by="name")
self.assertEqual(qb_employee_list, employee_list)
frappe.set_user("Administrator")
def tearDown(self):
frappe.db.rollback()

View File

@@ -202,7 +202,7 @@ def update_qty(bin_name, args):
sle = frappe.qb.DocType("Stock Ledger Entry")
# actual qty is not up to date in case of backdated transaction
if future_sle_exists(args, allow_force_reposting=False):
if future_sle_exists(args):
last_sle_qty = (
frappe.qb.from_(sle)
.select(sle.qty_after_transaction)

View File

@@ -13,7 +13,6 @@ from erpnext.accounts.utils import get_balance_on
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
compare_payment_schedules,
create_dn_against_so,
make_sales_order,
@@ -1026,14 +1025,13 @@ class TestDeliveryNote(FrappeTestCase):
self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
automatically_fetch_payment_terms()
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
so.payment_terms_template = "Test Receivable Template"
@@ -1053,8 +1051,6 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(so.payment_terms_template, si.payment_terms_template)
compare_payment_schedules(self, so, si)
automatically_fetch_payment_terms(enable=0)
def test_returned_qty_in_return_dn(self):
# SO ---> SI ---> DN
# |

View File

@@ -75,7 +75,9 @@ frappe.ui.form.on("Inventory Dimension", {
set_parent_fields(frm) {
if (frm.doc.apply_to_all_doctypes) {
frm.set_df_property("fetch_from_parent", "options", frm.doc.reference_document);
let options = ["\n", frm.doc.reference_document];
frm.set_df_property("fetch_from_parent", "options", options);
} else if (frm.doc.document_type && frm.doc.istable) {
frappe.call({
method: "erpnext.stock.doctype.inventory_dimension.inventory_dimension.get_parent_fields",
@@ -85,7 +87,7 @@ frappe.ui.form.on("Inventory Dimension", {
},
callback: (r) => {
if (r.message && r.message.length) {
frm.set_df_property("fetch_from_parent", "options", [""].concat(r.message));
frm.set_df_property("fetch_from_parent", "options", ["\n"].concat(r.message));
} else {
frm.set_df_property("fetch_from_parent", "hidden", 1);
}

View File

@@ -143,7 +143,6 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.apply_to_all_doctypes",
"description": "Set fieldname from which you want to fetch the data from the parent form.",
"fieldname": "fetch_from_parent",
"fieldtype": "Select",
@@ -189,7 +188,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-10-05 12:52:18.705431",
"modified": "2025-07-07 15:51:29.329064",
"modified_by": "Administrator",
"module": "Stock",
"name": "Inventory Dimension",
@@ -236,4 +235,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -40,16 +40,11 @@ class InventoryDimension(Document):
self.reset_value()
self.set_source_and_target_fieldname()
self.set_type_of_transaction()
self.set_fetch_value_from()
def set_type_of_transaction(self):
if self.apply_to_all_doctypes:
self.type_of_transaction = "Both"
def set_fetch_value_from(self):
if self.apply_to_all_doctypes:
self.fetch_from_parent = self.reference_document
def do_not_update_document(self):
if self.is_new() or not self.has_stock_ledger():
return

View File

@@ -154,6 +154,8 @@ class TestInventoryDimension(FrappeTestCase):
reference_document="Rack", dimension_name="Rack", apply_to_all_doctypes=1
)
inv_dimension.db_set("fetch_from_parent", "Rack")
self.assertEqual(inv_dimension.type_of_transaction, "Both")
self.assertEqual(inv_dimension.fetch_from_parent, "Rack")

View File

@@ -10,6 +10,7 @@ import json
import frappe
from frappe import _, msgprint
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder import Order
from frappe.query_builder.functions import Sum
from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
@@ -498,39 +499,44 @@ def get_items_based_on_default_supplier(supplier):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if txt:
conditions += "and mr.name like '%%" + txt + "%%' "
if filters.get("transaction_date"):
date = filters.get("transaction_date")[1]
conditions += f"and mr.transaction_date between '{date[0]}' and '{date[1]}' "
supplier = filters.get("supplier")
supplier_items = get_items_based_on_default_supplier(supplier)
if not supplier_items:
frappe.throw(_("{0} is not the default supplier for any items.").format(supplier))
material_requests = frappe.db.sql(
"""select distinct mr.name, transaction_date,company
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr.name = mr_item.parent
and mr_item.item_code in ({})
and mr.material_request_type = 'Purchase'
and mr.per_ordered < 99.99
and mr.docstatus = 1
and mr.status != 'Stopped'
and mr.company = %s
{}
order by mr_item.item_code ASC
limit {} offset {} """.format(
", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start)
),
(*tuple(supplier_items), filters.get("company")),
as_dict=1,
mr = frappe.qb.DocType("Material Request")
mr_item = frappe.qb.DocType("Material Request Item")
query = (
frappe.qb.from_(mr)
.from_(mr_item)
.select(mr.name)
.distinct()
.select(mr.transaction_date, mr.company)
.where(
(mr.name == mr_item.parent)
& (mr_item.item_code.isin(supplier_items))
& (mr.material_request_type == "Purchase")
& (mr.per_ordered < 99.99)
& (mr.docstatus == 1)
& (mr.status != "Stopped")
& (mr.company == filters.get("company"))
)
.orderby(mr_item.item_code, order=Order.asc)
.limit(cint(page_len))
.offset(cint(start))
)
if txt:
query = query.where(mr.name.like(f"%%{txt}%%"))
if filters.get("transaction_date"):
date = filters.get("transaction_date")[1]
query = query.where(mr.transaction_date[date[0] : date[1]])
material_requests = query.run(as_dict=True)
return material_requests

View File

@@ -1077,7 +1077,8 @@
"no_copy": 1,
"options": "Delivery Note",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "scan_barcode",
@@ -1242,7 +1243,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2024-11-13 16:55:14.129055",
"modified": "2025-08-06 16:41:02.690658",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@@ -1056,6 +1056,7 @@ class TestPurchaseReceipt(FrappeTestCase):
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
@change_settings("Accounts Settings", {"automatically_fetch_payment_terms": 1})
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
create_payment_terms_template,
@@ -1066,12 +1067,9 @@ class TestPurchaseReceipt(FrappeTestCase):
make_pr_against_po,
)
from erpnext.selling.doctype.sales_order.test_sales_order import (
automatically_fetch_payment_terms,
compare_payment_schedules,
)
automatically_fetch_payment_terms()
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
po.payment_terms_template = "Test Receivable Template"
@@ -1089,8 +1087,6 @@ class TestPurchaseReceipt(FrappeTestCase):
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
compare_payment_schedules(self, po, pi)
automatically_fetch_payment_terms(enable=0)
@change_settings("Stock Settings", {"allow_negative_stock": 1})
def test_neg_to_positive(self):
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -2761,6 +2757,63 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.reload()
self.assertEqual(pr.status, "To Bill")
def test_serial_no_exists_in_future(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
item_doc = make_item(
"Test Serial No Item Exists in Future",
{
"is_purchase_item": 1,
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "SN-SBNS-.#####",
},
)
source_warehouse = "_Test Warehouse - _TC"
target_warehouse = "_Test Warehouse 1 - _TC"
if not frappe.db.exists("Warehouse", target_warehouse):
create_warehouse("_Test Warehouse 1")
make_purchase_receipt(
item_code=item_doc.name,
qty=1,
rate=100,
serial_no="SN-SBNS-00001",
posting_date=add_days(today(), -2),
)
make_stock_entry(
item_code=item_doc.name,
qty=1,
rate=100,
to_warehouse=target_warehouse,
serial_no="SN-SBNS-00002",
posting_date=add_days(today(), -1),
)
make_stock_entry(
item_code=item_doc.name,
qty=1,
rate=100,
from_warehouse=source_warehouse,
to_warehouse=target_warehouse,
serial_no="SN-SBNS-00001",
posting_date=today(),
)
se = make_stock_entry(
item_code=item_doc.name,
qty=1,
rate=100,
from_warehouse=target_warehouse,
serial_no="SN-SBNS-00001",
posting_date=add_days(today(), -1),
do_not_submit=True,
)
self.assertRaises(frappe.ValidationError, se.submit)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -8,7 +8,18 @@ import frappe
from frappe import ValidationError, _
from frappe.model.naming import make_autoname
from frappe.query_builder.functions import Coalesce
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, now, nowdate, safe_json_loads
from frappe.utils import (
add_days,
cint,
cstr,
flt,
get_datetime,
get_link_to_form,
getdate,
now,
nowdate,
safe_json_loads,
)
from erpnext.controllers.stock_controller import StockController
from erpnext.stock.get_item_details import get_reserved_qty_for_so
@@ -197,7 +208,7 @@ class SerialNo(StockController):
for sle in frappe.db.sql(
"""
SELECT voucher_type, voucher_no,
posting_date, posting_time, incoming_rate, actual_qty, serial_no
posting_date, posting_time, incoming_rate, actual_qty, serial_no, posting_datetime
FROM
`tabStock Ledger Entry`
WHERE
@@ -251,8 +262,23 @@ class SerialNo(StockController):
_("Cannot delete Serial No {0}, as it is used in stock transactions").format(self.name)
)
def update_serial_no_reference(self, serial_no=None):
def update_serial_no_reference(self, serial_no=None, sle=None):
last_sle = self.get_last_sle(serial_no)
_last_sle_dict = last_sle.get("last_sle")
if (
_last_sle_dict
and sle.get("voucher_type") != "Stock Reconciliation"
and sle.get("voucher_no") != _last_sle_dict.get("voucher_no")
and get_datetime(sle.get("posting_datetime"))
< get_datetime(_last_sle_dict.get("posting_datetime"))
):
frappe.throw(
_(
"You can not complete this transaction because a future transaction exists for the serial number {0}"
).format(serial_no)
)
self.set_purchase_details(last_sle.get("purchase_sle"))
self.set_sales_details(last_sle.get("delivery_sle"))
self.set_maintenance_status()
@@ -770,7 +796,7 @@ def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
serial_no_doc.sales_order = None
serial_no_doc.validate_item()
serial_no_doc.update_serial_no_reference(serial_no)
serial_no_doc.update_serial_no_reference(serial_no, sle=args)
if is_new:
serial_no_doc.db_insert()

View File

@@ -748,12 +748,12 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None, ig
itemwise_batch_data = get_itemwise_batch(warehouse, posting_date, company, item_code)
for d in items:
if d.item_code in itemwise_batch_data:
if (d.item_code, d.warehouse) in itemwise_batch_data:
valuation_rate = get_stock_balance(
d.item_code, d.warehouse, posting_date, posting_time, with_valuation_rate=True
)[1]
for row in itemwise_batch_data.get(d.item_code):
for row in itemwise_batch_data.get((d.item_code, d.warehouse)):
if ignore_empty_stock and not row.qty:
continue
@@ -885,7 +885,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None):
columns, data = execute(filters)
for row in data:
itemwise_batch_data.setdefault(row[0], []).append(
itemwise_batch_data.setdefault((row[0], row[3]), []).append(
frappe._dict(
{
"item_code": row[0],

View File

@@ -13,7 +13,6 @@
"end_time",
"limits_dont_apply_on",
"item_based_reposting",
"do_reposting_for_each_stock_transaction",
"errors_notification_section",
"notify_reposting_error_to_role"
],
@@ -66,18 +65,12 @@
"fieldname": "errors_notification_section",
"fieldtype": "Section Break",
"label": "Errors Notification"
},
{
"default": "0",
"fieldname": "do_reposting_for_each_stock_transaction",
"fieldtype": "Check",
"label": "Do reposting for each Stock Transaction"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-04-24 12:19:40.204888",
"modified": "2025-07-08 11:27:46.659056",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reposting Settings",

View File

@@ -11,10 +11,6 @@ class StockRepostingSettings(Document):
def validate(self):
self.set_minimum_reposting_time_slot()
def before_save(self):
if self.do_reposting_for_each_stock_transaction:
self.item_based_reposting = 1
def set_minimum_reposting_time_slot(self):
"""Ensure that timeslot for reposting is at least 12 hours."""
if not self.limit_reposting_timeslot:

View File

@@ -38,51 +38,3 @@ class TestStockRepostingSettings(unittest.TestCase):
users = get_recipients()
self.assertTrue(user in users)
def test_do_reposting_for_each_stock_transaction(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 1)
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
item = make_item(
"_Test item for reposting check for each transaction", properties={"is_stock_item": 1}
).name
stock_entry = make_stock_entry(
item_code=item,
qty=1,
rate=100,
stock_entry_type="Material Receipt",
target="_Test Warehouse - _TC",
)
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
self.assertTrue(riv)
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
def test_do_not_reposting_for_each_stock_transaction(self):
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
frappe.db.set_single_value("Stock Reposting Settings", "do_reposting_for_each_stock_transaction", 0)
if frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"):
frappe.db.set_single_value("Stock Reposting Settings", "item_based_reposting", 0)
item = make_item(
"_Test item for do not reposting check for each transaction", properties={"is_stock_item": 1}
).name
stock_entry = make_stock_entry(
item_code=item,
qty=1,
rate=100,
stock_entry_type="Material Receipt",
target="_Test Warehouse - _TC",
)
riv = frappe.get_all("Repost Item Valuation", filters={"voucher_no": stock_entry.name}, pluck="name")
self.assertFalse(riv)

View File

@@ -635,8 +635,10 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
taxes_with_no_validity = []
for tax in taxes:
tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, "company")
if tax_company == args["company"]:
disabled, tax_company = frappe.get_cached_value(
"Item Tax Template", tax.item_tax_template, ["disabled", "company"]
)
if not disabled and tax_company == args["company"]:
if tax.valid_from or tax.maximum_net_rate:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date

View File

@@ -159,10 +159,11 @@ def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
def get_item_groups_map(svd_list: SVDList) -> dict[str, str]:
item_codes = set(i["item_code"] for i in svd_list)
ig_list = frappe.get_list(
"Item", fields=["item_code", "item_group"], filters=[("item_code", "in", item_codes)]
return frappe._dict(
frappe.get_all(
"Item", fields=["name", "item_group"], filters=[("name", "in", item_codes)], as_list=True
)
)
return {i["item_code"]: i["item_group"] for i in ig_list}
def get_item_groups_dict() -> ItemGroupsDict:

View File

@@ -274,14 +274,6 @@ class FIFOSlots:
d.actual_qty = d.bal_qty
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
serial_nos = d.serial_no if d.serial_no else []
if fifo_queue and isinstance(fifo_queue[0][0], str):
d.has_serial_no = 1
if d.actual_qty > 0:
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
else:
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
self.__update_balances(d, key)
@@ -439,10 +431,10 @@ class FIFOSlots:
transfer_qty_to_pop = 0
def __update_balances(self, row: dict, key: tuple | str):
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction or row.bal_qty
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction or flt(row.bal_qty)
if "total_qty" not in self.item_details[key]:
self.item_details[key]["total_qty"] = row.actual_qty or row.bal_qty
self.item_details[key]["total_qty"] = row.actual_qty or flt(row.bal_qty)
else:
self.item_details[key]["total_qty"] += row.actual_qty

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.utils import flt, today
from frappe.utils.nestedset import get_descendants_of
from pypika.terms import ExistsCriterion
from erpnext.accounts.doctype.pos_invoice.pos_invoice import get_pos_reserved_qty
@@ -21,6 +22,10 @@ def execute(filters=None):
columns = get_columns()
bin_list = get_bin_list(filters)
item_map = get_item_map(filters.get("item_code"), include_uom)
item_groups = []
if filters.get("item_group"):
item_groups.append(filters.item_group)
item_groups.extend(get_descendants_of("Item Group", filters.item_group))
warehouse_company = {}
data = []
@@ -40,7 +45,7 @@ def execute(filters=None):
if filters.brand and filters.brand != item.brand:
continue
elif filters.item_group and filters.item_group != item.item_group:
elif item_groups and item.item_group not in item_groups:
continue
elif filters.company and filters.company != company:

View File

@@ -489,32 +489,19 @@ class update_entries_after:
self.distinct_item_warehouses[key] = val
self.new_items_found = True
else:
# Check if the dependent voucher is reposted
# If not, then do not add it to the list
if not self.is_dependent_voucher_reposted(dependant_sle):
return
existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
dependent_voucher_detail_nos = self.get_dependent_voucher_detail_nos(key)
if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date):
if dependent_voucher_detail_nos and dependant_sle.voucher_detail_no in set(
dependent_voucher_detail_nos
):
existing_sle = self.distinct_item_warehouses[key].get("sle", {})
if getdate(existing_sle.get("posting_date")) > getdate(dependant_sle.posting_date):
self.distinct_item_warehouses[key] = val
self.new_items_found = True
elif dependant_sle.voucher_type == "Stock Entry" and is_transfer_stock_entry(
dependant_sle.voucher_no
):
if self.distinct_item_warehouses[key].get("transfer_entry_to_repost"):
return
val.sle_changed = True
dependent_voucher_detail_nos.append(dependant_sle.voucher_detail_no)
val.dependent_voucher_detail_nos = dependent_voucher_detail_nos
val["transfer_entry_to_repost"] = True
self.distinct_item_warehouses[key] = val
self.new_items_found = True
elif dependant_sle.voucher_detail_no not in set(dependent_voucher_detail_nos):
# Future dependent voucher needs to be repost to get the correct stock value
# If dependent voucher has not reposted, then add it to the list
dependent_voucher_detail_nos.append(dependant_sle.voucher_detail_no)
self.new_items_found = True
val.dependent_voucher_detail_nos = dependent_voucher_detail_nos
self.distinct_item_warehouses[key] = val
def is_dependent_voucher_reposted(self, dependant_sle) -> bool:
# Return False if the dependent voucher is not reposted
@@ -655,11 +642,15 @@ class update_entries_after:
if not sle.is_adjustment_entry:
sle.stock_value_difference = stock_value_difference
elif sle.is_adjustment_entry and not self.args.get("sle_id"):
sle.stock_value_difference = get_stock_value_difference(
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
sle.stock_value_difference = (
get_stock_value_difference(
sle.item_code, sle.warehouse, sle.posting_date, sle.posting_time, sle.voucher_no
)
* -1
)
sle.doctype = "Stock Ledger Entry"
sle.modified = now()
frappe.get_doc(sle).db_update()
if not self.args.get("sle_id"):
@@ -890,12 +881,19 @@ class update_entries_after:
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and frappe.get_cached_value(
sle.voucher_type, sle.voucher_no, "is_internal_supplier"
):
frappe.db.set_value(
f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", sle.outgoing_rate
if sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]:
details = frappe.get_cached_value(
sle.voucher_type,
sle.voucher_no,
["is_internal_supplier", "is_return", "return_against"],
as_dict=True,
)
if details.is_internal_supplier or (details.is_return and not details.return_against):
rate = outgoing_rate if details.is_return else sle.outgoing_rate
frappe.db.set_value(
f"{sle.voucher_type} Item", sle.voucher_detail_no, "valuation_rate", rate
)
else:
frappe.db.set_value(
"Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
@@ -1392,6 +1390,8 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
"posting_time",
"voucher_detail_no",
"posting_datetime as timestamp",
"voucher_type",
"voucher_no",
],
as_dict=1,
)
@@ -1819,3 +1819,10 @@ def get_stock_value_difference(item_code, warehouse, posting_date, posting_time,
difference_amount = query.run()
return flt(difference_amount[0][0]) if difference_amount else 0
@frappe.request_cache
def is_transfer_stock_entry(voucher_no):
purpose = frappe.get_cached_value("Stock Entry", voucher_no, "purpose")
return purpose in ["Material Transfer", "Material Transfer for Manufacture", "Send to Subcontractor"]

View File

@@ -109,6 +109,8 @@ def get_stock_balance(
from erpnext.stock.stock_ledger import get_previous_sle
frappe.has_permission("Item", "read", throw=True)
if posting_date is None:
posting_date = nowdate()
if posting_time is None:
@@ -127,8 +129,9 @@ def get_stock_balance(
extra_cond = ""
if inventory_dimensions_dict:
for field, value in inventory_dimensions_dict.items():
column = frappe.utils.sanitize_column(field)
args[field] = value
extra_cond += f" and {field} = %({field})s"
extra_cond += f" and {column} = %({field})s"
last_entry = get_previous_sle(args, extra_cond=extra_cond)
@@ -275,8 +278,6 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
if isinstance(args, str):
args = json.loads(args)
voucher_no = args.get("voucher_no") or args.get("name")
in_rate = None
if (args.get("serial_no") or "").strip():
in_rate = get_avg_purchase_rate(args.get("serial_no"))
@@ -299,12 +300,13 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
in_rate = (
_get_fifo_lifo_rate(previous_stock_queue, args.get("qty") or 0, valuation_method)
if previous_stock_queue
else 0
else None
)
elif valuation_method == "Moving Average":
in_rate = previous_sle.get("valuation_rate") or 0
in_rate = previous_sle.get("valuation_rate")
if in_rate is None:
voucher_no = args.get("voucher_no") or args.get("name")
in_rate = get_valuation_rate(
args.get("item_code"),
args.get("warehouse"),