Compare commits

...

109 Commits

Author SHA1 Message Date
Frappe PR Bot
37e00a66da chore(release): Bumped to Version 13.55.2
## [13.55.2](https://github.com/frappe/erpnext/compare/v13.55.1...v13.55.2) (2023-12-27)

### Bug Fixes

* incorrect price list in customer-wise item price report ([32f3365](32f3365ac7))
* **Payment Entry:** don't check empty references ([f0877ff](f0877ffa47))
2023-12-27 08:40:57 +00:00
rohitwaghchaure
4394c0113e Merge pull request #38951 from frappe/version-13-hotfix
chore: release v13
2023-12-27 14:09:51 +05:30
ruthra kumar
ab82e30fac Merge pull request #38947 from frappe/mergify/bp/version-13-hotfix/pr-38891
fix: incorrect price list in customer-wise item price report (backport #38891)
2023-12-26 12:16:39 +05:30
ruthra kumar
32f3365ac7 fix: incorrect price list in customer-wise item price report
(cherry picked from commit 9a00edb031)
2023-12-26 06:18:00 +00:00
Deepesh Garg
80012b7339 Merge pull request #38769 from frappe/deepeshgarg007-patch-1
chore: Loan Interest Accrual post topup
2023-12-22 08:40:48 +05:30
ruthra kumar
b049b52294 Merge pull request #38750 from barredterra/pe-empty-referneces
fix(Payment Entry): don't check empty references
2023-12-21 11:00:21 +05:30
Frappe PR Bot
82c28ac89f chore(release): Bumped to Version 13.55.1
## [13.55.1](https://github.com/frappe/erpnext/compare/v13.55.0...v13.55.1) (2023-12-20)

### Bug Fixes

* avoid over allocation during backdated assignment creation ([fee4eae](fee4eae96c))
* earned leave allocation in policy assignment ([14955c7](14955c70d4))
* earned leave exceeding annual allocation ([ee7c9ad](ee7c9add39))
* get customers for leaderboard ([10f02e6](10f02e60ce))
* get items for leaderboard ([3863c4e](3863c4e7fb))
* get sales partner for leaderboard ([b0f7de1](b0f7de1a0f))
* get sales person for leaderboard ([8dbb200](8dbb200fe3))
* get suppliers for leaderboard ([7df8425](7df8425756))
2023-12-20 04:45:38 +00:00
Deepesh Garg
692d98dd4d Merge pull request #38854 from frappe/version-13-hotfix
chore: release v13
2023-12-20 10:14:33 +05:30
Rucha Mahabal
5d1c35634c Merge pull request #38610 from ruchamahabal/EL-overallocation-v13
fix: earned leave exceeding annual allocation
2023-12-19 15:26:38 +05:30
Rucha Mahabal
9900274b27 test(fix): earned leave tests 2023-12-19 13:03:57 +05:30
Rucha Mahabal
14955c70d4 fix: earned leave allocation in policy assignment 2023-12-19 13:03:36 +05:30
Raffael Meyer
8c55e35d20 Merge pull request #38676 from frappe/mergify/bp/version-13-hotfix/pr-38672
fix: get data for leaderboard (backport #38672)
2023-12-15 16:27:22 +01:00
barredterra
e6e9f1dc26 chore: remove deprecation wrapper
not available in v13
2023-12-15 15:46:40 +01:00
Deepesh Garg
4f8b13ac57 chore: Loan Interest Accrual post topup 2023-12-15 12:46:59 +05:30
barredterra
f0877ffa47 fix(Payment Entry): don't check empty references
Manual partial backport of https://github.com/frappe/erpnext/pull/36649
2023-12-14 13:39:35 +01:00
barredterra
e291b5db3d chore: deprecate unused method
(cherry picked from commit 956c3c50a0)
2023-12-12 04:24:54 +00:00
barredterra
b0f7de1a0f fix: get sales partner for leaderboard
(cherry picked from commit 40c1acc961)
2023-12-12 04:24:54 +00:00
barredterra
8dbb200fe3 fix: get sales person for leaderboard
(cherry picked from commit 7babfd4ac4)
2023-12-12 04:24:54 +00:00
barredterra
7df8425756 fix: get suppliers for leaderboard
(cherry picked from commit 65df4b6aa8)
2023-12-12 04:24:54 +00:00
barredterra
3863c4e7fb fix: get items for leaderboard
(cherry picked from commit 2721ee3a8d)
2023-12-12 04:24:53 +00:00
barredterra
10f02e60ce fix: get customers for leaderboard
(cherry picked from commit 137b5a6108)
2023-12-12 04:24:53 +00:00
Rucha Mahabal
48eaa51c4a test: earned leave over-allocation 2023-12-07 13:04:51 +05:30
Rucha Mahabal
fee4eae96c fix: avoid over allocation during backdated assignment creation 2023-12-07 13:04:23 +05:30
Rucha Mahabal
ee7c9add39 fix: earned leave exceeding annual allocation 2023-12-07 13:01:57 +05:30
Frappe PR Bot
b6229515ef chore(release): Bumped to Version 13.55.0
# [13.55.0](https://github.com/frappe/erpnext/compare/v13.54.14...v13.55.0) (2023-12-05)

### Bug Fixes

* consider the `Valuation Method` while picking incorrect SLE (backport [#38592](https://github.com/frappe/erpnext/issues/38592)) ([#38596](https://github.com/frappe/erpnext/issues/38596)) ([1b78dd1](1b78dd17c9))
* make create button translatable (backport [#38165](https://github.com/frappe/erpnext/issues/38165)) ([#38412](https://github.com/frappe/erpnext/issues/38412)) ([bd371e6](bd371e697c))
* show difference amount in default currency ([6f44a16](6f44a1630f))
* update voucher currency for allocated entry ([3dfc145](3dfc1450a1))

### Features

* `Company` filter in `Stock Ledger Variance` report (backport [#38553](https://github.com/frappe/erpnext/issues/38553)) ([#38575](https://github.com/frappe/erpnext/issues/38575)) ([77e01eb](77e01ebacf))
* add currency column for allocated entries ([835c85a](835c85a087))
2023-12-05 13:50:34 +00:00
Deepesh Garg
6e25a5189c Merge pull request #38588 from frappe/version-13-hotfix
chore: release v13
2023-12-05 19:19:16 +05:30
mergify[bot]
1b78dd17c9 fix: consider the Valuation Method while picking incorrect SLE (backport #38592) (#38596)
* fix: incorrect SLE for `Moving Average` valuation method

(cherry picked from commit 8beec58670)

* feat: add `Valuation Method` column in `Stock Ledger Variance` report

(cherry picked from commit 16c297c2ec)

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-12-05 18:14:19 +05:30
mergify[bot]
77e01ebacf feat: Company filter in Stock Ledger Variance report (backport #38553) (#38575)
feat: `Company` filter in `Stock Ledger Variance` report

(cherry picked from commit fb3421fcce)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-12-05 12:32:57 +05:30
mergify[bot]
bd371e697c fix: make create button translatable (backport #38165) (#38412)
fix: make create button translatable (#38165)

* fix: make all create buttons translatable

* style: use double quotes

---------

Co-authored-by: PatrickDenis-stack <77415730+PatrickDenis-stack@users.noreply.github.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
(cherry picked from commit 8e4b591ea2)

Co-authored-by: Patrick Eissler <77415730+PatrickDEissler@users.noreply.github.com>
2023-12-01 11:33:35 +05:30
Raffael Meyer
2c4cee025b ci: pin semgrep to old version (#38426)
* ci: pin semgrep to old version

current version has problem with PRs originating from fork

* ci: unpin semgrep

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
2023-11-29 22:06:31 +05:30
Deepesh Garg
303becf1e3 Merge pull request #38390 from GursheenK/payment-reco-currency-symbol
fix: currency symbol in payment rec allocations
2023-11-29 17:22:12 +05:30
Frappe PR Bot
983c2133c2 chore(release): Bumped to Version 13.54.14
## [13.54.14](https://github.com/frappe/erpnext/compare/v13.54.13...v13.54.14) (2023-11-29)

### Bug Fixes

* patch - Duplicate entry quality inspection parameter (backport [#38262](https://github.com/frappe/erpnext/issues/38262)) ([#38265](https://github.com/frappe/erpnext/issues/38265)) ([095d99d](095d99dbd2))
* precision for invoice outstandings ([#38323](https://github.com/frappe/erpnext/issues/38323)) ([190f77a](190f77abff))
2023-11-29 04:55:27 +00:00
Deepesh Garg
a76c6ab042 Merge pull request #38385 from frappe/version-13-hotfix
chore: release v13
2023-11-29 10:24:11 +05:30
Gursheen Anand
6f44a1630f fix: show difference amount in default currency 2023-11-28 16:31:40 +05:30
Gursheen Anand
b7944a7c07 chore: hide currency column 2023-11-28 15:51:33 +05:30
Gursheen Anand
3dfc1450a1 fix: update voucher currency for allocated entry 2023-11-28 15:43:36 +05:30
Gursheen Anand
835c85a087 feat: add currency column for allocated entries 2023-11-28 15:42:30 +05:30
Gursheen Kaur Anand
190f77abff fix: precision for invoice outstandings (#38323)
fix: precision while setting inv outstanding
2023-11-26 18:56:34 +05:30
Frappe PR Bot
a37bdb54cd chore(release): Bumped to Version 13.54.13
## [13.54.13](https://github.com/frappe/erpnext/compare/v13.54.12...v13.54.13) (2023-11-22)

### Bug Fixes

* patch - Duplicate entry quality inspection parameter (backport [#38262](https://github.com/frappe/erpnext/issues/38262)) (backport [#38265](https://github.com/frappe/erpnext/issues/38265)) ([#38266](https://github.com/frappe/erpnext/issues/38266)) ([b28e29c](b28e29cab1))
2023-11-22 08:53:41 +00:00
mergify[bot]
b28e29cab1 fix: patch - Duplicate entry quality inspection parameter (backport #38262) (backport #38265) (#38266)
fix: patch - Duplicate entry quality inspection parameter (backport #38262) (#38265)

fix: patch - Duplicate entry quality inspection parameter (#38262)

(cherry picked from commit 0ca7527f7a)

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

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-11-22 14:22:30 +05:30
mergify[bot]
095d99dbd2 fix: patch - Duplicate entry quality inspection parameter (backport #38262) (#38265)
fix: patch - Duplicate entry quality inspection parameter (#38262)

(cherry picked from commit 0ca7527f7a)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-11-22 14:09:00 +05:30
Frappe PR Bot
01fc5f6865 chore(release): Bumped to Version 13.54.12
## [13.54.12](https://github.com/frappe/erpnext/compare/v13.54.11...v13.54.12) (2023-11-21)

### Bug Fixes

* `get_previous_allocation` return value ([6cb8a40](6cb8a40339))
* backward compatible query without pluck ([958db77](958db77cda))
* pass check permission in render_address ([65ae8d9](65ae8d9c05))
* **Timesheet:** reset billing hours equal to hours if they exceed actual hours (backport [#38134](https://github.com/frappe/erpnext/issues/38134)) ([#38154](https://github.com/frappe/erpnext/issues/38154)) ([1e43605](1e436052e2))
* **Timesheet:** warn user if billing hours > actual hours instead of resetting  (backport [#38239](https://github.com/frappe/erpnext/issues/38239)) ([#38242](https://github.com/frappe/erpnext/issues/38242)) ([a9429e1](a9429e160d))
* use `get_all` instead of `get_list` ([4b8ed0f](4b8ed0f6ae))

### Performance Improvements

* index most queried fields in Leave Ledger Entry ([eea7bbc](eea7bbcea7))
* limit rows to 1 for cf leave expiry query ([635c3d5](635c3d54f5))
2023-11-21 18:56:36 +00:00
Deepesh Garg
911e2c5b36 Merge pull request #38249 from frappe/version-13-hotfix
chore: release v13
2023-11-22 00:25:25 +05:30
mergify[bot]
a9429e160d fix(Timesheet): warn user if billing hours > actual hours instead of resetting (backport #38239) (#38242)
fix(Timesheet): warn user if billing hours > actual hours instead of resetting  (#38239)

* revert: "fix(Timesheet): reset billing hours equal to hours if they exceed actual hours"

This reverts commit 0ec8034507.

* fix: warn user if billing hours > actual hours

(cherry picked from commit ac91030b31)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2023-11-21 13:42:36 +05:30
Sagar Vora
5342cd0dfa Merge pull request #38170 from frappe/mergify/bp/version-13-hotfix/pr-38167
fix: pass check permission in `render_address` (backport #38167)
2023-11-18 17:43:40 +05:30
Sagar Vora
3bf84e1464 chore: fix conflicts 2023-11-18 17:42:06 +05:30
Vishakh Desai
65ae8d9c05 fix: pass check permission in render_address
(cherry picked from commit 45299fe4b3)

# Conflicts:
#	erpnext/accounts/party.py
2023-11-18 12:09:18 +00:00
Rucha Mahabal
35717124cd Merge pull request #38158 from ruchamahabal/perf-leave-balance-report-v13
perf: faster Employee Leave Balance report
2023-11-17 22:57:49 +05:30
Rucha Mahabal
89c107ea8b chore: remove states from json 2023-11-17 22:56:42 +05:30
Rucha Mahabal
958db77cda fix: backward compatible query without pluck 2023-11-17 21:11:42 +05:30
Rucha Mahabal
bc1da4678a refactor: retain backward compatible type hints 2023-11-17 20:45:34 +05:30
Rucha Mahabal
6cb8a40339 fix: get_previous_allocation return value 2023-11-17 20:19:39 +05:30
Rucha Mahabal
9139c14639 refactor(Leave Balance Summary report): remove unused department approver queries 2023-11-17 20:19:10 +05:30
Rucha Mahabal
461eb7a50d refactor: rewrite queries with frappe.qb + refactor type hints 2023-11-17 20:17:31 +05:30
Rucha Mahabal
635c3d54f5 perf: limit rows to 1 for cf leave expiry query 2023-11-17 20:17:11 +05:30
Rucha Mahabal
1bd3f4eeef refactor: remove unnecessary approver queries 2023-11-17 20:16:58 +05:30
Rucha Mahabal
4b8ed0f6ae fix: use get_all instead of get_list
- query report already filters records based on permissions for link fields

- allow employees access to leave balance report by default
2023-11-17 20:16:43 +05:30
Rucha Mahabal
eea7bbcea7 perf: index most queried fields in Leave Ledger Entry 2023-11-17 20:16:24 +05:30
mergify[bot]
1e436052e2 fix(Timesheet): reset billing hours equal to hours if they exceed actual hours (backport #38134) (#38154)
fix(Timesheet): reset billing hours equal to hours if they exceed actual hours (#38134)

(cherry picked from commit 20c6e9fca2)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2023-11-17 18:32:35 +05:30
Frappe PR Bot
91a132c011 chore(release): Bumped to Version 13.54.11
## [13.54.11](https://github.com/frappe/erpnext/compare/v13.54.10...v13.54.11) (2023-11-14)

### Bug Fixes

* close `Credit Limit Crossed` dialog (backport [#38052](https://github.com/frappe/erpnext/issues/38052)) ([#38057](https://github.com/frappe/erpnext/issues/38057)) ([5be5fde](5be5fde276))
2023-11-14 12:56:26 +00:00
Deepesh Garg
2263542a6b Merge pull request #38087 from frappe/version-13-hotfix
chore: release v13
2023-11-14 18:25:03 +05:30
mergify[bot]
5be5fde276 fix: close Credit Limit Crossed dialog (backport #38052) (#38057)
fix: close `Credit Limit Crossed` dialog (#38052)

(cherry picked from commit 94faa44697)

Co-authored-by: Arjun <arjun99c@gmail.com>
2023-11-12 18:07:50 +05:30
Frappe PR Bot
6a53e823f0 chore(release): Bumped to Version 13.54.10
## [13.54.10](https://github.com/frappe/erpnext/compare/v13.54.9...v13.54.10) (2023-11-08)

### Bug Fixes

* add missing disbursement account in update_old_loans patch ([37b1a0e](37b1a0e778))
* bump pygithub requirement to handle conflict ([#37800](https://github.com/frappe/erpnext/issues/37800)) ([13d5eec](13d5eec194))
* e-invoice jwt verification error ([#37818](https://github.com/frappe/erpnext/issues/37818)) ([8f4ded6](8f4ded6ad1))
* permission error while creating Supplier Quotation from Portal (backport [#37864](https://github.com/frappe/erpnext/issues/37864)) ([#37872](https://github.com/frappe/erpnext/issues/37872)) ([6952f0f](6952f0f082))
* Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport [#37499](https://github.com/frappe/erpnext/issues/37499)) ([#37918](https://github.com/frappe/erpnext/issues/37918)) ([4789eca](4789ecacea))
* remove voucher type and no for Item and Warehouse based reposting (backport [#37849](https://github.com/frappe/erpnext/issues/37849)) (backport [#37850](https://github.com/frappe/erpnext/issues/37850)) ([#37938](https://github.com/frappe/erpnext/issues/37938)) ([8cb0f69](8cb0f690d5))
2023-11-08 06:04:16 +00:00
Deepesh Garg
c808607975 Merge pull request #37966 from frappe/version-13-hotfix
chore: release v13
2023-11-08 11:33:04 +05:30
mergify[bot]
8cb0f690d5 fix: remove voucher type and no for Item and Warehouse based reposting (backport #37849) (backport #37850) (#37938)
* fix: remove voucher type and no for Item and Warehouse based reposting (backport #37849) (#37850)

* fix: remove voucher type and no for Item and Warehouse based reposting

(cherry picked from commit 0104897d69)

* chore: fix test cases

---------

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
(cherry picked from commit e19cade12d)

# Conflicts:
#	erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
#	erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
#	erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.json

* chore: fix conflicts

* chore: fix conflicts

* chore: fix conflicts

* fix: conflicts

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-11-07 16:07:17 +05:30
mergify[bot]
4789ecacea fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (backport #37499) (#37918)
fix: Quality Inspection Parameter migration - DuplicateEntryError due to case sensitivity (#37499)

* fix: account for case-insensitive database primary key for parameter names

* chore: linting

(cherry picked from commit b099590b2c)

Co-authored-by: Richard Case <110036763+casesolved-co-uk@users.noreply.github.com>
2023-11-05 06:22:42 +00:00
Anand Baburajan
d00257ffd7 Merge pull request #37891 from frappe/mergify/bp/version-13-hotfix/pr-37889
fix: add missing disbursement account in update_old_loans patch (backport #37889)
2023-11-03 21:48:57 +05:30
anandbaburajan
37b1a0e778 fix: add missing disbursement account in update_old_loans patch
(cherry picked from commit ad64065ec6)
2023-11-03 16:17:29 +00:00
mergify[bot]
6952f0f082 fix: permission error while creating Supplier Quotation from Portal (backport #37864) (#37872)
* fix: permission error while creating Supplier Quotation from Portal

(cherry picked from commit e019d43d0b)

# Conflicts:
#	erpnext/controllers/buying_controller.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-11-03 16:26:45 +05:30
Frappe PR Bot
c84384aa1b chore(release): Bumped to Version 13.54.9
## [13.54.9](https://github.com/frappe/erpnext/compare/v13.54.8...v13.54.9) (2023-11-01)

### Bug Fixes

* e-invoice jwt verification error (backport [#37818](https://github.com/frappe/erpnext/issues/37818)) ([#37820](https://github.com/frappe/erpnext/issues/37820)) ([428f7a6](428f7a61ef))
2023-11-01 07:59:52 +00:00
mergify[bot]
428f7a61ef fix: e-invoice jwt verification error (backport #37818) (#37820)
fix: e-invoice jwt verification error (#37818)

(cherry picked from commit 8f4ded6ad1)

Co-authored-by: Dany Robert <rtdany10@gmail.com>
2023-11-01 13:28:21 +05:30
Dany Robert
8f4ded6ad1 fix: e-invoice jwt verification error (#37818) 2023-11-01 13:27:24 +05:30
Frappe PR Bot
9607d69ada chore(release): Bumped to Version 13.54.8
## [13.54.8](https://github.com/frappe/erpnext/compare/v13.54.7...v13.54.8) (2023-10-31)

### Bug Fixes

* bump pygithub requirement to handle conflict (backport [#37800](https://github.com/frappe/erpnext/issues/37800)) ([#37808](https://github.com/frappe/erpnext/issues/37808)) ([aaba335](aaba335273))
2023-10-31 14:46:40 +00:00
mergify[bot]
aaba335273 fix: bump pygithub requirement to handle conflict (backport #37800) (#37808)
fix: bump pygithub requirement to handle conflict (#37800)

build: bump pygithub requirement

This conflicts with pyjwt

(cherry picked from commit 13d5eec194)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-10-31 20:04:31 +05:30
Ankush Menat
13d5eec194 fix: bump pygithub requirement to handle conflict (#37800)
build: bump pygithub requirement

This conflicts with pyjwt
2023-10-31 19:55:56 +05:30
Frappe PR Bot
411bba1b98 chore(release): Bumped to Version 13.54.7
## [13.54.7](https://github.com/frappe/erpnext/compare/v13.54.6...v13.54.7) (2023-10-31)

### Bug Fixes

* wrong german translation ([#37658](https://github.com/frappe/erpnext/issues/37658)) ([5669a89](5669a89afe))
2023-10-31 12:45:41 +00:00
rohitwaghchaure
49f2d868c7 Merge pull request #37786 from frappe/version-13-hotfix
chore: release v13
2023-10-31 18:14:20 +05:30
Raffael Meyer
335b6c84db Merge pull request #37666 from frappe/mergify/bp/version-13-hotfix/pr-37658
fix: wrong german translation (backport #37658)
2023-10-28 23:07:59 +02:00
barredterra
00dff0a219 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-37658 2023-10-28 22:42:43 +02:00
mergify[bot]
b55b428114 chore: fixed test case non_internal_transfer_delivery_note (backport #37671) (#37677)
chore: fixed test case non_internal_transfer_delivery_note (#37671)

(cherry picked from commit 2bcff4c7f2)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-25 13:55:17 +05:30
Frappe PR Bot
dd6498d347 chore(release): Bumped to Version 13.54.6
## [13.54.6](https://github.com/frappe/erpnext/compare/v13.54.5...v13.54.6) (2023-10-25)

### Bug Fixes

* incorrect cost center in the purchase invoice (backport [#37591](https://github.com/frappe/erpnext/issues/37591)) ([#37610](https://github.com/frappe/erpnext/issues/37610)) ([4fc45b0](4fc45b035a))
* remove from or target warehouse for non internal transfer entries (backport [#37612](https://github.com/frappe/erpnext/issues/37612)) ([#37629](https://github.com/frappe/erpnext/issues/37629)) ([b5b5187](b5b51879ee))
2023-10-25 03:45:08 +00:00
Raffael Meyer
5669a89afe fix: wrong german translation (#37658)
(cherry picked from commit fa5780ca81)
2023-10-25 03:44:15 +00:00
Deepesh Garg
6e871c51ef Merge pull request #37655 from frappe/version-13-hotfix
chore: release v13
2023-10-25 09:13:26 +05:30
mergify[bot]
b5b51879ee fix: remove from or target warehouse for non internal transfer entries (backport #37612) (#37629)
* fix: remove from or target warehouse for non internal transfer entries (#37612)

(cherry picked from commit 5136fe196b)

* chore: removed test cases

* chore: removed test case

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-24 08:41:41 +05:30
mergify[bot]
4fc45b035a fix: incorrect cost center in the purchase invoice (backport #37591) (#37610)
* fix: incorrect cost center in the purchase invoice (#37591)

(cherry picked from commit 14b009b093)

# Conflicts:
#	erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py

* chore: fix conflicts

---------

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-20 17:03:25 +05:30
Frappe PR Bot
24103554f5 chore(release): Bumped to Version 13.54.5
## [13.54.5](https://github.com/frappe/erpnext/compare/v13.54.4...v13.54.5) (2023-10-19)

### Bug Fixes

* e-commerce permissions for address (backport [#37554](https://github.com/frappe/erpnext/issues/37554)) ([#37560](https://github.com/frappe/erpnext/issues/37560)) ([49d0ab5](49d0ab5867))
* **gp:** wrong `allocated_amount` on multi sales person invoice ([7805c3a](7805c3acf6))
* Issues related to RFQ and Supplier Quotation on Portal (backport [#37565](https://github.com/frappe/erpnext/issues/37565)) (backport [#37577](https://github.com/frappe/erpnext/issues/37577)) ([#37588](https://github.com/frappe/erpnext/issues/37588)) ([bc907b2](bc907b22d4))
* keep customer/supplier website role by default ([52aff1f](52aff1f703))
* negative valuation rate in PR return (backport [#37424](https://github.com/frappe/erpnext/issues/37424)) ([#37462](https://github.com/frappe/erpnext/issues/37462)) ([66ad823](66ad823417))
* use `flt` to ignore TypeError (backport [#37481](https://github.com/frappe/erpnext/issues/37481)) ([#37489](https://github.com/frappe/erpnext/issues/37489)) ([8b4e692](8b4e69235a))
2023-10-19 11:36:26 +00:00
Deepesh Garg
d459421175 Merge pull request #37544 from frappe/version-13-hotfix
chore: release v13
2023-10-19 17:04:53 +05:30
mergify[bot]
bc907b22d4 fix: Issues related to RFQ and Supplier Quotation on Portal (backport #37565) (backport #37577) (#37588)
* fix: Issues related to RFQ and Supplier Quotation on Portal (backport #37565) (#37577)

* fix: Issues related to RFQ and Supplier Quotation on Portal (#37565)

fix: RFQ and Supplier Quotation for Portal
(cherry picked from commit 2851a41310)

* chore: removed backport changes

---------

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

# Conflicts:
#	erpnext/templates/includes/rfq.js

* chore: fix conflicts

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 16:39:38 +05:30
mergify[bot]
7e26449b9f chore: rewrite query using query builder (backport #37310) (#37585)
* chore: rewrite query using query builder

(cherry picked from commit 25718f5cc7)

* chore: fix shopping cart tests

(cherry picked from commit fb51cae88b)

* chore: fix test case

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2023-10-19 15:58:45 +05:30
mergify[bot]
49d0ab5867 fix: e-commerce permissions for address (backport #37554) (#37560)
* fix: E-commerce permissions

(cherry picked from commit f4d74990fe)

# Conflicts:
#	erpnext/accounts/party.py
#	erpnext/controllers/selling_controller.py

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-10-18 18:46:10 +05:30
Deepesh Garg
ddec91202a Merge branch 'version-13' into version-13-hotfix 2023-10-17 17:59:36 +05:30
Ankush Menat
fe681acaad Merge pull request #37534 from frappe/mergify/bp/version-13-hotfix/pr-37532
fix: keep customer/supplier website role by default (backport #37532)
2023-10-16 17:36:59 +05:30
Ankush Menat
52aff1f703 fix: keep customer/supplier website role by default
(cherry picked from commit d2096cfdb7)
2023-10-16 17:35:44 +05:30
ruthra kumar
1cfc6cfe5d Merge pull request #37509 from frappe/mergify/bp/version-13-hotfix/pr-37435
fix(gp): wrong `allocated_amount` when grouped by Sales Person (backport #37435)
2023-10-15 10:30:28 +05:30
Dany Robert
7805c3acf6 fix(gp): wrong allocated_amount on multi sales person invoice
(cherry picked from commit bda82bf1e9)
2023-10-15 08:14:26 +05:30
mergify[bot]
8b4e69235a fix: use flt to ignore TypeError (backport #37481) (#37489)
fix: use `flt` to ignore TypeError (#37481)

(cherry picked from commit d2b22db500)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-13 10:25:28 +05:30
Frappe PR Bot
35a62e2e8d chore(release): Bumped to Version 13.54.4
## [13.54.4](https://github.com/frappe/erpnext/compare/v13.54.3...v13.54.4) (2023-10-11)

### Bug Fixes

* negative valuation rate in PR return (backport [#37424](https://github.com/frappe/erpnext/issues/37424)) (backport [#37462](https://github.com/frappe/erpnext/issues/37462)) ([#37463](https://github.com/frappe/erpnext/issues/37463)) ([f6b3532](f6b35324ef))
2023-10-11 14:24:45 +00:00
mergify[bot]
f6b35324ef fix: negative valuation rate in PR return (backport #37424) (backport #37462) (#37463)
fix: negative valuation rate in PR return (backport #37424) (#37462)

* fix: negative valuation rate in PR return (#37424)

* fix: negative valuation rate in PR return

* test: add test case for PR return

(cherry picked from commit 26ad688584)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
(cherry picked from commit 66ad823417)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-10-11 19:53:10 +05:30
mergify[bot]
66ad823417 fix: negative valuation rate in PR return (backport #37424) (#37462)
* fix: negative valuation rate in PR return (#37424)

* fix: negative valuation rate in PR return

* test: add test case for PR return

(cherry picked from commit 26ad688584)

# Conflicts:
#	erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py

* chore: `conflicts`

---------

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-10-11 19:17:02 +05:30
ruthra kumar
f674635ecf Merge pull request #37440 from ruthra-kumar/patch_for_exchange_rate_service_provider
chore: patch to set default exchange rate service providers
2023-10-10 21:02:10 +05:30
Frappe PR Bot
0000c38563 chore(release): Bumped to Version 13.54.3
## [13.54.3](https://github.com/frappe/erpnext/compare/v13.54.2...v13.54.3) (2023-10-10)

### Bug Fixes

* change currency exchange API to api.frankfurter.app ([76919c4](76919c4af2))
2023-10-10 14:47:51 +00:00
Deepesh Garg
23f12f6463 Merge pull request #37432 from frappe/version-13-hotfix
chore: release v13
2023-10-10 20:16:20 +05:30
ruthra kumar
6f6db432b5 chore: patch to set default exchange rate service provider 2023-10-10 17:37:04 +05:30
ruthra kumar
fd8543ebd3 Merge pull request #37385 from ruthra-kumar/multiple_currency_exchange_provider
refactor: multiple service providers for Currency Exchange rates
2023-10-07 15:34:05 +05:30
ruthra kumar
22fb65621c refactor: access_key field for service_provider 2023-10-07 14:28:32 +05:30
ruthra kumar
55dfd8e995 refactor: selectable exchange rate service providers 2023-10-06 15:05:57 +05:30
ruthra kumar
c86cd99395 Merge pull request #37355 from dsnetprofitxl/fix-exchange-rate
fix: change currency exchange API to api.frankfurter.app
2023-10-04 21:58:13 +05:30
ruthra kumar
a3fd4db450 refactor(test): update exchange rate based on 'frankfurter' provider 2023-10-04 21:33:11 +05:30
dsnetprofitxl
76919c4af2 fix: change currency exchange API to api.frankfurter.app 2023-10-04 16:01:22 +05:30
54 changed files with 958 additions and 590 deletions

View File

@@ -11,10 +11,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up Python 3.8 - name: Set up Python 3.10
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.8 python-version: '3.10'
- name: Install and Run Pre-commit - name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3 uses: pre-commit/action@v2.0.3
@@ -22,10 +22,8 @@ jobs:
- name: Download Semgrep rules - name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- uses: returntocorp/semgrep-action@v1 - name: Download semgrep
env: run: pip install semgrep
SEMGREP_TIMEOUT: 120
with: - name: Run Semgrep rules
config: >- run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
r/python.lang.correctness
./frappe-semgrep-rules/rules

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
__version__ = "13.54.1" __version__ = "13.55.2"
def get_default_company(user=None): def get_default_company(user=None):

View File

@@ -358,6 +358,7 @@ def update_outstanding_amt(
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
ref_doc = frappe.get_doc(against_voucher_type, against_voucher) ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
bal = flt(bal, frappe.get_precision(against_voucher_type, "outstanding_amount"))
# Didn't use db_set for optimization purpose # Didn't use db_set for optimization purpose
ref_doc.outstanding_amount = bal ref_doc.outstanding_amount = bal
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal) frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)

View File

@@ -175,52 +175,53 @@ class PaymentEntry(AccountsController):
frappe.throw(fail_message.format(d.idx)) frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_with_latest_data(self): def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents( if self.references:
{ latest_references = get_outstanding_reference_documents(
"posting_date": self.posting_date, {
"company": self.company, "posting_date": self.posting_date,
"party_type": self.party_type, "company": self.company,
"payment_type": self.payment_type, "party_type": self.party_type,
"party": self.party, "payment_type": self.payment_type,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to, "party": self.party,
"get_outstanding_invoices": True, "party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_orders_to_be_billed": True, "get_outstanding_invoices": True,
} "get_orders_to_be_billed": True,
) }
)
# Group latest_references by (voucher_type, voucher_no) # Group latest_references by (voucher_type, voucher_no)
latest_lookup = {} latest_lookup = {}
for d in latest_references: for d in latest_references:
d = frappe._dict(d) d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d}) latest_lookup.update({(d.voucher_type, d.voucher_no): d})
for d in self.get("references"): for d in self.get("references"):
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) latest = latest_lookup.get((d.reference_doctype, d.reference_name))
# The reference has already been fully paid # The reference has already been fully paid
if not latest: if not latest:
frappe.throw( frappe.throw(
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name) _("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
) )
# The reference has already been partly paid # The reference has already been partly paid
elif latest.outstanding_amount < latest.invoice_amount and flt( elif latest.outstanding_amount < latest.invoice_amount and flt(
d.outstanding_amount, d.precision("outstanding_amount") d.outstanding_amount, d.precision("outstanding_amount")
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")): ) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
frappe.throw( frappe.throw(
_( _(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(_(d.reference_doctype), d.reference_name) ).format(_(d.reference_doctype), d.reference_name)
) )
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.") fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount): if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx)) frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well # Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount): if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx)) frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self): def delink_advance_entry_references(self):
for reference in self.references: for reference in self.references:

View File

@@ -295,6 +295,7 @@ class PaymentReconciliation(Document):
"amount": pay.get("amount"), "amount": pay.get("amount"),
"allocated_amount": allocated_amount, "allocated_amount": allocated_amount,
"difference_amount": pay.get("difference_amount"), "difference_amount": pay.get("difference_amount"),
"currency": inv.get("currency"),
} }
) )

View File

@@ -20,7 +20,8 @@
"section_break_5", "section_break_5",
"difference_amount", "difference_amount",
"column_break_7", "column_break_7",
"difference_account" "difference_account",
"currency"
], ],
"fields": [ "fields": [
{ {
@@ -37,7 +38,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Allocated Amount", "label": "Allocated Amount",
"options": "Currency", "options": "currency",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -112,7 +113,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 1, "hidden": 1,
"label": "Unreconciled Amount", "label": "Unreconciled Amount",
"options": "Currency", "options": "currency",
"read_only": 1 "read_only": 1
}, },
{ {
@@ -120,7 +121,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 1, "hidden": 1,
"label": "Amount", "label": "Amount",
"options": "Currency", "options": "currency",
"read_only": 1 "read_only": 1
}, },
{ {
@@ -129,11 +130,18 @@
"hidden": 1, "hidden": 1,
"label": "Reference Row", "label": "Reference Row",
"read_only": 1 "read_only": 1
},
{
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Currency",
"options": "Currency"
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-10-06 11:48:59.616562", "modified": "2023-11-28 16:30:43.344612",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation Allocation", "name": "Payment Reconciliation Allocation",

View File

@@ -1626,6 +1626,30 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
0, 0,
) )
def test_default_cost_center_for_purchase(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
for c_center in ["_Test Cost Center Selling", "_Test Cost Center Buying"]:
create_cost_center(cost_center_name=c_center)
item = create_item(
"_Test Cost Center Item For Purchase",
is_stock_item=1,
buying_cost_center="_Test Cost Center Buying - _TC",
selling_cost_center="_Test Cost Center Selling - _TC",
)
pi = make_purchase_invoice(
item=item.name, qty=1, rate=1000, update_stock=True, do_not_submit=True, cost_center=""
)
pi.items[0].cost_center = ""
pi.set_missing_values()
pi.calculate_taxes_and_totals()
pi.save()
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
def check_gl_entries(doc, voucher_no, expected_gle, posting_date): def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql( gl_entries = frappe.db.sql(

View File

@@ -2472,36 +2472,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(target_doc.company, "_Test Company 1") self.assertEqual(target_doc.company, "_Test Company 1")
self.assertEqual(target_doc.supplier, "_Test Internal Supplier") self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
def test_sle_for_target_warehouse(self):
se = make_stock_entry(
item_code="138-CMS Shoe",
target="Finished Goods - _TC",
company="_Test Company",
qty=1,
basic_rate=500,
)
si = frappe.copy_doc(test_records[0])
si.update_stock = 1
si.set_warehouse = "Finished Goods - _TC"
si.set_target_warehouse = "Stores - _TC"
si.get("items")[0].warehouse = "Finished Goods - _TC"
si.get("items")[0].target_warehouse = "Stores - _TC"
si.insert()
si.submit()
sles = frappe.get_all(
"Stock Ledger Entry", filters={"voucher_no": si.name}, fields=["name", "actual_qty"]
)
# check if both SLEs are created
self.assertEqual(len(sles), 2)
self.assertEqual(sum(d.actual_qty for d in sles), 0.0)
# tear down
si.cancel()
se.cancel()
def test_internal_transfer_gl_entry(self): def test_internal_transfer_gl_entry(self):
si = create_sales_invoice( si = create_sales_invoice(
company="_Test Company with perpetual inventory", company="_Test Company with perpetual inventory",

View File

@@ -4,12 +4,7 @@
import frappe import frappe
from frappe import _, msgprint, scrub from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import ( from frappe.contacts.doctype.address.address import get_company_address, get_default_address
get_address_display,
get_company_address,
get_default_address,
)
from frappe.contacts.doctype.contact.contact import get_contact_details
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
from frappe.utils import ( from frappe.utils import (
@@ -120,6 +115,7 @@ def _get_party_details(
party_address, party_address,
company_address, company_address,
shipping_address, shipping_address,
ignore_permissions=ignore_permissions,
) )
set_contact_details(party_details, party, party_type) set_contact_details(party_details, party, party_type)
set_other_values(party_details, party, party_type) set_other_values(party_details, party, party_type)
@@ -183,6 +179,8 @@ def set_address_details(
party_address=None, party_address=None,
company_address=None, company_address=None,
shipping_address=None, shipping_address=None,
*,
ignore_permissions=False
): ):
billing_address_field = ( billing_address_field = (
"customer_address" if party_type == "Lead" else party_type.lower() + "_address" "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
@@ -195,13 +193,17 @@ def set_address_details(
get_fetch_values(doctype, billing_address_field, party_details[billing_address_field]) get_fetch_values(doctype, billing_address_field, party_details[billing_address_field])
) )
# address display # address display
party_details.address_display = get_address_display(party_details[billing_address_field]) party_details.address_display = render_address(
party_details[billing_address_field], check_permissions=not ignore_permissions
)
# shipping address # shipping address
if party_type in ["Customer", "Lead"]: if party_type in ["Customer", "Lead"]:
party_details.shipping_address_name = shipping_address or get_party_shipping_address( party_details.shipping_address_name = shipping_address or get_party_shipping_address(
party_type, party.name party_type, party.name
) )
party_details.shipping_address = get_address_display(party_details["shipping_address_name"]) party_details.shipping_address = render_address(
party_details["shipping_address_name"], check_permissions=not ignore_permissions
)
if doctype: if doctype:
party_details.update( party_details.update(
get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name) get_fetch_values(doctype, "shipping_address_name", party_details.shipping_address_name)
@@ -224,7 +226,9 @@ def set_address_details(
party_details.update( party_details.update(
{ {
"shipping_address": shipping_address, "shipping_address": shipping_address,
"shipping_address_display": get_address_display(shipping_address), "shipping_address_display": render_address(
shipping_address, check_permissions=not ignore_permissions
),
**get_fetch_values(doctype, "shipping_address", shipping_address), **get_fetch_values(doctype, "shipping_address", shipping_address),
} }
) )
@@ -235,7 +239,8 @@ def set_address_details(
{ {
"billing_address": party_details.company_address, "billing_address": party_details.company_address,
"billing_address_display": ( "billing_address_display": (
party_details.company_address_display or get_address_display(party_details.company_address) party_details.company_address_display
or render_address(party_details.company_address, check_permissions=True)
), ),
**get_fetch_values(doctype, "billing_address", party_details.company_address), **get_fetch_values(doctype, "billing_address", party_details.company_address),
} }
@@ -277,7 +282,34 @@ def set_contact_details(party_details, party, party_type):
} }
) )
else: else:
party_details.update(get_contact_details(party_details.contact_person)) fields = [
"name as contact_person",
"salutation",
"first_name",
"last_name",
"email_id as contact_email",
"mobile_no as contact_mobile",
"phone as contact_phone",
"designation as contact_designation",
"department as contact_department",
]
contact_details = frappe.db.get_value(
"Contact", party_details.contact_person, fields, as_dict=True
)
contact_details.contact_display = " ".join(
filter(
None,
[
contact_details.get("salutation"),
contact_details.get("first_name"),
contact_details.get("last_name"),
],
)
)
party_details.update(contact_details)
def set_other_values(party_details, party, party_type): def set_other_values(party_details, party, party_type):
@@ -938,3 +970,13 @@ def add_party_account(party_type, party, company, account):
doc.append("accounts", accounts) doc.append("accounts", accounts)
doc.save() doc.save()
def render_address(address, check_permissions=True):
try:
from frappe.contacts.doctype.address.address import render_address as _render
except ImportError:
# Older frappe versions where this function is not available
from frappe.contacts.doctype.address.address import get_address_display as _render
return frappe.call(_render, address, check_permissions=check_permissions)

View File

@@ -457,6 +457,8 @@ class GrossProfitGenerator(object):
new_row.qty += flt(row.qty) new_row.qty += flt(row.qty)
new_row.buying_amount += flt(row.buying_amount, self.currency_precision) new_row.buying_amount += flt(row.buying_amount, self.currency_precision)
new_row.base_amount += flt(row.base_amount, self.currency_precision) new_row.base_amount += flt(row.base_amount, self.currency_precision)
if self.filters.get("group_by") == "Sales Person":
new_row.allocated_amount += flt(row.allocated_amount, self.currency_precision)
new_row = self.set_average_rate(new_row) new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row) self.grouped_data.append(new_row)
else: else:

View File

@@ -4,7 +4,7 @@
import frappe import frappe
from frappe import ValidationError, _, msgprint from frappe import ValidationError, _, msgprint
from frappe.contacts.doctype.address.address import get_address_display from frappe.contacts.doctype.address.address import render_address
from frappe.utils import cint, cstr, flt, getdate from frappe.utils import cint, cstr, flt, getdate
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
@@ -14,7 +14,8 @@ from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
from erpnext.controllers.subcontracting import Subcontracting from erpnext.controllers.subcontracting import Subcontracting
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor
from erpnext.stock.utils import get_incoming_rate from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.stock.utils import get_incoming_rate, get_valuation_method
class QtyMismatchError(ValidationError): class QtyMismatchError(ValidationError):
@@ -186,7 +187,9 @@ class BuyingController(StockController, Subcontracting):
for address_field, address_display_field in address_dict.items(): for address_field, address_display_field in address_dict.items():
if self.get(address_field): if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field))) self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def set_total_in_words(self): def set_total_in_words(self):
from frappe.utils import money_in_words from frappe.utils import money_in_words
@@ -504,9 +507,20 @@ class BuyingController(StockController, Subcontracting):
) )
if self.is_return: if self.is_return:
outgoing_rate = get_rate_for_return( if get_valuation_method(d.item_code) == "Moving Average":
self.doctype, self.name, d.item_code, self.return_against, item_row=d previous_sle = get_previous_sle(
) {
"item_code": d.item_code,
"warehouse": d.warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
}
)
outgoing_rate = flt(previous_sle.get("valuation_rate"))
else:
outgoing_rate = get_rate_for_return(
self.doctype, self.name, d.item_code, self.return_against, item_row=d
)
sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1}) sle.update({"outgoing_rate": outgoing_rate, "recalculate_rate": 1})
if d.from_warehouse: if d.from_warehouse:

View File

@@ -4,9 +4,9 @@
import frappe import frappe
from frappe import _, bold, throw from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime from frappe.utils import cint, cstr, flt, get_link_to_form, nowtime
from erpnext.accounts.party import render_address
from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.controllers.sales_and_purchase_return import get_rate_for_return from erpnext.controllers.sales_and_purchase_return import get_rate_for_return
from erpnext.controllers.stock_controller import StockController from erpnext.controllers.stock_controller import StockController
@@ -583,7 +583,9 @@ class SellingController(StockController):
for address_field, address_display_field in address_dict.items(): for address_field, address_display_field in address_dict.items():
if self.get(address_field): if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field))) self.set(
address_display_field, render_address(self.get(address_field), check_permissions=False)
)
def validate_for_duplicate_items(self): def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], [] check_list, chk_dupl_itm = [], []

View File

@@ -582,13 +582,21 @@ class StockController(AccountsController):
d.stock_uom_rate = d.rate / (d.conversion_factor or 1) d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
def validate_internal_transfer(self): def validate_internal_transfer(self):
if ( if self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt"):
self.doctype in ("Sales Invoice", "Delivery Note", "Purchase Invoice", "Purchase Receipt") if self.is_internal_transfer():
and self.is_internal_transfer() self.validate_in_transit_warehouses()
): self.validate_multi_currency()
self.validate_in_transit_warehouses() self.validate_packed_items()
self.validate_multi_currency() else:
self.validate_packed_items() self.validate_internal_transfer_warehouse()
def validate_internal_transfer_warehouse(self):
for row in self.items:
if row.get("target_warehouse"):
row.target_warehouse = None
if row.get("from_warehouse"):
row.from_warehouse = None
def validate_in_transit_warehouses(self): def validate_in_transit_warehouses(self):
if ( if (
@@ -906,8 +914,6 @@ def create_item_wise_repost_entries(voucher_type, voucher_no, allow_zero_rate=Fa
repost_entry = frappe.new_doc("Repost Item Valuation") repost_entry = frappe.new_doc("Repost Item Valuation")
repost_entry.based_on = "Item and Warehouse" repost_entry.based_on = "Item and Warehouse"
repost_entry.voucher_type = voucher_type
repost_entry.voucher_no = voucher_no
repost_entry.item_code = sle.item_code repost_entry.item_code = sle.item_code
repost_entry.warehouse = sle.warehouse repost_entry.warehouse = sle.warehouse

View File

@@ -4,6 +4,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.query_builder.functions import Concat_ws, Date
def execute(filters=None): def execute(filters=None):
@@ -69,53 +70,41 @@ def get_columns():
def get_data(filters): def get_data(filters):
return frappe.db.sql( lead = frappe.qb.DocType("Lead")
""" address = frappe.qb.DocType("Address")
SELECT dynamic_link = frappe.qb.DocType("Dynamic Link")
`tabLead`.name,
`tabLead`.lead_name, query = (
`tabLead`.status, frappe.qb.from_(lead)
`tabLead`.lead_owner, .left_join(dynamic_link)
`tabLead`.territory, .on((lead.name == dynamic_link.link_name) & (dynamic_link.parenttype == "Address"))
`tabLead`.source, .left_join(address)
`tabLead`.email_id, .on(address.name == dynamic_link.parent)
`tabLead`.mobile_no, .select(
`tabLead`.phone, lead.name,
`tabLead`.owner, lead.lead_name,
`tabLead`.company, lead.status,
concat_ws(', ', lead.lead_owner,
trim(',' from `tabAddress`.address_line1), lead.territory,
trim(',' from tabAddress.address_line2) lead.source,
) AS address, lead.email_id,
`tabAddress`.state, lead.mobile_no,
`tabAddress`.pincode, lead.phone,
`tabAddress`.country lead.owner,
FROM lead.company,
`tabLead` left join `tabDynamic Link` on ( (Concat_ws(", ", address.address_line1, address.address_line2)).as_("address"),
`tabLead`.name = `tabDynamic Link`.link_name and address.state,
`tabDynamic Link`.parenttype = 'Address') address.pincode,
left join `tabAddress` on ( address.country,
`tabAddress`.name=`tabDynamic Link`.parent) )
WHERE .where(lead.company == filters.company)
company = %(company)s .where(Date(lead.creation).between(filters.from_date, filters.to_date))
AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s
{conditions}
ORDER BY
`tabLead`.creation asc """.format(
conditions=get_conditions(filters)
),
filters,
as_dict=1,
) )
def get_conditions(filters):
conditions = []
if filters.get("territory"): if filters.get("territory"):
conditions.append(" and `tabLead`.territory=%(territory)s") query = query.where(lead.territory == filters.get("territory"))
if filters.get("status"): if filters.get("status"):
conditions.append(" and `tabLead`.status=%(status)s") query = query.where(lead.status == filters.get("status"))
return " ".join(conditions) if conditions else "" return query.run(as_dict=1)

View File

@@ -17,7 +17,6 @@ from erpnext.e_commerce.shopping_cart.cart import (
request_for_quotation, request_for_quotation,
update_cart, update_cart,
) )
from erpnext.tests.utils import create_test_contact_and_address
class TestShoppingCart(unittest.TestCase): class TestShoppingCart(unittest.TestCase):
@@ -28,7 +27,6 @@ class TestShoppingCart(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.set_user("Administrator") frappe.set_user("Administrator")
create_test_contact_and_address()
self.enable_shopping_cart() self.enable_shopping_cart()
if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}): if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}):
make_website_item(frappe.get_cached_doc("Item", "_Test Item")) make_website_item(frappe.get_cached_doc("Item", "_Test Item"))
@@ -46,48 +44,57 @@ class TestShoppingCart(unittest.TestCase):
frappe.db.sql("delete from `tabTax Rule`") frappe.db.sql("delete from `tabTax Rule`")
def test_get_cart_new_user(self): def test_get_cart_new_user(self):
self.login_as_new_user() self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
create_address_and_contact(
address_title="_Test Address for Customer 2",
first_name="_Test Contact for Customer 2",
email="test_contact_two_customer@example.com",
customer="_Test Customer 2",
)
# test if lead is created and quotation with new lead is fetched # test if lead is created and quotation with new lead is fetched
quotation = _get_cart_quotation() customer = frappe.get_doc("Customer", "_Test Customer 2")
quotation = _get_cart_quotation(party=customer)
self.assertEqual(quotation.quotation_to, "Customer") self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual( self.assertEqual(
quotation.contact_person, quotation.contact_person,
frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")), frappe.db.get_value("Contact", dict(email_id="test_contact_two_customer@example.com")),
) )
self.assertEqual(quotation.contact_email, frappe.session.user) self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation return quotation
def test_get_cart_customer(self): def test_get_cart_customer(self, customer="_Test Customer 2"):
def validate_quotation(): def validate_quotation(customer_name):
# test if quotation with customer is fetched # test if quotation with customer is fetched
quotation = _get_cart_quotation() party = frappe.get_doc("Customer", customer_name)
quotation = _get_cart_quotation(party=party)
self.assertEqual(quotation.quotation_to, "Customer") self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(quotation.party_name, "_Test Customer") self.assertEqual(quotation.party_name, customer_name)
self.assertEqual(quotation.contact_email, frappe.session.user) self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation return quotation
self.login_as_customer( quotation = validate_quotation(customer)
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
validate_quotation()
self.login_as_customer()
quotation = validate_quotation()
return quotation return quotation
def test_add_to_cart(self): def test_add_to_cart(self):
self.login_as_customer() self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
create_address_and_contact(
address_title="_Test Address for Customer 2",
first_name="_Test Contact for Customer 2",
email="test_contact_two_customer@example.com",
customer="_Test Customer 2",
)
# clear existing quotations # clear existing quotations
self.clear_existing_quotations() self.clear_existing_quotations()
# add first item # add first item
update_cart("_Test Item", 1) update_cart("_Test Item", 1)
quotation = self.test_get_cart_customer() quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") self.assertEqual(quotation.get("items")[0].item_code, "_Test Item")
self.assertEqual(quotation.get("items")[0].qty, 1) self.assertEqual(quotation.get("items")[0].qty, 1)
@@ -95,7 +102,7 @@ class TestShoppingCart(unittest.TestCase):
# add second item # add second item
update_cart("_Test Item 2", 1) update_cart("_Test Item 2", 1)
quotation = self.test_get_cart_customer() quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[1].item_code, "_Test Item 2") self.assertEqual(quotation.get("items")[1].item_code, "_Test Item 2")
self.assertEqual(quotation.get("items")[1].qty, 1) self.assertEqual(quotation.get("items")[1].qty, 1)
self.assertEqual(quotation.get("items")[1].amount, 20) self.assertEqual(quotation.get("items")[1].amount, 20)
@@ -108,7 +115,7 @@ class TestShoppingCart(unittest.TestCase):
# update first item # update first item
update_cart("_Test Item", 5) update_cart("_Test Item", 5)
quotation = self.test_get_cart_customer() quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[0].item_code, "_Test Item") self.assertEqual(quotation.get("items")[0].item_code, "_Test Item")
self.assertEqual(quotation.get("items")[0].qty, 5) self.assertEqual(quotation.get("items")[0].qty, 5)
self.assertEqual(quotation.get("items")[0].amount, 50) self.assertEqual(quotation.get("items")[0].amount, 50)
@@ -121,7 +128,7 @@ class TestShoppingCart(unittest.TestCase):
# remove first item # remove first item
update_cart("_Test Item", 0) update_cart("_Test Item", 0)
quotation = self.test_get_cart_customer() quotation = self.test_get_cart_customer("_Test Customer 2")
self.assertEqual(quotation.get("items")[0].item_code, "_Test Item 2") self.assertEqual(quotation.get("items")[0].item_code, "_Test Item 2")
self.assertEqual(quotation.get("items")[0].qty, 1) self.assertEqual(quotation.get("items")[0].qty, 1)
@@ -129,9 +136,20 @@ class TestShoppingCart(unittest.TestCase):
self.assertEqual(quotation.net_total, 20) self.assertEqual(quotation.net_total, 20)
self.assertEqual(len(quotation.get("items")), 1) self.assertEqual(len(quotation.get("items")), 1)
@unittest.skip("Flaky in CI")
def test_tax_rule(self): def test_tax_rule(self):
self.create_tax_rule() self.create_tax_rule()
self.login_as_customer()
self.login_as_customer(
"test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
)
create_address_and_contact(
address_title="_Test Address for Customer 2",
first_name="_Test Contact for Customer 2",
email="test_contact_two_customer@example.com",
customer="_Test Customer 2",
)
quotation = self.create_quotation() quotation = self.create_quotation()
from erpnext.accounts.party import set_taxes from erpnext.accounts.party import set_taxes
@@ -319,7 +337,7 @@ class TestShoppingCart(unittest.TestCase):
if frappe.db.exists("User", email): if frappe.db.exists("User", email):
return return
frappe.get_doc( user = frappe.get_doc(
{ {
"doctype": "User", "doctype": "User",
"user_type": "Website User", "user_type": "Website User",
@@ -329,6 +347,40 @@ class TestShoppingCart(unittest.TestCase):
} }
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)
user.add_roles("Customer")
def create_address_and_contact(**kwargs):
if not frappe.db.get_value("Address", {"address_title": kwargs.get("address_title")}):
frappe.get_doc(
{
"doctype": "Address",
"address_title": kwargs.get("address_title"),
"address_type": kwargs.get("address_type") or "Office",
"address_line1": kwargs.get("address_line1") or "Station Road",
"city": kwargs.get("city") or "_Test City",
"state": kwargs.get("state") or "Test State",
"country": kwargs.get("country") or "India",
"links": [
{"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"}
],
}
).insert()
if not frappe.db.get_value("Contact", {"first_name": kwargs.get("first_name")}):
contact = frappe.get_doc(
{
"doctype": "Contact",
"first_name": kwargs.get("first_name"),
"links": [
{"link_doctype": "Customer", "link_name": kwargs.get("customer") or "_Test Customer"}
],
}
)
contact.add_email(kwargs.get("email") or "test_contact_customer@example.com", is_primary=True)
contact.add_phone(kwargs.get("phone") or "+91 0000000000", is_primary_phone=True)
contact.insert()
test_dependencies = [ test_dependencies = [
"Sales Taxes and Charges Template", "Sales Taxes and Charges Template",

View File

@@ -296,18 +296,27 @@ class LeaveAllocation(Document):
def get_previous_allocation(from_date, leave_type, employee): def get_previous_allocation(from_date, leave_type, employee):
"""Returns document properties of previous allocation""" """Returns document properties of previous allocation"""
return frappe.db.get_value( Allocation = frappe.qb.DocType("Leave Allocation")
"Leave Allocation", allocations = (
filters={ frappe.qb.from_(Allocation)
"to_date": ("<", from_date), .select(
"leave_type": leave_type, Allocation.name,
"employee": employee, Allocation.from_date,
"docstatus": 1, Allocation.to_date,
}, Allocation.employee,
order_by="to_date DESC", Allocation.leave_type,
fieldname=["name", "from_date", "to_date", "employee", "leave_type"], )
as_dict=1, .where(
) (Allocation.employee == employee)
& (Allocation.leave_type == leave_type)
& (Allocation.to_date < from_date)
& (Allocation.docstatus == 1)
)
.orderby(Allocation.to_date, order=frappe.qb.desc)
.limit(1)
).run(as_dict=True)
return allocations[0] if allocations else None
def get_leave_allocation_for_period( def get_leave_allocation_for_period(

View File

@@ -706,19 +706,22 @@ def get_allocation_expiry_for_cf_leaves(
employee: str, leave_type: str, to_date: str, from_date: str employee: str, leave_type: str, to_date: str, from_date: str
) -> str: ) -> str:
"""Returns expiry of carry forward allocation in leave ledger entry""" """Returns expiry of carry forward allocation in leave ledger entry"""
expiry = frappe.get_all( Ledger = frappe.qb.DocType("Leave Ledger Entry")
"Leave Ledger Entry", expiry = (
filters={ frappe.qb.from_(Ledger)
"employee": employee, .select(Ledger.to_date)
"leave_type": leave_type, .where(
"is_carry_forward": 1, (Ledger.employee == employee)
"transaction_type": "Leave Allocation", & (Ledger.leave_type == leave_type)
"to_date": ["between", (from_date, to_date)], & (Ledger.is_carry_forward == 1)
"docstatus": 1, & (Ledger.transaction_type == "Leave Allocation")
}, & (Ledger.to_date.between(from_date, to_date))
fields=["to_date"], & (Ledger.docstatus == 1)
) )
return expiry[0]["to_date"] if expiry else "" .limit(1)
).run()
return expiry[0][0] if expiry else ""
@frappe.whitelist() @frappe.whitelist()
@@ -1017,7 +1020,7 @@ def get_leaves_for_period(
if leave_entry.leaves % 1: if leave_entry.leaves % 1:
half_day = 1 half_day = 1
half_day_date = frappe.db.get_value( half_day_date = frappe.db.get_value(
"Leave Application", {"name": leave_entry.transaction_name}, ["half_day_date"] "Leave Application", leave_entry.transaction_name, "half_day_date"
) )
leave_days += ( leave_days += (

View File

@@ -713,25 +713,31 @@ class TestLeaveApplication(unittest.TestCase):
self.assertEqual(details.leave_balance, 30) self.assertEqual(details.leave_balance, 30)
def test_earned_leaves_creation(self): def test_earned_leaves_creation(self):
from erpnext.hr.utils import allocate_earned_leaves from erpnext.hr.doctype.leave_policy_assignment.test_leave_policy_assignment import (
allocate_earned_leaves_for_months,
)
leave_period = get_leave_period() year_start = get_year_start(getdate())
year_end = get_year_ending(getdate())
frappe.flags.current_date = year_start
leave_period = get_leave_period(year_start, year_end)
employee = get_employee() employee = get_employee()
leave_type = "Test Earned Leave Type" leave_type = "Test Earned Leave Type"
make_policy_assignment(employee, leave_type, leave_period) make_policy_assignment(employee, leave_type, leave_period)
for i in range(0, 14): # leaves for 6 months = 3, but max leaves restricts allocation to 2
allocate_earned_leaves() frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 2)
allocate_earned_leaves_for_months(6)
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6) self.assertEqual(get_leave_balance_on(employee.name, leave_type, frappe.flags.current_date), 2)
# validate earned leaves creation without maximum leaves # validate earned leaves creation without maximum leaves
frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0) frappe.db.set_value("Leave Type", leave_type, "max_leaves_allowed", 0)
allocate_earned_leaves_for_months(5)
self.assertEqual(get_leave_balance_on(employee.name, leave_type, frappe.flags.current_date), 4.5)
for i in range(0, 6): frappe.flags.current_date = None
allocate_earned_leaves()
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
# test to not consider current leave in leave balance while submitting # test to not consider current leave in leave balance while submitting
def test_current_leave_on_submit(self): def test_current_leave_on_submit(self):
@@ -1254,7 +1260,7 @@ def set_leave_approver():
dept_doc.save(ignore_permissions=True) dept_doc.save(ignore_permissions=True)
def get_leave_period(): def get_leave_period(from_date=None, to_date=None):
leave_period_name = frappe.db.exists({"doctype": "Leave Period", "company": "_Test Company"}) leave_period_name = frappe.db.exists({"doctype": "Leave Period", "company": "_Test Company"})
if leave_period_name: if leave_period_name:
return frappe.get_doc("Leave Period", leave_period_name[0][0]) return frappe.get_doc("Leave Period", leave_period_name[0][0])
@@ -1263,8 +1269,8 @@ def get_leave_period():
dict( dict(
name="Test Leave Period", name="Test Leave Period",
doctype="Leave Period", doctype="Leave Period",
from_date=add_months(nowdate(), -6), from_date=from_date or add_months(nowdate(), -6),
to_date=add_months(nowdate(), 6), to_date=to_date or add_months(nowdate(), 6),
company="_Test Company", company="_Test Company",
is_active=1, is_active=1,
) )

View File

@@ -27,7 +27,8 @@
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Employee", "label": "Employee",
"options": "Employee" "options": "Employee",
"search_index": 1
}, },
{ {
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
@@ -57,13 +58,15 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Transaction Type", "label": "Transaction Type",
"options": "DocType" "options": "DocType",
"search_index": 1
}, },
{ {
"fieldname": "transaction_name", "fieldname": "transaction_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"label": "Transaction Name", "label": "Transaction Name",
"options": "transaction_type" "options": "transaction_type",
"search_index": 1
}, },
{ {
"fieldname": "leaves", "fieldname": "leaves",
@@ -123,7 +126,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-01-04 18:47:45.146652", "modified": "2023-11-17 12:36:36.963697",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Leave Ledger Entry", "name": "Leave Ledger Entry",

View File

@@ -225,3 +225,7 @@ def expire_carried_forward_allocation(allocation):
to_date=allocation.to_date, to_date=allocation.to_date,
) )
create_leave_ledger_entry(allocation, args) create_leave_ledger_entry(allocation, args)
def on_doctype_update():
frappe.db.add_index("Leave Ledger Entry", ["transaction_type", "transaction_name"])

View File

@@ -100,7 +100,7 @@ class LeavePolicyAssignment(Document):
return leave_allocations return leave_allocations
def create_leave_allocation( def create_leave_allocation(
self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining self, leave_type, annual_allocation, leave_type_details, date_of_joining
): ):
# Creates leave allocation for the given employee in the provided leave period # Creates leave allocation for the given employee in the provided leave period
carry_forward = self.carry_forward carry_forward = self.carry_forward
@@ -108,7 +108,7 @@ class LeavePolicyAssignment(Document):
carry_forward = 0 carry_forward = 0
new_leaves_allocated = self.get_new_leaves( new_leaves_allocated = self.get_new_leaves(
leave_type, new_leaves_allocated, leave_type_details, date_of_joining leave_type, annual_allocation, leave_type_details, date_of_joining
) )
allocation = frappe.get_doc( allocation = frappe.get_doc(
@@ -129,7 +129,7 @@ class LeavePolicyAssignment(Document):
allocation.submit() allocation.submit()
return allocation.name, new_leaves_allocated return allocation.name, new_leaves_allocated
def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): def get_new_leaves(self, leave_type, annual_allocation, leave_type_details, date_of_joining):
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
precision = get_field_precision( precision = get_field_precision(
@@ -146,20 +146,27 @@ class LeavePolicyAssignment(Document):
else: else:
# get leaves for past months if assignment is based on Leave Period / Joining Date # get leaves for past months if assignment is based on Leave Period / Joining Date
new_leaves_allocated = self.get_leaves_for_passed_months( new_leaves_allocated = self.get_leaves_for_passed_months(
leave_type, new_leaves_allocated, leave_type_details, date_of_joining leave_type, annual_allocation, leave_type_details, date_of_joining
) )
# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
elif getdate(date_of_joining) > getdate(self.effective_from): else:
remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / ( if getdate(date_of_joining) > getdate(self.effective_from):
date_diff(self.effective_to, self.effective_from) + 1 remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / (
) date_diff(self.effective_to, self.effective_from) + 1
new_leaves_allocated = ceil(new_leaves_allocated * remaining_period) )
new_leaves_allocated = ceil(annual_allocation * remaining_period)
else:
new_leaves_allocated = annual_allocation
# leave allocation should not exceed annual allocation as per policy assignment
if new_leaves_allocated > annual_allocation:
new_leaves_allocated = annual_allocation
return flt(new_leaves_allocated, precision) return flt(new_leaves_allocated, precision)
def get_leaves_for_passed_months( def get_leaves_for_passed_months(
self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining self, leave_type, annual_allocation, leave_type_details, date_of_joining
): ):
from erpnext.hr.utils import get_monthly_earned_leave from erpnext.hr.utils import get_monthly_earned_leave
@@ -184,7 +191,7 @@ class LeavePolicyAssignment(Document):
if months_passed > 0: if months_passed > 0:
monthly_earned_leave = get_monthly_earned_leave( monthly_earned_leave = get_monthly_earned_leave(
new_leaves_allocated, annual_allocation,
leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).earned_leave_frequency,
leave_type_details.get(leave_type).rounding, leave_type_details.get(leave_type).rounding,
) )

View File

@@ -5,8 +5,10 @@ import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, add_months, get_first_day, get_last_day, getdate from frappe.utils import add_days, add_months, get_first_day, get_last_day, get_year_start, getdate
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
from erpnext.hr.doctype.leave_application.test_leave_application import ( from erpnext.hr.doctype.leave_application.test_leave_application import (
get_employee, get_employee,
get_leave_period, get_leave_period,
@@ -15,6 +17,7 @@ from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_polic
from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import ( from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
create_assignment_for_multiple_employees, create_assignment_for_multiple_employees,
) )
from erpnext.hr.utils import allocate_earned_leaves
test_dependencies = ["Employee"] test_dependencies = ["Employee"]
@@ -34,6 +37,8 @@ class TestLeavePolicyAssignment(FrappeTestCase):
self.original_doj = employee.date_of_joining self.original_doj = employee.date_of_joining
self.employee = employee self.employee = employee
self.leave_type = "Test Earned Leave"
def test_grant_leaves(self): def test_grant_leaves(self):
leave_period = get_leave_period() leave_period = get_leave_period()
# allocation = 10 # allocation = 10
@@ -326,6 +331,90 @@ class TestLeavePolicyAssignment(FrappeTestCase):
self.assertEqual(effective_from, self.employee.date_of_joining) self.assertEqual(effective_from, self.employee.date_of_joining)
self.assertEqual(leaves_allocated, 3) self.assertEqual(leaves_allocated, 3)
def test_overallocation(self):
"""Tests if earned leave allocation does not exceed annual allocation"""
frappe.flags.current_date = get_year_start(getdate())
make_policy_assignment(
self.employee,
annual_allocation=22,
allocate_on_day="First Day",
start_date=frappe.flags.current_date,
)
# leaves for 12 months = 22
# With rounding, 22 leaves would be allocated in 11 months only
frappe.db.set_value("Leave Type", self.leave_type, "rounding", 1.0)
allocate_earned_leaves_for_months(11)
self.assertEqual(
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 22
)
# should not allocate more leaves than annual allocation
allocate_earned_leaves_for_months(1)
self.assertEqual(
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 22
)
def test_over_allocation_during_assignment_creation(self):
"""Tests backdated earned leave allocation does not exceed annual allocation"""
start_date = get_first_day(add_months(getdate(), -12))
# joining date set to 1Y ago
self.employee.date_of_joining = start_date
self.employee.save()
# create backdated assignment for last year
frappe.flags.current_date = get_first_day(getdate())
leave_policy_assignments = make_policy_assignment(
self.employee, start_date=start_date, allocate_on_day="Date of Joining"
)
# 13 months have passed but annual allocation = 12
# check annual allocation is not exceeded
leaves_allocated = get_allocated_leaves(leave_policy_assignments[0])
self.assertEqual(leaves_allocated, 12)
def test_overallocation_with_carry_forwarding(self):
"""Tests earned leave allocation with cf leaves does not exceed annual allocation"""
year_start = get_year_start(getdate())
# initial leave allocation = 5
leave_allocation = create_leave_allocation(
employee=self.employee.name,
employee_name=self.employee.employee_name,
leave_type=self.leave_type,
from_date=get_first_day(add_months(year_start, -1)),
to_date=get_last_day(add_months(year_start, -1)),
new_leaves_allocated=5,
carry_forward=0,
)
leave_allocation.submit()
frappe.flags.current_date = year_start
# carry forwarded leaves = 5
make_policy_assignment(
self.employee,
annual_allocation=22,
allocate_on_day="First Day",
start_date=year_start,
carry_forward=True,
)
frappe.db.set_value("Leave Type", self.leave_type, "rounding", 1.0)
allocate_earned_leaves_for_months(11)
# 5 carry forwarded leaves + 22 EL allocated = 27 leaves
self.assertEqual(
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 27
)
# should not allocate more leaves than annual allocation (22 excluding 5 cf leaves)
allocate_earned_leaves_for_months(1)
self.assertEqual(
get_leave_balance_on(self.employee.name, self.leave_type, frappe.flags.current_date), 27
)
def tearDown(self): def tearDown(self):
frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj) frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
frappe.flags.current_date = None frappe.flags.current_date = None
@@ -376,3 +465,51 @@ def setup_leave_period_and_policy(start_date, based_on_doj=False):
).insert() ).insert()
return leave_period, leave_policy return leave_period, leave_policy
def make_policy_assignment(
employee,
allocate_on_day="Last Day",
earned_leave_frequency="Monthly",
start_date=None,
annual_allocation=12,
carry_forward=0,
assignment_based_on="Leave Period",
):
leave_type = create_earned_leave_type("Test Earned Leave", allocate_on_day)
leave_period = create_leave_period("Test Earned Leave Period", start_date=start_date)
leave_policy = frappe.get_doc(
{
"doctype": "Leave Policy",
"title": "Test Earned Leave Policy",
"leave_policy_details": [
{"leave_type": leave_type.name, "annual_allocation": annual_allocation}
],
}
).insert()
data = {
"assignment_based_on": assignment_based_on,
"leave_policy": leave_policy.name,
"leave_period": leave_period.name,
"carry_forward": carry_forward,
}
leave_policy_assignments = create_assignment_for_multiple_employees(
[employee.name], frappe._dict(data)
)
return leave_policy_assignments
def get_allocated_leaves(assignment):
return frappe.db.get_value(
"Leave Allocation",
{"leave_policy_assignment": assignment},
"total_leaves_allocated",
)
def allocate_earned_leaves_for_months(months):
for i in range(0, months):
frappe.flags.current_date = add_months(frappe.flags.current_date, 1)
allocate_earned_leaves()

View File

@@ -1,26 +1,32 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "columns": [],
"creation": "2013-02-22 15:29:34", "creation": "2013-02-22 15:29:34",
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 3, "filters": [],
"is_standard": "Yes", "idx": 3,
"modified": "2017-02-24 20:18:04.317397", "is_standard": "Yes",
"modified_by": "Administrator", "letterhead": null,
"module": "HR", "modified": "2023-11-17 13:28:40.669200",
"name": "Employee Leave Balance", "modified_by": "Administrator",
"owner": "Administrator", "module": "HR",
"ref_doctype": "Employee", "name": "Employee Leave Balance",
"report_name": "Employee Leave Balance", "owner": "Administrator",
"report_type": "Script Report", "prepared_report": 0,
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"roles": [ "roles": [
{ {
"role": "HR User" "role": "HR User"
}, },
{ {
"role": "HR Manager" "role": "HR Manager"
},
{
"role": "Employee"
} }
] ]
} }

View File

@@ -85,19 +85,10 @@ def get_columns() -> List[Dict]:
def get_data(filters: Filters) -> List: def get_data(filters: Filters) -> List:
leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name") leave_types = get_leave_types()
conditions = get_conditions(filters) active_employees = get_employees(filters)
user = frappe.session.user precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
department_approver_map = get_department_leave_approver_map(filters.department)
active_employees = frappe.get_list(
"Employee",
filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"],
)
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types
row = None row = None
@@ -110,10 +101,6 @@ def get_data(filters: Filters) -> List:
row = frappe._dict({"leave_type": leave_type}) row = frappe._dict({"leave_type": leave_type})
for employee in active_employees: for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, []).append(
employee.leave_approver
)
if consolidate_leave_types: if consolidate_leave_types:
row = frappe._dict() row = frappe._dict()
else: else:
@@ -144,6 +131,35 @@ def get_data(filters: Filters) -> List:
return data return data
def get_leave_types() -> List[str]:
LeaveType = frappe.qb.DocType("Leave Type")
leave_types = (frappe.qb.from_(LeaveType).select(LeaveType.name).orderby(LeaveType.name)).run(
as_dict=True
)
return [leave_type.name for leave_type in leave_types]
def get_employees(filters: Filters) -> List[Dict]:
Employee = frappe.qb.DocType("Employee")
query = frappe.qb.from_(Employee).select(
Employee.name,
Employee.employee_name,
Employee.department,
)
for field in ["company", "department"]:
if filters.get(field):
query = query.where((getattr(Employee, field) == filters.get(field)))
if filters.get("employee"):
query = query.where(Employee.name == filters.get("employee"))
if filters.get("employee_status"):
query = query.where(Employee.status == filters.get("employee_status"))
return query.run(as_dict=True)
def get_opening_balance( def get_opening_balance(
employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
) -> float: ) -> float:
@@ -168,48 +184,6 @@ def get_opening_balance(
return opening_balance return opening_balance
def get_conditions(filters: Filters) -> Dict:
conditions = {}
if filters.employee:
conditions["name"] = filters.employee
if filters.company:
conditions["company"] = filters.company
if filters.department:
conditions["department"] = filters.department
if filters.employee_status:
conditions["status"] = filters.employee_status
return conditions
def get_department_leave_approver_map(department: Optional[str] = None):
# get current department and all its child
department_list = frappe.get_list(
"Department",
filters={"disabled": 0},
or_filters={"name": department, "parent_department": department},
pluck="name",
)
# retrieve approvers list from current department and from its subsequent child departments
approver_list = frappe.get_all(
"Department Approver",
filters={"parentfield": "leave_approvers", "parent": ("in", department_list)},
fields=["parent", "approver"],
as_list=True,
)
approvers = {}
for k, v in approver_list:
approvers.setdefault(k, []).append(v)
return approvers
def get_allocated_and_expired_leaves( def get_allocated_and_expired_leaves(
from_date: str, to_date: str, employee: str, leave_type: str from_date: str, to_date: str, employee: str, leave_type: str
) -> Tuple[float, float, float]: ) -> Tuple[float, float, float]:
@@ -244,7 +218,7 @@ def get_leave_ledger_entries(
from_date: str, to_date: str, employee: str, leave_type: str from_date: str, to_date: str, employee: str, leave_type: str
) -> List[Dict]: ) -> List[Dict]:
ledger = frappe.qb.DocType("Leave Ledger Entry") ledger = frappe.qb.DocType("Leave Ledger Entry")
records = ( return (
frappe.qb.from_(ledger) frappe.qb.from_(ledger)
.select( .select(
ledger.employee, ledger.employee,
@@ -270,8 +244,6 @@ def get_leave_ledger_entries(
) )
).run(as_dict=True) ).run(as_dict=True)
return records
def get_chart_data(data: List, filters: Filters) -> Dict: def get_chart_data(data: List, filters: Filters) -> Dict:
labels = [] labels = []

View File

@@ -6,9 +6,6 @@ import frappe
from frappe import _ from frappe import _
from erpnext.hr.doctype.leave_application.leave_application import get_leave_details from erpnext.hr.doctype.leave_application.leave_application import get_leave_details
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import (
get_department_leave_approver_map,
)
def execute(filters=None): def execute(filters=None):
@@ -54,17 +51,11 @@ def get_data(filters, leave_types):
active_employees = frappe.get_all( active_employees = frappe.get_all(
"Employee", "Employee",
filters=conditions, filters=conditions,
fields=["name", "employee_name", "department", "user_id", "leave_approver"], fields=["name", "employee_name", "department", "user_id"],
) )
department_approver_map = get_department_leave_approver_map(filters.get("department"))
data = [] data = []
for employee in active_employees: for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, [])
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
row = [employee.name, employee.employee_name, employee.department] row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date) available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types: for leave_type in leave_types:

View File

@@ -459,7 +459,7 @@ def generate_leave_encashment():
def allocate_earned_leaves(): def allocate_earned_leaves():
"""Allocate earned leaves to Employees""" """Allocate earned leaves to Employees"""
e_leave_types = get_earned_leaves() e_leave_types = get_earned_leaves()
today = getdate() today = frappe.flags.current_date or getdate()
for e_leave_type in e_leave_types: for e_leave_type in e_leave_types:
@@ -496,18 +496,28 @@ def allocate_earned_leaves():
def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type): def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
allocation = frappe.get_doc("Leave Allocation", allocation.name)
annual_allocation = flt(annual_allocation, allocation.precision("total_leaves_allocated"))
earned_leaves = get_monthly_earned_leave( earned_leaves = get_monthly_earned_leave(
annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding
) )
allocation = frappe.get_doc("Leave Allocation", allocation.name)
new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves)
new_allocation_without_cf = flt(
flt(allocation.get_existing_leave_count()) + flt(earned_leaves),
allocation.precision("total_leaves_allocated"),
)
if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0: if new_allocation > e_leave_type.max_leaves_allowed and e_leave_type.max_leaves_allowed > 0:
new_allocation = e_leave_type.max_leaves_allowed new_allocation = e_leave_type.max_leaves_allowed
if new_allocation != allocation.total_leaves_allocated: if (
today_date = today() new_allocation != allocation.total_leaves_allocated
# annual allocation as per policy should not be exceeded
and new_allocation_without_cf <= annual_allocation
):
today_date = frappe.flags.current_date or getdate()
allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False) allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
create_additional_leave_ledger_entry(allocation, earned_leaves, today_date) create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)

View File

@@ -306,7 +306,7 @@ def get_last_accrual_date(loan, posting_date):
def get_last_disbursement_date(loan, posting_date): def get_last_disbursement_date(loan, posting_date):
last_disbursement_date = frappe.db.get_value( last_disbursement_date = frappe.db.get_value(
"Loan Disbursement", "Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)}, {"docstatus": 1, "against_loan": loan, "posting_date": ("<=", posting_date)},
"MAX(posting_date)", "MAX(posting_date)",
) )

View File

@@ -378,3 +378,4 @@ erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.update_asset_value_for_manual_depr_entries erpnext.patches.v13_0.update_asset_value_for_manual_depr_entries
erpnext.patches.v13_0.update_docs_link erpnext.patches.v13_0.update_docs_link
erpnext.patches.v13_0.correct_asset_value_if_je_with_workflow erpnext.patches.v13_0.correct_asset_value_if_je_with_workflow
execute:frappe.db.set_value("Accounts Settings", "Accounts Settings", "service_provider", "frankfurter.app")

View File

@@ -3,23 +3,27 @@ import frappe
def execute(): def execute():
frappe.reload_doc("stock", "doctype", "quality_inspection_parameter") frappe.reload_doc("stock", "doctype", "quality_inspection_parameter")
params = set()
# get all distinct parameters from QI readigs table # get all parameters from QI readings table
reading_params = frappe.db.get_all( for (p,) in frappe.db.get_all(
"Quality Inspection Reading", fields=["distinct specification"] "Quality Inspection Reading", fields=["specification"], as_list=True
) ):
reading_params = [d.specification for d in reading_params] params.add(p.strip())
# get all distinct parameters from QI Template as some may be unused in QI # get all parameters from QI Template as some may be unused in QI
template_params = frappe.db.get_all( for (p,) in frappe.db.get_all(
"Item Quality Inspection Parameter", fields=["distinct specification"] "Item Quality Inspection Parameter", fields=["specification"], as_list=True
) ):
template_params = [d.specification for d in template_params] params.add(p.strip())
params = list(set(reading_params + template_params)) # because db primary keys are case insensitive, so duplicates will cause an exception
params = set({x.casefold(): x for x in params}.values())
for parameter in params: for parameter in params:
if not frappe.db.exists("Quality Inspection Parameter", parameter): if frappe.db.exists("Quality Inspection Parameter", parameter):
frappe.get_doc( continue
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True) frappe.get_doc(
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True)

View File

@@ -125,6 +125,7 @@ def execute():
loan_type_doc.company = loan.company loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account loan_type_doc.payment_account = loan.payment_account
loan_type_doc.disbursement_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account loan_type_doc.penalty_income_account = penalty_account

View File

@@ -71,6 +71,12 @@ class Timesheet(Document):
if args.is_billable: if args.is_billable:
if flt(args.billing_hours) == 0.0: if flt(args.billing_hours) == 0.0:
args.billing_hours = args.hours args.billing_hours = args.hours
elif flt(args.billing_hours) > flt(args.hours):
frappe.msgprint(
_("Warning - Row {0}: Billing Hours are more than Actual Hours").format(args.idx),
indicator="orange",
alert=True,
)
else: else:
args.billing_hours = 0 args.billing_hours = 0

View File

@@ -13,7 +13,7 @@ frappe.ui.form.on("Communication", {
frappe.confirm(__(confirm_msg, [__("Issue")]), () => { frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
frm.trigger('make_issue_from_communication'); frm.trigger('make_issue_from_communication');
}) })
}, "Create"); }, __("Create"));
} }
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) { if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {

View File

@@ -1378,7 +1378,7 @@ class GSPConnector:
def set_einvoice_data(self, res): def set_einvoice_data(self, res):
enc_signed_invoice = res.get("SignedInvoice") enc_signed_invoice = res.get("SignedInvoice")
dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)["data"] dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})["data"]
self.invoice.irn = res.get("Irn") self.invoice.irn = res.get("Irn")
self.invoice.ewaybill = res.get("EwbNo") self.invoice.ewaybill = res.get("EwbNo")

View File

@@ -532,6 +532,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
primary_action={ primary_action={
"label": "Send Email", "label": "Send Email",
"server_action": "erpnext.selling.doctype.customer.customer.send_emails", "server_action": "erpnext.selling.doctype.customer.customer.send_emails",
"hide_on_success": True,
"args": { "args": {
"customer": customer, "customer": customer,
"customer_outstanding": customer_outstanding, "customer_outstanding": customer_outstanding,

View File

@@ -3,11 +3,11 @@
import frappe import frappe
from frappe import _ from frappe import _, qb
from frappe.query_builder import Criterion
from erpnext import get_default_company from erpnext import get_default_company
from erpnext.accounts.party import get_party_details from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_price_list_rate_for
def execute(filters=None): def execute(filters=None):
@@ -50,6 +50,42 @@ def get_columns(filters=None):
] ]
def fetch_item_prices(
customer: str = None, price_list: str = None, selling_price_list: str = None, items: list = None
):
price_list_map = frappe._dict()
ip = qb.DocType("Item Price")
and_conditions = []
or_conditions = []
if items:
and_conditions.append(ip.item_code.isin([x.item_code for x in items]))
and_conditions.append(ip.selling == True)
or_conditions.append(ip.customer == None)
or_conditions.append(ip.price_list == None)
if customer:
or_conditions.append(ip.customer == customer)
if price_list:
or_conditions.append(ip.price_list == price_list)
if selling_price_list:
or_conditions.append(ip.price_list == selling_price_list)
res = (
qb.from_(ip)
.select(ip.item_code, ip.price_list, ip.price_list_rate)
.where(Criterion.all(and_conditions))
.where(Criterion.any(or_conditions))
.run(as_dict=True)
)
for x in res:
price_list_map.update({(x.item_code, x.price_list): x.price_list_rate})
return price_list_map
def get_data(filters=None): def get_data(filters=None):
data = [] data = []
customer_details = get_customer_details(filters) customer_details = get_customer_details(filters)
@@ -59,9 +95,17 @@ def get_data(filters=None):
"Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code" "Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code"
) )
item_stock_map = {item.item_code: item.available for item in item_stock_map} item_stock_map = {item.item_code: item.available for item in item_stock_map}
price_list_map = fetch_item_prices(
customer_details.customer,
customer_details.price_list,
customer_details.selling_price_list,
items,
)
for item in items: for item in items:
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0 price_list_rate = price_list_map.get(
(item.item_code, customer_details.price_list or customer_details.selling_price_list), 0.0
)
available_stock = item_stock_map.get(item.item_code) available_stock = item_stock_map.get(item.item_code)
data.append( data.append(

View File

@@ -33,6 +33,7 @@ def after_install():
add_standard_navbar_items() add_standard_navbar_items()
add_app_name() add_app_name()
add_non_standard_user_types() add_non_standard_user_types()
update_roles()
frappe.db.commit() frappe.db.commit()
@@ -237,6 +238,12 @@ def create_custom_role(data):
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)
def update_roles():
website_user_roles = ("Customer", "Supplier")
for role in website_user_roles:
frappe.db.set_value("Role", role, "desk_access", 0)
def create_user_type(user_type, data): def create_user_type(user_type, data):
if frappe.db.exists("User Type", user_type): if frappe.db.exists("User Type", user_type):
doc = frappe.get_cached_doc("User Type", user_type) doc = frappe.get_cached_doc("User Type", user_type)

View File

@@ -1,5 +1,4 @@
import frappe import frappe
from frappe.utils import cint
def get_leaderboards(): def get_leaderboards():
@@ -54,12 +53,13 @@ def get_leaderboards():
@frappe.whitelist() @frappe.whitelist()
def get_all_customers(date_range, company, field, limit=None): def get_all_customers(date_range, company, field, limit=None):
filters = [["docstatus", "=", "1"], ["company", "=", company]]
from_date, to_date = parse_date_range(date_range)
if field == "outstanding_amount": if field == "outstanding_amount":
filters = [["docstatus", "=", "1"], ["company", "=", company]] if from_date and to_date:
if date_range: filters.append(["posting_date", "between", [from_date, to_date]])
date_range = frappe.parse_json(date_range)
filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]]) return frappe.get_list(
return frappe.db.get_all(
"Sales Invoice", "Sales Invoice",
fields=["customer as name", "sum(outstanding_amount) as value"], fields=["customer as name", "sum(outstanding_amount) as value"],
filters=filters, filters=filters,
@@ -69,26 +69,20 @@ def get_all_customers(date_range, company, field, limit=None):
) )
else: else:
if field == "total_sales_amount": if field == "total_sales_amount":
select_field = "sum(so_item.base_net_amount)" select_field = "base_net_total"
elif field == "total_qty_sold": elif field == "total_qty_sold":
select_field = "sum(so_item.stock_qty)" select_field = "total_qty"
date_condition = get_date_condition(date_range, "so.transaction_date") if from_date and to_date:
filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.db.sql( return frappe.get_list(
""" "Sales Order",
select so.customer as name, {0} as value fields=["customer as name", f"sum({select_field}) as value"],
FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item filters=filters,
ON so.name = so_item.parent group_by="customer",
where so.docstatus = 1 {1} and so.company = %s order_by="value desc",
group by so.customer limit=limit,
order by value DESC
limit %s
""".format(
select_field, date_condition
),
(company, cint(limit)),
as_dict=1,
) )
@@ -96,55 +90,58 @@ def get_all_customers(date_range, company, field, limit=None):
def get_all_items(date_range, company, field, limit=None): def get_all_items(date_range, company, field, limit=None):
if field in ("available_stock_qty", "available_stock_value"): if field in ("available_stock_qty", "available_stock_value"):
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)" select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
return frappe.db.get_all( results = frappe.db.get_all(
"Bin", "Bin",
fields=["item_code as name", "{0} as value".format(select_field)], fields=["item_code as name", "{0} as value".format(select_field)],
group_by="item_code", group_by="item_code",
order_by="value desc", order_by="value desc",
limit=limit, limit=limit,
) )
readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
return [item for item in results if item["name"] in readable_active_items]
else: else:
if field == "total_sales_amount": if field == "total_sales_amount":
select_field = "sum(order_item.base_net_amount)" select_field = "base_net_amount"
select_doctype = "Sales Order" select_doctype = "Sales Order"
elif field == "total_purchase_amount": elif field == "total_purchase_amount":
select_field = "sum(order_item.base_net_amount)" select_field = "base_net_amount"
select_doctype = "Purchase Order" select_doctype = "Purchase Order"
elif field == "total_qty_sold": elif field == "total_qty_sold":
select_field = "sum(order_item.stock_qty)" select_field = "stock_qty"
select_doctype = "Sales Order" select_doctype = "Sales Order"
elif field == "total_qty_purchased": elif field == "total_qty_purchased":
select_field = "sum(order_item.stock_qty)" select_field = "stock_qty"
select_doctype = "Purchase Order" select_doctype = "Purchase Order"
date_condition = get_date_condition(date_range, "sales_order.transaction_date") filters = [["docstatus", "=", "1"], ["company", "=", company]]
from_date, to_date = parse_date_range(date_range)
if from_date and to_date:
filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.db.sql( child_doctype = f"{select_doctype} Item"
""" return frappe.get_list(
select order_item.item_code as name, {0} as value select_doctype,
from `tab{1}` sales_order join `tab{1} Item` as order_item fields=[
on sales_order.name = order_item.parent f"`tab{child_doctype}`.item_code as name",
where sales_order.docstatus = 1 f"sum(`tab{child_doctype}`.{select_field}) as value",
and sales_order.company = %s {2} ],
group by order_item.item_code filters=filters,
order by value desc order_by="value desc",
limit %s group_by=f"`tab{child_doctype}`.item_code",
""".format( limit=limit,
select_field, select_doctype, date_condition )
),
(company, cint(limit)),
as_dict=1,
) # nosec
@frappe.whitelist() @frappe.whitelist()
def get_all_suppliers(date_range, company, field, limit=None): def get_all_suppliers(date_range, company, field, limit=None):
filters = [["docstatus", "=", "1"], ["company", "=", company]]
from_date, to_date = parse_date_range(date_range)
if field == "outstanding_amount": if field == "outstanding_amount":
filters = [["docstatus", "=", "1"], ["company", "=", company]] if from_date and to_date:
if date_range: filters.append(["posting_date", "between", [from_date, to_date]])
date_range = frappe.parse_json(date_range)
filters.append(["posting_date", "between", [date_range[0], date_range[1]]]) return frappe.get_list(
return frappe.db.get_all(
"Purchase Invoice", "Purchase Invoice",
fields=["supplier as name", "sum(outstanding_amount) as value"], fields=["supplier as name", "sum(outstanding_amount) as value"],
filters=filters, filters=filters,
@@ -154,48 +151,40 @@ def get_all_suppliers(date_range, company, field, limit=None):
) )
else: else:
if field == "total_purchase_amount": if field == "total_purchase_amount":
select_field = "sum(purchase_order_item.base_net_amount)" select_field = "base_net_total"
elif field == "total_qty_purchased": elif field == "total_qty_purchased":
select_field = "sum(purchase_order_item.stock_qty)" select_field = "total_qty"
date_condition = get_date_condition(date_range, "purchase_order.modified") if from_date and to_date:
filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.db.sql( return frappe.get_list(
""" "Purchase Order",
select purchase_order.supplier as name, {0} as value fields=["supplier as name", f"sum({select_field}) as value"],
FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item` filters=filters,
as purchase_order_item ON purchase_order.name = purchase_order_item.parent group_by="supplier",
where order_by="value desc",
purchase_order.docstatus = 1 limit=limit,
{1} )
and purchase_order.company = %s
group by purchase_order.supplier
order by value DESC
limit %s""".format(
select_field, date_condition
),
(company, cint(limit)),
as_dict=1,
) # nosec
@frappe.whitelist() @frappe.whitelist()
def get_all_sales_partner(date_range, company, field, limit=None): def get_all_sales_partner(date_range, company, field, limit=None):
if field == "total_sales_amount": if field == "total_sales_amount":
select_field = "sum(`base_net_total`)" select_field = "base_net_total"
elif field == "total_commission": elif field == "total_commission":
select_field = "sum(`total_commission`)" select_field = "total_commission"
filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company} filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
if date_range: from_date, to_date = parse_date_range(date_range)
date_range = frappe.parse_json(date_range) if from_date and to_date:
filters["transaction_date"] = ["between", [date_range[0], date_range[1]]] filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.get_list( return frappe.get_list(
"Sales Order", "Sales Order",
fields=[ fields=[
"`sales_partner` as name", "sales_partner as name",
"{} as value".format(select_field), f"sum({select_field}) as value",
], ],
filters=filters, filters=filters,
group_by="sales_partner", group_by="sales_partner",
@@ -206,24 +195,25 @@ def get_all_sales_partner(date_range, company, field, limit=None):
@frappe.whitelist() @frappe.whitelist()
def get_all_sales_person(date_range, company, field=None, limit=0): def get_all_sales_person(date_range, company, field=None, limit=0):
date_condition = get_date_condition(date_range, "sales_order.transaction_date") filters = [
["docstatus", "=", "1"],
["company", "=", company],
["Sales Team", "sales_person", "is", "set"],
]
from_date, to_date = parse_date_range(date_range)
if from_date and to_date:
filters.append(["transaction_date", "between", [from_date, to_date]])
return frappe.db.sql( return frappe.get_list(
""" "Sales Order",
select sales_team.sales_person as name, sum(sales_order.base_net_total) as value fields=[
from `tabSales Order` as sales_order join `tabSales Team` as sales_team "`tabSales Team`.sales_person as name",
on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order' "sum(`tabSales Team`.allocated_amount) as value",
where sales_order.docstatus = 1 ],
and sales_order.company = %s filters=filters,
{date_condition} group_by="`tabSales Team`.sales_person",
group by sales_team.sales_person order_by="value desc",
order by value DESC limit=limit,
limit %s
""".format(
date_condition=date_condition
),
(company, cint(limit)),
as_dict=1,
) )
@@ -236,3 +226,11 @@ def get_date_condition(date_range, field):
field, frappe.db.escape(from_date), frappe.db.escape(to_date) field, frappe.db.escape(from_date), frappe.db.escape(to_date)
) )
return date_condition return date_condition
def parse_date_range(date_range):
if date_range:
date_range = frappe.parse_json(date_range)
return date_range[0], date_range[1]
return None, None

View File

@@ -1184,6 +1184,21 @@ class TestDeliveryNote(FrappeTestCase):
frappe.db.rollback() frappe.db.rollback()
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0) frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
def test_non_internal_transfer_delivery_note(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
dn = create_delivery_note(do_not_submit=True)
warehouse = create_warehouse("Internal Transfer Warehouse", company=dn.company)
dn.items[0].db_set("target_warehouse", warehouse)
dn.reload()
self.assertEqual(dn.items[0].target_warehouse, warehouse)
dn.save()
dn.reload()
self.assertFalse(dn.items[0].target_warehouse)
def create_delivery_note(**args): def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note") dn = frappe.new_doc("Delivery Note")

View File

@@ -742,6 +742,8 @@ def create_item(
opening_stock=0, opening_stock=0,
is_fixed_asset=0, is_fixed_asset=0,
asset_category=None, asset_category=None,
buying_cost_center=None,
selling_cost_center=None,
company="_Test Company", company="_Test Company",
): ):
if not frappe.db.exists("Item", item_code): if not frappe.db.exists("Item", item_code):
@@ -759,7 +761,15 @@ def create_item(
item.is_purchase_item = is_purchase_item item.is_purchase_item = is_purchase_item
item.is_customer_provided_item = is_customer_provided_item item.is_customer_provided_item = is_customer_provided_item
item.customer = customer or "" item.customer = customer or ""
item.append("item_defaults", {"default_warehouse": warehouse, "company": company}) item.append(
"item_defaults",
{
"default_warehouse": warehouse,
"company": company,
"selling_cost_center": selling_cost_center,
"buying_cost_center": buying_cost_center,
},
)
item.save() item.save()
else: else:
item = frappe.get_doc("Item", item_code) item = frappe.get_doc("Item", item_code)

View File

@@ -1069,88 +1069,6 @@ class TestPurchaseReceipt(FrappeTestCase):
pr1.reload() pr1.reload()
pr1.cancel() pr1.cancel()
def test_stock_transfer_from_purchase_receipt(self):
pr1 = make_purchase_receipt(
warehouse="Work In Progress - TCP1", company="_Test Company with perpetual inventory"
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
)
pr.supplier_warehouse = ""
pr.items[0].from_warehouse = "Work In Progress - TCP1"
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
self.assertFalse(gl_entries)
expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
pr.cancel()
pr1.cancel()
def test_stock_transfer_from_purchase_receipt_with_valuation(self):
create_warehouse(
"_Test Warehouse for Valuation",
company="_Test Company with perpetual inventory",
properties={"account": "_Test Account Stock In Hand - TCP1"},
)
pr1 = make_purchase_receipt(
warehouse="_Test Warehouse for Valuation - TCP1",
company="_Test Company with perpetual inventory",
)
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
)
pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1"
pr.supplier_warehouse = ""
pr.append(
"taxes",
{
"charge_type": "On Net Total",
"account_head": "_Test Account Shipping Charges - TCP1",
"category": "Valuation and Total",
"cost_center": "Main - TCP1",
"description": "Test",
"rate": 9,
},
)
pr.submit()
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
sl_entries = get_sl_entries("Purchase Receipt", pr.name)
expected_gle = [
["Stock In Hand - TCP1", 272.5, 0.0],
["_Test Account Stock In Hand - TCP1", 0.0, 250.0],
["_Test Account Shipping Charges - TCP1", 0.0, 22.5],
]
expected_sle = {"_Test Warehouse for Valuation - TCP1": -5, "Stores - TCP1": 5}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
for i, gle in enumerate(gl_entries):
self.assertEqual(gle.account, expected_gle[i][0])
self.assertEqual(gle.debit, expected_gle[i][1])
self.assertEqual(gle.credit, expected_gle[i][2])
pr.cancel()
pr1.cancel()
def test_subcontracted_pr_for_multi_transfer_batches(self): def test_subcontracted_pr_for_multi_transfer_batches(self):
from erpnext.buying.doctype.purchase_order.purchase_order import ( from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_receipt, make_purchase_receipt,
@@ -1996,6 +1914,75 @@ class TestPurchaseReceipt(FrappeTestCase):
ste7.reload() ste7.reload()
self.assertEqual(ste7.items[0].valuation_rate, valuation_rate) self.assertEqual(ste7.items[0].valuation_rate, valuation_rate)
def test_valuation_rate_in_return_purchase_receipt_for_moving_average(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.stock_ledger import get_previous_sle
# Step - 1: Create an Item (Valuation Method = Moving Average)
item_code = make_item(properties={"is_stock_item": 1, "valuation_method": "Moving Average"}).name
# Step - 2: Create a Purchase Receipt (Qty = 10, Rate = 100)
pr = make_purchase_receipt(qty=10, rate=100, item_code=item_code)
# Step - 3: Create a Material Receipt Stock Entry (Qty = 100, Basic Rate = 10)
warehouse = "_Test Warehouse - _TC"
make_stock_entry(
purpose="Material Receipt",
item_code=item_code,
to_warehouse=warehouse,
qty=100,
rate=10,
)
# Step - 4: Create a Material Issue Stock Entry (Qty = 100, Basic Rate = 18.18 [Auto Fetched])
make_stock_entry(
purpose="Material Issue", item_code=item_code, from_warehouse=warehouse, qty=100
)
# Step - 5: Create a Return Purchase Return (Qty = -8, Rate = 100 [Auto fetched])
return_pr = make_purchase_receipt(
is_return=1,
return_against=pr.name,
item_code=item_code,
qty=-8,
)
sle = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_no": return_pr.name, "voucher_detail_no": return_pr.items[0].name},
["posting_date", "posting_time", "outgoing_rate", "valuation_rate"],
as_dict=1,
)
previous_sle_valuation_rate = get_previous_sle(
{
"item_code": item_code,
"warehouse": warehouse,
"posting_date": sle.posting_date,
"posting_time": sle.posting_time,
}
).get("valuation_rate")
# Test - 1: Valuation Rate should be equal to Outgoing Rate
self.assertEqual(flt(sle.outgoing_rate, 2), flt(sle.valuation_rate, 2))
# Test - 2: Valuation Rate should be equal to Previous SLE Valuation Rate
self.assertEqual(flt(sle.valuation_rate, 2), flt(previous_sle_valuation_rate, 2))
def non_internal_transfer_purchase_receipt(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
pr_doc = make_purchase_receipt(do_not_submit=True)
warehouse = create_warehouse("Internal Transfer Warehouse", pr_doc.company)
pr_doc.items[0].db_set("target_warehouse", "warehouse")
pr_doc.reload()
self.assertEqual(pr_doc.items[0].from_warehouse, warehouse.name)
pr_doc.save()
pr_doc.reload()
self.assertFalse(pr_doc.items[0].from_warehouse)
def prepare_data_for_internal_transfer(): def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -5,7 +5,7 @@
from unittest.mock import MagicMock, call from unittest.mock import MagicMock, call
import frappe import frappe
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import nowdate from frappe.utils import nowdate
from frappe.utils.data import add_to_date, today from frappe.utils.data import add_to_date, today
@@ -173,6 +173,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
riv.set_status("Skipped") riv.set_status("Skipped")
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_prevention_of_cancelled_transaction_riv(self): def test_prevention_of_cancelled_transaction_riv(self):
frappe.flags.dont_execute_stock_reposts = True frappe.flags.dont_execute_stock_reposts = True
@@ -295,6 +296,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
accounts_settings.acc_frozen_upto = "" accounts_settings.acc_frozen_upto = ""
accounts_settings.save() accounts_settings.save()
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_create_repost_entry_for_cancelled_document(self): def test_create_repost_entry_for_cancelled_document(self):
pr = make_purchase_receipt( pr = make_purchase_receipt(
company="_Test Company with perpetual inventory", company="_Test Company with perpetual inventory",

View File

@@ -1414,6 +1414,7 @@ class TestStockEntry(FrappeTestCase):
self.assertEqual(se.items[0].item_name, item.item_name) self.assertEqual(se.items[0].item_name, item.item_name)
self.assertEqual(se.items[0].stock_uom, item.stock_uom) self.assertEqual(se.items[0].stock_uom, item.stock_uom)
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_reposting_for_depedent_warehouse(self): def test_reposting_for_depedent_warehouse(self):
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import repost_sl_entries
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

View File

@@ -48,7 +48,7 @@
"label": "Limit timeslot for Stock Reposting" "label": "Limit timeslot for Stock Reposting"
}, },
{ {
"default": "0", "default": "1",
"fieldname": "item_based_reposting", "fieldname": "item_based_reposting",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Use Item based reposting" "label": "Use Item based reposting"
@@ -57,7 +57,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-11-02 01:22:45.155841", "modified": "2023-11-01 16:14:29.080697",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Reposting Settings", "name": "Stock Reposting Settings",
@@ -77,4 +77,4 @@
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1 "track_changes": 1
} }

View File

@@ -751,6 +751,12 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
data = frappe.get_attr(path)(args.get("item_code"), company) data = frappe.get_attr(path)(args.get("item_code"), company)
if data and (data.selling_cost_center or data.buying_cost_center): if data and (data.selling_cost_center or data.buying_cost_center):
if args.get("customer") and data.selling_cost_center:
return data.selling_cost_center
elif args.get("supplier") and data.buying_cost_center:
return data.buying_cost_center
return data.selling_cost_center or data.buying_cost_center return data.selling_cost_center or data.buying_cost_center
if not cost_center and args.get("cost_center"): if not cost_center and args.get("cost_center"):

View File

@@ -13,10 +13,18 @@ const DIFFERENCE_FIELD_NAMES = [
frappe.query_reports["Stock Ledger Variance"] = { frappe.query_reports["Stock Ledger Variance"] = {
"filters": [ "filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"reqd": 1,
"default": frappe.defaults.get_user_default("Company")
},
{ {
"fieldname": "item_code", "fieldname": "item_code",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Item", "label": __("Item"),
"options": "Item", "options": "Item",
get_query: function() { get_query: function() {
return { return {
@@ -27,7 +35,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
{ {
"fieldname": "warehouse", "fieldname": "warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Warehouse", "label": __("Warehouse"),
"options": "Warehouse", "options": "Warehouse",
get_query: function() { get_query: function() {
return { return {
@@ -38,7 +46,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
{ {
"fieldname": "difference_in", "fieldname": "difference_in",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Difference In", "label": __("Difference In"),
"options": [ "options": [
"", "",
"Qty", "Qty",
@@ -49,7 +57,7 @@ frappe.query_reports["Stock Ledger Variance"] = {
{ {
"fieldname": "include_disabled", "fieldname": "include_disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Include Disabled", "label": __("Include Disabled"),
} }
], ],

View File

@@ -55,6 +55,11 @@ def get_columns():
"label": _("Warehouse"), "label": _("Warehouse"),
"options": "Warehouse", "options": "Warehouse",
}, },
{
"fieldname": "valuation_method",
"fieldtype": "Data",
"label": _("Valuation Method"),
},
{ {
"fieldname": "voucher_type", "fieldname": "voucher_type",
"fieldtype": "Link", "fieldtype": "Link",
@@ -194,6 +199,7 @@ def get_columns():
def get_data(filters=None): def get_data(filters=None):
filters = frappe._dict(filters or {}) filters = frappe._dict(filters or {})
item_warehouse_map = get_item_warehouse_combinations(filters) item_warehouse_map = get_item_warehouse_combinations(filters)
valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
data = [] data = []
if item_warehouse_map: if item_warehouse_map:
@@ -206,8 +212,17 @@ def get_data(filters=None):
continue continue
for row in report_data: for row in report_data:
if has_difference(row, precision, filters.difference_in): if has_difference(
data.append(add_item_warehouse_details(row, item_warehouse)) row, precision, filters.difference_in, item_warehouse.valuation_method or valuation_method
):
row.update(
{
"item_code": item_warehouse.item_code,
"warehouse": item_warehouse.warehouse,
"valuation_method": item_warehouse.valuation_method or valuation_method,
}
)
data.append(row)
break break
return data return data
@@ -229,8 +244,14 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict:
.select( .select(
bin.item_code, bin.item_code,
bin.warehouse, bin.warehouse,
item.valuation_method,
)
.where(
(item.is_stock_item == 1)
& (item.has_serial_no == 0)
& (warehouse.is_group == 0)
& (warehouse.company == filters.company)
) )
.where((item.is_stock_item == 1) & (item.has_serial_no == 0) & (warehouse.is_group == 0))
) )
if filters.item_code: if filters.item_code:
@@ -243,37 +264,27 @@ def get_item_warehouse_combinations(filters: dict = None) -> dict:
return query.run(as_dict=1) return query.run(as_dict=1)
def has_difference(row, precision, difference_in): def has_difference(row, precision, difference_in, valuation_method):
has_qty_difference = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision) if valuation_method == "Moving Average":
has_value_difference = ( qty_diff = flt(row.difference_in_qty, precision)
flt(row.diff_value_diff, precision) value_diff = flt(row.diff_value_diff, precision)
or flt(row.fifo_value_diff, precision) valuation_diff = flt(row.valuation_diff, precision)
or flt(row.fifo_difference_diff, precision) else:
) qty_diff = flt(row.difference_in_qty, precision) or flt(row.fifo_qty_diff, precision)
has_valuation_difference = flt(row.valuation_diff, precision) or flt( value_diff = (
row.fifo_valuation_diff, precision flt(row.diff_value_diff, precision)
) or flt(row.fifo_value_diff, precision)
or flt(row.fifo_difference_diff, precision)
)
valuation_diff = flt(row.valuation_diff, precision) or flt(row.fifo_valuation_diff, precision)
if difference_in == "Qty" and has_qty_difference: if difference_in == "Qty" and qty_diff:
return True return True
elif difference_in == "Value" and has_value_difference: elif difference_in == "Value" and value_diff:
return True return True
elif difference_in == "Valuation" and has_valuation_difference: elif difference_in == "Valuation" and valuation_diff:
return True return True
elif difference_in not in ["Qty", "Value", "Valuation"] and ( elif difference_in not in ["Qty", "Value", "Valuation"] and (
has_qty_difference or has_value_difference or has_valuation_difference qty_diff or value_diff or valuation_diff
): ):
return True return True
return False
def add_item_warehouse_details(row, item_warehouse):
row.update(
{
"item_code": item_warehouse.item_code,
"warehouse": item_warehouse.warehouse,
}
)
return row

View File

@@ -658,14 +658,16 @@ class update_entries_after(object):
get_rate_for_return, # don't move this import to top get_rate_for_return, # don't move this import to top
) )
rate = get_rate_for_return( if self.valuation_method == "Moving Average":
sle.voucher_type, rate = flt(self.data[self.args.warehouse].previous_sle.valuation_rate)
sle.voucher_no, else:
sle.item_code, rate = get_rate_for_return(
voucher_detail_no=sle.voucher_detail_no, sle.voucher_type,
sle=sle, sle.voucher_no,
) sle.item_code,
voucher_detail_no=sle.voucher_detail_no,
sle=sle,
)
elif ( elif (
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
and sle.voucher_detail_no and sle.voucher_detail_no

View File

@@ -7,7 +7,7 @@
{% if d.thumbnail or d.image %} {% if d.thumbnail or d.image %}
{{ product_image(d.thumbnail or d.image, no_border=True) }} {{ product_image(d.thumbnail or d.image, no_border=True) }}
{% else %} {% else %}
<div class="no-image-cart-item" style="min-height: 100px;"> <div class="no-image-cart-item" style="min-height: 50px;">
{{ frappe.utils.get_abbr(d.item_name) or "NA" }} {{ frappe.utils.get_abbr(d.item_name) or "NA" }}
</div> </div>
{% endif %} {% endif %}

View File

@@ -81,7 +81,7 @@ rfq = Class.extend({
doc: doc doc: doc
}, },
btn: this, btn: this,
callback: function(r){ callback: function(r) {
frappe.unfreeze(); frappe.unfreeze();
if(r.message){ if(r.message){
$('.btn-sm').hide() $('.btn-sm').hide()

View File

@@ -1,19 +1,25 @@
{% from "erpnext/templates/includes/macros.html" import product_image_square, product_image %} {% from "erpnext/templates/includes/macros.html" import product_image_square, product_image %}
{% macro item_name_and_description(d, doc) %} {% macro item_name_and_description(d, doc) %}
<div class="row"> <div class="row">
<div class="col-3"> <div class="col-3">
{{ product_image(d.image) }} {% if d.image %}
</div> {{ product_image(d.image) }}
<div class="col-9"> {% else %}
{{ d.item_code }} <div class="website-image h-100 w-100" style="background-color:var(--gray-100);text-align: center;line-height: 3.6;">
<p class="text-muted small">{{ d.description }}</p> {{ frappe.utils.get_abbr(d.item_name)}}
</div>
{% endif %}
</div>
<div class="col-9">
{{ d.item_code }}
<p class="text-muted small">{{ d.description }}</p>
{% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %} {% set supplier_part_no = frappe.db.get_value("Item Supplier", {'parent': d.item_code, 'supplier': doc.supplier}, "supplier_part_no") %}
<p class="text-muted small supplier-part-no"> <p class="text-muted small supplier-part-no">
{% if supplier_part_no %} {% if supplier_part_no %}
{{_("Supplier Part No") + ": "+ supplier_part_no}} {{_("Supplier Part No") + ": "+ supplier_part_no}}
{% endif %} {% endif %}
</p> </p>
</div> </div>
</div> </div>
{% endmacro %} {% endmacro %}

View File

@@ -153,7 +153,6 @@
</div> </div>
{% endif %} {% endif %}
{% if attachments %} {% if attachments %}
<div class="order-item-table"> <div class="order-item-table">
<div class="row order-items order-item-header text-muted"> <div class="row order-items order-item-header text-muted">
@@ -181,6 +180,7 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block script %} {% block script %}
<script> {% include "templates/pages/order.js" %} </script> <script> {% include "templates/pages/order.js" %} </script>
<script> <script>

View File

@@ -1,7 +1,7 @@
{% extends "templates/web.html" %} {% extends "templates/web.html" %}
{% block header %} {% block header %}
<h1>{{ doc.name }}</h1> <h1 style="margin-top: 10px;">{{ doc.name }}</h1>
{% endblock %} {% endblock %}
{% block script %} {% block script %}
@@ -16,7 +16,7 @@
{% if doc.items %} {% if doc.items %}
<button class="btn btn-primary btn-sm" <button class="btn btn-primary btn-sm"
type="button"> type="button">
{{ _("Submit") }}</button> {{ _("Make Quotation") }}</button>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@@ -369,7 +369,7 @@ Base,Basis,
Base URL,Basis-URL, Base URL,Basis-URL,
Based On,Basiert auf, Based On,Basiert auf,
Based On Payment Terms,Basierend auf Zahlungsbedingungen, Based On Payment Terms,Basierend auf Zahlungsbedingungen,
Basic,Grundeinkommen, Basic,Basic,
Batch,Charge, Batch,Charge,
Batch Entries,Batch-Einträge, Batch Entries,Batch-Einträge,
Batch ID is mandatory,Batch-ID ist obligatorisch, Batch ID is mandatory,Batch-ID ist obligatorisch,
Can't render this file because it is too large.

View File

@@ -4,7 +4,7 @@ googlemaps # used in ERPNext, but dependency is defined in Frappe
pandas>=1.1.5,<2.0.0 pandas>=1.1.5,<2.0.0
plaid-python~=7.2.1 plaid-python~=7.2.1
pycountry~=20.7.3 pycountry~=20.7.3
PyGithub~=1.54.1 PyGithub~=2.1.1
python-stdnum~=1.16 python-stdnum~=1.16
python-youtube~=0.8.0 python-youtube~=0.8.0
taxjar~=1.9.2 taxjar~=1.9.2