Compare commits

...

75 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
35 changed files with 596 additions and 361 deletions

View File

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

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = "13.54.7"
__version__ = "13.55.2"
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"]:
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
ref_doc.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))
def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
}
)
if self.references:
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
}
)
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
for d in self.get("references"):
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
for d in self.get("references"):
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif latest.outstanding_amount < latest.invoice_amount and flt(
d.outstanding_amount, d.precision("outstanding_amount")
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
)
# The reference has already been partly paid
elif latest.outstanding_amount < latest.invoice_amount and flt(
d.outstanding_amount, d.precision("outstanding_amount")
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
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."
).format(_(d.reference_doctype), d.reference_name)
)
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."
).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):
frappe.throw(fail_message.format(d.idx))
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self):
for reference in self.references:

View File

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

View File

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

View File

@@ -226,7 +226,9 @@ def set_address_details(
party_details.update(
{
"shipping_address": shipping_address,
"shipping_address_display": render_address(shipping_address),
"shipping_address_display": render_address(
shipping_address, check_permissions=not ignore_permissions
),
**get_fetch_values(doctype, "shipping_address", shipping_address),
}
)

View File

@@ -4,7 +4,7 @@
import frappe
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 erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
@@ -187,7 +187,9 @@ class BuyingController(StockController, Subcontracting):
for address_field, address_display_field in address_dict.items():
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):
from frappe.utils import money_in_words

View File

@@ -914,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.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.warehouse = sle.warehouse

View File

@@ -296,18 +296,27 @@ class LeaveAllocation(Document):
def get_previous_allocation(from_date, leave_type, employee):
"""Returns document properties of previous allocation"""
return frappe.db.get_value(
"Leave Allocation",
filters={
"to_date": ("<", from_date),
"leave_type": leave_type,
"employee": employee,
"docstatus": 1,
},
order_by="to_date DESC",
fieldname=["name", "from_date", "to_date", "employee", "leave_type"],
as_dict=1,
)
Allocation = frappe.qb.DocType("Leave Allocation")
allocations = (
frappe.qb.from_(Allocation)
.select(
Allocation.name,
Allocation.from_date,
Allocation.to_date,
Allocation.employee,
Allocation.leave_type,
)
.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(

View File

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

View File

@@ -713,25 +713,31 @@ class TestLeaveApplication(unittest.TestCase):
self.assertEqual(details.leave_balance, 30)
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()
leave_type = "Test Earned Leave Type"
make_policy_assignment(employee, leave_type, leave_period)
for i in range(0, 14):
allocate_earned_leaves()
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6)
# leaves for 6 months = 3, but max leaves restricts allocation to 2
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, frappe.flags.current_date), 2)
# validate earned leaves creation without maximum leaves
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):
allocate_earned_leaves()
self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
frappe.flags.current_date = None
# test to not consider current leave in leave balance while submitting
def test_current_leave_on_submit(self):
@@ -1254,7 +1260,7 @@ def set_leave_approver():
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"})
if leave_period_name:
return frappe.get_doc("Leave Period", leave_period_name[0][0])
@@ -1263,8 +1269,8 @@ def get_leave_period():
dict(
name="Test Leave Period",
doctype="Leave Period",
from_date=add_months(nowdate(), -6),
to_date=add_months(nowdate(), 6),
from_date=from_date or add_months(nowdate(), -6),
to_date=to_date or add_months(nowdate(), 6),
company="_Test Company",
is_active=1,
)

View File

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

View File

@@ -225,3 +225,7 @@ def expire_carried_forward_allocation(allocation):
to_date=allocation.to_date,
)
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
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
carry_forward = self.carry_forward
@@ -108,7 +108,7 @@ class LeavePolicyAssignment(Document):
carry_forward = 0
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(
@@ -129,7 +129,7 @@ class LeavePolicyAssignment(Document):
allocation.submit()
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
precision = get_field_precision(
@@ -146,20 +146,27 @@ class LeavePolicyAssignment(Document):
else:
# get leaves for past months if assignment is based on Leave Period / Joining Date
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
elif getdate(date_of_joining) > getdate(self.effective_from):
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)
else:
if getdate(date_of_joining) > getdate(self.effective_from):
remaining_period = (date_diff(self.effective_to, date_of_joining) + 1) / (
date_diff(self.effective_to, self.effective_from) + 1
)
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)
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
@@ -184,7 +191,7 @@ class LeavePolicyAssignment(Document):
if months_passed > 0:
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).rounding,
)

View File

@@ -5,8 +5,10 @@ import unittest
import frappe
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 (
get_employee,
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 (
create_assignment_for_multiple_employees,
)
from erpnext.hr.utils import allocate_earned_leaves
test_dependencies = ["Employee"]
@@ -34,6 +37,8 @@ class TestLeavePolicyAssignment(FrappeTestCase):
self.original_doj = employee.date_of_joining
self.employee = employee
self.leave_type = "Test Earned Leave"
def test_grant_leaves(self):
leave_period = get_leave_period()
# allocation = 10
@@ -326,6 +331,90 @@ class TestLeavePolicyAssignment(FrappeTestCase):
self.assertEqual(effective_from, self.employee.date_of_joining)
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):
frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
frappe.flags.current_date = None
@@ -376,3 +465,51 @@ def setup_leave_period_and_policy(start_date, based_on_doj=False):
).insert()
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,
"apply_user_permissions": 1,
"creation": "2013-02-22 15:29:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:18:04.317397",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Leave Balance",
"owner": "Administrator",
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"add_total_row": 0,
"columns": [],
"creation": "2013-02-22 15:29:34",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"is_standard": "Yes",
"letterhead": null,
"modified": "2023-11-17 13:28:40.669200",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Leave Balance",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Employee",
"report_name": "Employee Leave Balance",
"report_type": "Script Report",
"roles": [
{
"role": "HR User"
},
},
{
"role": "HR Manager"
},
{
"role": "Employee"
}
]
}

View File

@@ -85,19 +85,10 @@ def get_columns() -> List[Dict]:
def get_data(filters: Filters) -> List:
leave_types = frappe.db.get_list("Leave Type", pluck="name", order_by="name")
conditions = get_conditions(filters)
leave_types = get_leave_types()
active_employees = get_employees(filters)
user = frappe.session.user
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))
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
consolidate_leave_types = len(active_employees) > 1 and filters.consolidate_leave_types
row = None
@@ -110,10 +101,6 @@ def get_data(filters: Filters) -> List:
row = frappe._dict({"leave_type": leave_type})
for employee in active_employees:
leave_approvers = department_approver_map.get(employee.department_name, []).append(
employee.leave_approver
)
if consolidate_leave_types:
row = frappe._dict()
else:
@@ -144,6 +131,35 @@ def get_data(filters: Filters) -> List:
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(
employee: str, leave_type: str, filters: Filters, carry_forwarded_leaves: float
) -> float:
@@ -168,48 +184,6 @@ def get_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(
from_date: str, to_date: str, employee: str, leave_type: str
) -> Tuple[float, float, float]:
@@ -244,7 +218,7 @@ def get_leave_ledger_entries(
from_date: str, to_date: str, employee: str, leave_type: str
) -> List[Dict]:
ledger = frappe.qb.DocType("Leave Ledger Entry")
records = (
return (
frappe.qb.from_(ledger)
.select(
ledger.employee,
@@ -270,8 +244,6 @@ def get_leave_ledger_entries(
)
).run(as_dict=True)
return records
def get_chart_data(data: List, filters: Filters) -> Dict:
labels = []

View File

@@ -6,9 +6,6 @@ import frappe
from frappe import _
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):
@@ -54,17 +51,11 @@ def get_data(filters, leave_types):
active_employees = frappe.get_all(
"Employee",
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 = []
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]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:

View File

@@ -459,7 +459,7 @@ def generate_leave_encashment():
def allocate_earned_leaves():
"""Allocate earned leaves to Employees"""
e_leave_types = get_earned_leaves()
today = getdate()
today = frappe.flags.current_date or getdate()
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):
allocation = frappe.get_doc("Leave Allocation", allocation.name)
annual_allocation = flt(annual_allocation, allocation.precision("total_leaves_allocated"))
earned_leaves = get_monthly_earned_leave(
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_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:
new_allocation = e_leave_type.max_leaves_allowed
if new_allocation != allocation.total_leaves_allocated:
today_date = today()
if (
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)
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):
last_disbursement_date = frappe.db.get_value(
"Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
{"docstatus": 1, "against_loan": loan, "posting_date": ("<=", posting_date)},
"MAX(posting_date)",
)

View File

@@ -3,23 +3,27 @@ import frappe
def execute():
frappe.reload_doc("stock", "doctype", "quality_inspection_parameter")
params = set()
# get all distinct parameters from QI readigs table
reading_params = frappe.db.get_all(
"Quality Inspection Reading", fields=["distinct specification"]
)
reading_params = [d.specification for d in reading_params]
# get all parameters from QI readings table
for (p,) in frappe.db.get_all(
"Quality Inspection Reading", fields=["specification"], as_list=True
):
params.add(p.strip())
# get all distinct parameters from QI Template as some may be unused in QI
template_params = frappe.db.get_all(
"Item Quality Inspection Parameter", fields=["distinct specification"]
)
template_params = [d.specification for d in template_params]
# get all parameters from QI Template as some may be unused in QI
for (p,) in frappe.db.get_all(
"Item Quality Inspection Parameter", fields=["specification"], as_list=True
):
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:
if not frappe.db.exists("Quality Inspection Parameter", parameter):
frappe.get_doc(
{"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
).insert(ignore_permissions=True)
if frappe.db.exists("Quality Inspection Parameter", parameter):
continue
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.mode_of_payment = loan.mode_of_payment
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.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account

View File

@@ -71,6 +71,12 @@ class Timesheet(Document):
if args.is_billable:
if flt(args.billing_hours) == 0.0:
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:
args.billing_hours = 0

View File

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

View File

@@ -1378,7 +1378,7 @@ class GSPConnector:
def set_einvoice_data(self, res):
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.ewaybill = res.get("EwbNo")

View File

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

View File

@@ -3,11 +3,11 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.query_builder import Criterion
from erpnext import get_default_company
from erpnext.accounts.party import get_party_details
from erpnext.stock.get_item_details import get_price_list_rate_for
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):
data = []
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"
)
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:
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)
data.append(

View File

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

@@ -5,7 +5,7 @@
from unittest.mock import MagicMock, call
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import nowdate
from frappe.utils.data import add_to_date, today
@@ -173,6 +173,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
riv.set_status("Skipped")
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_prevention_of_cancelled_transaction_riv(self):
frappe.flags.dont_execute_stock_reposts = True
@@ -295,6 +296,7 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
accounts_settings.acc_frozen_upto = ""
accounts_settings.save()
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
def test_create_repost_entry_for_cancelled_document(self):
pr = make_purchase_receipt(
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].stock_uom, item.stock_uom)
@change_settings("Stock Reposting Settings", {"item_based_reposting": 0})
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.warehouse.test_warehouse import create_warehouse

View File

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

View File

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

View File

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

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