Compare commits

...

163 Commits

Author SHA1 Message Date
Frappe PR Bot
28c9f2adab chore(release): Bumped to Version 15.33.3
## [15.33.3](https://github.com/frappe/erpnext/compare/v15.33.2...v15.33.3) (2024-08-21)

### Bug Fixes

* Auto Create Serial and Batch Bundle For Outward (backport [#42778](https://github.com/frappe/erpnext/issues/42778)) ([#42792](https://github.com/frappe/erpnext/issues/42792)) ([7cc7179](7cc7179b05))
* backport german translations from develop ([9e9de4c](9e9de4c99e))
* bank reconcilation tool cost center company filter adding ([cd59940](cd5994017c))
* Create Sales Order from Quotation for Prospect ([f547bef](f547befeb9))
* create SO from Quot for Prospect --conflicts ([ec0201c](ec0201cb85))
* create SO from Quot for Prospect --conflicts ([5d7fb1d](5d7fb1d945))
* disable rename from warehouse ([3a1ad6e](3a1ad6e844))
* disable rename from warehouse ([40abd82](40abd82e2d))
* dropping index to improve performance (backport [#42820](https://github.com/frappe/erpnext/issues/42820)) ([#42821](https://github.com/frappe/erpnext/issues/42821)) ([b24de3e](b24de3e35b))
* german translations ([751c209](751c20984f))
* german translations of "HR" ([6f7fdbe](6f7fdbefac))
* ignore pricing rule while making DN from Pick List (backport [#42763](https://github.com/frappe/erpnext/issues/42763)) ([#42768](https://github.com/frappe/erpnext/issues/42768)) ([aba54ba](aba54ba18f))
* not able to create the batch (backport [#42784](https://github.com/frappe/erpnext/issues/42784)) ([#42785](https://github.com/frappe/erpnext/issues/42785)) ([0f9849e](0f9849e672))
* **patch:** replace repost with direct sql to update 'against_voucher ([e420fa9](e420fa9779))
* removed extra filter condition ([b84ca04](b84ca04975))
* set up filters for dimensions ([abb8866](abb88662c1))
* translatability of boldened text ([4914481](4914481105))
* update the testcase format ([33542cb](33542cb909))
* update the testcase format ([549dc28](549dc286d0))

### Performance Improvements

* asset creation from purchase receipt ([1040198](1040198ce1))
* data import for stock entries (backport [#42711](https://github.com/frappe/erpnext/issues/42711)) ([#42819](https://github.com/frappe/erpnext/issues/42819)) ([0344442](0344442d42))
2024-08-21 05:22:44 +00:00
ruthra kumar
d04f7ffe87 Merge pull request #42832 from frappe/version-15-hotfix
chore: release v15
2024-08-21 10:51:29 +05:30
Khushi Rawat
21e5c01f11 Merge pull request #42828 from frappe/mergify/bp/version-15-hotfix/pr-42824
perf: asset creation from purchase receipt (backport #42824)
2024-08-20 15:17:51 +05:30
mergify[bot]
ae6c1a30ac refactor: brand js and allow quick entry (backport #42829) (#42830)
* refactor: brand js and allow quick entry (#42829)

* refactor: brand js and allow quick entry

* refactor: brand js and allow quick entry --prettier

* refactor: brand js and allow quick entry --prettier

(cherry picked from commit a1183f0165)

# Conflicts:
#	erpnext/setup/doctype/brand/brand.json

* refactor: brand js and allow quick entry --refactor

---------

Co-authored-by: Nihantra C. Patel <141945075+Nihantra-Patel@users.noreply.github.com>
2024-08-20 15:03:23 +05:30
Khushi Rawat
1040198ce1 perf: asset creation from purchase receipt
(cherry picked from commit 6e84fc5143)
2024-08-20 08:12:48 +00:00
Nihantra C. Patel
69878b7847 Merge pull request #42826 from frappe/mergify/bp/version-15-hotfix/pr-42379
fix: Create Sales Order from Quotation for Prospect (backport #42379)
2024-08-20 11:57:29 +05:30
Nihantra C. Patel
ec0201cb85 fix: create SO from Quot for Prospect --conflicts 2024-08-20 11:34:27 +05:30
Nihantra C. Patel
5d7fb1d945 fix: create SO from Quot for Prospect --conflicts 2024-08-20 11:31:49 +05:30
Nihantra Patel
33542cb909 fix: update the testcase format
(cherry picked from commit ee44022249)
2024-08-20 05:11:02 +00:00
Nihantra Patel
549dc286d0 fix: update the testcase format
(cherry picked from commit 29d50b770e)
2024-08-20 05:11:02 +00:00
Nihantra Patel
f547befeb9 fix: Create Sales Order from Quotation for Prospect
(cherry picked from commit 2f63fae31d)

# Conflicts:
#	erpnext/selling/doctype/quotation/quotation.py
2024-08-20 05:11:02 +00:00
ruthra kumar
a144059c7c Merge pull request #42825 from frappe/mergify/bp/version-15-hotfix/pr-42731
fix: bank reconcilation tool cost center company filter adding (backport #42731)
2024-08-20 10:35:21 +05:30
Parameshwari Palanisamy
b96361e837 refactor: update dialog_manager.js
(cherry picked from commit 6d19aae423)
2024-08-20 04:44:47 +00:00
creative-paramu
cd5994017c fix: bank reconcilation tool cost center company filter adding
(cherry picked from commit 6e2ac09821)
2024-08-20 04:44:47 +00:00
Raffael Meyer
431fb62803 Merge pull request #42823 from barredterra/backport-german-translations 2024-08-19 19:25:16 +02:00
barredterra
9e9de4c99e fix: backport german translations from develop 2024-08-19 16:43:22 +02:00
Raffael Meyer
d3369368db Merge pull request #42804 from frappe/mergify/bp/version-15-hotfix/pr-42800
fix: translatability of boldened text (backport #42800)
2024-08-19 16:03:11 +02:00
mergify[bot]
b24de3e35b fix: dropping index to improve performance (backport #42820) (#42821)
fix: dropping index to improve performance (#42820)

fix: droping index to improve peformance
(cherry picked from commit 5404b21c7d)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-19 17:34:24 +05:30
mergify[bot]
0344442d42 perf: data import for stock entries (backport #42711) (#42819)
perf: data import for stock entries (#42711)

(cherry picked from commit 1511280464)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-19 14:55:41 +05:30
Raffael Meyer
0decd0955b Merge pull request #42805 from barredterra/de-hr-transl 2024-08-17 02:49:22 +02:00
barredterra
6f7fdbefac fix: german translations of "HR" 2024-08-17 02:48:39 +02:00
barredterra
dbd466b6b2 chore: resolve conflicts 2024-08-17 02:24:58 +02:00
barredterra
4914481105 fix: translatability of boldened text
(cherry picked from commit af0ae930ca)

# Conflicts:
#	erpnext/controllers/accounts_controller.py
2024-08-17 00:21:05 +00:00
Raffael Meyer
8514c01a91 Merge pull request #42798 from barredterra/20240816-de-translations 2024-08-16 19:11:37 +02:00
barredterra
751c20984f fix: german translations 2024-08-16 18:54:06 +02:00
Frappe PR Bot
81b1cbd367 chore(release): Bumped to Version 15.33.2
## [15.33.2](https://github.com/frappe/erpnext/compare/v15.33.1...v15.33.2) (2024-08-16)

### Bug Fixes

* **patch:** replace repost with direct sql to update 'against_voucher ([4d9f522](4d9f522f22))
2024-08-16 11:09:43 +00:00
ruthra kumar
a72383da0e Merge pull request #42796 from frappe/mergify/bp/version-15/pr-42791
fix(patch): Use sql to update 'against_voucher' rather than reposting (backport #42791)
2024-08-16 16:38:27 +05:30
ruthra kumar
eb8213c4e7 chore: update patches.txt
(cherry picked from commit 1721175a20)
2024-08-16 10:26:26 +00:00
ruthra kumar
4d9f522f22 fix(patch): replace repost with direct sql to update 'against_voucher
(cherry picked from commit 13bb48434f)
2024-08-16 10:26:26 +00:00
ruthra kumar
528f42c713 Merge pull request #42794 from frappe/mergify/bp/version-15-hotfix/pr-42791
fix(patch): Use sql to update 'against_voucher' rather than reposting (backport #42791)
2024-08-16 15:55:14 +05:30
mergify[bot]
7cc7179b05 fix: Auto Create Serial and Batch Bundle For Outward (backport #42778) (#42792)
fix: Auto Create Serial and Batch Bundle For Outward (#42778)

(cherry picked from commit 48c3b0d094)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-16 15:54:14 +05:30
ruthra kumar
cfaad685a4 chore: update patches.txt
(cherry picked from commit 1721175a20)
2024-08-16 10:02:44 +00:00
ruthra kumar
e420fa9779 fix(patch): replace repost with direct sql to update 'against_voucher
(cherry picked from commit 13bb48434f)
2024-08-16 10:02:44 +00:00
Frappe PR Bot
fbf1160357 chore(release): Bumped to Version 15.33.1
## [15.33.1](https://github.com/frappe/erpnext/compare/v15.33.0...v15.33.1) (2024-08-16)

### Bug Fixes

* not able to create the batch (backport [#42784](https://github.com/frappe/erpnext/issues/42784)) (backport [#42785](https://github.com/frappe/erpnext/issues/42785)) ([#42786](https://github.com/frappe/erpnext/issues/42786)) ([a3e3585](a3e3585e50))
2024-08-16 06:19:07 +00:00
mergify[bot]
a3e3585e50 fix: not able to create the batch (backport #42784) (backport #42785) (#42786)
fix: not able to create the batch (backport #42784) (#42785)

fix: not able to create the batch (#42784)

(cherry picked from commit 32c4fab14f)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
(cherry picked from commit 0f9849e672)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2024-08-16 11:47:53 +05:30
mergify[bot]
0f9849e672 fix: not able to create the batch (backport #42784) (#42785)
fix: not able to create the batch (#42784)

(cherry picked from commit 32c4fab14f)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-16 11:39:15 +05:30
ruthra kumar
3be037221a Merge pull request #42783 from frappe/mergify/bp/version-15-hotfix/pr-42774
refactor: Loosen account currency validation on groups (backport #42774)
2024-08-16 10:41:56 +05:30
ruthra kumar
b2e108afcc refactor: allow foreign currency accounts in Supplier Group
(cherry picked from commit 164498bafb)
2024-08-16 04:30:12 +00:00
ruthra kumar
114a5f8cca refactor: allow foreign currency accounts in customer group
(cherry picked from commit 066e935892)
2024-08-16 04:30:12 +00:00
mergify[bot]
aba54ba18f fix: ignore pricing rule while making DN from Pick List (backport #42763) (#42768)
fix: ignore pricing rule while making DN from Pick List (#42763)

(cherry picked from commit 0db82ec93a)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-14 23:03:01 +05:30
Nihantra C. Patel
6a7768367e Merge pull request #42771 from frappe/mergify/bp/version-15-hotfix/pr-42769
fix: disable rename from warehouse (backport #42769)
2024-08-14 17:15:26 +05:30
Nihantra C. Patel
3a1ad6e844 fix: disable rename from warehouse 2024-08-14 16:37:36 +05:30
Nihantra C. Patel
40abd82e2d fix: disable rename from warehouse
(cherry picked from commit c1812f74e8)

# Conflicts:
#	erpnext/stock/doctype/warehouse/warehouse.json
2024-08-14 11:03:49 +00:00
Khushi Rawat
2933a86458 Merge pull request #42767 from frappe/mergify/bp/version-15-hotfix/pr-42765
fix: set up filters for dimensions (backport #42765)
2024-08-14 14:45:14 +05:30
Khushi Rawat
ecf0d0b388 chore: linters check
(cherry picked from commit 0d42793397)
2024-08-14 09:07:38 +00:00
Khushi Rawat
b84ca04975 fix: removed extra filter condition
(cherry picked from commit 6fa29376a0)
2024-08-14 09:07:38 +00:00
Khushi Rawat
abb88662c1 fix: set up filters for dimensions
(cherry picked from commit 2fd8de2f76)
2024-08-14 09:07:37 +00:00
Frappe PR Bot
73af5be1c3 chore(release): Bumped to Version 15.33.0
# [15.33.0](https://github.com/frappe/erpnext/compare/v15.32.1...v15.33.0) (2024-08-14)

### Bug Fixes

* allow sale of asset for internal transfer ([97cadfe](97cadfe5d3))
* cancel Journal Entry on cancellation of asset value adjustment ([a429f2f](a429f2f626))
* currency changing while making PR from the PO (backport [#42718](https://github.com/frappe/erpnext/issues/42718)) ([#42721](https://github.com/frappe/erpnext/issues/42721)) ([50b1fa5](50b1fa5deb))
* delivery note creation issue (backport [#42696](https://github.com/frappe/erpnext/issues/42696)) ([#42697](https://github.com/frappe/erpnext/issues/42697)) ([6f16ae3](6f16ae3e00))
* dimensions in common party journal entry ([fd4143e](fd4143e686))
* duplicate labels in stock entry (backport [#42756](https://github.com/frappe/erpnext/issues/42756)) ([#42758](https://github.com/frappe/erpnext/issues/42758)) ([8624a0a](8624a0abce))
* error message in coa importer ([20c1bcd](20c1bcd654))
* **Exchange Rate Revaluation:** translatable strings ([da6eea7](da6eea7743))
* fetch months last date to avoid miscalculation ([765c110](765c1104c4))
* force fetch updates for subcription ([582fffc](582fffca93))
* german translation of exit ([a2df276](a2df276880))
* german translations for incoterms ([9789648](9789648175))
* ledger entries for pos return with update outstanding for self ([8cd1952](8cd1952da3))
* Maintain same rate on qty change on Quotation to Sales Order ([7ed7c22](7ed7c22469))
* patch to fix incorrect against_voucher references in ledger ([389227b](389227bce8))
* pre-commit for better code formatting ([94f4c92](94f4c92a03))
* price list when invoice created from timesheet ([2926915](2926915a06))
* price list when invoice created from timesheet ([536dc47](536dc47eb0))
* promotional scheme doctype fields in consitency with pricing rule ([#42432](https://github.com/frappe/erpnext/issues/42432)) ([87d8603](87d8603d1d))
* resolved conflict ([defd554](defd5541b0))
* Sort lists before calling itertools.groupby ([d8939e0](d8939e0bb0))
* text color in sales funnel report based on theme ([a8de8ae](a8de8aecf5))
* typeerror on payment entry ([64e75a8](64e75a8e08))
* update 'Paid Amount' on forex payment request ([c71f06b](c71f06be9e))
* Update Rate as per Valuation Rate for Internal Transfers only if Setting is Enabled ([#42050](https://github.com/frappe/erpnext/issues/42050)) ([6e833cc](6e833cce6a))
* warning message for negative stock (backport [#42683](https://github.com/frappe/erpnext/issues/42683)) ([#42710](https://github.com/frappe/erpnext/issues/42710)) ([a990577](a99057754d))

### Features

* changes in opportunity.py to show contacts and addresses from referenced and opportunities ([3cac4a5](3cac4a598f))
2024-08-14 08:02:34 +00:00
ruthra kumar
81e3d269dc Merge pull request #42745 from frappe/version-15-hotfix
chore: release v15
2024-08-14 13:31:20 +05:30
ruthra kumar
3dafa98eef Merge pull request #42761 from frappe/mergify/bp/version-15-hotfix/pr-42720
fix: incorrect 'against_voucher' for Pos return with 'Update Outstanding for Self' disabled. (backport #42720)
2024-08-14 12:27:24 +05:30
ruthra kumar
3699b96adf refactor: move patch to v14 and update patches.txt
(cherry picked from commit da2286802a)
2024-08-14 06:26:48 +00:00
ruthra kumar
f782af8ab3 test: against_voucher for pos_returns without updating for self
(cherry picked from commit 3fb0858321)
2024-08-14 06:26:48 +00:00
ruthra kumar
2af48e40a1 refactor: update patches.txt
(cherry picked from commit 4dc0d3a003)
2024-08-14 06:26:48 +00:00
ruthra kumar
389227bce8 fix: patch to fix incorrect against_voucher references in ledger
(cherry picked from commit 487d0a55f5)
2024-08-14 06:26:48 +00:00
ruthra kumar
8cd1952da3 fix: ledger entries for pos return with update outstanding for self
(cherry picked from commit 2cd9b28e5b)
2024-08-14 06:26:48 +00:00
Khushi Rawat
f128fcd86c Merge pull request #42759 from frappe/mergify/bp/version-15-hotfix/pr-42719
fix: linkage between asset repair and asset value adjustment (backport #42719)
2024-08-14 11:23:33 +05:30
Khushi Rawat
d890d02b5c test: new depreciation after cancelling asset repair
(cherry picked from commit 88a5824e31)
2024-08-13 19:01:02 +00:00
Khushi Rawat
defd5541b0 fix: resolved conflict
(cherry picked from commit c085b6159b)
2024-08-13 19:01:01 +00:00
mergify[bot]
8624a0abce fix: duplicate labels in stock entry (backport #42756) (#42758)
fix: duplicate labels in stock entry (#42756)

(cherry picked from commit 8aadc18ee8)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-13 20:31:05 +05:30
ruthra kumar
13b79c6781 Merge pull request #42750 from frappe/mergify/bp/version-15-hotfix/pr-40964
fix: price list when invoice created from timesheet (backport #40964)
2024-08-13 17:35:50 +05:30
Nihantra C. Patel
2926915a06 fix: price list when invoice created from timesheet
(cherry picked from commit 882227a460)
2024-08-13 11:41:40 +00:00
Nihantra C. Patel
536dc47eb0 fix: price list when invoice created from timesheet
(cherry picked from commit 39d6df7c7d)
2024-08-13 11:41:39 +00:00
ruthra kumar
225843265e Merge pull request #42749 from frappe/mergify/bp/version-15-hotfix/pr-42221
fix: Force Fetching updates on Subscriptions (backport #42221)
2024-08-13 16:47:35 +05:30
ruthra kumar
a1f98603a6 refactor: test for force-fetch on future subscription
(cherry picked from commit fd680380bb)
2024-08-13 10:53:24 +00:00
ruthra kumar
ab59b33606 chore: minor naming change
(cherry picked from commit d8b6767697)
2024-08-13 10:53:24 +00:00
ruthra kumar
1f1e934675 refactor: don't process future subscriptions
(cherry picked from commit 3a11577411)
2024-08-13 10:53:24 +00:00
ruthra kumar
582fffca93 fix: force fetch updates for subcription
(cherry picked from commit 1ef890db73)
2024-08-13 10:53:23 +00:00
Khushi Rawat
e9b3a24221 Merge pull request #42742 from frappe/mergify/bp/version-15-hotfix/pr-42689
fix: fetch month's last date to avoid miscalculation (backport #42689)
2024-08-13 15:32:04 +05:30
ruthra kumar
0d5ac078f6 Merge pull request #42741 from frappe/mergify/bp/version-15-hotfix/pr-42726
fix: text color in sales funnel report based on theme (backport #42726)
2024-08-13 14:36:13 +05:30
ruthra kumar
f109164f88 Merge pull request #42739 from frappe/mergify/bp/version-15-hotfix/pr-42590
fix: error message in coa importer (backport #42590)
2024-08-13 14:35:53 +05:30
Khushi Rawat
765c1104c4 fix: fetch months last date to avoid miscalculation
(cherry picked from commit 70ff4e7644)
2024-08-13 08:42:33 +00:00
ljain112
a8de8aecf5 fix: text color in sales funnel report based on theme
(cherry picked from commit 61bc0925d5)
2024-08-13 08:36:31 +00:00
ljain112
20c1bcd654 fix: error message in coa importer
(cherry picked from commit 5d0a38dfc7)
2024-08-13 08:33:21 +00:00
ruthra kumar
6eaad90535 Merge pull request #42735 from frappe/mergify/bp/version-15-hotfix/pr-42733
refactor: add a new filter flag in Process Statement of Accounts (backport #42733)
2024-08-13 11:49:29 +05:30
Smit Vora
91a5c562d8 Merge pull request #42489 from frappe/mergify/bp/version-15-hotfix/pr-42050
fix: Update Rate as per Valuation Rate for Internal Transfers only if Setting is Enabled (backport #42050)
2024-08-13 11:25:09 +05:30
ruthra kumar
70d5593ace chore: resolve conflict 2024-08-13 11:21:09 +05:30
ruthra kumar
f3401243be refactor: pass filter to General Ledger
(cherry picked from commit 90880c8c01)
2024-08-13 05:41:35 +00:00
ruthra kumar
19dfeca96d refactor: cr and dr note filter in Statement of Accounts
(cherry picked from commit 0cf478c4c2)

# Conflicts:
#	erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.json
2024-08-13 05:41:35 +00:00
ruthra kumar
774db56762 refactor: combine vouchers from both ignore
(cherry picked from commit 4cc5cd5a71)
2024-08-13 05:41:35 +00:00
Khushi Rawat
d15c535f86 Merge pull request #42730 from frappe/mergify/bp/version-15-hotfix/pr-42729
fix: cancel Journal Entry on cancellation of asset value adjustment (backport #42729)
2024-08-13 02:31:24 +05:30
Khushi Rawat
a429f2f626 fix: cancel Journal Entry on cancellation of asset value adjustment
(cherry picked from commit 4b7f1f2d67)
2024-08-12 20:43:30 +00:00
ruthra kumar
dfbc7e8b33 Merge pull request #42725 from frappe/mergify/bp/version-15-hotfix/pr-42672
fix: update 'Paid Amount' on forex payment request (backport #42672)
2024-08-12 16:31:35 +05:30
ruthra kumar
b41f10c1b9 chore: resolve conflict 2024-08-12 16:13:59 +05:30
ruthra kumar
44745a757d test: currency conversion on foreign currency account
(cherry picked from commit f913c0fde1)
2024-08-12 10:38:35 +00:00
ruthra kumar
47bf2d408b test: make use of test fixture
(cherry picked from commit d6d0a1b38d)

# Conflicts:
#	erpnext/accounts/doctype/payment_request/test_payment_request.py
2024-08-12 10:38:35 +00:00
ruthra kumar
c71f06be9e fix: update 'Paid Amount' on forex payment request
(cherry picked from commit 7b0dfb2a05)
2024-08-12 10:38:34 +00:00
mergify[bot]
50b1fa5deb fix: currency changing while making PR from the PO (backport #42718) (#42721)
fix: currency changing while making PR from the PO (#42718)

(cherry picked from commit 17ba0cff44)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-12 15:20:04 +05:30
mergify[bot]
a99057754d fix: warning message for negative stock (backport #42683) (#42710)
fix: warning message for negative stock (#42683)

(cherry picked from commit deccb007c1)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-11 12:35:13 +05:30
mergify[bot]
6f16ae3e00 fix: delivery note creation issue (backport #42696) (#42697)
fix: delivery note creation issue (#42696)

(cherry picked from commit b65072cd98)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-09 15:24:34 +05:30
ruthra kumar
38d80aab99 Merge pull request #42695 from frappe/mergify/bp/version-15-hotfix/pr-42428
fix: Sort lists before calling itertools.groupby (backport #42428)
2024-08-09 12:44:38 +05:30
Corentin Forler
d8939e0bb0 fix: Sort lists before calling itertools.groupby
(cherry picked from commit 45a6ecbd38)
2024-08-09 06:23:57 +00:00
ruthra kumar
f376abf0fe Merge pull request #42693 from frappe/mergify/bp/version-15-hotfix/pr-42691
fix: typeerror on payment entry (backport #42691)
2024-08-09 11:52:18 +05:30
ruthra kumar
64e75a8e08 fix: typeerror on payment entry
(cherry picked from commit 9cdca0d662)
2024-08-09 06:11:16 +00:00
Raffael Meyer
67a210f722 Merge pull request #42684 from barredterra/exit-translation
fix: german translation of exit
2024-08-08 22:23:34 +02:00
barredterra
a2df276880 fix: german translation of exit 2024-08-08 20:20:22 +02:00
Raffael Meyer
151d293aaf Merge pull request #42680 from frappe/mergify/bp/version-15-hotfix/pr-42679
fix(Exchange Rate Revaluation): translatable strings (backport #42679)
2024-08-08 13:17:27 +02:00
barredterra
13f6f3259d refactor(Exchange Rate Revaluation): remove unused variables
(cherry picked from commit 8bd84e9d51)
2024-08-08 10:09:53 +00:00
barredterra
da6eea7743 fix(Exchange Rate Revaluation): translatable strings
(cherry picked from commit dc29fb8759)
2024-08-08 10:09:52 +00:00
ruthra kumar
b28fa8b994 Merge pull request #42678 from frappe/mergify/bp/version-15-hotfix/pr-42673
fix: Maintain same rate on qty change on Quotation to Sales Order (backport #42673)
2024-08-08 13:43:46 +05:30
ruthra kumar
7ed7c22469 fix: Maintain same rate on qty change on Quotation to Sales Order
(cherry picked from commit 91ce9fce9b)
2024-08-08 08:04:30 +00:00
Frappe PR Bot
6a4f06cf4a chore(release): Bumped to Version 15.32.1
## [15.32.1](https://github.com/frappe/erpnext/compare/v15.32.0...v15.32.1) (2024-08-08)

### Bug Fixes

* promotional scheme doctype fields in consitency with pricing rule ([#42432](https://github.com/frappe/erpnext/issues/42432)) ([2aa90fd](2aa90fdc14))
2024-08-08 07:06:38 +00:00
ruthra kumar
dfe0079573 Merge pull request #42674 from frappe/mergify/bp/version-15/pr-42550
fix: promotional scheme doctype fields in consitency with pricing rule (backport #42432) (backport #42550)
2024-08-08 12:35:25 +05:30
Lakshit Jain
a983d65404 chore: resolve conflicts (#42553)
(cherry picked from commit 78eb443614)
2024-08-08 06:43:38 +00:00
Lakshit Jain
2aa90fdc14 fix: promotional scheme doctype fields in consitency with pricing rule (#42432)
* fix: add "round_free_qty" check box in promotional scheme

* fix: add `add_for_price_list` field

* fix: set_query in setup for promotional scheme

---------

(cherry picked from commit 8624aeca54)

# Conflicts:
#	erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
#	erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
(cherry picked from commit 87d8603d1d)
2024-08-08 06:43:38 +00:00
Shariq Ansari
a81bc28afb Merge pull request #42668 from frappe/mergify/bp/version-15-hotfix/pr-42596
fix: changes in opportunity.py to show contacts and addresses created from opportunity (backport #42596)
2024-08-08 11:39:44 +05:30
Smit Vora
70e40183bb Merge pull request #42551 from frappe/mergify/bp/version-15-hotfix/pr-42392
fix: allow sale of asset for internal transfer (backport #42392)
2024-08-08 11:22:00 +05:30
Smit Vora
bdfc1ddfc3 Merge pull request #42550 from frappe/mergify/bp/version-15-hotfix/pr-42432
fix: promotional scheme doctype fields in consitency with pricing rule (backport #42432)
2024-08-08 11:21:09 +05:30
Raffael Meyer
fb770f5633 Merge pull request #42669 from barredterra/incoterms-translation 2024-08-07 20:52:32 +02:00
barredterra
9789648175 fix: german translations for incoterms 2024-08-07 20:08:54 +02:00
Dietmar Fischer
94f4c92a03 fix: pre-commit for better code formatting
(cherry picked from commit 511a0b9f37)
2024-08-07 14:29:48 +00:00
Dietmar Fischer
3cac4a598f feat: changes in opportunity.py to show contacts and addresses from referenced and opportunities
(cherry picked from commit 61576ca030)
2024-08-07 14:29:47 +00:00
ruthra kumar
654fea8a3b Merge pull request #42661 from frappe/mergify/bp/version-15-hotfix/pr-42578
fix: dimensions in common party journal entry (backport #42578)
2024-08-07 16:23:54 +05:30
ruthra kumar
838d341d91 test: dimension inheritance on Exc Gain/Loss JV on Common party
(cherry picked from commit 8040544216)
2024-08-07 10:34:09 +00:00
ljain112
fd4143e686 fix: dimensions in common party journal entry
(cherry picked from commit ac629ede79)
2024-08-07 10:34:09 +00:00
Frappe PR Bot
2e3b2db03b chore(release): Bumped to Version 15.32.0
# [15.32.0](https://github.com/frappe/erpnext/compare/v15.31.5...v15.32.0) (2024-08-07)

### Bug Fixes

* 'undefined' in PL and BS report summary on Consolidated report ([3da7071](3da7071327))
* all warehouse filter for the stock report (backport [#42584](https://github.com/frappe/erpnext/issues/42584)) ([#42604](https://github.com/frappe/erpnext/issues/42604)) ([830b95b](830b95bdcb))
* company filter for filtring tax withheld vouchers ([3ad1f2d](3ad1f2d0d0))
* disable primary action button only when there are no active capitalization ([2d644ac](2d644ac066))
* Discount and taxes in return document should follow the reference document (backport [#41911](https://github.com/frappe/erpnext/issues/41911)) ([#42574](https://github.com/frappe/erpnext/issues/42574)) ([9321408](9321408154))
* do not update item price and last purchase rate for inter transf… (backport [#42616](https://github.com/frappe/erpnext/issues/42616)) ([#42633](https://github.com/frappe/erpnext/issues/42633)) ([59b9b7d](59b9b7dc91))
* german translations ([f27e9f3](f27e9f3089))
* inter transfer delivery note issue with batch (backport [#42552](https://github.com/frappe/erpnext/issues/42552)) ([#42556](https://github.com/frappe/erpnext/issues/42556)) ([97cc308](97cc3082e1))
* min height for rows in sales funnel ([2f81c99](2f81c99143))
* resolved conflict ([#42557](https://github.com/frappe/erpnext/issues/42557)) ([c3293d1](c3293d110c))
* reverse debit credit for party gl entry in payment entry based on negative amount ([#42367](https://github.com/frappe/erpnext/issues/42367)) ([14f9aef](14f9aef55c))
* set currency on change of company considering customer default currency (backport [#42405](https://github.com/frappe/erpnext/issues/42405)) ([#42547](https://github.com/frappe/erpnext/issues/42547)) ([7c8d13c](7c8d13c51a))
* set query filters for sales / purchase tax template on PE ([dc9cf74](dc9cf74be8))
* use get_last_day to get the correct date (backport [#42564](https://github.com/frappe/erpnext/issues/42564)) ([#42598](https://github.com/frappe/erpnext/issues/42598)) ([2de86eb](2de86eb0f4))

### Features

* expiry date column in Available Batch Report (backport [#42628](https://github.com/frappe/erpnext/issues/42628)) ([#42642](https://github.com/frappe/erpnext/issues/42642)) ([d8768c5](d8768c5377))
2024-08-07 09:30:48 +00:00
ruthra kumar
51fa336350 Merge pull request #42649 from frappe/version-15-hotfix
chore: release v15
2024-08-07 14:59:36 +05:30
Khushi Rawat
0a3c0ecea2 Merge pull request #42654 from frappe/mergify/bp/version-15-hotfix/pr-42593
fix: disable primary action button only when there are no active capitalization (backport #42593)
2024-08-06 20:19:11 +05:30
Khushi Rawat
2d644ac066 fix: disable primary action button only when there are no active capitalization
(cherry picked from commit bb877f4a6b)
2024-08-06 13:43:27 +00:00
mergify[bot]
d8768c5377 feat: expiry date column in Available Batch Report (backport #42628) (#42642)
feat: expiry date column in Available Batch Report (#42628)

(cherry picked from commit faff84c6e5)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-06 14:45:12 +05:30
ruthra kumar
65c1387a04 Merge pull request #42646 from frappe/mergify/bp/version-15-hotfix/pr-42592
fix: company filter for filtring tax withheld vouchers (backport #42592)
2024-08-06 13:38:47 +05:30
ljain112
3ad1f2d0d0 fix: company filter for filtring tax withheld vouchers
(cherry picked from commit cfe2ae604b)
2024-08-06 07:47:21 +00:00
ruthra kumar
af31f3b291 Merge pull request #42644 from frappe/mergify/bp/version-15-hotfix/pr-42545
fix: min height for rows in sales funnel (backport #42545)
2024-08-06 13:09:29 +05:30
ljain112
2f81c99143 fix: min height for rows in sales funnel
(cherry picked from commit fd71d8af52)
2024-08-06 07:33:19 +00:00
ruthra kumar
e679b336ef Merge pull request #42638 from frappe/mergify/bp/version-15-hotfix/pr-42636
refactor: posting date should not be considered for ignore filters in GL (backport #42636)
2024-08-06 11:43:46 +05:30
ruthra kumar
82c17632f7 refactor: posting date is not considered for ignore filters in GL
(cherry picked from commit c930f8ba9d)
2024-08-06 05:26:25 +00:00
mergify[bot]
59b9b7dc91 fix: do not update item price and last purchase rate for inter transf… (backport #42616) (#42633)
fix: do not update item price and last purchase rate for inter transf… (#42616)

fix: do not update item price and last purchase rate for inter transfer transaction
(cherry picked from commit c8af544ef3)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-06 08:19:59 +05:30
ruthra kumar
d242d5330c Merge pull request #42626 from frappe/mergify/bp/version-15-hotfix/pr-42555
refactor: date filters should be mandatory in Sales Pipeline Analytics report (backport #42555)
2024-08-05 15:32:17 +05:30
ruthra kumar
05c606b65d refactor(test): use test fixture and supply from and to dates
(cherry picked from commit 4253caf910)
2024-08-05 09:39:30 +00:00
ruthra kumar
52578b03b4 refactor: consider empty-string as Not Assigned
(cherry picked from commit 213b2ba942)
2024-08-05 09:39:30 +00:00
ruthra kumar
8978a54479 refactor: report columns should be based on from and to dates
(cherry picked from commit 751a25c4b7)
2024-08-05 09:39:30 +00:00
ruthra kumar
b44598cb7a refactor: make 'from_date' and 'to_date' mandatory
(cherry picked from commit 3617b41b95)
2024-08-05 09:39:30 +00:00
ruthra kumar
48e05b6110 refactor: date filters should be explicit
(cherry picked from commit 40c166a0a0)
2024-08-05 09:39:30 +00:00
ruthra kumar
3640bc7f88 Merge pull request #42623 from frappe/mergify/bp/version-15-hotfix/pr-42621
refactor(test): clear data before GL report tests (backport #42621)
2024-08-05 14:02:19 +05:30
ruthra kumar
531410820f refactor(test): clear old records on GL report tests
(cherry picked from commit 56620785a0)
2024-08-05 08:15:31 +00:00
ruthra kumar
d08eba7308 Merge pull request #42614 from frappe/mergify/bp/version-15-hotfix/pr-42597
refactor: filter to ignore system generated cr / dr reconciliation journals on general ledger (backport #42597)
2024-08-05 11:34:00 +05:30
ruthra kumar
c1bfa0a31a refactor(test): filter and reconcile concerned vouchers
(cherry picked from commit 9ade269b7a)
2024-08-05 05:44:25 +00:00
ruthra kumar
155ca14b19 test: clear old data
(cherry picked from commit 991069bfbc)
2024-08-05 05:44:25 +00:00
ruthra kumar
3ee5991896 refactor: make use of date filters on ignore filterss
(cherry picked from commit 03f3ab522f)
2024-08-05 05:44:25 +00:00
ruthra kumar
7ec7e11634 test: ignore filter for system generated cr / dr note journals
(cherry picked from commit 3ffac73598)
2024-08-05 05:44:25 +00:00
ruthra kumar
17a6224a3f refactor: ignore system generated cr / dr notes on general ledger
(cherry picked from commit bb8c9b5a58)
2024-08-05 05:44:25 +00:00
ruthra kumar
e9bcb1bbb2 refactor: ignore filter in general ledger for cr / dr notes
(cherry picked from commit 59d5beee20)
2024-08-05 05:44:25 +00:00
Frappe PR Bot
d8080910c6 chore(release): Bumped to Version 15.31.5
## [15.31.5](https://github.com/frappe/erpnext/compare/v15.31.4...v15.31.5) (2024-08-03)

### Bug Fixes

* use get_last_day to get the correct date (backport [#42564](https://github.com/frappe/erpnext/issues/42564)) ([#42602](https://github.com/frappe/erpnext/issues/42602)) ([02a31ca](02a31caa05))
2024-08-03 03:58:31 +00:00
mergify[bot]
02a31caa05 fix: use get_last_day to get the correct date (backport #42564) (#42602)
fix: use get_last_day to get the correct date (#42564)

(cherry picked from commit 5d58eb67a6)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2024-08-03 09:27:16 +05:30
mergify[bot]
830b95bdcb fix: all warehouse filter for the stock report (backport #42584) (#42604)
fix: all warehouse filter for the stock report

(cherry picked from commit a4311e345d)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2024-08-03 09:02:25 +05:30
mergify[bot]
2de86eb0f4 fix: use get_last_day to get the correct date (backport #42564) (#42598)
fix: use get_last_day to get the correct date (#42564)

(cherry picked from commit 5d58eb67a6)

Co-authored-by: Khushi Rawat <142375893+khushi8112@users.noreply.github.com>
2024-08-02 17:10:37 +05:30
Raffael Meyer
9616c59ed8 Merge pull request #42581 from barredterra/german-translations-01082024 2024-08-01 16:56:24 +02:00
barredterra
f27e9f3089 fix: german translations 2024-08-01 16:33:19 +02:00
mergify[bot]
9321408154 fix: Discount and taxes in return document should follow the reference document (backport #41911) (#42574)
fix: Discount and taxes in return document should follow the reference document (#41911)

* fix: Discount and taxes in return document should follow the reference document

* fix: Ignore Pricing rule on debit/credit note if created against PI/SI with test cases

* fix: linter issue

(cherry picked from commit 281198456d)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2024-08-01 15:45:18 +05:30
mergify[bot]
97cc3082e1 fix: inter transfer delivery note issue with batch (backport #42552) (#42556)
fix: inter transfer delivery note issue with batch (#42552)

(cherry picked from commit f620ef20ae)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-08-01 13:22:33 +05:30
ruthra kumar
b7b4de7c8d Merge pull request #42566 from frappe/mergify/bp/version-15-hotfix/pr-42563
fix: set query filters for sales / purchase tax template on PE (backport #42563)
2024-07-31 17:28:56 +05:30
ruthra kumar
dc9cf74be8 fix: set query filters for sales / purchase tax template on PE
(cherry picked from commit 9fe47ac101)
2024-07-31 11:39:12 +00:00
ruthra kumar
4e826fb3e9 Merge pull request #42560 from frappe/mergify/bp/version-15-hotfix/pr-42558
fix: 'undefined' in PL and BS report summary on Consolidated report (backport #42558)
2024-07-31 16:19:29 +05:30
Raffael Meyer
553513c4c6 Merge pull request #42561 from barredterra/backport-translations 2024-07-31 12:48:52 +02:00
barredterra
5cfa2d0692 chore: backport german translations from develop 2024-07-31 12:19:13 +02:00
ruthra kumar
3da7071327 fix: 'undefined' in PL and BS report summary on Consolidated report
(cherry picked from commit dd5a5e4919)
2024-07-31 10:04:00 +00:00
Nabin Hait
c3293d110c fix: resolved conflict (#42557) 2024-07-31 14:54:52 +05:30
Lakshit Jain
78eb443614 chore: resolve conflicts (#42553) 2024-07-31 13:38:38 +05:30
ljain112
97cadfe5d3 fix: allow sale of asset for internal transfer
(cherry picked from commit 972329cc16)
2024-07-31 07:54:58 +00:00
Lakshit Jain
87d8603d1d fix: promotional scheme doctype fields in consitency with pricing rule (#42432)
* fix: add "round_free_qty" check box in promotional scheme

* fix: add `add_for_price_list` field

* fix: set_query in setup for promotional scheme

---------

(cherry picked from commit 8624aeca54)

# Conflicts:
#	erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
#	erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
2024-07-31 07:51:57 +00:00
Smit Vora
7dc68cabcc Merge pull request #42548 from frappe/mergify/bp/version-15-hotfix/pr-42367
fix: reverse debit credit for party gl entry in payment entry based on negative amount (backport #42367)
2024-07-31 13:17:08 +05:30
mergify[bot]
7c8d13c51a fix: set currency on change of company considering customer default currency (backport #42405) (#42547)
fix: set currency on change of company considering customer default currency (#42405)

(cherry picked from commit 4986f28a89)

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

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2024-07-31 13:02:12 +05:30
Lakshit Jain
14f9aef55c fix: reverse debit credit for party gl entry in payment entry based on negative amount (#42367)
* fix: do not absolute the amount for party gl entries

* fix: reverse debit credit for party gl entry based on negative amount

* refactor: reduce nesting of if condition

---------

(cherry picked from commit a694390a12)
2024-07-31 07:16:57 +00:00
Ninad Parikh
6e833cce6a fix: Update Rate as per Valuation Rate for Internal Transfers only if Setting is Enabled (#42050)
* fix: update rate for internal transfers only if settings enabled

* fix: better naming

* fix: create field for storing incoming rate in purchase doctypes

* fix: use qty instead of qty_in_stock_uom

* fix: add description, refactor for readablility

* test: test case to validate internal transfers at arm's length price

* fix: minor fix

* fix: deletion of code not required

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
(cherry picked from commit 723ac0ffc4)
2024-07-26 12:58:12 +00:00
102 changed files with 4784 additions and 496 deletions

View File

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

View File

@@ -202,7 +202,7 @@ class Account(NestedSet):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
frappe.bold(_("Account Type")), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)

View File

@@ -47,9 +47,11 @@ def validate_columns(data):
no_of_columns = max([len(d) for d in data])
if no_of_columns > 8:
if no_of_columns != 8:
frappe.throw(
_("More columns found than expected. Please compare the uploaded file with standard template"),
_(
"Columns are not according to template. Please compare the uploaded file with standard template"
),
title=(_("Wrong Template")),
)

View File

@@ -85,18 +85,16 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
},
make_jv: function (frm) {
let revaluation_journal = null;
let zero_balance_journal = null;
frappe.call({
method: "make_jv_entries",
doc: frm.doc,
freeze: true,
freeze_message: "Making Journal Entries...",
freeze_message: __("Creating Journal Entries..."),
callback: function (r) {
if (r.message) {
let response = r.message;
if (response["revaluation_jv"] || response["zero_balance_jv"]) {
frappe.msgprint(__("Journals have been created"));
frappe.msgprint(__("Journal entries have been created"));
}
}
},

View File

@@ -165,8 +165,25 @@ frappe.ui.form.on("Payment Entry", {
filters: filters,
};
});
},
frm.set_query("sales_taxes_and_charges_template", function () {
return {
filters: {
company: frm.doc.company,
disabled: false,
},
};
});
frm.set_query("purchase_taxes_and_charges_template", function () {
return {
filters: {
company: frm.doc.company,
disabled: false,
},
};
});
},
refresh: function (frm) {
erpnext.hide_company(frm);
frm.events.hide_unhide_fields(frm);
@@ -213,7 +230,7 @@ frappe.ui.form.on("Payment Entry", {
hide_unhide_fields: function (frm) {
var company_currency = frm.doc.company
? frappe.get_doc(":Company", frm.doc.company).default_currency
? frappe.get_doc(":Company", frm.doc.company)?.default_currency
: "";
frm.toggle_display(

View File

@@ -37,7 +37,6 @@ from erpnext.accounts.utils import (
get_account_currency,
get_balance_on,
get_outstanding_invoices,
get_party_types_from_account_type,
)
from erpnext.controllers.accounts_controller import (
AccountsController,
@@ -1119,90 +1118,82 @@ class PaymentEntry(AccountsController):
self.make_advance_gl_entries(cancel=cancel)
def add_party_gl_entries(self, gl_entries):
if self.party_account:
if self.payment_type == "Receive":
against_account = self.paid_to
else:
against_account = self.paid_from
if not self.party_account:
return
party_gl_dict = self.get_gl_dict(
if self.payment_type == "Receive":
against_account = self.paid_to
else:
against_account = self.paid_from
party_account_type = frappe.db.get_value("Party Type", self.party_type, "account_type")
party_gl_dict = self.get_gl_dict(
{
"account": self.party_account,
"party_type": self.party_type,
"party": self.party,
"against": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
},
item=self,
)
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
if (
d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]
and d.allocated_amount < 0
and (
(party_account_type == "Receivable" and self.payment_type == "Pay")
or (party_account_type == "Payable" and self.payment_type == "Receive")
)
):
# reversing dr_cr because because it will get reversed in gl processing due to negative amount
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
gle.update(
{
"account": self.party_account,
"party_type": self.party_type,
"party": self.party,
"against": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
},
item=self,
dr_or_cr: allocated_amount_in_company_currency,
dr_or_cr + "_in_account_currency": d.allocated_amount,
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)
gl_entries.append(gle)
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
if self.book_advance_payments_in_separate_party_account:
gle.update(
{
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
}
)
gl_entries.append(gle)
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
if self.book_advance_payments_in_separate_party_account:
gle.update(
{
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
}
)
gl_entries.append(gle)
gl_entries.append(gle)
def make_advance_gl_entries(
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"

View File

@@ -338,6 +338,17 @@ class PaymentRequest(Document):
payment_entry.received_amount = amount
payment_entry.get("references")[0].allocated_amount = amount
# Update 'Paid Amount' on Forex transactions
if self.currency != ref_doc.company_currency:
if (
self.payment_request_type == "Outward"
and payment_entry.paid_from_account_currency == ref_doc.company_currency
and payment_entry.paid_from_account_currency != payment_entry.paid_to_account_currency
):
payment_entry.paid_amount = payment_entry.base_paid_amount = (
payment_entry.target_exchange_rate * payment_entry.received_amount
)
for dimension in get_accounting_dimensions():
payment_entry.update({dimension: self.get(dimension)})

View File

@@ -4,10 +4,12 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.setup.utils import get_exchange_rate
@@ -32,7 +34,7 @@ payment_method = [
]
class TestPaymentRequest(unittest.TestCase):
class TestPaymentRequest(FrappeTestCase):
def setUp(self):
if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"):
frappe.get_doc(payment_gateway).insert(ignore_permissions=True)
@@ -260,3 +262,19 @@ class TestPaymentRequest(unittest.TestCase):
# Try to make Payment Request more than SO amount, should give validation
pr2.grand_total = 900
self.assertRaises(frappe.ValidationError, pr2.save)
def test_conversion_on_foreign_currency_accounts(self):
po_doc = create_purchase_order(supplier="_Test Supplier USD", currency="USD", do_not_submit=1)
po_doc.conversion_rate = 80
po_doc.items[0].qty = 1
po_doc.items[0].rate = 10
po_doc.save().submit()
pr = make_payment_request(dt=po_doc.doctype, dn=po_doc.name, recipient_id="nabin@erpnext.com")
pr = frappe.get_doc(pr).save().submit()
pe = pr.create_payment_entry()
self.assertEqual(pe.base_paid_amount, 800)
self.assertEqual(pe.paid_amount, 800)
self.assertEqual(pe.base_received_amount, 800)
self.assertEqual(pe.received_amount, 10)

View File

@@ -87,19 +87,15 @@ class POSClosingEntry(StatusUpdater):
as_dict=1,
)[0]
if pos_invoice.consolidated_invoice:
invalid_row.setdefault("msg", []).append(
_("POS Invoice is {}").format(frappe.bold("already consolidated"))
)
invalid_row.setdefault("msg", []).append(_("POS Invoice is already consolidated"))
invalid_rows.append(invalid_row)
continue
if pos_invoice.pos_profile != self.pos_profile:
invalid_row.setdefault("msg", []).append(
_("POS Profile doesn't matches {}").format(frappe.bold(self.pos_profile))
_("POS Profile doesn't match {}").format(frappe.bold(self.pos_profile))
)
if pos_invoice.docstatus != 1:
invalid_row.setdefault("msg", []).append(
_("POS Invoice is not {}").format(frappe.bold("submitted"))
)
invalid_row.setdefault("msg", []).append(_("POS Invoice is not submitted"))
if pos_invoice.owner != self.user:
invalid_row.setdefault("msg", []).append(
_("POS Invoice isn't created by user {}").format(frappe.bold(self.owner))

View File

@@ -188,7 +188,7 @@ class POSInvoice(SalesInvoice):
def validate(self):
if not cint(self.is_pos):
frappe.throw(
_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment"))
_("POS Invoice should have the field {0} checked.").format(frappe.bold(_("Include Payment")))
)
# run on validate method of selling controller

View File

@@ -97,16 +97,15 @@ class POSInvoiceMergeLog(Document):
return_against_status = frappe.db.get_value("POS Invoice", return_against, "status")
if return_against_status != "Consolidated":
# if return entry is not getting merged in the current pos closing and if it is not consolidated
bold_unconsolidated = frappe.bold("not Consolidated")
msg = _("Row #{}: Original Invoice {} of return invoice {} is {}.").format(
d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated
)
msg = _(
"Row #{}: The original Invoice {} of return invoice {} is not consolidated."
).format(d.idx, bold_return_against, bold_pos_invoice)
msg += " "
msg += _(
"Original invoice should be consolidated before or along with the return invoice."
"The original invoice should be consolidated before or along with the return invoice."
)
msg += "<br><br>"
msg += _("You can add original invoice {} manually to proceed.").format(
msg += _("You can add the original invoice {} manually to proceed.").format(
bold_return_against
)
frappe.throw(msg)

View File

@@ -186,7 +186,8 @@ class PricingRule(Document):
if not self.priority:
throw(
_("As the field {0} is enabled, the field {1} is mandatory.").format(
frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")
frappe.bold(_("Apply Discount on Discounted Rate")),
frappe.bold(_("Priority")),
)
)
@@ -194,7 +195,7 @@ class PricingRule(Document):
throw(
_(
"As the field {0} is enabled, the value of the field {1} should be more than 1."
).format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority"))
).format(frappe.bold(_("Apply Discount on Discounted Rate")), frappe.bold(_("Priority")))
)
def validate_applicable_for_selling_or_buying(self):

View File

@@ -6,7 +6,9 @@ import unittest
import frappe
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.get_item_details import get_item_details
@@ -1311,6 +1313,69 @@ class TestPricingRule(unittest.TestCase):
pricing_rule.is_recursive = True
self.assertRaises(frappe.ValidationError, pricing_rule.save)
def test_ignore_pricing_rule_for_credit_note(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
pricing_rule = make_pricing_rule(
discount_percentage=20,
selling=1,
buying=1,
priority=1,
title="_Test Pricing Rule",
)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
item = si.items[0]
si.submit()
self.assertEqual(item.discount_percentage, 20)
self.assertEqual(item.rate, 80)
# change discount on pricing rule
pricing_rule.discount_percentage = 30
pricing_rule.save()
credit_note = make_return_doc(si.doctype, si.name)
credit_note.save()
self.assertEqual(credit_note.ignore_pricing_rule, 1)
self.assertEqual(credit_note.pricing_rules, [])
self.assertEqual(credit_note.items[0].discount_percentage, 20)
self.assertEqual(credit_note.items[0].rate, 80)
self.assertEqual(credit_note.items[0].pricing_rules, None)
credit_note.delete()
si.cancel()
def test_ignore_pricing_rule_for_debit_note(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
pricing_rule = make_pricing_rule(
discount_percentage=20,
buying=1,
priority=1,
title="_Test Pricing Rule",
)
pi = make_purchase_invoice(do_not_submit=True, supplier="_Test Supplier 1", qty=1)
item = pi.items[0]
pi.submit()
self.assertEqual(item.discount_percentage, 20)
self.assertEqual(item.rate, 40)
# change discount on pricing rule
pricing_rule.discount_percentage = 30
pricing_rule.save()
# create debit note from purchase invoice
debit_note = make_return_doc(pi.doctype, pi.name)
debit_note.save()
self.assertEqual(debit_note.ignore_pricing_rule, 1)
self.assertEqual(debit_note.pricing_rules, [])
self.assertEqual(debit_note.items[0].discount_percentage, 20)
self.assertEqual(debit_note.items[0].rate, 40)
self.assertEqual(debit_note.items[0].pricing_rules, None)
debit_note.delete()
pi.cancel()
test_dependencies = ["Campaign"]

View File

@@ -16,6 +16,7 @@
"cost_center",
"territory",
"ignore_exchange_rate_revaluation_journals",
"ignore_cr_dr_notes",
"column_break_14",
"to_date",
"finance_book",
@@ -383,10 +384,16 @@
"fieldname": "ignore_exchange_rate_revaluation_journals",
"fieldtype": "Check",
"label": "Ignore Exchange Rate Revaluation Journals"
},
{
"default": "0",
"fieldname": "ignore_cr_dr_notes",
"fieldtype": "Check",
"label": "Ignore System Generated Credit / Debit Notes"
}
],
"links": [],
"modified": "2023-12-18 12:20:08.965120",
"modified": "2024-08-13 10:41:18.381165",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -54,6 +54,7 @@ class ProcessStatementOfAccounts(Document):
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
from_date: DF.Date | None
group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"]
ignore_cr_dr_notes: DF.Check
ignore_exchange_rate_revaluation_journals: DF.Check
include_ageing: DF.Check
include_break: DF.Check
@@ -133,6 +134,9 @@ def get_statement_dict(doc, get_statement_dict=False):
if doc.ignore_exchange_rate_revaluation_journals:
filters.update({"ignore_err": True})
if doc.ignore_cr_dr_notes:
filters.update({"ignore_cr_dr_notes": True})
if doc.report == "General Ledger":
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
col, res = get_soa(filters)

View File

@@ -2,6 +2,18 @@
// For license information, please see license.txt
frappe.ui.form.on("Promotional Scheme", {
setup: function (frm) {
frm.set_query("for_price_list", "price_discount_slabs", (doc) => {
return {
filters: {
selling: doc.selling,
buying: doc.buying,
currency: doc.currency,
},
};
});
},
refresh: function (frm) {
frm.trigger("set_options_for_applicable_for");
frm.trigger("toggle_reqd_apply_on");

View File

@@ -51,6 +51,7 @@ price_discount_fields = [
"discount_percentage",
"validate_applied_rule",
"apply_multiple_pricing_rules",
"for_price_list",
]
product_discount_fields = [
@@ -63,6 +64,7 @@ product_discount_fields = [
"recurse_for",
"apply_recursion_over",
"apply_multiple_pricing_rules",
"round_free_qty",
]

View File

@@ -21,6 +21,7 @@
"rate",
"discount_amount",
"discount_percentage",
"for_price_list",
"section_break_11",
"warehouse",
"threshold_percentage",
@@ -120,6 +121,13 @@
"fieldtype": "Float",
"label": "Discount Percentage"
},
{
"depends_on": "eval:doc.rate_or_discount!=\"Rate\"",
"fieldname": "for_price_list",
"fieldtype": "Link",
"label": "For Price List",
"options": "Price List"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
@@ -169,7 +177,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-16 00:25:33.843996",
"modified": "2024-07-23 12:33:46.574950",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Price Discount",
@@ -177,4 +185,4 @@
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -19,6 +19,7 @@ class PromotionalSchemePriceDiscount(Document):
disable: DF.Check
discount_amount: DF.Currency
discount_percentage: DF.Float
for_price_list: DF.Link | None
max_amount: DF.Currency
max_qty: DF.Float
min_amount: DF.Currency

View File

@@ -22,6 +22,7 @@
"column_break_9",
"free_item_uom",
"free_item_rate",
"round_free_qty",
"section_break_12",
"warehouse",
"threshold_percentage",
@@ -181,12 +182,18 @@
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)",
"mandatory_depends_on": "is_recursive"
},
{
"default": "0",
"fieldname": "round_free_qty",
"fieldtype": "Check",
"label": "Round Free Qty"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-12 12:53:58.199108",
"modified": "2024-07-22 17:25:07.880984",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Product Discount",
@@ -195,4 +202,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -53,6 +53,7 @@ class PromotionalSchemeProductDiscount(Document):
"20",
]
recurse_for: DF.Float
round_free_qty: DF.Check
rule_description: DF.SmallText
same_item: DF.Check
threshold_percentage: DF.Percent

View File

@@ -377,16 +377,16 @@ class PurchaseInvoice(BuyingController):
if account.report_type != "Balance Sheet":
frappe.throw(
_(
"Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account."
).format(frappe.bold("Credit To")),
"Please ensure that the {0} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account."
).format(frappe.bold(_("Credit To"))),
title=_("Invalid Account"),
)
if self.supplier and account.account_type != "Payable":
frappe.throw(
_(
"Please ensure {} account {} is a Payable account. Change the account type to Payable or select a different account."
).format(frappe.bold("Credit To"), frappe.bold(self.credit_to)),
"Please ensure that the {0} account {1} is a Payable account. You can change the account type to Payable or select a different account."
).format(frappe.bold(_("Credit To")), frappe.bold(self.credit_to)),
title=_("Invalid Account"),
)
@@ -634,7 +634,7 @@ class PurchaseInvoice(BuyingController):
"To submit the invoice without purchase order please set {0} as {1} in {2}"
).format(
frappe.bold(_("Purchase Order Required")),
frappe.bold("No"),
frappe.bold(_("No")),
get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
)
throw(msg, title=_("Mandatory Purchase Order"))
@@ -655,7 +655,7 @@ class PurchaseInvoice(BuyingController):
"To submit the invoice without purchase receipt please set {0} as {1} in {2}"
).format(
frappe.bold(_("Purchase Receipt Required")),
frappe.bold("No"),
frappe.bold(_("No")),
get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
)
throw(msg, title=_("Mandatory Purchase Receipt"))

View File

@@ -57,6 +57,7 @@
"base_net_rate",
"base_net_amount",
"valuation_rate",
"sales_incoming_rate",
"item_tax_amount",
"landed_cost_voucher_amount",
"rm_supp_cost",
@@ -958,12 +959,22 @@
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"description": "Valuation rate for the item as per Sales Invoice (Only for Internal Transfers)",
"fieldname": "sales_incoming_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Sales Incoming Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-06-14 11:57:07.171700",
"modified": "2024-07-19 12:12:42.449298",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -79,6 +79,7 @@ class PurchaseInvoiceItem(Document):
rejected_serial_no: DF.Text | None
rejected_warehouse: DF.Link | None
rm_supp_cost: DF.Currency
sales_incoming_rate: DF.Currency
sales_invoice_item: DF.Data | None
serial_and_batch_bundle: DF.Link | None
serial_no: DF.Text | None

View File

@@ -514,7 +514,7 @@ class SalesInvoice(SellingController):
)
if pos_closing_entry and pos_closing_entry[0]:
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
frappe.bold("Consolidated Sales Invoice"),
frappe.bold(_("Consolidated Sales Invoice")),
get_link_to_form("POS Closing Entry", pos_closing_entry[0]),
)
frappe.throw(msg, title=_("Not Allowed"))
@@ -858,7 +858,7 @@ class SalesInvoice(SellingController):
if account.report_type != "Balance Sheet":
msg = (
_("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To"))
_("Please ensure {} account is a Balance Sheet account.").format(frappe.bold(_("Debit To")))
+ " "
)
msg += _(
@@ -869,7 +869,7 @@ class SalesInvoice(SellingController):
if self.customer and account.account_type != "Receivable":
msg = (
_("Please ensure {} account {} is a Receivable account.").format(
frappe.bold("Debit To"), frappe.bold(self.debit_to)
frappe.bold(_("Debit To")), frappe.bold(self.debit_to)
)
+ " "
)
@@ -1316,6 +1316,10 @@ class SalesInvoice(SellingController):
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
# Do not book income for transfer within same company
if self.is_internal_transfer():
continue
if item.is_fixed_asset:
asset = self.get_asset(item)
@@ -1374,37 +1378,33 @@ class SalesInvoice(SellingController):
self.set_asset_status(asset)
else:
# Do not book income for transfer within same company
if not self.is_internal_transfer():
income_account = (
item.income_account
if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account
)
income_account = (
item.income_account
if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account
)
amount, base_amount = self.get_amount_and_base_amount(
item, enable_discount_accounting
)
amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict(
{
"account": income_account,
"against": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount"))
if account_currency == self.company_currency
else flt(amount, item.precision("net_amount"))
),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict(
{
"account": income_account,
"against": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount"))
if account_currency == self.company_currency
else flt(amount, item.precision("net_amount"))
),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
# expense account gl entries
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
@@ -1479,6 +1479,10 @@ class SalesInvoice(SellingController):
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
payment_mode.base_amount -= flt(self.change_amount)
against_voucher = self.name
if self.is_return and self.return_against and not self.update_outstanding_for_self:
against_voucher = self.return_against
if payment_mode.base_amount:
# POS, make payment entries
gl_entries.append(
@@ -1492,7 +1496,7 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": payment_mode.base_amount
if self.party_account_currency == self.company_currency
else payment_mode.amount,
"against_voucher": self.name,
"against_voucher": against_voucher,
"against_voucher_type": self.doctype,
"cost_center": self.cost_center,
},

View File

@@ -5,6 +5,7 @@ import copy
import json
import frappe
from frappe import qb
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, nowdate, today
@@ -3083,6 +3084,84 @@ class TestSalesInvoice(FrappeTestCase):
party_link.delete()
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
def test_sales_invoice_against_supplier_usd_with_dimensions(self):
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
# create a customer
customer = make_customer(customer="_Test Common Supplier USD")
cust_doc = frappe.get_doc("Customer", customer)
cust_doc.default_currency = "USD"
cust_doc.save()
# create a supplier
supplier = create_supplier(supplier_name="_Test Common Supplier USD").name
supp_doc = frappe.get_doc("Supplier", supplier)
supp_doc.default_currency = "USD"
supp_doc.save()
# create a party link between customer & supplier
party_link = create_party_link("Supplier", supplier, customer)
# enable common party accounting
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1)
# create a dimension and make it mandatory
if not frappe.get_all("Accounting Dimension", filters={"document_type": "Department"}):
dim = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
"dimension_defaults": [{"company": "_Test Company", "mandatory_for_bs": True}],
}
)
dim.save()
else:
dim = frappe.get_doc(
"Accounting Dimension",
frappe.get_all("Accounting Dimension", filters={"document_type": "Department"})[0],
)
dim.disabled = False
dim.dimension_defaults = []
dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True})
dim.save()
# create a sales invoice
si = create_sales_invoice(
customer=customer, parent_cost_center="_Test Cost Center - _TC", do_not_submit=True
)
si.department = "All Departments"
si.save().submit()
# check outstanding of sales invoice
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(flt(si.outstanding_amount), 0.0)
# check creation of journal entry
jv = frappe.get_all(
"Journal Entry Account",
{
"account": si.debit_to,
"party_type": "Customer",
"party": si.customer,
"reference_type": si.doctype,
"reference_name": si.name,
"department": "All Departments",
},
pluck="credit_in_account_currency",
)
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
dim.disabled = True
dim.save()
party_link.delete()
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
def test_payment_statuses(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
@@ -3758,6 +3837,40 @@ class TestSalesInvoice(FrappeTestCase):
]
self.assertEqual(expected, actual)
def test_pos_returns_without_update_outstanding_for_self(self):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
pos_profile = make_pos_profile()
pos_profile.payments = []
pos_profile.append("payments", {"default": 1, "mode_of_payment": "Cash"})
pos_profile.save()
pos = create_sales_invoice(qty=10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append(
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
)
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500})
pos.save().submit()
pos_return = make_sales_return(pos.name)
pos_return.update_outstanding_for_self = False
pos_return.save().submit()
gle = qb.DocType("GL Entry")
res = (
qb.from_(gle)
.select(gle.against_voucher)
.distinct()
.where(
gle.is_cancelled.eq(0) & gle.voucher_no.eq(pos_return.name) & gle.against_voucher.notnull()
)
.run(as_list=1)
)
self.assertEqual(len(res), 1)
self.assertEqual(res[0][0], pos_return.return_against)
def set_advance_flag(company, flag, default_account):
frappe.db.set_value(

View File

@@ -38,6 +38,12 @@ frappe.ui.form.on("Subscription", {
__("Actions")
);
frm.add_custom_button(
__("Force-Fetch Subscription Updates"),
() => frm.trigger("force_fetch_subscription_updates"),
__("Actions")
);
frm.add_custom_button(
__("Cancel Subscription"),
() => frm.trigger("cancel_this_subscription"),
@@ -82,4 +88,11 @@ frappe.ui.form.on("Subscription", {
}
});
},
force_fetch_subscription_updates: function (frm) {
frm.call("force_fetch_subscription_updates").then((r) => {
if (!r.exec) {
frm.reload_doc();
}
});
},
});

View File

@@ -717,6 +717,31 @@ class Subscription(Document):
self.update_subscription_period(posting_date or nowdate())
self.save()
@frappe.whitelist()
def force_fetch_subscription_updates(self):
"""
Process Subscription and create Invoices even if current date doesn't lie between current_invoice_start and currenct_invoice_end
It makes use of 'Proces Subscription' to force processing in a specific 'posting_date'
"""
# Don't process future subscriptions
if nowdate() < self.current_invoice_start:
frappe.msgprint(_("Subscription for Future dates cannot be processed."))
return
processing_date = None
if self.generate_invoice_at == "Beginning of the current subscription period":
processing_date = self.current_invoice_start
elif self.generate_invoice_at == "End of the current subscription period":
processing_date = self.current_invoice_end
elif self.generate_invoice_at == "Days before the current subscription period":
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
process_subscription = frappe.new_doc("Process Subscription")
process_subscription.posting_date = processing_date
process_subscription.subscription = self.name
process_subscription.save().submit()
def is_prorate() -> int:
return cint(frappe.db.get_single_value("Subscription Settings", "prorate"))

View File

@@ -521,6 +521,18 @@ class TestSubscription(FrappeTestCase):
subscription.process(posting_date="2023-01-22")
self.assertEqual(len(subscription.invoices), 2)
def test_future_subscription(self):
"""Force-Fetch should not process future subscriptions"""
subscription = create_subscription(
start_date=add_months(nowdate(), 1),
submit_invoice=0,
generate_new_invoices_past_due_date=1,
party="_Test Subscription Customer John Doe",
)
subscription.force_fetch_subscription_updates()
subscription.reload()
self.assertEqual(len(subscription.invoices), 0)
def make_plans():
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")

View File

@@ -375,12 +375,14 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
AND ja.party in %s
AND j.apply_tds = 1
AND j.tax_withholding_category = %s
AND j.company = %s
""",
(
tax_details.from_date,
tax_details.to_date,
tuple(parties),
tax_details.get("tax_withholding_category"),
company,
),
as_dict=1,
)
@@ -497,6 +499,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
"unallocated_amount": (">", 0),
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
"tax_withholding_category": tax_details.get("tax_withholding_category"),
"company": inv.company,
}
field = "sum(tax_withholding_net_total)"

View File

@@ -104,7 +104,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
if total_credit:
data.append(total_credit)
report_summary = get_bs_summary(
report_summary, primitive_summary = get_bs_summary(
companies,
asset,
liability,
@@ -175,7 +175,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
report_summary = get_pl_summary(
report_summary, primitive_summary = get_pl_summary(
companies, "", income, expense, net_profit_loss, company_currency, filters, True
)

View File

@@ -209,6 +209,11 @@ frappe.query_reports["General Ledger"] = {
label: __("Ignore Exchange Rate Revaluation Journals"),
fieldtype: "Check",
},
{
fieldname: "ignore_cr_dr_notes",
label: __("Ignore System Generated Credit / Debit Notes"),
fieldtype: "Check",
},
],
};

View File

@@ -236,6 +236,23 @@ def get_conditions(filters):
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if filters.get("ignore_cr_dr_notes"):
system_generated_cr_dr_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": filters.get("company"),
"docstatus": 1,
"voucher_type": ("in", ["Credit Note", "Debit Note"]),
"is_system_generated": 1,
},
as_list=True,
)
if system_generated_cr_dr_journals:
vouchers_to_ignore = (filters.get("voucher_no_not_in") or []) + [
x[0] for x in system_generated_cr_dr_journals
]
filters.update({"voucher_no_not_in": vouchers_to_ignore})
if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s")

View File

@@ -2,13 +2,32 @@
# MIT License. See license.txt
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, today
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.general_ledger.general_ledger import execute
from erpnext.controllers.sales_and_purchase_return import make_return_doc
class TestGeneralLedger(FrappeTestCase):
def setUp(self):
self.company = "_Test Company"
self.clear_old_entries()
def clear_old_entries(self):
doctype_list = [
"GL Entry",
"Payment Ledger Entry",
"Sales Invoice",
"Purchase Invoice",
"Payment Entry",
"Journal Entry",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
"""
Checks the correctness of balance after exchange rate revaluation
@@ -248,3 +267,68 @@ class TestGeneralLedger(FrappeTestCase):
)
)
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
def test_ignore_cr_dr_notes_filter(self):
si = create_sales_invoice()
cr_note = make_return_doc(si.doctype, si.name)
cr_note.submit()
pr = frappe.get_doc("Payment Reconciliation")
pr.company = si.company
pr.party_type = "Customer"
pr.party = si.customer
pr.receivable_payable_account = si.debit_to
pr.get_unreconciled_entries()
invoices = [invoice.as_dict() for invoice in pr.invoices if invoice.invoice_number == si.name]
payments = [payment.as_dict() for payment in pr.payments if payment.reference_name == cr_note.name]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
system_generated_journal = frappe.db.get_all(
"Journal Entry",
filters={
"docstatus": 1,
"reference_type": si.doctype,
"reference_name": si.name,
"voucher_type": "Credit Note",
"is_system_generated": True,
},
fields=["name"],
)
self.assertEqual(len(system_generated_journal), 1)
expected = set([si.name, cr_note.name, system_generated_journal[0].name])
# Without ignore_cr_dr_notes
columns, data = execute(
frappe._dict(
{
"company": si.company,
"from_date": si.posting_date,
"to_date": si.posting_date,
"account": [si.debit_to],
"group_by": "Group by Voucher (Consolidated)",
"ignore_cr_dr_notes": False,
}
)
)
actual = set([x.voucher_no for x in data if x.voucher_no])
self.assertEqual(expected, actual)
# Without ignore_cr_dr_notes
expected = set([si.name, cr_note.name])
columns, data = execute(
frappe._dict(
{
"company": si.company,
"from_date": si.posting_date,
"to_date": si.posting_date,
"account": [si.debit_to],
"group_by": "Group by Voucher (Consolidated)",
"ignore_cr_dr_notes": True,
}
)
)
actual = set([x.voucher_no for x in data if x.voucher_no])
self.assertEqual(expected, actual)

View File

@@ -336,7 +336,7 @@ def get_tds_docs(filters):
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
if not tds_accounts:
frappe.throw(
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
_("No {0} Accounts found for this company.").format(frappe.bold(_("Tax Withholding"))),
title=_("Accounts Missing Error"),
)
gle = frappe.qb.DocType("GL Entry")

View File

@@ -188,11 +188,21 @@ frappe.ui.form.on("Asset", {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.is_composite_asset) {
$(".primary-action").prop("hidden", true);
$(".form-message").text("Capitalize this asset to confirm");
frappe.call({
method: "erpnext.assets.doctype.asset.asset.has_active_capitalization",
args: {
asset: frm.doc.name,
},
callback: function (r) {
if (!r.message) {
$(".primary-action").prop("hidden", true);
$(".form-message").text("Capitalize this asset to confirm");
frm.add_custom_button(__("Capitalize Asset"), function () {
frm.trigger("create_asset_capitalization");
frm.add_custom_button(__("Capitalize Asset"), function () {
frm.trigger("create_asset_capitalization");
});
}
},
});
}
}

View File

@@ -221,7 +221,6 @@
"read_only": 1
},
{
"depends_on": "eval:!doc.is_composite_asset",
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
@@ -580,7 +579,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-07-07 22:27:14.733839",
"modified": "2024-08-01 16:39:09.340973",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -669,7 +669,7 @@ class Asset(AccountsController):
if not fixed_asset_account:
frappe.throw(
_("Set {0} in asset category {1} for company {2}").format(
frappe.bold("Fixed Asset Account"),
frappe.bold(_("Fixed Asset Account")),
frappe.bold(self.asset_category),
frappe.bold(self.company),
),
@@ -904,7 +904,7 @@ def transfer_asset(args):
@frappe.whitelist()
def get_item_details(item_code, asset_category, gross_purchase_amount):
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
asset_category_doc = frappe.get_cached_doc("Asset Category", asset_category)
books = []
for d in asset_category_doc.finance_books:
books.append(
@@ -1035,6 +1035,14 @@ def get_asset_value_after_depreciation(asset_name, finance_book=None):
return asset.get_value_after_depreciation(finance_book)
@frappe.whitelist()
def has_active_capitalization(asset):
active_capitalizations = frappe.db.count(
"Asset Capitalization", filters={"target_asset": asset, "docstatus": 1}
)
return active_capitalizations > 0
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)

View File

@@ -234,7 +234,7 @@ class TestAsset(AssetSetup):
pro_rata_amount, _, _ = _get_pro_rata_amt(
asset.finance_books[0],
9000,
get_last_day(add_months(purchase_date, 1)),
add_days(get_last_day(add_months(purchase_date, 1)), 1),
date,
original_schedule_date=get_last_day(nowdate()),
)
@@ -320,7 +320,7 @@ class TestAsset(AssetSetup):
pro_rata_amount, _, _ = _get_pro_rata_amt(
asset.finance_books[0],
9000,
get_last_day(add_months(purchase_date, 1)),
add_days(get_last_day(add_months(purchase_date, 1)), 1),
date,
original_schedule_date=get_last_day(nowdate()),
)

View File

@@ -11,6 +11,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
onload() {
this.setup_queries();
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
refresh() {

View File

@@ -291,7 +291,9 @@ class AssetDepreciationSchedule(Document):
if skip_row:
continue
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
schedule_date = get_last_day(
add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
)
if not current_fiscal_year_end_date:
current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
elif getdate(schedule_date) > getdate(current_fiscal_year_end_date):
@@ -330,8 +332,10 @@ class AssetDepreciationSchedule(Document):
getdate(asset_doc.available_for_use_date),
(asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
)
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
from_date = get_last_day(from_date)
if self.depreciation_schedule:
from_date = self.depreciation_schedule[-1].schedule_date
from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
depreciation_amount, days, months = _get_pro_rata_amt(
row,
@@ -353,9 +357,8 @@ class AssetDepreciationSchedule(Document):
and not self.opening_accumulated_depreciation
and not self.flags.wdv_it_act_applied
):
from_date = add_days(
asset_doc.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
from_date = asset_doc.available_for_use_date
# needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = _get_pro_rata_amt(
row,
depreciation_amount,
@@ -406,6 +409,8 @@ class AssetDepreciationSchedule(Document):
(n + self.opening_number_of_booked_depreciations)
* cint(row.frequency_of_depreciation),
)
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
asset_doc.to_date = get_last_day(asset_doc.to_date)
depreciation_amount_without_pro_rata = depreciation_amount
@@ -421,7 +426,7 @@ class AssetDepreciationSchedule(Document):
depreciation_amount_without_pro_rata, depreciation_amount
)
schedule_date = add_days(schedule_date, days)
schedule_date = add_days(schedule_date, days - 1)
if not depreciation_amount:
continue
@@ -504,7 +509,10 @@ class AssetDepreciationSchedule(Document):
continue
if not accumulated_depreciation:
if i > 0 and asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment:
if i > 0 and (
asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment
or asset_doc.flags.increase_in_asset_value_due_to_repair
):
accumulated_depreciation = self.get("depreciation_schedule")[
i - 1
].accumulated_depreciation_amount
@@ -553,9 +561,11 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
# from_date = 01/01/2022
if row.depreciation_method in ("Straight Line", "Manual"):
prev_depreciation_start_date = add_months(
row.depreciation_start_date,
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
prev_depreciation_start_date = get_last_day(
add_months(
row.depreciation_start_date,
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
)
)
from_date = asset_doc.available_for_use_date
days = date_diff(prev_depreciation_start_date, from_date) + 1
@@ -610,7 +620,7 @@ def _get_pro_rata_amt(
has_wdv_or_dd_non_yearly_pro_rata=False,
original_schedule_date=None,
):
days = date_diff(to_date, from_date)
days = date_diff(to_date, from_date) + 1
months = month_diff(to_date, from_date)
if has_wdv_or_dd_non_yearly_pro_rata:
total_days = get_total_days(original_schedule_date or to_date, 12)
@@ -670,7 +680,7 @@ def get_straight_line_or_manual_depr_amount(
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
elif asset.flags.increase_in_asset_value_due_to_repair:
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
number_of_pending_depreciations
)
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
@@ -729,10 +739,15 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount):
)
),
add_days(
add_months(
row.depreciation_start_date,
(row.frequency_of_depreciation * (asset.opening_number_of_booked_depreciations + 1))
* -1,
get_last_day(
add_months(
row.depreciation_start_date,
(
row.frequency_of_depreciation
* (asset.opening_number_of_booked_depreciations + 1)
)
* -1,
),
),
1,
),
@@ -1029,6 +1044,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
date_of_return=None,
value_after_depreciation=None,
ignore_booked_entry=False,
difference_amount=None,
):
for row in asset_doc.get("finance_books"):
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
@@ -1043,6 +1059,8 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
)
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
if asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment and not value_after_depreciation:
value_after_depreciation = row.value_after_depreciation + difference_amount
if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
"Written Down Value",

View File

@@ -29,6 +29,15 @@ frappe.ui.form.on("Asset Repair", {
};
});
frm.set_query("purchase_invoice", function () {
return {
filters: {
company: frm.doc.company,
docstatus: 1,
},
};
});
frm.set_query("warehouse", "stock_items", function () {
return {
filters: {

View File

@@ -117,7 +117,9 @@ class AssetRepair(AccountsController):
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
make_new_active_asset_depr_schedules_and_cancel_current_ones(
self.asset_doc, notes, ignore_booked_entry=True
)
self.asset_doc.save()
add_asset_activity(
@@ -154,7 +156,9 @@ class AssetRepair(AccountsController):
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
make_new_active_asset_depr_schedules_and_cancel_current_ones(
self.asset_doc, notes, ignore_booked_entry=True
)
self.asset_doc.save()
add_asset_activity(

View File

@@ -56,7 +56,8 @@ class AssetValueAdjustment(Document):
)
def on_cancel(self):
self.update_asset(self.current_asset_value)
frappe.get_doc("Journal Entry", self.journal_entry).cancel()
self.update_asset()
add_asset_activity(
self.asset,
_("Asset's value adjusted after cancellation of Asset Value Adjustment {0}").format(
@@ -144,7 +145,7 @@ class AssetValueAdjustment(Document):
self.db_set("journal_entry", je.name)
def update_asset(self, asset_value):
def update_asset(self, asset_value=None):
asset = frappe.get_doc("Asset", self.asset)
if not asset.calculate_depreciation:
@@ -170,7 +171,11 @@ class AssetValueAdjustment(Document):
)
make_new_active_asset_depr_schedules_and_cancel_current_ones(
asset, notes, value_after_depreciation=asset_value, ignore_booked_entry=True
asset,
notes,
value_after_depreciation=asset_value,
ignore_booked_entry=True,
difference_amount=self.difference_amount,
)
asset.flags.ignore_validate_update_after_submit = True
asset.save()

View File

@@ -12,6 +12,7 @@ from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_repair
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -128,6 +129,136 @@ class TestAssetValueAdjustment(unittest.TestCase):
self.assertEqual(schedules, expected_schedules)
def test_depreciation_after_cancelling_asset_repair(self):
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
asset_doc = frappe.get_doc("Asset", asset_name)
asset_doc.calculate_depreciation = 1
asset_doc.available_for_use_date = "2023-01-15"
asset_doc.purchase_date = "2023-01-15"
asset_doc.append(
"finance_books",
{
"expected_value_after_useful_life": 200,
"depreciation_method": "Straight Line",
"total_number_of_depreciations": 12,
"frequency_of_depreciation": 1,
"depreciation_start_date": "2023-01-31",
},
)
asset_doc.submit()
post_depreciation_entries(getdate("2023-08-21"))
# create asset repair
asset_repair = create_asset_repair(asset=asset_doc, capitalize_repair_cost=1, submit=1)
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
self.assertEqual(first_asset_depr_schedule.status, "Active")
# create asset value adjustment
current_value = get_asset_value_after_depreciation(asset_doc.name)
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name,
current_asset_value=current_value,
new_asset_value=50000.0,
date="2023-08-21",
)
adj_doc.submit()
first_asset_depr_schedule.load_from_db()
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
self.assertEqual(second_asset_depr_schedule.status, "Active")
self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
# Test gl entry creted from asset value adjustemnet
expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 5625.29),
("_Test Depreciations - _TC", 5625.29, 0.0),
)
gle = frappe.db.sql(
"""select account, debit, credit from `tabGL Entry`
where voucher_type='Journal Entry' and voucher_no = %s
order by account""",
adj_doc.journal_entry,
)
self.assertSequenceEqual(gle, expected_gle)
# test depreciation schedule after asset repair and asset value adjustemnet
expected_schedules = [
["2023-01-31", 5474.73, 5474.73],
["2023-02-28", 9983.33, 15458.06],
["2023-03-31", 9983.33, 25441.39],
["2023-04-30", 9983.33, 35424.72],
["2023-05-31", 9983.33, 45408.05],
["2023-06-30", 9983.33, 55391.38],
["2023-07-31", 9983.33, 65374.71],
["2023-08-31", 2766.67, 68141.38],
["2023-09-30", 2766.67, 70908.05],
["2023-10-31", 2766.67, 73674.72],
["2023-11-30", 2766.67, 76441.39],
["2023-12-31", 2766.67, 79208.06],
["2024-01-31", 2766.67, 81974.73],
["2024-02-29", 2766.67, 84741.4],
["2024-03-31", 2766.67, 87508.07],
["2024-04-30", 2766.67, 90274.74],
["2024-05-31", 2766.67, 93041.41],
["2024-06-30", 2766.67, 95808.08],
["2024-07-31", 2766.67, 98574.75],
["2024-08-31", 2766.67, 101341.42],
["2024-09-30", 2766.67, 104108.09],
["2024-10-31", 2766.67, 106874.76],
["2024-11-30", 2766.67, 109641.43],
["2024-12-31", 2766.67, 112408.1],
["2025-01-15", 2766.61, 115174.71],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in second_asset_depr_schedule.get("depreciation_schedule")
]
self.assertEqual(schedules, expected_schedules)
# Cancel asset repair
asset_repair.cancel()
asset_repair.load_from_db()
second_asset_depr_schedule.load_from_db()
third_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
self.assertEqual(third_asset_depr_schedule.status, "Active")
self.assertEqual(second_asset_depr_schedule.status, "Cancelled")
# After cancelling asset repair asset life will be decreased and new depreciation schedule should be calculated
expected_schedules = [
["2023-01-31", 5474.73, 5474.73],
["2023-02-28", 9983.33, 15458.06],
["2023-03-31", 9983.33, 25441.39],
["2023-04-30", 9983.33, 35424.72],
["2023-05-31", 9983.33, 45408.05],
["2023-06-30", 9983.33, 55391.38],
["2023-07-31", 9983.33, 65374.71],
["2023-08-31", 8133.33, 73508.04],
["2023-09-30", 8133.33, 81641.37],
["2023-10-31", 8133.33, 89774.7],
["2023-11-30", 8133.33, 97908.03],
["2023-12-31", 8133.33, 106041.36],
["2024-01-15", 8133.35, 114174.71],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in third_asset_depr_schedule.get("depreciation_schedule")
]
self.assertEqual(schedules, expected_schedules)
def make_asset_value_adjustment(**args):
args = frappe._dict(args)

View File

@@ -14,6 +14,9 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_
def update_last_purchase_rate(doc, is_submit) -> None:
"""updates last_purchase_rate in item table for each item"""
if doc.get("is_internal_supplier"):
return
this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))
for d in doc.get("items"):

View File

@@ -85,7 +85,6 @@ force_item_fields = (
"brand",
"stock_uom",
"is_fixed_asset",
"item_tax_rate",
"pricing_rules",
"weight_per_unit",
"weight_uom",
@@ -234,7 +233,7 @@ class AccountsController(TransactionBase):
).format(
frappe.bold(document_type),
get_link_to_form(self.doctype, self.get("return_against")),
frappe.bold("Update Outstanding for Self"),
frappe.bold(_("Update Outstanding for Self")),
get_link_to_form("Payment Reconciliation", "Payment Reconciliation"),
)
)
@@ -743,7 +742,6 @@ class AccountsController(TransactionBase):
args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
if item.get(fieldname) is None or fieldname in force_item_fields:
@@ -753,7 +751,10 @@ class AccountsController(TransactionBase):
fieldname
):
item.set(fieldname, value)
elif fieldname == "item_tax_rate" and not (
self.get("is_return") and self.get("return_against")
):
item.set(fieldname, value)
elif fieldname == "serial_no":
# Ensure that serial numbers are matched against Stock UOM
item_conversion_factor = item.get("conversion_factor") or 1.0
@@ -1961,7 +1962,9 @@ class AccountsController(TransactionBase):
def raise_missing_debit_credit_account_error(self, party_type, party):
"""Raise an error if debit to/credit to account does not exist."""
db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
db_or_cr = (
frappe.bold(_("Debit To")) if self.doctype == "Sales Invoice" else frappe.bold(_("Credit To"))
)
rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
link_to_party = frappe.utils.get_link_to_form(party_type, party)
@@ -2432,6 +2435,15 @@ class AccountsController(TransactionBase):
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
advance_entry.is_advance = "Yes"
# update dimesions
dimensions_dict = frappe._dict()
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
dimensions_dict[dim.fieldname] = self.get(dim.fieldname)
reconcilation_entry.update(dimensions_dict)
advance_entry.update(dimensions_dict)
if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
@@ -3075,9 +3087,9 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(
_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.").format(
frappe.bold("default warehouse"), frappe.bold(item.item_code)
)
_(
"Cannot find a default warehouse for item {0}. Please set one in the Item Master or in Stock Settings."
).format(frappe.bold(item.item_code))
)
set_child_tax_template_and_map(item, child_item, p_doc)

View File

@@ -314,18 +314,22 @@ class BuyingController(SubcontractingController):
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
)
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
if self.get("is_old_subcontracting_flow"):
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
item.valuation_rate = (
item.base_net_amount
net_rate
+ item.item_tax_amount
+ item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)
) / qty_in_stock_uom
else:
item.valuation_rate = (
item.base_net_amount
net_rate
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("rate_difference_with_purchase_invoice"))
@@ -336,72 +340,88 @@ class BuyingController(SubcontractingController):
update_regional_item_valuation_rate(self)
def set_incoming_rate(self):
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
"""
Override item rate with incoming rate for internal stock transfer
"""
if self.doctype not in ("Purchase Receipt", "Purchase Invoice"):
return
if not (self.doctype == "Purchase Receipt" or self.get("update_stock")):
return
if cint(self.get("is_return")):
# Get outgoing rate based on original item cost based on valuation method
return
if not self.is_internal_transfer():
return
allow_at_arms_length_price = frappe.get_cached_value(
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
)
if allow_at_arms_length_price:
return
self.set_sales_incoming_rate_for_internal_transfer()
for d in self.get("items"):
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
if d.rate == d.sales_incoming_rate:
continue
d.rate = d.sales_incoming_rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
def set_sales_incoming_rate_for_internal_transfer(self):
"""
Set incoming rate from the sales transaction against which the
purchase is made (internal transfer)
"""
ref_doctype_map = {
"Purchase Order": "Sales Order Item",
"Purchase Receipt": "Delivery Note Item",
"Purchase Invoice": "Sales Invoice Item",
}
ref_doctype = ref_doctype_map.get(self.doctype)
items = self.get("items")
for d in items:
if not cint(self.get("is_return")):
# Get outgoing rate based on original item cost based on valuation method
for d in self.get("items"):
if not d.get(frappe.scrub(ref_doctype)):
posting_time = self.get("posting_time")
if not posting_time:
posting_time = nowtime()
if not d.get(frappe.scrub(ref_doctype)):
posting_time = self.get("posting_time")
if not posting_time and self.doctype == "Purchase Order":
posting_time = nowtime()
outgoing_rate = get_incoming_rate(
{
"item_code": d.item_code,
"warehouse": d.get("from_warehouse"),
"posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": posting_time,
"qty": -1 * flt(d.get("stock_qty")),
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
"voucher_detail_no": d.name,
},
raise_error_if_no_rate=False,
)
outgoing_rate = get_incoming_rate(
{
"item_code": d.item_code,
"warehouse": d.get("from_warehouse"),
"posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": posting_time,
"qty": -1 * flt(d.get("stock_qty")),
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
"voucher_detail_no": d.name,
},
raise_error_if_no_rate=False,
)
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
field = (
"incoming_rate"
if self.get("is_internal_supplier") and not self.doctype == "Purchase Order"
else "rate"
)
rate = flt(
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
* (d.conversion_factor or 1),
d.precision("rate"),
)
if self.is_internal_transfer():
if self.doctype == "Purchase Receipt" or self.get("update_stock"):
if rate != d.rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
d.sales_incoming_rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
d.sales_incoming_rate = flt(
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
* (d.conversion_factor or 1),
d.precision("rate"),
)
def validate_for_subcontracting(self):
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
@@ -566,11 +586,9 @@ class BuyingController(SubcontractingController):
if d.from_warehouse:
sle.dependant_sle_voucher_detail_no = d.name
else:
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
sle.update(
{
"incoming_rate": incoming_rate,
"incoming_rate": d.valuation_rate,
"recalculate_rate": 1
if (self.is_subcontracted and (d.bom or d.get("fg_item"))) or d.from_warehouse
else 0,

View File

@@ -150,7 +150,7 @@ def validate_item_attribute_value(attributes_list, attribute, attribute_value, i
)
msg += "<br>" + _(
"To still proceed with editing this Attribute Value, enable {0} in Item Variant Settings."
).format(frappe.bold("Allow Rename Attribute Value"))
).format(frappe.bold(_("Allow Rename Attribute Value")))
frappe.throw(msg, InvalidItemAttributeValueError, title=_("Edit Not Allowed"))

View File

@@ -319,6 +319,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
doc.ignore_pricing_rule = 1
doc.pricing_rules = []
doc.return_against = source.name
doc.set_warehouse = ""
if doctype == "Sales Invoice" or doctype == "POS Invoice":
@@ -478,6 +480,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1 * source_doc.qty
target_doc.pricing_rules = None
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype

View File

@@ -435,6 +435,9 @@ class SellingController(StockController):
if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
allow_at_arms_length_price = frappe.get_cached_value(
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
)
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
@@ -481,6 +484,9 @@ class SellingController(StockController):
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
if allow_at_arms_length_price:
continue
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
@@ -691,7 +697,7 @@ class SellingController(StockController):
duplicate_items_msg = _("Item {0} entered multiple times.").format(frappe.bold(d.item_code))
duplicate_items_msg += "<br><br>"
duplicate_items_msg += _("Please enable {} in {} to allow same item in multiple rows").format(
frappe.bold("Allow Item to Be Added Multiple Times in a Transaction"),
frappe.bold(_("Allow Item to Be Added Multiple Times in a Transaction")),
get_link_to_form("Selling Settings", "Selling Settings"),
)
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:

View File

@@ -92,6 +92,9 @@ class calculate_taxes_and_totals:
self.doc.base_tax_withholding_net_total = sum_base_net_amount
def validate_item_tax_template(self):
if self.doc.get("is_return") and self.doc.get("return_against"):
return
for item in self._items:
if item.item_code and item.get("item_tax_template"):
item_doc = frappe.get_cached_doc("Item", item.item_code)
@@ -241,7 +244,6 @@ class calculate_taxes_and_totals:
"tax_fraction_for_current_item",
"grand_total_fraction_for_current_item",
]
if tax.charge_type != "Actual" and not (
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
):

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, getdate, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -13,6 +13,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.buying.doctype.purchase_order.test_purchase_order import prepare_data_for_internal_transfer
from erpnext.stock.doctype.item.test_item import create_item
@@ -804,6 +805,41 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
@change_settings("Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1})
def test_16_internal_transfer_at_arms_length_price(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company)
warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company)
arms_length_price = 40
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
debit_to="Debtors - TCP1",
target_warehouse=target_warehouse,
warehouse=warehouse,
income_account="Sales - TCP1",
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
update_stock=True,
do_not_save=True,
do_not_submit=True,
)
si.items[0].rate = arms_length_price
si.save()
# rate should not reset to incoming rate
self.assertEqual(si.items[0].rate, arms_length_price)
frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0)
si.items[0].rate = arms_length_price
si.save()
# rate should reset to incoming rate
self.assertEqual(si.items[0].rate, 100)
def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)

View File

@@ -93,7 +93,26 @@ class Opportunity(TransactionBase, CRMNote):
def onload(self):
ref_doc = frappe.get_doc(self.opportunity_from, self.party_name)
load_address_and_contact(ref_doc)
load_address_and_contact(self)
ref_doc_contact_list = ref_doc.get("__onload").get("contact_list")
opportunity_doc_contact_list = [
contact
for contact in self.get("__onload").get("contact_list")
if contact not in ref_doc_contact_list
]
ref_doc_contact_list.extend(opportunity_doc_contact_list)
ref_doc.set_onload("contact_list", ref_doc_contact_list)
ref_doc_addr_list = ref_doc.get("__onload").get("addr_list")
opportunity_doc_addr_list = [
addr for addr in self.get("__onload").get("addr_list") if addr not in ref_doc_addr_list
]
ref_doc_addr_list.extend(opportunity_doc_addr_list)
ref_doc.set_onload("addr_list", ref_doc_addr_list)
self.set("__onload", ref_doc.get("__onload"))
def after_insert(self):

View File

@@ -27,6 +27,29 @@ class TestProspect(unittest.TestCase):
address_doc.reload()
self.assertEqual(address_doc.has_link("Prospect", prospect_doc.name), True)
def test_make_customer_from_prospect(self):
from erpnext.crm.doctype.prospect.prospect import make_customer as make_customer_from_prospect
frappe.delete_doc_if_exists("Customer", "_Test Prospect")
prospect = frappe.get_doc(
{
"doctype": "Prospect",
"company_name": "_Test Prospect",
"customer_group": "_Test Customer Group",
}
)
prospect.insert()
customer = make_customer_from_prospect("_Test Prospect")
self.assertEqual(customer.doctype, "Customer")
self.assertEqual(customer.company_name, "_Test Prospect")
self.assertEqual(customer.customer_group, "_Test Customer Group")
customer.company = "_Test Company"
customer.insert()
def make_prospect(**args):
args = frappe._dict(args)

View File

@@ -108,7 +108,9 @@ class OpportunitySummaryBySalesStage:
self.grouped_data = []
grouping_key = lambda o: (o["sales_stage"], o[based_on]) # noqa
for (sales_stage, _based_on), rows in groupby(self.query_result, grouping_key):
for (sales_stage, _based_on), rows in groupby(
sorted(self.query_result, key=grouping_key), key=grouping_key
):
self.grouped_data.append(
{
"sales_stage": sales_stage,

View File

@@ -8,7 +8,7 @@ from itertools import groupby
import frappe
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe.utils import cint, flt
from frappe.utils import cint, flt, getdate
from erpnext.setup.utils import get_exchange_rate
@@ -21,7 +21,15 @@ class SalesPipelineAnalytics:
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
def validate_filters(self):
if not self.filters.from_date:
frappe.throw(_("From Date is mandatory"))
if not self.filters.to_date:
frappe.throw(_("To Date is mandatory"))
def run(self):
self.validate_filters()
self.get_columns()
self.get_data()
self.get_chart_data()
@@ -114,7 +122,9 @@ class SalesPipelineAnalytics:
self.grouped_data = []
grouping_key = lambda o: (o.get(self.pipeline_by) or "Not Assigned", o[self.period_by]) # noqa
for (pipeline_by, period_by), rows in groupby(self.query_result, grouping_key):
for (pipeline_by, period_by), rows in groupby(
sorted(self.query_result, key=grouping_key), grouping_key
):
self.grouped_data.append(
{
self.pipeline_by: pipeline_by,
@@ -185,7 +195,7 @@ class SalesPipelineAnalytics:
count_or_amount = info.get(based_on)
if self.filters.get("pipeline_by") == "Owner":
if value == "Not Assigned" or value == "[]" or value is None:
if value == "Not Assigned" or value == "[]" or value is None or not value:
assigned_to = ["Not Assigned"]
else:
assigned_to = json.loads(value)
@@ -227,10 +237,9 @@ class SalesPipelineAnalytics:
def get_month_list(self):
month_list = []
current_date = date.today()
month_number = date.today().month
current_date = getdate(self.filters.get("from_date"))
for _month in range(month_number, 13):
while current_date < getdate(self.filters.get("to_date")):
month_list.append(current_date.strftime("%B"))
current_date = current_date + relativedelta(months=1)

View File

@@ -1,19 +1,21 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
class TestSalesPipelineAnalytics(unittest.TestCase):
@classmethod
def setUpClass(self):
class TestSalesPipelineAnalytics(FrappeTestCase):
def setUp(self):
frappe.db.delete("Opportunity")
create_company()
create_customer()
create_opportunity()
def test_sales_pipeline_analytics(self):
self.from_date = "2021-01-01"
self.to_date = "2021-12-31"
self.check_for_monthly_and_number()
self.check_for_monthly_and_amount()
self.check_for_quarterly_and_number()
@@ -28,6 +30,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -43,6 +47,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -59,6 +65,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -74,6 +82,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -90,6 +100,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -105,6 +117,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -121,6 +135,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -136,6 +152,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -153,8 +171,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"opportunity_type": "Sales",
"company": "Best Test",
"opportunity_source": "Cold Calling",
"from_date": "2021-08-01",
"to_date": "2021-08-31",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)

View File

@@ -96,7 +96,7 @@ def add_bank_accounts(response, bank, company):
frappe.throw(
_(
"Please setup and enable a group account with the Account Type - {0} for the company {1}"
).format(frappe.bold("Bank"), company)
).format(frappe.bold(_("Bank")), company)
)
for account in response["accounts"]:

View File

@@ -357,6 +357,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
erpnext.patches.v14_0.update_pos_return_ledger_entries #2024-08-16
# below migration patch should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
@@ -370,3 +371,4 @@ erpnext.patches.v15_0.update_warehouse_field_in_asset_repair_consumed_item_docty
erpnext.patches.v15_0.update_asset_repair_field_in_stock_entry
erpnext.patches.v15_0.update_total_number_of_booked_depreciations
erpnext.patches.v15_0.do_not_use_batchwise_valuation
erpnext.patches.v15_0.drop_index_posting_datetime_from_sle

View File

@@ -0,0 +1,127 @@
import frappe
from frappe import qb
from erpnext.accounts.utils import update_voucher_outstanding
def get_valid_against_voucher_ref(pos_returns):
sinv = qb.DocType("Sales Invoice")
res = (
qb.from_(sinv)
.select(sinv.name, sinv.return_against)
.where(sinv.name.isin(pos_returns) & sinv.return_against.notnull())
.orderby(sinv.name)
.run(as_dict=True)
)
return res
def build_dict_of_valid_against_reference(pos_returns):
_against_ref_dict = frappe._dict()
res = get_valid_against_voucher_ref(pos_returns)
for x in res:
_against_ref_dict[x.name] = x.return_against
return _against_ref_dict
def fix_incorrect_against_voucher_ref(affected_pos_returns):
if affected_pos_returns:
valid_against_voucher_dict = build_dict_of_valid_against_reference(affected_pos_returns)
gle = qb.DocType("GL Entry")
gles_with_invalid_against = (
qb.from_(gle)
.select(gle.name, gle.voucher_no)
.where(
gle.voucher_no.isin(affected_pos_returns)
& gle.against_voucher.notnull()
& gle.against_voucher.eq(gle.voucher_no)
& gle.is_cancelled.eq(0)
)
.run(as_dict=True)
)
# Update GL
if gles_with_invalid_against:
for gl in gles_with_invalid_against:
frappe.db.set_value(
"GL Entry",
gl.name,
"against_voucher",
valid_against_voucher_dict[gl.voucher_no],
)
# Update Payment Ledger
ple = qb.DocType("Payment Ledger Entry")
for x in affected_pos_returns:
qb.update(ple).set(ple.against_voucher_no, valid_against_voucher_dict[x]).where(
ple.voucher_no.eq(x) & ple.delinked.eq(0)
).run()
def get_pos_returns_with_invalid_against_ref():
sinv = qb.DocType("Sales Invoice")
pos_returns_without_self = (
qb.from_(sinv)
.select(sinv.name)
.where(
sinv.docstatus.eq(1)
& sinv.is_pos.eq(1)
& sinv.is_return.eq(1)
& sinv.return_against.notnull()
& sinv.update_outstanding_for_self.eq(0)
)
.run()
)
if pos_returns_without_self:
pos_returns_without_self = [x[0] for x in pos_returns_without_self]
gle = qb.DocType("GL Entry")
gl_against_references = (
qb.from_(gle)
.select(gle.voucher_no, gle.against_voucher)
.where(
gle.voucher_no.isin(pos_returns_without_self)
& gle.against_voucher.notnull()
& gle.against_voucher.eq(gle.voucher_no)
& gle.is_cancelled.eq(0)
)
.run()
)
if gl_against_references:
_vouchers = list(set([x[0] for x in gl_against_references]))
invoice_return_against = (
qb.from_(sinv)
.select(sinv.name, sinv.return_against)
.where(sinv.name.isin(_vouchers) & sinv.return_against.notnull())
.orderby(sinv.name)
.run()
)
valid_references = set(invoice_return_against)
actual_references = set(gl_against_references)
invalid_references = actual_references.difference(valid_references)
if invalid_references:
return [x[0] for x in invalid_references]
return None
def update_outstanding_for_affected(affected_pos_returns):
if affected_pos_returns:
sinv = qb.DocType("Sales Invoice")
pos_with_accounts = (
qb.from_(sinv)
.select(sinv.return_against, sinv.debit_to, sinv.customer)
.where(sinv.name.isin(affected_pos_returns))
.run(as_dict=True)
)
for x in pos_with_accounts:
update_voucher_outstanding("Sales Invoice", x.return_against, x.debit_to, "Customer", x.customer)
def execute():
affected_pos_returns = get_pos_returns_with_invalid_against_ref()
fix_incorrect_against_voucher_ref(affected_pos_returns)
update_outstanding_for_affected(affected_pos_returns)

View File

@@ -0,0 +1,16 @@
import click
import frappe
def execute():
table = "tabStock Ledger Entry"
index = "posting_datetime_creation_index"
if not frappe.db.has_index(table, index):
return
try:
frappe.db.sql_ddl(f"ALTER TABLE `{table}` DROP INDEX `{index}`")
click.echo(f"✓ dropped {index} index from {table}")
except Exception:
frappe.log_error("Failed to drop index")

View File

@@ -379,6 +379,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None, currency=None
target.project = timesheet.parent_project
if customer:
target.customer = customer
default_price_list = frappe.get_value("Customer", customer, "default_price_list")
if default_price_list:
target.selling_price_list = default_price_list
if currency:
target.currency = currency

View File

@@ -374,6 +374,14 @@ erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager {
label: "Cost Center",
options: "Cost Center",
depends_on: "eval:doc.action=='Create Voucher' && doc.document_type=='Payment Entry'",
get_query: () => {
return {
filters: {
is_group: 0,
company: this.company,
},
};
},
},
{
fieldtype: "Section Break",

View File

@@ -534,6 +534,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
quotation_to: me.frm.doc.quotation_to,
supplier: me.frm.doc.supplier,
currency: me.frm.doc.currency,
is_internal_supplier: me.frm.doc.is_internal_supplier,
is_internal_customer: me.frm.doc.is_internal_customer,
update_stock: update_stock,
conversion_rate: me.frm.doc.conversion_rate,
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
@@ -826,47 +828,80 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
var me = this;
var set_pricing = function() {
if(me.frm.doc.company && me.frm.fields_dict.currency) {
var company_currency = me.get_company_currency();
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
if (!me.frm.doc.currency) {
me.frm.set_value("currency", company_currency);
}
if (me.frm.doc.currency == company_currency) {
me.frm.set_value("conversion_rate", 1.0);
}
if (me.frm.doc.price_list_currency == company_currency) {
me.frm.set_value('plc_conversion_rate', 1.0);
}
if (company_doc){
if (company_doc.default_letter_head) {
if(me.frm.fields_dict.letter_head) {
me.frm.set_value("letter_head", company_doc.default_letter_head);
}
}
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_selling_terms);
}
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
"Material Request", "Purchase Receipt"];
// Purchase Invoice is excluded as per issue #3345
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_buying_terms);
}
}
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
() => get_party_currency(),
() => me.update_item_tax_map(),
() => me.apply_default_taxes(),
() => me.apply_pricing_rule()
() => me.apply_pricing_rule(),
() => set_terms(),
() => set_letter_head(),
]);
}
}
var get_party_currency = function() {
if (me.is_a_mapped_document()) {
return;
}
var party_type = frappe.meta.has_field(me.frm.doc.doctype, "customer") ? "Customer" : "Supplier";
var party_name = me.frm.doc[party_type.toLowerCase()];
if (party_name) {
frappe.call({
method: "frappe.client.get_value",
args: {
doctype: party_type,
filters: { name: party_name },
fieldname: "default_currency",
},
callback: function (r) {
if (r.message) {
set_currency(r.message.default_currency);
}
}
})
} else {
set_currency();
}
}
var set_currency = function(party_default_currency) {
var company_currency = me.get_company_currency();
var currency = party_default_currency || company_currency;
if (me.frm.doc.currency != currency) {
me.frm.set_value("currency", currency);
}
if (me.frm.doc.currency == company_currency) {
me.frm.set_value("conversion_rate", 1.0);
}
if (me.frm.doc.price_list_currency == company_currency) {
me.frm.set_value('plc_conversion_rate', 1.0);
}
me.frm.script_manager.trigger("currency");
}
var set_terms = function() {
if (frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && !me.frm.doc.tc_name) {
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
var selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"];
var company_terms_fieldname = selling_doctypes.includes(me.frm.doc.doctype) ? "default_selling_terms" : "default_buying_terms";
if (company_doc && company_doc[company_terms_fieldname]) {
me.frm.set_value("tc_name", company_doc[company_terms_fieldname]);
}
}
}
var set_letter_head = function() {
if(me.frm.fields_dict.letter_head) {
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
if (company_doc && company_doc.default_letter_head) {
me.frm.set_value("letter_head", company_doc.default_letter_head);
}
}
}
var set_party_account = function(set_pricing) {
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
if(me.frm.doc.doctype=="Sales Invoice") {
@@ -1218,16 +1253,24 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
is_a_mapped_document(item) {
const mapped_item_field_map = {
"Delivery Note Item": ["si_detail", "so_detail", "dn_detail"],
"Sales Invoice Item": ["dn_detail", "so_detail", "sales_invoice_item"],
"Purchase Receipt Item": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
"Purchase Invoice Item": ["purchase_order_item", "pr_detail", "po_detail"],
"Delivery Note": ["si_detail", "so_detail", "dn_detail"],
"Sales Invoice": ["dn_detail", "so_detail", "sales_invoice_item"],
"Purchase Receipt": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
"Purchase Invoice": ["purchase_order_item", "pr_detail", "po_detail"],
"Sales Order": ["prevdoc_docname", "quotation_item"],
};
const mappped_fields = mapped_item_field_map[item.doctype] || [];
const mappped_fields = mapped_item_field_map[this.frm.doc.doctype] || [];
return mappped_fields
.map((field) => item[field])
.filter(Boolean).length > 0;
if (item) {
return mappped_fields
.map((field) => item[field])
.filter(Boolean).length > 0;
} else if (this.frm.doc?.items) {
let first_row = this.frm.doc.items[0];
let mapped_rows = mappped_fields.filter(d => first_row[d])
return mapped_rows?.length > 0;
}
}
batch_no(doc, cdt, cdn) {
@@ -1621,7 +1664,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
"conversion_factor": me.frm.doc.conversion_factor,
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
"coupon_code": me.frm.doc.coupon_code
"coupon_code": me.frm.doc.coupon_code,
"is_internal_supplier": me.frm.doc.is_internal_supplier,
"is_internal_customer": me.frm.doc.is_internal_customer,
};
}
@@ -1949,6 +1994,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
let item_rates = {};
let item_tax_templates = {};
if (me.frm.doc.is_return && me.frm.doc.return_against) return;
$.each(this.frm.doc.items || [], function(i, item) {
if (item.item_code) {
// Use combination of name and item code in case same item is added multiple times

View File

@@ -21,6 +21,7 @@
"gender",
"lead_name",
"opportunity_name",
"prospect_name",
"account_manager",
"image",
"defaults_tab",
@@ -570,6 +571,14 @@
{
"fieldname": "column_break_nwor",
"fieldtype": "Column Break"
},
{
"fieldname": "prospect_name",
"fieldtype": "Link",
"label": "From Prospect",
"no_copy": 1,
"options": "Prospect",
"print_hide": 1
}
],
"icon": "fa fa-user",

View File

@@ -77,6 +77,7 @@ class Customer(TransactionBase):
payment_terms: DF.Link | None
portal_users: DF.Table[PortalUser]
primary_address: DF.Text | None
prospect_name: DF.Link | None
represents_company: DF.Link | None
sales_team: DF.Table[SalesTeam]
salutation: DF.Link | None

View File

@@ -347,8 +347,8 @@ def make_sales_order(source_name: str, target_doc=None):
return _make_sales_order(source_name, target_doc)
def _make_sales_order(source_name, target_doc=None, customer_group=None, ignore_permissions=False):
customer = _make_customer(source_name, ignore_permissions, customer_group)
def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
customer = _make_customer(source_name, ignore_permissions)
ordered_items = frappe._dict(
frappe.db.get_all(
"Sales Order Item",
@@ -502,51 +502,71 @@ def _make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
return doclist
def _make_customer(source_name, ignore_permissions=False, customer_group=None):
def _make_customer(source_name, ignore_permissions=False):
quotation = frappe.db.get_value(
"Quotation", source_name, ["order_type", "party_name", "customer_name"], as_dict=1
"Quotation",
source_name,
["order_type", "quotation_to", "party_name", "customer_name"],
as_dict=1,
)
if quotation and quotation.get("party_name"):
if not frappe.db.exists("Customer", quotation.get("party_name")):
lead_name = quotation.get("party_name")
customer_name = frappe.db.get_value(
"Customer", {"lead_name": lead_name}, ["name", "customer_name"], as_dict=True
)
if not customer_name:
from erpnext.crm.doctype.lead.lead import _make_customer
if quotation.quotation_to == "Customer":
return frappe.get_doc("Customer", quotation.party_name)
customer_doclist = _make_customer(lead_name, ignore_permissions=ignore_permissions)
customer = frappe.get_doc(customer_doclist)
customer.flags.ignore_permissions = ignore_permissions
customer.customer_group = customer_group
# Check if a Customer already exists for the Lead or Prospect.
existing_customer = None
if quotation.quotation_to == "Lead":
existing_customer = frappe.db.get_value("Customer", {"lead_name": quotation.party_name})
elif quotation.quotation_to == "Prospect":
existing_customer = frappe.db.get_value("Customer", {"prospect_name": quotation.party_name})
try:
customer.insert()
return customer
except frappe.NameError:
if frappe.defaults.get_global_default("cust_master_name") == "Customer Name":
customer.run_method("autoname")
customer.name += "-" + lead_name
customer.insert()
return customer
else:
raise
except frappe.MandatoryError as e:
mandatory_fields = e.args[0].split(":")[1].split(",")
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
if existing_customer:
return frappe.get_doc("Customer", existing_customer)
frappe.local.message_log = []
lead_link = frappe.utils.get_link_to_form("Lead", lead_name)
message = (
_("Could not auto create Customer due to the following missing mandatory field(s):")
+ "<br>"
)
message += "<br><ul><li>" + "</li><li>".join(mandatory_fields) + "</li></ul>"
message += _("Please create Customer from Lead {0}.").format(lead_link)
# If no Customer exists, create a new Customer or Prospect.
if quotation.quotation_to == "Lead":
return create_customer_from_lead(quotation.party_name, ignore_permissions=ignore_permissions)
elif quotation.quotation_to == "Prospect":
return create_customer_from_prospect(quotation.party_name, ignore_permissions=ignore_permissions)
frappe.throw(message, title=_("Mandatory Missing"))
else:
return customer_name
else:
return frappe.get_doc("Customer", quotation.get("party_name"))
return None
def create_customer_from_lead(lead_name, ignore_permissions=False):
from erpnext.crm.doctype.lead.lead import _make_customer
customer = _make_customer(lead_name, ignore_permissions=ignore_permissions)
customer.flags.ignore_permissions = ignore_permissions
try:
customer.insert()
return customer
except frappe.MandatoryError as e:
handle_mandatory_error(e, customer, lead_name)
def create_customer_from_prospect(prospect_name, ignore_permissions=False):
from erpnext.crm.doctype.prospect.prospect import make_customer as make_customer_from_prospect
customer = make_customer_from_prospect(prospect_name)
customer.flags.ignore_permissions = ignore_permissions
try:
customer.insert()
return customer
except frappe.MandatoryError as e:
handle_mandatory_error(e, customer, prospect_name)
def handle_mandatory_error(e, customer, lead_name):
from frappe.utils import get_link_to_form
mandatory_fields = e.args[0].split(":")[1].split(",")
mandatory_fields = [customer.meta.get_label(field.strip()) for field in mandatory_fields]
frappe.local.message_log = []
message = _("Could not auto create Customer due to the following missing mandatory field(s):") + "<br>"
message += "<br><ul><li>" + "</li><li>".join(mandatory_fields) + "</li></ul>"
message += _("Please create Customer from Lead {0}.").format(get_link_to_form("Lead", lead_name))
frappe.throw(message, title=_("Mandatory Missing"))

View File

@@ -936,6 +936,10 @@ def make_delivery_note(source_name, target_doc=None, kwargs=None):
}
def set_missing_values(source, target):
if kwargs.get("ignore_pricing_rule"):
# Skip pricing rule when the dn is creating from the pick list
target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")

View File

@@ -193,6 +193,9 @@ erpnext.SalesFunnel = class SalesFunnel {
this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0;
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
const min_height = (this.options.height * 0.1) / this.options.data.length;
const height = this.options.height * 0.9;
// calculate total weightage
// as height decreases, area decreases by the square of the reduction
// hence, compensating by squaring the index value
@@ -202,7 +205,7 @@ erpnext.SalesFunnel = class SalesFunnel {
// calculate height for each data
$.each(this.options.data, function (i, d) {
d.height = (me.options.height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage;
d.height = (height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage + min_height;
});
this.elements.canvas = $("<canvas></canvas>")
@@ -245,7 +248,7 @@ erpnext.SalesFunnel = class SalesFunnel {
context.fill();
// draw text
context.fillStyle = "black";
context.fillStyle = getComputedStyle(document.body).getPropertyValue("--text-color");
context.textBaseline = "middle";
context.font = "1.1em sans-serif";
context.fillText(__(title), width + 20, y_mid);

View File

@@ -93,7 +93,7 @@ def get_opp_by_lead_source(from_date, to_date, company):
summary = {}
sales_stages = set()
group_key = lambda o: (o["source"], o["sales_stage"]) # noqa
for (source, sales_stage), rows in groupby(cp_opportunities, group_key):
for (source, sales_stage), rows in groupby(sorted(cp_opportunities, key=group_key), group_key):
summary.setdefault(source, {})[sales_stage] = sum(r["compound_amount"] for r in rows)
sales_stages.add(sales_stage)

View File

@@ -3,22 +3,14 @@
frappe.ui.form.on("Brand", {
setup: (frm) => {
frm.fields_dict["brand_defaults"].grid.get_field("default_warehouse").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("default_warehouse", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: { company: row.company },
};
};
});
frm.fields_dict["brand_defaults"].grid.get_field("default_discount_account").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("default_discount_account", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
@@ -27,13 +19,9 @@ frappe.ui.form.on("Brand", {
is_group: 0,
},
};
};
});
frm.fields_dict["brand_defaults"].grid.get_field("buying_cost_center").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("buying_cost_center", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
@@ -41,25 +29,17 @@ frappe.ui.form.on("Brand", {
company: row.company,
},
};
};
});
frm.fields_dict["brand_defaults"].grid.get_field("expense_account").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("expense_account", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
query: "erpnext.controllers.queries.get_expense_account",
filters: { company: row.company },
};
};
});
frm.fields_dict["brand_defaults"].grid.get_field("default_provisional_account").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("default_provisional_account", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
@@ -68,13 +48,9 @@ frappe.ui.form.on("Brand", {
is_group: 0,
},
};
};
});
frm.fields_dict["brand_defaults"].grid.get_field("selling_cost_center").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("selling_cost_center", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
@@ -82,18 +58,14 @@ frappe.ui.form.on("Brand", {
company: row.company,
},
};
};
});
frm.fields_dict["brand_defaults"].grid.get_field("income_account").get_query = function (
doc,
cdt,
cdn
) {
frm.set_query("income_account", "brand_defaults", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
query: "erpnext.controllers.queries.get_income_account",
filters: { company: row.company },
};
};
});
},
});

View File

@@ -56,10 +56,11 @@
"idx": 1,
"image_field": "image",
"links": [],
"modified": "2021-03-01 15:57:30.005783",
"modified": "2024-08-20 14:10:21.377962",
"modified_by": "Administrator",
"module": "Setup",
"name": "Brand",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -108,4 +109,4 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC"
}
}

View File

@@ -203,7 +203,7 @@ class Company(NestedSet):
):
frappe.throw(
_("'{0}' should be in company currency {1}.").format(
frappe.bold("Default Advance Received Account"), frappe.bold(self.default_currency)
frappe.bold(_("Default Advance Received Account")), frappe.bold(self.default_currency)
)
)
@@ -214,7 +214,7 @@ class Company(NestedSet):
):
frappe.throw(
_("'{0}' should be in company currency {1}.").format(
frappe.bold("Default Advance Paid Account"), frappe.bold(self.default_currency)
frappe.bold(_("Default Advance Paid Account")), frappe.bold(self.default_currency)
)
)
@@ -447,7 +447,7 @@ class Company(NestedSet):
):
frappe.throw(
_("Set default {0} account for non stock items").format(
frappe.bold("Provisional Account")
frappe.bold(_("Provisional Account"))
)
)

View File

@@ -42,7 +42,6 @@ class CustomerGroup(NestedSet):
def validate_currency_for_receivable_and_advance_account(self):
for x in self.accounts:
company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency")
receivable_account_currency = None
advance_account_currency = None
@@ -56,21 +55,6 @@ class CustomerGroup(NestedSet):
"Account", x.advance_account, "account_currency"
)
if receivable_account_currency and receivable_account_currency != company_default_currency:
frappe.throw(
_("Receivable Account: {0} must be in Company default currency: {1}").format(
frappe.bold(x.account),
frappe.bold(company_default_currency),
)
)
if advance_account_currency and advance_account_currency != company_default_currency:
frappe.throw(
_("Advance Account: {0} must be in Company default currency: {1}").format(
frappe.bold(x.advance_account), frappe.bold(company_default_currency)
)
)
if (
receivable_account_currency
and advance_account_currency

View File

@@ -37,7 +37,6 @@ class SupplierGroup(NestedSet):
def validate_currency_for_payable_and_advance_account(self):
for x in self.accounts:
company_default_currency = frappe.get_cached_value("Company", x.company, "default_currency")
payable_account_currency = None
advance_account_currency = None
@@ -49,21 +48,6 @@ class SupplierGroup(NestedSet):
"Account", x.advance_account, "account_currency"
)
if payable_account_currency and payable_account_currency != company_default_currency:
frappe.throw(
_("Payable Account: {0} must be in Company default currency: {1}").format(
frappe.bold(x.account),
frappe.bold(company_default_currency),
)
)
if advance_account_currency and advance_account_currency != company_default_currency:
frappe.throw(
_("Advance Account: {0} must be in Company default currency: {1}").format(
frappe.bold(x.advance_account), frappe.bold(company_default_currency)
)
)
if (
payable_account_currency
and advance_account_currency

View File

@@ -3,7 +3,7 @@
frappe.ui.form.on("Batch", {
setup: (frm) => {
frm.fields_dict["item"].get_query = function (doc, cdt, cdn) {
frm.set_query("item", () => {
return {
query: "erpnext.controllers.queries.item_query",
filters: {
@@ -11,7 +11,7 @@ frappe.ui.form.on("Batch", {
has_batch_no: 1,
},
};
};
});
},
refresh: (frm) => {
if (!frm.is_new()) {

View File

@@ -188,9 +188,9 @@ class Batch(Document):
if has_expiry_date and not self.expiry_date:
frappe.throw(
msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.").format(
frappe.bold("Shelf Life in Days"),
frappe.bold(_("Shelf Life in Days")),
get_link_to_form("Item", self.item),
frappe.bold("Batch Expiry Date"),
frappe.bold(_("Batch Expiry Date")),
),
title=_("Expiry Date Mandatory"),
)

View File

@@ -328,6 +328,9 @@ class DeliveryNote(SellingController):
return
for item in self.items:
if item.use_serial_batch_fields:
continue
if item.pick_list_item and not item.serial_and_batch_bundle:
filters = {
"item_code": item.item_code,

View File

@@ -19,6 +19,7 @@
"consider_rejected_warehouses",
"get_item_locations",
"pick_manually",
"ignore_pricing_rule",
"section_break_6",
"scan_barcode",
"column_break_13",
@@ -200,11 +201,18 @@
"fieldname": "pick_manually",
"fieldtype": "Check",
"label": "Pick Manually"
},
{
"default": "0",
"description": "If enabled then system won't apply the pricing rule on the delivery note which will be create from the pick list",
"fieldname": "ignore_pricing_rule",
"fieldtype": "Check",
"label": "Ignore Pricing Rule"
}
],
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 22:49:16.954637",
"modified": "2024-08-14 13:20:42.168827",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",

View File

@@ -50,6 +50,7 @@ class PickList(Document):
customer_name: DF.Data | None
for_qty: DF.Float
group_same_items: DF.Check
ignore_pricing_rule: DF.Check
locations: DF.Table[PickListItem]
material_request: DF.Link | None
naming_series: DF.Literal["STO-PICK-.YYYY.-"]
@@ -1097,7 +1098,8 @@ def create_delivery_note(source_name, target_doc=None):
)
)
for customer, rows in groupby(sales_orders, key=lambda so: so["customer"]):
group_key = lambda so: so["customer"] # noqa
for customer, rows in groupby(sorted(sales_orders, key=group_key), key=group_key):
sales_dict[customer] = {row.sales_order for row in rows}
if sales_dict:
@@ -1143,7 +1145,7 @@ def create_dn_with_so(sales_dict, pick_list):
for customer in sales_dict:
for so in sales_dict[customer]:
delivery_note = None
kwargs = {"skip_item_mapping": True}
kwargs = {"skip_item_mapping": True, "ignore_pricing_rule": pick_list.ignore_pricing_rule}
delivery_note = create_delivery_note_from_sales_order(so, delivery_note, kwargs=kwargs)
break
if delivery_note:
@@ -1178,6 +1180,7 @@ def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
dn_item.batch_no = location.batch_no
dn_item.serial_no = location.serial_no
dn_item.use_serial_batch_fields = location.use_serial_batch_fields
update_delivery_note_item(source_doc, dn_item, delivery_note)

View File

@@ -1205,3 +1205,64 @@ class TestPickList(FrappeTestCase):
pl_doc.submit()
frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 0)
def test_ignore_pricing_rule_in_pick_list(self):
frappe.flags.print_stmt = False
warehouse = "_Test Warehouse - _TC"
item = make_item(
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"batch_number_series": "IPR-PICKLT-.######",
"create_new_batch": 1,
}
).name
make_stock_entry(
item=item,
to_warehouse=warehouse,
qty=2,
basic_rate=100,
)
pricing_rule = frappe.get_doc(
{
"doctype": "Pricing Rule",
"title": "Same Free Item",
"price_or_product_discount": "Product",
"selling": 1,
"apply_on": "Item Code",
"items": [
{
"item_code": item,
}
],
"same_item": 1,
"is_recursive": 1,
"recurse_for": 2,
"free_qty": 1,
"company": "_Test Company",
"customer": "_Test Customer",
}
)
pricing_rule.save()
frappe.flags.print_stmt = True
so = make_sales_order(item_code=item, qty=2, rate=100, do_not_save=True)
so.set_warehouse = warehouse
so.submit()
self.assertEqual(len(so.items), 2)
self.assertTrue(so.items[1].is_free_item)
pl = create_pick_list(so.name)
pl.ignore_pricing_rule = 1
pl.save()
pl.submit()
self.assertEqual(len(pl.locations), 1)
delivery_note = create_delivery_note(pl.name)
self.assertEqual(len(delivery_note.items), 1)

View File

@@ -67,6 +67,7 @@
"base_net_rate",
"base_net_amount",
"valuation_rate",
"sales_incoming_rate",
"item_tax_amount",
"rm_supp_cost",
"landed_cost_voucher_amount",
@@ -1124,12 +1125,22 @@
"fieldtype": "Check",
"label": "Return Qty from Rejected Warehouse",
"read_only": 1
},
{
"description": "Valuation rate for the item as per Sales Invoice (Only for Internal Transfers)",
"fieldname": "sales_incoming_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Sales Incoming Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-05-28 09:48:24.448815",
"modified": "2024-07-19 12:14:21.521466",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -88,6 +88,7 @@ class PurchaseReceiptItem(Document):
return_qty_from_rejected_warehouse: DF.Check
returned_qty: DF.Float
rm_supp_cost: DF.Currency
sales_incoming_rate: DF.Currency
sales_order: DF.Link | None
sales_order_item: DF.Data | None
sample_quantity: DF.Int

View File

@@ -701,6 +701,57 @@ class TestSerialandBatchBundle(FrappeTestCase):
serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)
self.assertEqual(serial_nos, serial_nos1)
def test_auto_create_serial_and_batch_bundle_for_outward_for_batch_item(self):
item_code = make_item(
"Test Auto Create Batch Bundle for Outward 1",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"batch_number_series": "ACSBBO-TACSB-.#####",
},
).name
if not frappe.db.exists("Batch", "ACSBBO-TACSB-00001"):
frappe.get_doc(
{
"doctype": "Batch",
"batch_id": "ACSBBO-TACSB-00001",
"item": item_code,
"company": "_Test Company",
}
).insert(ignore_permissions=True)
make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=500,
use_serial_batch_fields=True,
batch_no="ACSBBO-TACSB-00001",
)
dispatch = make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=500,
do_not_submit=True,
)
original_value = frappe.db.get_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
)
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0)
self.assertRaises(frappe.ValidationError, dispatch.submit)
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
dispatch.submit()
frappe.db.set_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", original_value
)
def get_batch_from_bundle(bundle):
from erpnext.stock.serial_batch_bundle import get_batch_nos

View File

@@ -303,7 +303,7 @@
"depends_on": "from_warehouse",
"fieldname": "source_warehouse_address",
"fieldtype": "Link",
"label": "Source Warehouse Address",
"label": "Source Warehouse Address Link",
"options": "Address"
},
{
@@ -333,7 +333,7 @@
"depends_on": "to_warehouse",
"fieldname": "target_warehouse_address",
"fieldtype": "Link",
"label": "Target Warehouse Address",
"label": "Target Warehouse Address Link",
"options": "Address"
},
{
@@ -686,10 +686,10 @@
"read_only": 1
},
{
"fieldname": "tab_connections",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
"fieldname": "tab_connections",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"icon": "fa fa-file-text",
@@ -697,7 +697,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-06-26 19:12:17.937088",
"modified": "2024-08-13 19:02:42.386955",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Entry",

View File

@@ -898,8 +898,8 @@ class StockEntry(StockController):
).format(
item.idx,
frappe.bold(label),
frappe.bold("Manufacture"),
frappe.bold("Material Consumption for Manufacture"),
frappe.bold(_("Manufacture")),
frappe.bold(_("Material Consumption for Manufacture")),
)
)
@@ -909,7 +909,7 @@ class StockEntry(StockController):
):
frappe.throw(
_("Only one {0} entry can be created against the Work Order {1}").format(
frappe.bold("Manufacture"), frappe.bold(self.work_order)
frappe.bold(_("Manufacture")), frappe.bold(self.work_order)
)
)

View File

@@ -4,7 +4,7 @@
from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, nowtime, today
from frappe.utils import add_days, cstr, flt, get_time, getdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import (
@@ -1780,6 +1780,74 @@ class TestStockEntry(FrappeTestCase):
frappe.db.set_value("Serial and Batch Bundle", sbb, "type_of_transaction", "Inward")
self.assertRaises(frappe.ValidationError, se.submit)
def test_stock_entry_for_same_posting_date_and_time(self):
warehouse = "_Test Warehouse - _TC"
item_code = "Test Stock Entry For Same Posting Datetime 1"
make_item(item_code, {"is_stock_item": 1})
posting_date = nowdate()
posting_time = nowtime()
for index in range(25):
se = make_stock_entry(
item_code=item_code,
qty=1,
to_warehouse=warehouse,
posting_date=posting_date,
posting_time=posting_time,
do_not_submit=True,
purpose="Material Receipt",
basic_rate=100,
)
se.append(
"items",
{
"item_code": item_code,
"item_name": se.items[0].item_name,
"description": se.items[0].description,
"t_warehouse": se.items[0].t_warehouse,
"basic_rate": 100,
"qty": 1,
"stock_qty": 1,
"conversion_factor": 1,
"expense_account": se.items[0].expense_account,
"cost_center": se.items[0].cost_center,
"uom": se.items[0].uom,
"stock_uom": se.items[0].stock_uom,
},
)
se.remarks = f"The current number is {cstr(index)}"
se.submit()
sles = frappe.get_all(
"Stock Ledger Entry",
fields=[
"posting_date",
"posting_time",
"actual_qty",
"qty_after_transaction",
"incoming_rate",
"stock_value_difference",
"stock_value",
],
filters={"item_code": item_code, "warehouse": warehouse},
order_by="creation",
)
self.assertEqual(len(sles), 50)
i = 0
for sle in sles:
i += 1
self.assertEqual(getdate(sle.posting_date), getdate(posting_date))
self.assertEqual(get_time(sle.posting_time), get_time(posting_time))
self.assertEqual(sle.actual_qty, 1)
self.assertEqual(sle.qty_after_transaction, i)
self.assertEqual(sle.incoming_rate, 100)
self.assertEqual(sle.stock_value_difference, 100)
self.assertEqual(sle.stock_value, 100 * i)
def make_serialized_item(**args):
args = frappe._dict(args)

View File

@@ -351,4 +351,3 @@ def on_doctype_update():
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
frappe.db.add_index("Stock Ledger Entry", ["warehouse", "item_code"], "item_warehouse")
frappe.db.add_index("Stock Ledger Entry", ["posting_datetime", "creation"])

View File

@@ -1043,6 +1043,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
self.assertEqual(50, _get_stock_credit(final_consumption))
def test_tie_breaking(self):
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_entries
frappe.flags.dont_execute_stock_reposts = True
self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
@@ -1085,6 +1087,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
self.assertEqual([10, 11], ordered_qty_after_transaction())
first.cancel()
repost_entries()
self.assertEqual([1], ordered_qty_after_transaction())
backdated = make_stock_entry(

View File

@@ -255,7 +255,7 @@ class StockReservationEntry(Document):
if self.has_batch_no
else _("Warehouse"),
frappe.bold(self.warehouse),
frappe.bold("Stock Reservation Entry"),
frappe.bold(_("Stock Reservation Entry")),
)
frappe.throw(msg)
@@ -497,7 +497,8 @@ def validate_stock_reservation_settings(voucher: object) -> None:
if not frappe.db.get_single_value("Stock Settings", "enable_stock_reservation"):
msg = _("Please enable {0} in the {1}.").format(
frappe.bold("Stock Reservation"), frappe.bold("Stock Settings")
frappe.bold(_("Stock Reservation")),
frappe.bold(_("Stock Settings")),
)
frappe.throw(msg)

View File

@@ -32,6 +32,7 @@
"allow_negative_stock",
"show_barcode_field",
"clean_description_html",
"allow_internal_transfer_at_arms_length_price",
"quality_inspection_settings_section",
"action_if_quality_inspection_is_not_submitted",
"column_break_23",
@@ -439,6 +440,13 @@
"fieldtype": "Check",
"label": "Do Not Update Serial / Batch on Creation of Auto Bundle"
},
{
"default": "0",
"description": "If enabled, the item rate won't adjust to the valuation rate during internal transfers, but accounting will still use the valuation rate.",
"fieldname": "allow_internal_transfer_at_arms_length_price",
"fieldtype": "Check",
"label": "Allow Internal Transfers at Arm's Length Price"
},
{
"default": "0",
"depends_on": "eval:doc.valuation_method === \"Moving Average\"",

View File

@@ -27,6 +27,7 @@ class StockSettings(Document):
action_if_quality_inspection_is_rejected: DF.Literal["Stop", "Warn"]
allow_from_dn: DF.Check
allow_from_pr: DF.Check
allow_internal_transfer_at_arms_length_price: DF.Check
allow_negative_stock: DF.Check
allow_partial_reservation: DF.Check
allow_to_edit_stock_uom_qty_for_purchase: DF.Check
@@ -174,7 +175,7 @@ class StockSettings(Document):
if self.allow_negative_stock and self.enable_stock_reservation:
frappe.throw(
_("As {0} is enabled, you can not enable {1}.").format(
frappe.bold("Stock Reservation"), frappe.bold("Allow Negative Stock")
frappe.bold(_("Stock Reservation")), frappe.bold(_("Allow Negative Stock"))
)
)
@@ -186,7 +187,7 @@ class StockSettings(Document):
if self.allow_negative_stock:
frappe.throw(
_("As {0} is enabled, you can not enable {1}.").format(
frappe.bold("Allow Negative Stock"), frappe.bold("Stock Reservation")
frappe.bold(_("Allow Negative Stock")), frappe.bold(_("Stock Reservation"))
)
)
@@ -206,7 +207,7 @@ class StockSettings(Document):
if bin_with_negative_stock:
frappe.throw(
_("As there are negative stock, you can not enable {0}.").format(
frappe.bold("Stock Reservation")
frappe.bold(_("Stock Reservation"))
)
)
@@ -220,7 +221,7 @@ class StockSettings(Document):
if has_reserved_stock:
frappe.throw(
_("As there are reserved stock, you cannot disable {0}.").format(
frappe.bold("Stock Reservation")
frappe.bold(_("Stock Reservation"))
)
)

View File

@@ -263,7 +263,7 @@
"idx": 1,
"is_tree": 1,
"links": [],
"modified": "2024-01-24 16:27:28.299520",
"modified": "2024-08-14 16:08:15.733597",
"modified_by": "Administrator",
"module": "Stock",
"name": "Warehouse",
@@ -320,6 +320,5 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "warehouse_name",
"track_changes": 1
}
}

View File

@@ -807,6 +807,9 @@ def get_price_list_rate(args, item_doc, out=None):
if price_list_rate is None or frappe.db.get_single_value(
"Stock Settings", "update_existing_price_list_rate"
):
if args.get("is_internal_supplier") or args.get("is_internal_customer"):
return out
if args.price_list and args.rate:
insert_item_price(args)
@@ -818,7 +821,11 @@ def get_price_list_rate(args, item_doc, out=None):
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
return out
if not out.price_list_rate and args.transaction_type == "buying":
if (
not args.get("is_internal_supplier")
and not out.price_list_rate
and args.transaction_type == "buying"
):
from erpnext.stock.doctype.item.item import get_last_purchase_details
out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))

View File

@@ -54,6 +54,12 @@ def get_columns(filters):
"width": 150,
"options": "Batch",
},
{
"label": _("Expiry Date"),
"fieldname": "expiry_date",
"fieldtype": "Date",
"width": 120,
},
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
]
)
@@ -97,6 +103,7 @@ def get_batchwise_data_from_stock_ledger(filters):
table.item_code,
table.batch_no,
table.warehouse,
batch.expiry_date,
Sum(table.actual_qty).as_("balance_qty"),
)
.where(table.is_cancelled == 0)
@@ -127,6 +134,7 @@ def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters):
table.item_code,
ch_table.batch_no,
table.warehouse,
batch.expiry_date,
Sum(ch_table.qty).as_("balance_qty"),
)
.where((table.is_cancelled == 0) & (table.docstatus == 1))
@@ -152,10 +160,14 @@ def get_query_based_on_filters(query, batch, table, filters):
if filters.batch_no:
query = query.where(batch.name == filters.batch_no)
if not filters.include_expired_batches:
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
if filters.to_date == today():
query = query.where(batch.batch_qty > 0)
if filters.to_date == today():
if not filters.include_expired_batches:
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
query = query.where(batch.batch_qty > 0)
else:
query = query.where(table.posting_date <= filters.to_date)
if filters.warehouse:
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])

View File

@@ -465,10 +465,13 @@ class FIFOSlots:
)
)
for field in ["item_code", "warehouse"]:
for field in ["item_code"]:
if self.filters.get(field):
query = query.where(bundle[field] == self.filters.get(field))
if self.filters.get("warehouse"):
query = self.__get_warehouse_conditions(bundle, query)
bundle_wise_serial_nos = frappe._dict({})
for bundle_name, serial_no in query.run():
bundle_wise_serial_nos.setdefault(bundle_name, []).append(serial_no)

View File

@@ -114,18 +114,23 @@ def validate_filters(filters):
def get_warehouse_list(filters):
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
if not filters.get("warehouse"):
return frappe.get_all(
"Warehouse",
filters={"company": filters.get("company"), "is_group": 0},
fields=["name"],
order_by="name",
)
wh = frappe.qb.DocType("Warehouse")
query = frappe.qb.from_(wh).select(wh.name).where(wh.is_group == 0)
warehouse = frappe.qb.DocType("Warehouse")
lft, rgt = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"])
user_permitted_warehouse = get_permitted_documents("Warehouse")
if user_permitted_warehouse:
query = query.where(wh.name.isin(set(user_permitted_warehouse)))
elif filters.get("warehouse"):
query = query.where(wh.name == filters.get("warehouse"))
return query.run(as_dict=True)
return (
frappe.qb.from_(warehouse)
.select("name")
.where((warehouse.lft >= lft) & (warehouse.rgt <= rgt))
.run(as_dict=True)
)
def add_warehouse_column(columns, warehouse_list):

View File

@@ -221,7 +221,15 @@ class SerialBatchBundle:
not self.sle.is_cancelled
and not self.sle.serial_and_batch_bundle
and self.item_details.has_batch_no == 1
and self.item_details.create_new_batch
and (
self.item_details.create_new_batch
or (
frappe.db.get_single_value(
"Stock Settings", "auto_create_serial_and_batch_bundle_for_outward"
)
and self.sle.actual_qty < 0
)
)
):
self.make_serial_batch_no_bundle()
elif not self.sle.is_cancelled:

View File

@@ -6,13 +6,14 @@ import gzip
import json
import frappe
from frappe import _, scrub
from frappe import _, bold, scrub
from frappe.model.meta import get_field_precision
from frappe.query_builder.functions import Sum
from frappe.utils import (
cint,
cstr,
flt,
format_date,
get_link_to_form,
getdate,
now,
@@ -646,6 +647,7 @@ class update_entries_after:
and (
posting_datetime = %(posting_datetime)s
)
and creation = %(creation)s
order by
creation ASC
for update
@@ -743,9 +745,29 @@ class update_entries_after:
return self.distinct_item_warehouses[key].dependent_voucher_detail_nos
def validate_previous_sle_qty(self, sle):
previous_sle = self.data[sle.warehouse].previous_sle
if previous_sle and previous_sle.get("qty_after_transaction") < 0 and sle.get("actual_qty") > 0:
frappe.msgprint(
_(
"The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'>documentation<a>."
).format(
bold(sle.item_code),
bold(sle.warehouse),
bold(format_date(previous_sle.posting_date)),
sle.voucher_no,
bold(format_date(previous_sle.posting_date)),
bold(previous_sle.posting_time),
),
title=_("Warning on Negative Stock"),
indicator="blue",
)
def process_sle(self, sle):
# previous sle data for this warehouse
self.wh_data = self.data[sle.warehouse]
self.validate_previous_sle_qty(sle)
self.affected_transactions.add((sle.voucher_type, sle.voucher_no))
if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock):
@@ -1505,6 +1527,11 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
voucher_no = args.get("voucher_no")
voucher_condition = f"and voucher_no != '{voucher_no}'"
elif args.get("creation"):
creation = args.get("creation")
operator = "<="
voucher_condition = f"and creation < '{creation}'"
sle = frappe.db.sql(
f"""
select *, posting_datetime as "timestamp"

Some files were not shown because too many files have changed in this diff Show More