Compare commits

...

128 Commits

Author SHA1 Message Date
Frappe PR Bot
4a95c9d642 chore(release): Bumped to Version 13.46.1
## [13.46.1](https://github.com/frappe/erpnext/compare/v13.46.0...v13.46.1) (2023-02-15)

### Bug Fixes

* asset_depreciation_and_balances report doesn't reflect manual depr entries ([62dc68b](62dc68bb57))
* opening_accumulated_depreciation and precision in charts ([6308fca](6308fca587))
2023-02-15 10:33:52 +00:00
Anand Baburajan
f6707b2b92 Merge pull request #34075 from frappe/mergify/bp/version-13/pr-34073
fix: manual depr entries in asset_depreciations_and_balances report and some misc bugs [v14] (backport #34058) (backport #34073)
2023-02-15 16:02:25 +05:30
anandbaburajan
62dc68bb57 fix: asset_depreciation_and_balances report doesn't reflect manual depr entries
(cherry picked from commit 1535c3d856)
(cherry picked from commit 6227c16374)
2023-02-15 08:36:50 +00:00
anandbaburajan
f80fb97c71 chore: break look if je processed
(cherry picked from commit a220dc0c9c)
(cherry picked from commit ff2e617c0c)
2023-02-15 08:36:50 +00:00
anandbaburajan
6308fca587 fix: opening_accumulated_depreciation and precision in charts
(cherry picked from commit 47cc8ab6c6)
(cherry picked from commit 4f10f48f7c)
2023-02-15 08:36:50 +00:00
Frappe PR Bot
ab71a7bba8 chore(release): Bumped to Version 13.46.0
# [13.46.0](https://github.com/frappe/erpnext/compare/v13.45.1...v13.46.0) (2023-02-14)

### Bug Fixes

* `amount` in `Material Request` ([813e8bb](813e8bb664))
* allow PI cancel if linked asset is cancelled ([fbeaabf](fbeaabffc9))
* conflicts ([a9f5be3](a9f5be3f98))
* currency formatting in item-wise sales history ([#33903](https://github.com/frappe/erpnext/issues/33903)) ([f641039](f6410393ce))
* Debit and Credit not equal while submitting PI containing asset item ([#33092](https://github.com/frappe/erpnext/issues/33092)) ([5be4c6f](5be4c6ffbc))
* german chart of accounts "SKR03" ([#33909](https://github.com/frappe/erpnext/issues/33909)) ([b2a3e01](b2a3e014e9))
* incorrect actual qty in Bin ([8f42833](8f42833fba))
* LWP calculation ([c1de4e4](c1de4e4420))
* negative stock error ([2f4ffe1](2f4ffe137e))
* stock entry from item dashboard (stock levels) ([8106c64](8106c64c91))
* **UX:** make Item attachments public by default (backport [#32196](https://github.com/frappe/erpnext/issues/32196)) ([#33949](https://github.com/frappe/erpnext/issues/33949)) ([124d7de](124d7dea1b))
* validate working day list against holidays ([a8ea3ef](a8ea3efae2))

### Features

* Setting to allow Sales Order creation against expired quotation ([#33952](https://github.com/frappe/erpnext/issues/33952)) ([f04542e](f04542eac9))
2023-02-14 10:40:00 +00:00
Deepesh Garg
958a3320e8 Merge pull request #34052 from frappe/version-13-hotfix
chore: release v13
2023-02-14 16:07:17 +05:30
Saurabh
60d2bf939b Merge pull request #34041 from saurabh6790/fix-lwp-calculations
fix: LWP calculation
2023-02-14 15:26:01 +05:30
Saurabh
a8ea3efae2 fix: validate working day list against holidays 2023-02-14 13:14:58 +05:30
Saurabh
c1de4e4420 fix: LWP calculation 2023-02-14 10:52:10 +05:30
mergify[bot]
f04542eac9 feat: Setting to allow Sales Order creation against expired quotation (#33952)
* feat: Setting to allow Sales Order creation against expired quotation (#33952)

* feat: Setting to allow Sales Order creation against expired quotation

* chore: linting issues

(cherry picked from commit 148703bfc2)

# Conflicts:
#	erpnext/selling/doctype/selling_settings/selling_settings.json

* chore: Resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-13 11:39:30 +05:30
mergify[bot]
f6410393ce fix: currency formatting in item-wise sales history (#33903)
fix: currency formatting in item-wise sales history (#33903)

* fix(item-sales-history): currency formatting

* chore: linting issues

* fix: convert raw sql to qb

(cherry picked from commit 2cc7239dd5)

Co-authored-by: Dany Robert <danyrt@wahni.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-11 11:10:58 +05:30
mergify[bot]
5be4c6ffbc fix: Debit and Credit not equal while submitting PI containing asset item (#33092)
fix: Debit and Credit not equal while submitting PI containing asset item

(cherry picked from commit dc8d635120)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-11 09:02:06 +05:30
mergify[bot]
124d7dea1b fix(UX): make Item attachments public by default (backport #32196) (#33949)
* fix(UX): make Item attachments public by default (#32196)

(cherry picked from commit fffc245922)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-02-10 21:39:33 +05:30
mergify[bot]
bbcdd1e2e2 chore: typo in stock_entry get_uom_details (backport #33998) (#34004)
chore: typo in stock_entry get_uom_details (#33998)

fix: typo in stock_entry get_uom_details
(cherry picked from commit 185c543b73)

Co-authored-by: Akshay <60477442+akshayitzme@users.noreply.github.com>
2023-02-10 20:55:52 +05:30
Anand Baburajan
b6bc29ac92 Merge pull request #33963 from frappe/mergify/bp/version-13-hotfix/pr-33946
fix: allow cancelling purchase invoice if linked asset is already cancelled (backport #33946)
2023-02-10 14:15:44 +05:30
Anand Baburajan
e9a453c430 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-33946 2023-02-08 23:23:31 +05:30
rohitwaghchaure
7e1d5e3595 Merge pull request #33956 from frappe/mergify/bp/version-13-hotfix/pr-33936
fix: negative stock error (backport #33936)
2023-02-07 19:13:35 +05:30
rohitwaghchaure
a9f5be3f98 fix: conflicts 2023-02-07 13:46:40 +05:30
anandbaburajan
d6b0e622ea chore: use continue, not break
(cherry picked from commit 3380dc5dea)
2023-02-05 10:38:00 +00:00
anandbaburajan
fbeaabffc9 fix: allow PI cancel if linked asset is cancelled
(cherry picked from commit b961321de5)
2023-02-05 10:38:00 +00:00
Sagar Sharma
c7c611d929 Merge pull request #33959 from frappe/mergify/bp/version-13-hotfix/pr-33942
fix: stock entry from item dashboard (stock levels) (backport #33942)
2023-02-05 10:06:14 +05:30
s-aga-r
8106c64c91 fix: stock entry from item dashboard (stock levels)
(cherry picked from commit dc0ddf8d7e)
2023-02-05 04:17:35 +00:00
Rohit Waghchaure
bd1191783b test: test case
(cherry picked from commit 9ae7578b07)
2023-02-04 18:06:07 +00:00
Rohit Waghchaure
2f4ffe137e fix: negative stock error
(cherry picked from commit 6d513e2519)

# Conflicts:
#	erpnext/stock/stock_ledger.py
2023-02-04 18:06:07 +00:00
Sagar Sharma
6735b09dd9 Merge pull request #33951 from frappe/mergify/bp/version-13-hotfix/pr-33940
chore: report `Warehouse wise Item Balance Age and Value` (backport #33940)
2023-02-04 18:58:46 +05:30
s-aga-r
745bef8ebc chore: conflicts 2023-02-04 13:51:17 +05:30
s-aga-r
5a9673ae1f chore: add Item Name column in Warehouse wise Item Balance Age and Value report
(cherry picked from commit 56356ffbb9)
2023-02-04 07:59:04 +00:00
s-aga-r
8d0b45b835 chore: column width in Warehouse wise Item Balance Age and Value report
(cherry picked from commit d7a665cb84)

# Conflicts:
#	erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
2023-02-04 07:59:04 +00:00
Frappe PR Bot
6a9660de65 chore(release): Bumped to Version 13.45.1
## [13.45.1](https://github.com/frappe/erpnext/compare/v13.45.0...v13.45.1) (2023-02-01)

### Bug Fixes

* incorrect actual qty in Bin ([e3ad0b1](e3ad0b1655))
2023-02-01 17:46:11 +00:00
rohitwaghchaure
edbbb2469f Merge pull request #33928 from frappe/mergify/bp/version-13/pr-33922
fix: incorrect actual qty in Bin (backport #33918) (backport #33922)
2023-02-01 23:14:23 +05:30
Rohit Waghchaure
e3ad0b1655 fix: incorrect actual qty in Bin
(cherry picked from commit f8c852c54c)
(cherry picked from commit 8f42833fba)
2023-02-01 16:51:50 +00:00
rohitwaghchaure
7738ca1ce0 Merge pull request #33922 from frappe/mergify/bp/version-13-hotfix/pr-33918
fix: incorrect actual qty in Bin (backport #33918)
2023-02-01 22:20:31 +05:30
Rohit Waghchaure
8f42833fba fix: incorrect actual qty in Bin
(cherry picked from commit f8c852c54c)
2023-02-01 12:54:28 +00:00
mergify[bot]
b2a3e014e9 fix: german chart of accounts "SKR03" (#33909)
fix: german chart of accounts "SKR03" (#33909)

* fix: german chart of accounts "SKR03"

- Added some missing account types and tax rates
- Added some missing accounts

* style: convert indentation to tabs

* fix: space before percentage sign

* feat: add some expense accounts

* refactor: replace unicode characters with utf-8

for better readability

* revert: add back groups for Bank and Cash accounts

Removed in 7d0d9c6900

(cherry picked from commit 3c7b460fd8)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-02-01 14:17:59 +05:30
Sagar Sharma
64018c29f3 Merge pull request #33898 from frappe/mergify/bp/version-13-hotfix/pr-33869
fix: `amount` in `Material Request` (backport #33869)
2023-01-31 16:01:49 +05:30
s-aga-r
813e8bb664 fix: amount in Material Request
(cherry picked from commit 6b781d78e0)
2023-01-31 09:15:48 +00:00
Frappe PR Bot
81e4be37ff chore(release): Bumped to Version 13.45.0
# [13.45.0](https://github.com/frappe/erpnext/compare/v13.44.0...v13.45.0) (2023-01-31)

### Bug Fixes

* disposal_was_made_on_original_schedule_date ([939a312](939a3121b7))
* enable customs in Selling Workpace by default ([#33853](https://github.com/frappe/erpnext/issues/33853)) ([54c1642](54c1642e3b))
* Fetch commission rate from sales partner ([#33851](https://github.com/frappe/erpnext/issues/33851)) ([3425a3b](3425a3bef9))
* **gp:** fetch buying amount from dn related to so ([be5edd3](be5edd329f))
* item rate not fetching ([bb56415](bb5641535b))
* manual depr entry not updating asset value [v13] ([#33890](https://github.com/frappe/erpnext/issues/33890)) ([f5efb20](f5efb2057c))
* use correct filter name in `item_query` (backport [#33814](https://github.com/frappe/erpnext/issues/33814)) ([#33817](https://github.com/frappe/erpnext/issues/33817)) ([b38ad66](b38ad66012))

### Features

* **gp:** test for inv and dn related via so ([b72a35a](b72a35a622))

### Performance Improvements

* show update items dialog ([0ff5099](0ff5099cbc))
* Timeout while doing payment reconciliation (v13) ([#33818](https://github.com/frappe/erpnext/issues/33818)) ([4bf3e31](4bf3e310e1))
2023-01-31 06:19:21 +00:00
Deepesh Garg
62edb118eb Merge pull request #33872 from frappe/version-13-hotfix
chore: release v13
2023-01-31 11:47:48 +05:30
Anand Baburajan
f5efb2057c fix: manual depr entry not updating asset value [v13] (#33890)
fix: asset value for manual depr entries
2023-01-31 11:00:06 +05:30
mergify[bot]
3425a3bef9 fix: Fetch commission rate from sales partner (#33851)
* fix: Fetch commission rate from sales partner (#33851)
2023-01-31 10:17:05 +05:30
Deepesh Garg
4bf3e310e1 perf: Timeout while doing payment reconciliation (v13) (#33818)
perf: Timeout while doing payment reconciliation
2023-01-31 09:37:45 +05:30
ruthra kumar
abb466e2fb Merge pull request #33876 from frappe/mergify/bp/version-13-hotfix/pr-33736
fix(gp): fetch buying amount from dn related to so (backport #33736)
2023-01-31 09:29:56 +05:30
HENRY Florian
54c1642e3b fix: enable customs in Selling Workpace by default (#33853)
* fix: enable customs in Selling Workpace by default

* fix: enable customs in Selling Workpace by default
2023-01-31 08:50:34 +05:30
mergify[bot]
b6839d8f51 ci: bump isort to 5.12.0 (backport #33875) (#33880)
ci: bump isort to 5.12.0 (#33875)

[skip ci]

(cherry picked from commit 2bad86d8d8)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-01-31 08:49:28 +05:30
Anand Baburajan
af3ad155e5 Merge pull request #33885 from AnandBaburajan/fix_disposal_was_made_on_original_schedule_date_v13
fix: disposal_was_made_on_original_schedule_date [v13]
2023-01-30 22:55:34 +05:30
anandbaburajan
939a3121b7 fix: disposal_was_made_on_original_schedule_date 2023-01-30 22:10:51 +05:30
Dany Robert
ac6186e16f chore: linting issues
(cherry picked from commit d69c839369)
2023-01-30 13:35:51 +00:00
Dany Robert
b72a35a622 feat(gp): test for inv and dn related via so
(cherry picked from commit 1f6ab86a65)
2023-01-30 13:35:50 +00:00
Dany Robert
0d8a4bf936 chore: linting issue
(cherry picked from commit ef90e24931)
2023-01-30 13:35:50 +00:00
Dany Robert
be5edd329f fix(gp): fetch buying amount from dn related to so
(cherry picked from commit e8e20da78e)
2023-01-30 13:35:50 +00:00
rohitwaghchaure
0902a5c440 Merge pull request #33847 from frappe/mergify/bp/version-13-hotfix/pr-33845
fix: item rate not fetching (backport #33845)
2023-01-28 22:41:06 +05:30
Rohit Waghchaure
bb5641535b fix: item rate not fetching
(cherry picked from commit 0d7f98b496)
2023-01-28 09:41:20 +00:00
Sagar Vora
fc7aac9d41 Merge pull request #33833 from frappe/mergify/bp/version-13-hotfix/pr-33831
perf: show update items dialog (backport #33831)
2023-01-27 04:19:30 +00:00
Devin Slauenwhite
0ff5099cbc perf: show update items dialog
(cherry picked from commit a835c1a418)
2023-01-27 04:18:50 +00:00
mergify[bot]
b38ad66012 fix: use correct filter name in item_query (backport #33814) (#33817)
fix: use correct filter name in `item_query` (#33814)

(cherry picked from commit da323cbb40)

Co-authored-by: Daizy Modi <modidaizy5217@gmail.com>
2023-01-25 17:38:03 +05:30
Frappe PR Bot
71395b9a8e chore(release): Bumped to Version 13.44.0
# [13.44.0](https://github.com/frappe/erpnext/compare/v13.43.2...v13.44.0) (2023-01-25)

### Bug Fixes

* accumulated_depreciation in reverse_depreciation_entry_made_after_disposal ([b7e9e4a](b7e9e4a7c5))
* backport of [#32226](https://github.com/frappe/erpnext/issues/32226) ([d6913ff](d6913fffe6))
* calculate correct amount for qty == 0 (backport [#33739](https://github.com/frappe/erpnext/issues/33739)) ([#33752](https://github.com/frappe/erpnext/issues/33752)) ([d650432](d6504320b1))
* conflicts ([d717ca0](d717ca0325))
* conflicts ([055f853](055f8536c3))
* don't add template item in sales/purchase transaction ([f81d4a7](f81d4a79ea))
* e-Invoicing for SEZ Customer(v13) ([#33796](https://github.com/frappe/erpnext/issues/33796)) ([1b11566](1b11566485))
* **ecommerce:** breadcrumb: fallback to `/all-products` ([#33718](https://github.com/frappe/erpnext/issues/33718)) ([2da543e](2da543ebd4))
* fb issue in asset chart ([ae031ce](ae031cea63))
* incorrect actual qty for the packed item ([09e13d2](09e13d279c))
* incorrect row order and accumulated_depreciation when schedule with multiple FBs is scrapped ([7174a2c](7174a2cd93))
* linter issue ([593d7f3](593d7f3dd6))
* linting ([13906cb](13906cba9a))
* **minor:** Label updates in Statement of Accounts ([#33639](https://github.com/frappe/erpnext/issues/33639)) ([47e500c](47e500c2eb))
* missing constant definition ([fc4be1b](fc4be1b337))
* patch item_reposting_for_incorrect_sl_and_gl ([1c5c067](1c5c06716b))
* rewrite logic for duplicate check in Item Attribute ([4741ce1](4741ce13c6))
* Short closed order, receipt and delivery note status on cancellation ([#33743](https://github.com/frappe/erpnext/issues/33743)) ([3daaa02](3daaa021eb))

### Features

* provision to select date type based on filter ([5ed6a74](5ed6a74fc4))
2023-01-25 04:00:37 +00:00
Deepesh Garg
d3aa37aece Merge pull request #33801 from frappe/version-13-hotfix
chore: release v13
2023-01-25 09:29:03 +05:30
Deepesh Garg
1b11566485 fix: e-Invoicing for SEZ Customer(v13) (#33796) 2023-01-25 08:57:52 +05:30
mergify[bot]
28f5d28201 ci: documentation helper (backport #33757) (#33799)
ci: documentation helper (#33757)

refactor: documentation helper
(cherry picked from commit d155042edd)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-01-24 16:47:13 +05:30
Anand Baburajan
421814e9b3 Merge pull request #33773 from AnandBaburajan/asset_bug_fixes_13
fix: backport #32226 and fix some asset bugs related to finance books [v13]
2023-01-23 21:09:05 +05:30
Anand Baburajan
814333b0cc Merge branch 'version-13-hotfix' into asset_bug_fixes_13 2023-01-23 14:22:46 +05:30
rohitwaghchaure
0862f670ee Merge pull request #33787 from frappe/mergify/bp/version-13-hotfix/pr-33684
feat: [minor] date type based on filter in Work Order Summary report (backport #33684)
2023-01-23 14:16:43 +05:30
rohitwaghchaure
593d7f3dd6 fix: linter issue 2023-01-23 13:22:24 +05:30
Rohit Waghchaure
5ed6a74fc4 feat: provision to select date type based on filter
(cherry picked from commit 20c8873208)
2023-01-23 07:44:08 +00:00
Anand Baburajan
76b6833b61 Merge branch 'version-13-hotfix' into asset_bug_fixes_13 2023-01-22 13:57:31 +05:30
anandbaburajan
b7e9e4a7c5 fix: accumulated_depreciation in reverse_depreciation_entry_made_after_disposal 2023-01-22 13:56:56 +05:30
Sagar Sharma
54b2f78a99 Merge pull request #33780 from frappe/mergify/bp/version-13-hotfix/pr-33778
fix: missing constant definition (backport #33778)
2023-01-21 22:32:59 +05:30
barredterra
fc4be1b337 fix: missing constant definition
(cherry picked from commit 547d37b1db)
2023-01-21 16:59:37 +00:00
Anand Baburajan
b9b110674e Merge branch 'version-13-hotfix' into asset_bug_fixes_13 2023-01-21 20:04:20 +05:30
anandbaburajan
7959e41a81 chore: fix circular import issue and rename date_of_sale to date_of_disposal 2023-01-21 19:58:26 +05:30
anandbaburajan
d6913fffe6 fix: backport of #32226 2023-01-21 19:29:39 +05:30
anandbaburajan
7174a2cd93 fix: incorrect row order and accumulated_depreciation when schedule with multiple FBs is scrapped 2023-01-21 18:41:19 +05:30
mergify[bot]
47e500c2eb fix(minor): Label updates in Statement of Accounts (#33639)
fix(minor): Label updates in Statement of Accounts (#33639)
2023-01-21 15:44:27 +05:30
mergify[bot]
4511d41329 Removed an unnecessary check in code which always evaluates to true (#33710)
fix: removed an unnecessary check which always evaluates to true
2023-01-21 12:03:33 +05:30
rohitwaghchaure
7243f71d7d Merge pull request #33762 from frappe/mergify/bp/version-13-hotfix/pr-33759
fix: incorrect actual qty for the packed item (backport #33759)
2023-01-21 09:46:59 +05:30
mergify[bot]
3daaa021eb fix: Short closed order, receipt and delivery note status on cancellation (#33743)
fix: Short closed order, receipt, and delivery note status on cancellation (#33743)
2023-01-20 23:41:14 +05:30
Rohit Waghchaure
09e13d279c fix: incorrect actual qty for the packed item
(cherry picked from commit 02566a02a8)
2023-01-20 18:10:16 +00:00
mergify[bot]
d6504320b1 fix: calculate correct amount for qty == 0 (backport #33739) (#33752)
fix: calculate correct amount for qty == 0 (#33739)

(cherry picked from commit 327b6fdb32)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-01-20 23:38:37 +05:30
Raffael Meyer
af3a0e56f6 chore: bump python version for docs-checker (#33756) 2023-01-20 22:52:33 +05:30
mergify[bot]
2da543ebd4 fix(ecommerce): breadcrumb: fallback to /all-products (#33718)
fix(ecommerce): breadcrumb: fallback to `/all-products` (#33718)

* fix(ecommerce): breadcrumb: fallback to `/all-products`

* fix(item_group): use `==` instead of `is`

* test(ecommerce): breadcrumb

(cherry picked from commit a94aa7a79f)

Co-authored-by: Sabu Siyad <hello@ssiyad.com>
2023-01-20 19:26:05 +05:30
anandbaburajan
ae031cea63 fix: fb issue in asset chart 2023-01-20 15:39:01 +05:30
rohitwaghchaure
6135d2972e Merge pull request #33742 from frappe/mergify/bp/version-13-hotfix/pr-33695
fix: patch item_reposting_for_incorrect_sl_and_gl (backport #33695)
2023-01-20 14:35:10 +05:30
rohitwaghchaure
d717ca0325 fix: conflicts 2023-01-20 14:05:14 +05:30
Rohit Waghchaure
1c5c06716b fix: patch item_reposting_for_incorrect_sl_and_gl
(cherry picked from commit dbde3a3421)

# Conflicts:
#	erpnext/patches.txt
2023-01-20 06:14:02 +00:00
rohitwaghchaure
61d06dd702 Merge pull request #33732 from frappe/mergify/bp/version-13-hotfix/pr-33723
fix: don't add template item in sales/purchase transaction (backport #33723)
2023-01-20 08:30:41 +05:30
Sagar Sharma
b702a02f61 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-33723 2023-01-19 22:04:05 +05:30
rohitwaghchaure
055f8536c3 fix: conflicts 2023-01-19 16:57:17 +05:30
Sagar Sharma
40ab5b034c Merge pull request #33734 from frappe/mergify/bp/version-13-hotfix/pr-33619
fix: rewrite logic for duplicate check in Item Attribute (backport #33619)
2023-01-19 15:29:17 +05:30
unknown
13906cba9a fix: linting
(cherry picked from commit 2ca4d3fb71)
2023-01-19 07:59:03 +00:00
unknown
4741ce13c6 fix: rewrite logic for duplicate check in Item Attribute
Previously, Item Attribute values were not checked for case-insensitive duplicates, and Item tttribute abbreviations were forced to be uppercase. This commit fixes both problems.

(cherry picked from commit 974e12c837)
2023-01-19 07:59:02 +00:00
Rohit Waghchaure
f81d4a79ea fix: don't add template item in sales/purchase transaction
(cherry picked from commit 2c83fff1a1)

# Conflicts:
#	erpnext/buying/doctype/purchase_order/test_purchase_order.py
2023-01-19 07:13:29 +00:00
Frappe PR Bot
550daf2108 chore(release): Bumped to Version 13.43.2
## [13.43.2](https://github.com/frappe/erpnext/compare/v13.43.1...v13.43.2) (2023-01-17)

### Bug Fixes

* allow to create sales order from expired quotation ([#33582](https://github.com/frappe/erpnext/issues/33582)) ([2f81f15](2f81f15f02))
* asset value in fixed asset register ([#33608](https://github.com/frappe/erpnext/issues/33608)) ([42fe63d](42fe63da2c))
* better comparision of difference value between stock and account ([a450c8d](a450c8dce9))
* don't check other warehouse ledgers to calculate valuation rate ([66bf107](66bf1071bb))
* handle post depr entries fail and fix asset repair link ([5f7dc8a](5f7dc8a5b9))
* only group similar items in print format if group_same_items is checked in pick list (backport [#33627](https://github.com/frappe/erpnext/issues/33627)) ([#33631](https://github.com/frappe/erpnext/issues/33631)) ([7dcf0f0](7dcf0f0866))
* Return against internal purchase invoice ([#33635](https://github.com/frappe/erpnext/issues/33635)) ([eef0f45](eef0f453d2))
* Sales ORder Connections on Material Request ([97488ae](97488aee88))
* Updating SO throws ordered_qty not allowed to change after submission ([a46aa80](a46aa808be))

### Reverts

* Reverting changes done on 33495 ([#33662](https://github.com/frappe/erpnext/issues/33662)) ([0f0a2b1](0f0a2b100c))
2023-01-17 15:36:54 +00:00
Deepesh Garg
959eae1b5c Merge pull request #33702 from frappe/version-13-hotfix
chore: release v13
2023-01-17 21:05:24 +05:30
Anand Baburajan
f2d83b1b21 Merge pull request #33689 from AnandBaburajan/misc_asset_fixes_v13
fix: handle asset depr entries posting failure and fix asset repair link [v13]
2023-01-17 15:14:10 +05:30
Anand Baburajan
f1670e922f Merge branch 'version-13-hotfix' into misc_asset_fixes_v13 2023-01-17 11:21:29 +05:30
Ankush Menat
ec780ac263 chore: ignore b028 2023-01-17 10:59:03 +05:30
Sagar Sharma
847171bd14 Merge pull request #33693 from frappe/mergify/bp/version-13-hotfix/pr-33690
fix: Sales Order Connections Tabs do not show linked Material Request or "+" button  (intoduce by #33304) (backport #33690)
2023-01-17 10:08:17 +05:30
Florian HENRY
97488aee88 fix: Sales ORder Connections on Material Request
(cherry picked from commit e19161a8ee)
2023-01-17 04:37:12 +00:00
Anand Baburajan
2b3a0ba9c4 Merge branch 'version-13-hotfix' into misc_asset_fixes_v13 2023-01-17 00:44:05 +05:30
anandbaburajan
5f7dc8a5b9 fix: handle post depr entries fail and fix asset repair link 2023-01-17 00:39:35 +05:30
ruthra kumar
edc20ae8b8 Merge pull request #33672 from frappe/mergify/bp/version-13-hotfix/pr-33646
Revert "fix: Updating SO throws ordered_qty not allowed to change after submission" (backport #33646)
2023-01-16 15:28:33 +05:30
ruthra kumar
6ebc9c5c82 Merge pull request #33669 from frappe/mergify/bp/version-13-hotfix/pr-33662
revert: Reverting changes done on 33495 (backport #33662)
2023-01-16 14:25:55 +05:30
Sagar Sharma
5a4d92b1bc Merge pull request #33666 from frappe/mergify/bp/version-13-hotfix/pr-33664
chore: `Sales Order` link in `Pick List` (backport #33664)
2023-01-16 13:53:28 +05:30
Sagar Sharma
ff48c44496 Merge branch 'version-13-hotfix' into mergify/bp/version-13-hotfix/pr-33664 2023-01-16 10:30:13 +05:30
mergify[bot]
2f81f15f02 fix: allow to create sales order from expired quotation (#33582)
fix: allow to create sales order from expired quotation (#33582)

(cherry picked from commit dceef0397a)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-01-16 10:02:41 +05:30
ruthra kumar
f1bb8933c1 Revert "fix: Updating SO throws ordered_qty not allowed to change after submission" (#33646)
(cherry picked from commit 333907b7a5)
2023-01-16 04:09:26 +00:00
ruthra kumar
0f0a2b100c revert: Reverting changes done on 33495 (#33662)
'ordered_qty' will not be fetched from `tabBin`

(cherry picked from commit be382054e5)
2023-01-16 04:03:32 +00:00
mergify[bot]
eef0f453d2 fix: Return against internal purchase invoice (#33635)
fix: Return against internal purchase invoice (#33635)

(cherry picked from commit 906ad10d16)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-01-16 08:48:52 +05:30
s-aga-r
e4af69bc93 chore: Sales Order link in Pick List
(cherry picked from commit b3759890d7)
2023-01-15 17:35:52 +00:00
mergify[bot]
42fe63da2c fix: asset value in fixed asset register (#33608)
fix: asset value in fixed asset register

(cherry picked from commit aa1f2a7297)

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
2023-01-15 17:33:17 +05:30
ruthra kumar
e23d7aa968 Merge pull request #33629 from frappe/mergify/bp/version-13-hotfix/pr-33622
fix: Updating SO throws ordered_qty not allowed to change after submission (backport #33622)
2023-01-13 10:15:14 +05:30
ruthra kumar
a46aa808be fix: Updating SO throws ordered_qty not allowed to change after submission
(cherry picked from commit 391f42db04)
2023-01-13 08:22:50 +05:30
mergify[bot]
f3b6b4609e chore: reuse doc object in test_pick_list_grouping_before_print (backport #33636) (#33638)
chore: reuse doc object in test_pick_list_grouping_before_print (#33636)

(cherry picked from commit e22d56484d)

Co-authored-by: Ritwik Puri <ritwikpuri5678@gmail.com>
2023-01-13 01:33:46 +05:30
mergify[bot]
7dcf0f0866 fix: only group similar items in print format if group_same_items is checked in pick list (backport #33627) (#33631)
fix: only group similar items in print format if group_same_items is checked in pick list (#33627)

* fix: only group similar items if group same items is checked in pick list

* test: non grouping of locations if group_same_items is false

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
(cherry picked from commit cfb0bb1eaa)

Co-authored-by: Ritwik Puri <ritwikpuri5678@gmail.com>
2023-01-12 20:32:37 +05:30
Sagar Sharma
29dcce53db Merge pull request #33615 from frappe/mergify/bp/version-13-hotfix/pr-33611
fix: better comparision of `difference_value` of Stock and Account (backport #33611)
2023-01-11 15:44:03 +05:30
Smit Vora
a450c8dce9 fix: better comparision of difference value between stock and account
(cherry picked from commit be05aea101)
2023-01-11 09:24:10 +00:00
Sagar Sharma
287411667a Merge pull request #33602 from frappe/mergify/bp/version-13-hotfix/pr-33597
fix: don't check other warehouse ledgers to calculate valuation rate (backport #33597)
2023-01-10 22:58:10 +05:30
Frappe PR Bot
ab30e2a9c7 chore(release): Bumped to Version 13.43.1
## [13.43.1](https://github.com/frappe/erpnext/compare/v13.43.0...v13.43.1) (2023-01-10)

### Bug Fixes

* better handling of duplicate bundle items ([0b952e8](0b952e8bba))
* remove hard-coded roles for populating leave balance reports ([#249](https://github.com/frappe/erpnext/issues/249)) ([#33557](https://github.com/frappe/erpnext/issues/33557)) ([c20d469](c20d469f31))
* remove unnecessary permissions from Appointment and Appointment Booking Settings ([#33468](https://github.com/frappe/erpnext/issues/33468)) ([a50ad1d](a50ad1d292))
2023-01-10 16:51:45 +00:00
Deepesh Garg
65dd72a0b0 Merge pull request #33600 from frappe/version-13-hotfix
chore: release v13
2023-01-10 22:20:15 +05:30
Rohit Waghchaure
66bf1071bb fix: don't check other warehouse ledgers to calculate valuation rate
(cherry picked from commit ef2bf3c223)
2023-01-10 10:05:12 +00:00
Sagar Sharma
410e617834 Merge pull request #33587 from frappe/mergify/bp/version-13-hotfix/pr-33562
fix: better handling of duplicate bundle items (backport #33562)
2023-01-09 22:55:56 +05:30
ruthra kumar
0b952e8bba fix: better handling of duplicate bundle items
(cherry picked from commit c717e87c9e)
2023-01-09 16:54:55 +00:00
mergify[bot]
e9d85a3ee4 ci: bump node in release workflow (backport #33574) (#33576)
ci: bump node in release workflow (#33574)

[skip ci]

(cherry picked from commit 1ad1fc4c7d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-01-09 13:26:18 +05:30
Rucha Mahabal
c20d469f31 fix: remove hard-coded roles for populating leave balance reports (#249) (#33557) 2023-01-06 14:28:15 +05:30
Sagar Sharma
7d0a118eab Merge pull request #33546 from frappe/mergify/bp/version-13-hotfix/pr-33543
chore: enable `No Copy` attribute for `route` in Item Group (backport #33543)
2023-01-05 13:32:52 +05:30
s-aga-r
2394f64872 chore: enable No Copy attribute for route in Item Group
(cherry picked from commit 348dc32514)
2023-01-05 07:33:04 +00:00
Daizy Modi
a50ad1d292 fix: remove unnecessary permissions from Appointment and Appointment Booking Settings (#33468)
* fix: remove unnecessary permissions from Appointment and Appointment Booking Settings

* fix: remove line

* fix: use more intuitive import

Co-authored-by: Sagar Vora <sagar@resilient.tech>
2023-01-04 21:44:44 +05:30
89 changed files with 1758 additions and 955 deletions

View File

@@ -66,7 +66,8 @@ ignore =
F841,
E713,
E712,
B023
B023,
B028
max-line-length = 200

View File

@@ -3,52 +3,71 @@ from urllib.parse import urlparse
import requests
docs_repos = [
"frappe_docs",
"erpnext_documentation",
WEBSITE_REPOS = [
"erpnext_com",
"frappe_io",
]
DOCUMENTATION_DOMAINS = [
"docs.erpnext.com",
"frappeframework.com",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def docs_link_exists(body):
for line in body.splitlines():
for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split('/')
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
elif parsed_url.netloc == "docs.erpnext.com":
return True
def is_valid_url(url: str) -> bool:
parts = urlparse(url)
return all((parts.scheme, parts.netloc, parts.path))
def is_documentation_link(word: str) -> bool:
if not word.startswith("http") or not is_valid_url(word):
return False
parsed_url = urlparse(word)
if parsed_url.netloc in DOCUMENTATION_DOMAINS:
return True
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split("/")
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS:
return True
return False
def contains_documentation_link(body: str) -> bool:
return any(
is_documentation_link(word)
for line in body.splitlines()
for word in line.split()
)
def check_pull_request(number: str) -> "tuple[int, str]":
response = requests.get(f"https://api.github.com/repos/frappe/erpnext/pulls/{number}")
if not response.ok:
return 1, "Pull Request Not Found! ⚠️"
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (
not title.startswith("feat")
or not head_sha
or "no-docs" in body
or "backport" in body
):
return 0, "Skipping documentation checks... 🏃"
if contains_documentation_link(body):
return 0, "Documentation Link Found. You're Awesome! 🎉"
return 1, "Documentation Link Not Found! ⚠️"
if __name__ == "__main__":
pr = sys.argv[1]
response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
if response.ok:
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (title.startswith("feat")
and head_sha
and "no-docs" not in body
and "backport" not in body
):
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")
exit_code, message = check_pull_request(sys.argv[1])
print(message)
sys.exit(exit_code)

View File

@@ -12,7 +12,7 @@ jobs:
- name: 'Setup Environment'
uses: actions/setup-python@v2
with:
python-version: 3.6
python-version: '3.10'
- name: 'Clone repo'
uses: actions/checkout@v2

View File

@@ -13,10 +13,10 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js v14
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
node-version: 18
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
@@ -28,4 +28,4 @@ jobs:
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release
run: npx semantic-release

View File

@@ -32,8 +32,8 @@ repos:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"

View File

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

View File

@@ -1,38 +1,38 @@
{
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"root_type": "Asset",
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Gesch\u00e4ftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"B\u00fcroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Geschäftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"Büroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
@@ -60,36 +60,46 @@
"Durchlaufende Posten": {
"account_number": "1590"
},
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1,
"Abziehbare Vorsteuer 7%": {
"account_number": "1571"
"Abziehbare Vorsteuer 7 %": {
"account_number": "1571",
"account_type": "Tax",
"tax_rate": 7.0
},
"Abziehbare Vorsteuer 19%": {
"account_number": "1576"
"Abziehbare Vorsteuer 19 %": {
"account_number": "1576",
"account_type": "Tax",
"tax_rate": 19.0
},
"Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
"Abziehbare Vorsteuer nach § 13b UStG 19 %": {
"account_number": "1577",
"account_type": "Tax",
"tax_rate": 19.0
}
}
},
"III. Wertpapiere": {
"is_group": 1
"is_group": 1,
"Anteile an verbundenen Unternehmen (Umlaufvermögen)": {
"account_number": "1340"
},
"Anteile an herrschender oder mit Mehrheit beteiligter Gesellschaft": {
"account_number": "1344"
},
"Sonstige Wertpapiere": {
"account_number": "1348"
}
},
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1,
"Kasse": {
"account_type": "Cash",
"is_group": 1,
"account_type": "Cash",
"Kasse": {
"is_group": 1,
"account_number": "1000",
"account_type": "Cash"
}
@@ -111,21 +121,21 @@
"C - Rechnungsabgrenzungsposten": {
"is_group": 1,
"Aktive Rechnungsabgrenzung": {
"account_number": "0980"
"account_number": "0980"
}
},
"D - Aktive latente Steuern": {
"is_group": 1,
"Aktive latente Steuern": {
"account_number": "0983"
"account_number": "0983"
}
},
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1
}
},
"Passiva": {
"is_group": 1,
},
"Passiva": {
"is_group": 1,
"root_type": "Liability",
"A. Eigenkapital": {
"is_group": 1,
@@ -200,26 +210,32 @@
},
"Umsatzsteuer": {
"is_group": 1,
"account_type": "Tax",
"Umsatzsteuer 7%": {
"account_number": "1771"
"Umsatzsteuer 7 %": {
"account_number": "1771",
"account_type": "Tax",
"tax_rate": 7.0
},
"Umsatzsteuer 19%": {
"account_number": "1776"
"Umsatzsteuer 19 %": {
"account_number": "1776",
"account_type": "Tax",
"tax_rate": 19.0
},
"Umsatzsteuer-Vorauszahlung": {
"account_number": "1780"
"account_number": "1780",
"account_type": "Tax"
},
"Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781"
},
"Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787"
"Umsatzsteuer nach § 13b UStG 19 %": {
"account_number": "1787",
"account_type": "Tax",
"tax_rate": 19.0
},
"Umsatzsteuer Vorjahr": {
"account_number": "1790"
},
"Umsatzsteuer fr\u00fchere Jahre": {
"Umsatzsteuer frühere Jahre": {
"account_number": "1791"
}
}
@@ -234,44 +250,56 @@
"E. Passive latente Steuern": {
"is_group": 1
}
},
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erl\u00f6skonten 8": {
},
"Erlöse u. Erträge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erlöskonten 8": {
"is_group": 1,
"Erl\u00f6se": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 19%": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 7%": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Ertr\u00e4ge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"Erlöse": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erlöse USt. 19 %": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erlöse USt. 7 %": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und ähnliche Erträge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Außerordentliche Erträge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Erträge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"root_type": "Expense",
"Fremdleistungen": {
"account_number": "3100",
"account_type": "Expense Account"
},
"Fremdleistungen ohne Vorsteuer": {
"account_number": "3109",
"account_type": "Expense Account"
},
"Bauleistungen eines im Inland ansässigen Unternehmers 19 % Vorsteuer und 19 % Umsatzsteuer": {
"account_number": "3120",
"account_type": "Expense Account"
},
"Wareneingang": {
"account_number": "3200"
},
@@ -298,234 +326,234 @@
"Gegenkonto 4996-4998": {
"account_number": "4999"
},
"Abschreibungen": {
"is_group": 1,
"Abschreibungen": {
"is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830",
"account_type": "Accumulated Depreciation"
"account_number": "4830",
"account_type": "Accumulated Depreciation"
},
"Abschreibungen auf Gebäude": {
"account_number": "4831",
"account_type": "Depreciation"
"account_number": "4831",
"account_type": "Depreciation"
},
"Abschreibungen auf Kfz": {
"account_number": "4832",
"account_type": "Depreciation"
"account_number": "4832",
"account_type": "Depreciation"
},
"Sofortabschreibung GWG": {
"account_number": "4855",
"account_type": "Expense Account"
"account_number": "4855",
"account_type": "Expense Account"
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Geh\u00e4lter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Verm\u00f6genswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfsl\u00f6hne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeitr\u00e4ge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beitr\u00e4ge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"B\u00fcrobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, B\u00fccher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchf\u00fchrungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008"
},
"Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820"
},
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830"
},
"Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Gehälter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen für Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Vermögenswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfslöhne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeiträge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beiträge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"Bürobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, Bücher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchführungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschluß- u. Prüfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleingeräte": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen für kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen für KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvorträge Debitoren": {
"account_number": "9008"
},
"Saldenvorträge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschränkt abzugsfähig": {
"account_number": "1820"
},
"Sonderausgaben unbeschränkt abzugsfähig": {
"account_number": "1830"
},
"Außergewöhnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
}

View File

@@ -87,6 +87,7 @@ class JournalEntry(AccountsController):
self.check_credit_limit()
self.make_gl_entries()
self.update_advance_paid()
self.update_asset_value()
self.update_expense_claim()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -235,6 +236,34 @@ class JournalEntry(AccountsController):
for d in to_remove:
self.remove(d)
def update_asset_value(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
):
processed_assets.append(d.reference_name)
asset = frappe.db.get_value(
"Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1
)
if asset.calculate_depreciation:
continue
depr_value = d.debit or d.credit
frappe.db.set_value(
"Asset",
d.reference_name,
"value_after_depreciation",
asset.value_after_depreciation - depr_value,
)
def update_inter_company_jv(self):
if (
self.voucher_type == "Inter Company Journal Entry"
@@ -293,19 +322,41 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if d.reference_type == "Asset" and d.reference_name:
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
):
processed_assets.append(d.reference_name)
asset = frappe.get_doc("Asset", d.reference_name)
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
idx = cint(s.finance_book_id) or 1
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation += s.depreciation_amount
finance_books.db_update()
if asset.calculate_depreciation:
for s in asset.get("schedules"):
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
asset.set_status()
idx = cint(s.finance_book_id) or 1
finance_books = asset.get("finance_books")[idx - 1]
finance_books.value_after_depreciation += s.depreciation_amount
finance_books.db_update()
asset.set_status()
break
else:
depr_value = d.debit or d.credit
frappe.db.set_value(
"Asset",
d.reference_name,
"value_after_depreciation",
asset.value_after_depreciation + depr_value,
)
def unlink_inter_company_jv(self):
if (

View File

@@ -1255,6 +1255,7 @@ def get_outstanding_reference_documents(args):
args.get("party_type"),
args.get("party"),
args.get("party_account"),
args.get("company"),
filters=args,
condition=condition,
)

View File

@@ -211,7 +211,7 @@ class PaymentReconciliation(Document):
condition += " and cost_center = '{0}' ".format(self.cost_center)
non_reconciled_invoices = get_outstanding_invoices(
self.party_type, self.party, self.receivable_payable_account, condition=condition
self.party_type, self.party, self.receivable_payable_account, self.company, condition=condition
)
if self.invoice_limit:

View File

@@ -49,7 +49,6 @@
<br>
{% endif %}
{{ _("Against") }}: {{ row.against }}
<br>{{ _("Remarks") }}: {{ row.remarks }}
{% if row.bill_no %}
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}

View File

@@ -598,7 +598,7 @@ class PurchaseInvoice(BuyingController):
def make_supplier_gl_entry(self, gl_entries):
# Checked both rounding_adjustment and rounded_total
# because rounded_total had value even before introcution of posting GLE based on rounded total
# because rounded_total had value even before introduction of posting GLE based on rounded total
grand_total = (
self.rounded_total if (self.rounding_adjustment and self.rounded_total) else self.grand_total
)
@@ -799,10 +799,7 @@ class PurchaseInvoice(BuyingController):
else item.deferred_expense_account
)
if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, None)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
dummy, amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
if item.purchase_receipt:

View File

@@ -1790,6 +1790,8 @@
"width": "50%"
},
{
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"hide_days": 1,
@@ -2045,7 +2047,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-09-16 17:44:22.227332",
"modified": "2023-01-28 19:45:47.538163",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2101,4 +2103,4 @@
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@@ -7,17 +7,7 @@ from frappe import _, msgprint, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.mapper import get_mapped_doc
from frappe.model.utils import get_fetch_values
from frappe.utils import (
add_days,
add_months,
cint,
cstr,
flt,
formatdate,
get_link_to_form,
getdate,
nowdate,
)
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
from six import iteritems
import erpnext
@@ -33,10 +23,12 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
from erpnext.accounts.utils import get_account_currency
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_disposal_account_and_cost_center,
get_gl_entries_on_asset_disposal,
get_gl_entries_on_asset_regain,
make_depreciation_entry,
reset_depreciation_schedule,
reverse_depreciation_entry_made_after_disposal,
)
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController
@@ -1114,18 +1106,20 @@ class SalesInvoice(SellingController):
asset = self.get_asset(item)
if self.is_return:
if asset.calculate_depreciation:
self.reverse_depreciation_entry_made_after_sale(asset)
self.reset_depreciation_schedule(asset)
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset, item.base_net_amount, item.finance_book
)
asset.db_set("disposal_date", None)
if asset.calculate_depreciation:
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
reset_depreciation_schedule(asset, self.posting_date)
else:
if asset.calculate_depreciation:
self.depreciate_asset(asset)
depreciate_asset(asset, self.posting_date)
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book
@@ -1193,95 +1187,6 @@ class SalesInvoice(SellingController):
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
)
def depreciate_asset(self, asset):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_sale=self.posting_date)
asset.save()
make_depreciation_entry(asset.name, self.posting_date)
asset.load_from_db()
def reset_depreciation_schedule(self, asset):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=self.posting_date)
self.modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
asset.load_from_db()
def modify_depreciation_schedule_for_asset_repairs(self, asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_sale(self, asset):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == posting_date_of_original_invoice:
if not self.sale_was_made_on_original_schedule_date(
asset, schedule, row, posting_date_of_original_invoice
) or self.sale_happens_in_the_future(posting_date_of_original_invoice):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry)
asset.finance_books[0].value_after_depreciation += depreciation_amount
asset.save()
def get_posting_date_of_sales_invoice(self):
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def sale_was_made_on_original_schedule_date(
self, asset, schedule, row, posting_date_of_original_invoice
):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if orginal_schedule_date == posting_date_of_original_invoice:
return True
return False
def sale_happens_in_the_future(self, posting_date_of_original_invoice):
if posting_date_of_original_invoice > getdate():
return True
return False
def get_depreciation_amount_in_je(self, journal_entry):
if journal_entry.accounts[0].debit_in_account_currency:
return journal_entry.accounts[0].debit_in_account_currency
else:
return journal_entry.accounts[0].credit_in_account_currency
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):

View File

@@ -1117,6 +1117,46 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`")
def test_bin_details_of_packed_item(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import make_item
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
si = create_sales_invoice(
item_code="_Test Product Bundle Item New",
update_stock=1,
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
si.transaction_date = nowdate()
si.save()
packed_item = si.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_pos_si_without_payment(self):
make_pos_profile()

View File

@@ -135,6 +135,34 @@ def get_assets(filters):
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != ''
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
gle.debit
else
0
end), 0) as accumulated_depreciation_as_on_from_date,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
and a.disposal_date <= %(to_date)s and gle.posting_date <= a.disposal_date then
gle.debit
else
0
end), 0) as depreciation_eliminated_during_the_period,
ifnull(sum(case when gle.posting_date >= %(from_date)s and gle.posting_date <= %(to_date)s
and (ifnull(a.disposal_date, 0) = 0 or gle.posting_date <= a.disposal_date) then
gle.debit
else
0
end), 0) as depreciation_amount_during_the_period
from `tabGL Entry` gle
join `tabAsset` a on
gle.against_voucher = a.name
join `tabAsset Category Account` aca on
aca.parent = a.asset_category and aca.company_name = %(company)s
join `tabCompany` company on
company.name = %(company)s
where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
group by a.asset_category
union
SELECT a.asset_category,
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and (a.disposal_date < %(from_date)s or a.disposal_date > %(to_date)s) then
0

View File

@@ -378,15 +378,14 @@ class Deferred_Revenue_and_Expense_Report(object):
ret += [{}]
# add total row
if ret is not []:
if self.filters.type == "Revenue":
total_row = frappe._dict({"name": "Total Deferred Income"})
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
if self.filters.type == "Revenue":
total_row = frappe._dict({"name": "Total Deferred Income"})
elif self.filters.type == "Expense":
total_row = frappe._dict({"name": "Total Deferred Expense"})
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)
for idx, period in enumerate(self.period_list, 0):
total_row[period.key] = self.period_total[idx].total
ret.append(total_row)
return ret

View File

@@ -25,8 +25,8 @@
<thead>
<tr>
<th style="width: 12%">{%= __("Date") %}</th>
<th style="width: 15%">{%= __("Ref") %}</th>
<th style="width: 25%">{%= __("Party") %}</th>
<th style="width: 15%">{%= __("Reference") %}</th>
<th style="width: 25%">{%= __("Remarks") %}</th>
<th style="width: 15%">{%= __("Debit") %}</th>
<th style="width: 15%">{%= __("Credit") %}</th>
<th style="width: 18%">{%= __("Balance (Dr - Cr)") %}</th>
@@ -45,7 +45,6 @@
<br>
{% } %}
{{ __("Against") }}: {%= data[i].against %}
<br>{%= __("Remarks") %}: {%= data[i].remarks %}
{% if(data[i].bill_no) { %}
<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}

View File

@@ -585,10 +585,35 @@ class GrossProfitGenerator(object):
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
elif row.sales_order and row.so_detail:
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
if incoming_amount:
return incoming_amount
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
return 0.0
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
from frappe.query_builder.functions import Sum
delivery_note = frappe.qb.DocType("Delivery Note")
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
query = (
frappe.qb.from_(delivery_note)
.inner_join(delivery_note_item)
.on(delivery_note.name == delivery_note_item.parent)
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
.where(delivery_note.docstatus == 1)
.where(delivery_note_item.item_code == item_code)
.where(delivery_note_item.against_sales_order == sales_order)
.where(delivery_note_item.so_detail == so_detail)
.groupby(delivery_note_item.item_code)
)
incoming_amount = query.run()
return flt(incoming_amount[0][0]) if incoming_amount else 0
def get_average_buying_rate(self, row, item_code):
args = row
@@ -665,7 +690,8 @@ class GrossProfitGenerator(object):
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,

View File

@@ -301,3 +301,82 @@ class TestGrossProfit(FrappeTestCase):
columns, data = execute(filters=filters)
self.assertGreater(len(data), 0)
def test_order_connected_dn_and_inv(self):
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
"""
Test gp calculation when invoice and delivery note aren't directly connected.
SO -- INV
|
DN
"""
se = make_stock_entry(
company=self.company,
item_code=self.item,
target=self.warehouse,
qty=3,
basic_rate=100,
do_not_submit=True,
)
item = se.items[0]
se.append(
"items",
{
"item_code": item.item_code,
"s_warehouse": item.s_warehouse,
"t_warehouse": item.t_warehouse,
"qty": 10,
"basic_rate": 200,
"conversion_factor": item.conversion_factor or 1.0,
"transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
"serial_no": item.serial_no,
"batch_no": item.batch_no,
"cost_center": item.cost_center,
"expense_account": item.expense_account,
},
)
se = se.save().submit()
so = make_sales_order(
customer=self.customer,
company=self.company,
warehouse=self.warehouse,
item=self.item,
qty=4,
do_not_save=False,
do_not_submit=False,
)
from erpnext.selling.doctype.sales_order.sales_order import (
make_delivery_note,
make_sales_invoice,
)
make_delivery_note(so.name).submit()
sinv = make_sales_invoice(so.name).submit()
filters = frappe._dict(
company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
)
columns, data = execute(filters=filters)
expected_entry = {
"parent_invoice": sinv.name,
"currency": "INR",
"sales_invoice": self.item,
"customer": self.customer,
"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
"item_code": self.item,
"item_name": self.item,
"warehouse": "Stores - _GP",
"qty": 4.0,
"avg._selling_rate": 100.0,
"valuation_rate": 125.0,
"selling_amount": 400.0,
"buying_amount": 500.0,
"gross_profit": -100.0,
"gross_profit_%": -25.0,
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])

View File

@@ -840,7 +840,7 @@ def remove_return_pos_invoices(party_type, party, invoice_list):
return invoice_list
def get_outstanding_invoices(party_type, party, account, condition=None, filters=None):
def get_outstanding_invoices(party_type, party, account, company, condition=None, filters=None):
outstanding_invoices = []
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
@@ -892,61 +892,73 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
invoice_list = remove_return_pos_invoices(party_type, party, invoice_list)
payment_entries = frappe.db.sql(
"""
select against_voucher_type, against_voucher,
ifnull(sum({payment_dr_or_cr}), 0) as payment_amount
from `tabGL Entry`
where party_type = %(party_type)s and party = %(party)s
and account = %(account)s
and {payment_dr_or_cr} > 0
and against_voucher is not null and against_voucher != ''
and is_cancelled=0
group by against_voucher_type, against_voucher
""".format(
payment_dr_or_cr=payment_dr_or_cr
),
{"party_type": party_type, "party": party, "account": account},
as_dict=True,
)
if invoice_list:
invoices = [d.voucher_no for d in invoice_list]
payment_entries = frappe.db.sql(
"""
select against_voucher_type, against_voucher,
ifnull(sum({payment_dr_or_cr}), 0) as payment_amount
from `tabGL Entry`
where
company = %(company)s
and party_type = %(party_type)s and party = %(party)s
and account = %(account)s
and {payment_dr_or_cr} > 0
and ifnull(against_voucher, '') != ''
and is_cancelled=0
and against_voucher in %(invoices)s
group by against_voucher_type, against_voucher
""".format(
payment_dr_or_cr=payment_dr_or_cr,
),
{
"company": company,
"party_type": party_type,
"party": party,
"account": account,
"invoices": invoices,
},
as_dict=True,
)
pe_map = frappe._dict()
for d in payment_entries:
pe_map.setdefault((d.against_voucher_type, d.against_voucher), d.payment_amount)
pe_map = frappe._dict()
for d in payment_entries:
pe_map.setdefault((d.against_voucher_type, d.against_voucher), d.payment_amount)
for d in invoice_list:
payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
if outstanding_amount > 0.5 / (10**precision):
if (
filters
and filters.get("outstanding_amt_greater_than")
and not (
outstanding_amount >= filters.get("outstanding_amt_greater_than")
and outstanding_amount <= filters.get("outstanding_amt_less_than")
)
):
continue
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append(
frappe._dict(
{
"voucher_no": d.voucher_no,
"voucher_type": d.voucher_type,
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"payment_amount": payment_amount,
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
"currency": d.currency,
}
for d in invoice_list:
payment_amount = pe_map.get((d.voucher_type, d.voucher_no), 0)
outstanding_amount = flt(d.invoice_amount - payment_amount, precision)
if outstanding_amount > 0.5 / (10**precision):
if (
filters
and filters.get("outstanding_amt_greater_than")
and not (
outstanding_amount >= filters.get("outstanding_amt_greater_than")
and outstanding_amount <= filters.get("outstanding_amt_less_than")
)
)
):
continue
if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices:
outstanding_invoices.append(
frappe._dict(
{
"voucher_no": d.voucher_no,
"voucher_type": d.voucher_type,
"posting_date": d.posting_date,
"invoice_amount": flt(d.invoice_amount),
"payment_amount": payment_amount,
"outstanding_amount": outstanding_amount,
"due_date": d.due_date,
"currency": d.currency,
}
)
)
outstanding_invoices = sorted(
outstanding_invoices, key=lambda k: k["due_date"] or getdate(nowdate())
)
outstanding_invoices = sorted(
outstanding_invoices, key=lambda k: k["due_date"] or getdate(nowdate())
)
return outstanding_invoices

View File

@@ -132,6 +132,10 @@ frappe.ui.form.on('Asset', {
}, __("Manage"));
}
if (frm.doc.depr_entry_posting_status === "Failed") {
frm.trigger("set_depr_posting_failure_alert");
}
frm.trigger("setup_chart");
}
@@ -142,6 +146,19 @@ frappe.ui.form.on('Asset', {
}
},
set_depr_posting_failure_alert: function (frm) {
const alert = `
<div class="row">
<div class="col-xs-12 col-sm-6">
<span class="indicator whitespace-nowrap red">
<span>Failed to post depreciation entries</span>
</span>
</div>
</div>`;
frm.dashboard.set_headline_alert(alert);
},
toggle_reference_doc: function(frm) {
if (frm.doc.purchase_receipt && frm.doc.purchase_invoice && frm.doc.docstatus === 1) {
frm.set_df_property('purchase_invoice', 'read_only', 1);
@@ -184,39 +201,58 @@ frappe.ui.form.on('Asset', {
})
},
setup_chart: function(frm) {
var x_intervals = [frm.doc.purchase_date];
var asset_values = [frm.doc.gross_purchase_amount];
var last_depreciation_date = frm.doc.purchase_date;
if(frm.doc.opening_accumulated_depreciation) {
last_depreciation_date = frappe.datetime.add_months(frm.doc.next_depreciation_date,
-1*frm.doc.frequency_of_depreciation);
x_intervals.push(last_depreciation_date);
asset_values.push(flt(frm.doc.gross_purchase_amount) -
flt(frm.doc.opening_accumulated_depreciation));
setup_chart: async function(frm) {
if(frm.doc.finance_books.length > 1) {
return
}
$.each(frm.doc.schedules || [], function(i, v) {
x_intervals.push(v.schedule_date);
var asset_value = flt(frm.doc.gross_purchase_amount) - flt(v.accumulated_depreciation_amount);
if(v.journal_entry) {
last_depreciation_date = v.schedule_date;
asset_values.push(asset_value);
} else {
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
asset_values.push(null);
} else {
asset_values.push(asset_value)
}
var x_intervals = [frappe.format(frm.doc.purchase_date, { fieldtype: 'Date' })];
var asset_values = [frm.doc.gross_purchase_amount];
if(frm.doc.calculate_depreciation) {
if(frm.doc.opening_accumulated_depreciation) {
var depreciation_date = frappe.datetime.add_months(
frm.doc.finance_books[0].depreciation_start_date,
-1 * frm.doc.finance_books[0].frequency_of_depreciation
);
x_intervals.push(frappe.format(depreciation_date, { fieldtype: 'Date' }));
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
}
});
$.each(frm.doc.schedules || [], function(i, v) {
x_intervals.push(frappe.format(v.schedule_date, { fieldtype: 'Date' }));
var asset_value = flt(frm.doc.gross_purchase_amount - v.accumulated_depreciation_amount, precision('gross_purchase_amount'));
if(v.journal_entry) {
asset_values.push(asset_value);
} else {
if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
asset_values.push(null);
} else {
asset_values.push(asset_value);
}
}
});
} else {
if(frm.doc.opening_accumulated_depreciation) {
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
asset_values.push(flt(frm.doc.gross_purchase_amount - frm.doc.opening_accumulated_depreciation, precision('gross_purchase_amount')));
}
let depr_entries = (await frappe.call({
method: "get_manual_depreciation_entries",
doc: frm.doc,
})).message;
$.each(depr_entries || [], function(i, v) {
x_intervals.push(frappe.format(v.posting_date, { fieldtype: 'Date' }));
let last_asset_value = asset_values[asset_values.length - 1]
asset_values.push(last_asset_value - v.value);
});
}
if(in_list(["Scrapped", "Sold"], frm.doc.status)) {
x_intervals.push(frm.doc.disposal_date);
x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: 'Date' }));
asset_values.push(0);
last_depreciation_date = frm.doc.disposal_date;
}
frm.dashboard.render_graph({

View File

@@ -68,6 +68,7 @@
"column_break_51",
"purchase_receipt_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from"
],
"fields": [
@@ -473,6 +474,16 @@
"fieldname": "section_break_36",
"fieldtype": "Section Break",
"label": "Finance Books"
},
{
"fieldname": "depr_entry_posting_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Depreciation Entry Posting Status",
"no_copy": 1,
"options": "\nSuccessful\nFailed",
"print_hide": 1,
"read_only": 1
}
],
"idx": 72,
@@ -487,15 +498,21 @@
{
"group": "Repair",
"link_doctype": "Asset Repair",
"link_fieldname": "asset_name"
"link_fieldname": "asset"
},
{
"group": "Value",
"link_doctype": "Asset Value Adjustment",
"link_fieldname": "asset"
},
{
"group": "Journal Entry",
"link_doctype": "Journal Entry",
"link_fieldname": "reference_name",
"table_fieldname": "accounts"
}
],
"modified": "2022-07-20 16:22:44.437579",
"modified": "2023-01-31 01:03:09.467817",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -27,6 +27,7 @@ from erpnext.accounts.general_ledger import make_reverse_gl_entries
from erpnext.assets.doctype.asset.depreciation import (
get_depreciation_accounts,
get_disposal_account_and_cost_center,
is_last_day_of_the_month,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.controllers.accounts_controller import AccountsController
@@ -79,12 +80,12 @@ class Asset(AccountsController):
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
)
def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None):
def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None):
if self.calculate_depreciation:
self.value_after_depreciation = 0
self.set_depreciation_rate()
self.make_depreciation_schedule(date_of_sale)
self.set_accumulated_depreciation(date_of_sale, date_of_return)
self.make_depreciation_schedule(date_of_disposal)
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
else:
self.finance_books = []
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
@@ -223,7 +224,7 @@ class Asset(AccountsController):
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
)
def make_depreciation_schedule(self, date_of_sale):
def make_depreciation_schedule(self, date_of_disposal):
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
"schedules"
):
@@ -279,17 +280,17 @@ class Asset(AccountsController):
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_sale:
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book, depreciation_amount, from_date, date_of_sale
finance_book, depreciation_amount, from_date, date_of_disposal
)
if depreciation_amount > 0:
self.append(
"schedules",
{
"schedule_date": date_of_sale,
"schedule_date": date_of_disposal,
"depreciation_amount": depreciation_amount,
"depreciation_method": finance_book.depreciation_method,
"finance_book": finance_book.finance_book,
@@ -364,6 +365,9 @@ class Asset(AccountsController):
},
)
if len(self.get("finance_books")) > 1 and any(start):
self.sort_depreciation_schedule()
# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
# JE: Journal Entry, FB: Finance Book
def clear_depreciation_schedule(self):
@@ -399,6 +403,14 @@ class Asset(AccountsController):
return start
def sort_depreciation_schedule(self):
self.schedules = sorted(
self.schedules, key=lambda s: (int(s.finance_book_id), getdate(s.schedule_date))
)
for idx, s in enumerate(self.schedules, 1):
s.idx = idx
def get_from_date(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
@@ -531,7 +543,7 @@ class Asset(AccountsController):
return True
def set_accumulated_depreciation(
self, date_of_sale=None, date_of_return=None, ignore_booked_entry=False
self, date_of_disposal=None, date_of_return=None, ignore_booked_entry=False
):
straight_line_idx = [
d.idx for d in self.get("schedules") if d.depreciation_method == "Straight Line"
@@ -544,7 +556,9 @@ class Asset(AccountsController):
if int(d.finance_book_id) not in finance_books:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
value_after_depreciation = flt(
self.get("finance_books")[cint(d.finance_book_id) - 1].value_after_depreciation
)
finance_books.append(int(d.finance_book_id))
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
@@ -554,7 +568,7 @@ class Asset(AccountsController):
if (
straight_line_idx
and i == max(straight_line_idx) - 1
and not date_of_sale
and not date_of_disposal
and not date_of_return
):
book = self.get("finance_books")[cint(d.finance_book_id) - 1]
@@ -569,9 +583,6 @@ class Asset(AccountsController):
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
)
def get_value_after_depreciation(self, idx):
return flt(self.get("finance_books")[cint(idx) - 1].value_after_depreciation)
def validate_expected_value_after_useful_life(self):
for row in self.get("finance_books"):
accumulated_depreciation_after_full_schedule = [
@@ -626,15 +637,20 @@ class Asset(AccountsController):
movement.cancel()
def delete_depreciation_entries(self):
for d in self.get("schedules"):
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
d.db_set("journal_entry", None)
if self.calculate_depreciation:
for d in self.get("schedules"):
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
else:
depr_entries = self.get_manual_depreciation_entries()
self.db_set(
"value_after_depreciation",
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
)
for depr_entry in depr_entries or []:
frappe.get_doc("Journal Entry", depr_entry.name).cancel()
self.db_set(
"value_after_depreciation",
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
)
def set_status(self, status=None):
"""Get and update status"""
@@ -665,6 +681,19 @@ class Asset(AccountsController):
status = "Cancelled"
return status
def get_value_after_depreciation(self, finance_book=None):
if not self.calculate_depreciation:
return flt(self.value_after_depreciation, self.precision("gross_purchase_amount"))
if not finance_book:
return flt(
self.get("finance_books")[0].value_after_depreciation, self.precision("gross_purchase_amount")
)
for row in self.get("finance_books"):
if finance_book == row.finance_book:
return flt(row.value_after_depreciation, self.precision("gross_purchase_amount"))
def get_default_finance_book_idx(self):
if not self.get("default_finance_book") and self.company:
self.default_finance_book = erpnext.get_default_finance_book(self.company)
@@ -674,6 +703,24 @@ class Asset(AccountsController):
if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1
@frappe.whitelist()
def get_manual_depreciation_entries(self):
(_, _, depreciation_expense_account) = get_depreciation_accounts(self)
gle = frappe.qb.DocType("GL Entry")
records = (
frappe.qb.from_(gle)
.select(gle.voucher_no.as_("name"), gle.debit.as_("value"), gle.posting_date)
.where(gle.against_voucher == self.name)
.where(gle.account == depreciation_expense_account)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.orderby(gle.posting_date)
).run(as_dict=True)
return records
def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document()
if not purchase_document:
@@ -838,7 +885,6 @@ def update_maintenance_status():
def make_post_gl_entry():
asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"])
for asset_category in asset_categories:
@@ -991,7 +1037,7 @@ def make_journal_entry(asset_name):
depreciation_expense_account,
) = get_depreciation_accounts(asset)
depreciation_cost_center, depreciation_series = frappe.db.get_value(
depreciation_cost_center, depreciation_series = frappe.get_cached_value(
"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
@@ -1058,6 +1104,13 @@ def is_cwip_accounting_enabled(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
@frappe.whitelist()
def get_asset_value_after_depreciation(asset_name, finance_book=None):
asset = frappe.get_doc("Asset", asset_name)
return asset.get_value_after_depreciation(finance_book)
def get_total_days(date, frequency):
period_start_date = add_months(date, cint(frequency) * -1)
@@ -1067,12 +1120,6 @@ def get_total_days(date, frequency):
return date_diff(date, period_start_date)
def is_last_day_of_the_month(date):
last_day_of_the_month = get_last_day(date)
return getdate(last_day_of_the_month) == getdate(date)
@erpnext.allow_regional
def get_depreciation_amount(asset, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):

View File

@@ -4,7 +4,17 @@
import frappe
from frappe import _
from frappe.utils import cint, flt, getdate, today
from frappe.utils import (
add_months,
cint,
flt,
get_last_day,
get_link_to_form,
getdate,
nowdate,
today,
)
from frappe.utils.user import get_users_with_role
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
@@ -20,9 +30,22 @@ def post_depreciation_entries(date=None):
if not date:
date = today()
for asset in get_depreciable_assets(date):
make_depreciation_entry(asset, date)
frappe.db.commit()
failed_asset_names = []
for asset_name in get_depreciable_assets(date):
try:
make_depreciation_entry(asset_name, date)
frappe.db.commit()
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
frappe.db.commit()
def get_depreciable_assets(date):
@@ -121,6 +144,8 @@ def make_depreciation_entry(asset_name, date=None):
finance_books.value_after_depreciation -= d.depreciation_amount
finance_books.db_update()
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
asset.set_status()
return asset
@@ -184,6 +209,42 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
return credit_account, debit_account
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
for asset_name in failed_asset_names:
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
recipients = get_users_with_role("System Manager")
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+ "."
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
asset_links = ", ".join(asset_links)
return asset_links
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -195,6 +256,11 @@ def scrap_asset(asset_name):
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
)
date = today()
depreciate_asset(asset, date)
asset.reload()
depreciation_series = frappe.get_cached_value(
"Company", asset.company, "series_for_depreciation_entry"
)
@@ -202,7 +268,7 @@ def scrap_asset(asset_name):
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Journal Entry"
je.naming_series = depreciation_series
je.posting_date = today()
je.posting_date = date
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
@@ -213,7 +279,7 @@ def scrap_asset(asset_name):
je.flags.ignore_permissions = True
je.submit()
frappe.db.set_value("Asset", asset_name, "disposal_date", today())
frappe.db.set_value("Asset", asset_name, "disposal_date", date)
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
asset.set_status("Scrapped")
@@ -224,6 +290,9 @@ def scrap_asset(asset_name):
def restore_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
reset_depreciation_schedule(asset, asset.disposal_date)
je = asset.journal_entry_for_scrap
asset.db_set("disposal_date", None)
@@ -234,6 +303,99 @@ def restore_asset(asset_name):
asset.set_status()
def depreciate_asset(asset, date):
asset.flags.ignore_validate_update_after_submit = True
asset.prepare_depreciation_data(date_of_disposal=date)
asset.save()
make_depreciation_entry(asset.name, date)
def reset_depreciation_schedule(asset, date):
asset.flags.ignore_validate_update_after_submit = True
# recreate original depreciation schedule of the asset
asset.prepare_depreciation_data(date_of_return=date)
modify_depreciation_schedule_for_asset_repairs(asset)
asset.save()
def modify_depreciation_schedule_for_asset_repairs(asset):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
for repair in asset_repairs:
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
asset.prepare_depreciation_data()
def reverse_depreciation_entry_made_after_disposal(asset, date):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
if schedule.finance_book != finance_book:
row = 0
finance_book = schedule.finance_book
else:
row += 1
if schedule.schedule_date == date:
if not disposal_was_made_on_original_schedule_date(
asset, schedule, row, date
) or disposal_happens_in_the_future(date):
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
reverse_journal_entry.posting_date = nowdate()
frappe.flags.is_reverse_depr_entry = True
reverse_journal_entry.submit()
frappe.flags.is_reverse_depr_entry = False
asset.flags.ignore_validate_update_after_submit = True
schedule.journal_entry = None
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
idx = cint(schedule.finance_book_id)
asset.finance_books[idx - 1].value_after_depreciation += depreciation_amount
asset.save()
def get_depreciation_amount_in_je(journal_entry):
if journal_entry.accounts[0].debit_in_account_currency:
return journal_entry.accounts[0].debit_in_account_currency
else:
return journal_entry.accounts[0].credit_in_account_currency
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
for finance_book in asset.get("finance_books"):
if schedule.finance_book == finance_book.finance_book:
orginal_schedule_date = add_months(
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
)
if is_last_day_of_the_month(finance_book.depreciation_start_date):
orginal_schedule_date = get_last_day(orginal_schedule_date)
if orginal_schedule_date == posting_date_of_disposal:
return True
return False
def disposal_happens_in_the_future(posting_date_of_disposal):
if posting_date_of_disposal > getdate():
return True
return False
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
(
fixed_asset_account,
@@ -307,18 +469,8 @@ def get_asset_details(asset, finance_book=None):
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
idx = 1
if finance_book:
for d in asset.finance_books:
if d.finance_book == finance_book:
idx = d.idx
break
value_after_depreciation = asset.get_value_after_depreciation(finance_book)
value_after_depreciation = (
asset.finance_books[idx - 1].value_after_depreciation
if asset.calculate_depreciation
else asset.value_after_depreciation
)
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
return (
@@ -358,3 +510,9 @@ def get_disposal_account_and_cost_center(company):
frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company))
return disposal_account, depreciation_cost_center
def is_last_day_of_the_month(date):
last_day_of_the_month = get_last_day(date)
return getdate(last_day_of_the_month) == getdate(date)

View File

@@ -4,11 +4,22 @@
import unittest
import frappe
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
from frappe.utils import (
add_days,
add_months,
cstr,
flt,
get_first_day,
get_last_day,
getdate,
nowdate,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import make_sales_invoice, update_maintenance_status
from erpnext.assets.doctype.asset.depreciation import (
is_last_day_of_the_month,
post_depreciation_entries,
restore_asset,
scrap_asset,
@@ -153,28 +164,59 @@ class TestAsset(AssetSetup):
self.assertEqual(doc.items[0].is_fixed_asset, 1)
def test_scrap_asset(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-01-01",
purchase_date="2020-01-01",
available_for_use_date=purchase_date,
purchase_date=purchase_date,
expected_value_after_useful_life=10000,
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
post_depreciation_entries(date=add_months("2020-01-01", 4))
post_depreciation_entries(date=add_months(purchase_date, 2))
asset.load_from_db()
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
self.assertEquals(accumulated_depr_amount, 18000.0)
scrap_asset(asset.name)
asset.load_from_db()
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
self.assertEquals(
accumulated_depr_amount,
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
)
self.assertEqual(asset.status, "Scrapped")
self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = (
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
(
"_Test Accumulated Depreciations - _TC",
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
(
"_Test Gain/Loss on Asset Disposal - _TC",
flt(82000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
)
gle = frappe.db.sql(
@@ -183,7 +225,7 @@ class TestAsset(AssetSetup):
order by account""",
asset.journal_entry_for_scrap,
)
self.assertEqual(gle, expected_gle)
self.assertSequenceEqual(gle, expected_gle)
restore_asset(asset.name)
@@ -191,34 +233,57 @@ class TestAsset(AssetSetup):
self.assertFalse(asset.journal_entry_for_scrap)
self.assertEqual(asset.status, "Partially Depreciated")
accumulated_depr_amount = flt(
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
asset.precision("gross_purchase_amount"),
)
this_month_depr_amount = 9000.0 if is_last_day_of_the_month(date) else 0
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
def test_gle_made_by_asset_sale(self):
date = nowdate()
purchase_date = add_months(get_first_day(date), -2)
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-06-06",
purchase_date="2020-01-01",
available_for_use_date=purchase_date,
purchase_date=purchase_date,
expected_value_after_useful_life=10000,
total_number_of_depreciations=3,
frequency_of_depreciation=10,
depreciation_start_date="2020-12-31",
total_number_of_depreciations=10,
frequency_of_depreciation=1,
submit=1,
)
post_depreciation_entries(date="2021-01-01")
post_depreciation_entries(date=add_months(purchase_date, 2))
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
si.customer = "_Test Customer"
si.set_posting_time = 1
si.posting_date = "2021-10-31"
si.due_date = "2021-10-31"
si.get("items")[0].rate = 75000
si.due_date = nowdate()
si.get("items")[0].rate = 25000
si.insert()
si.submit()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
)
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
expected_gle = (
("_Test Accumulated Depreciations - _TC", 50490.2, 0.0),
(
"_Test Accumulated Depreciations - _TC",
flt(18000.0 + pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 0.0, 25490.2),
("Debtors - _TC", 75000.0, 0.0),
(
"_Test Gain/Loss on Asset Disposal - _TC",
flt(57000.0 - pro_rata_amount, asset.precision("gross_purchase_amount")),
0.0,
),
("Debtors - _TC", 25000.0, 0.0),
)
gle = frappe.db.sql(
@@ -228,14 +293,9 @@ class TestAsset(AssetSetup):
si.name,
)
for i, gle_entry in enumerate(gle):
self.assertEqual(gle_entry[0], expected_gle[i][0])
self.assertEqual(flt(gle_entry[1], 1), flt(expected_gle[i][1], 1))
self.assertEqual(flt(gle_entry[2], 1), flt(expected_gle[i][2], 1))
self.assertSequenceEqual(gle, expected_gle)
si.load_from_db()
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_with_maintenance_required_status_after_sale(self):
@@ -1351,6 +1411,36 @@ class TestDepreciationBasics(AssetSetup):
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
def test_manual_depreciation_for_existing_asset(self):
asset = create_asset(
item_code="Macbook Pro",
is_existing_asset=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
submit=1,
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get("value_after_depreciation"), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
jv.voucher_type = "Depreciation Entry"
jv.insert()
jv.submit()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 100000)
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
@@ -1387,6 +1477,7 @@ def create_asset(**args):
"location": args.location or "Test Location",
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "",
}
)

View File

@@ -6,6 +6,7 @@ import unittest
import frappe
from frappe.utils import flt, nowdate
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.test_asset import (
create_asset,
create_asset_data,
@@ -105,20 +106,20 @@ class TestAssetRepair(unittest.TestCase):
def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value(asset)
initial_asset_value = get_asset_value_after_depreciation(asset.name)
asset_repair = create_asset_repair(asset=asset, stock_consumption=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
asset = create_asset(calculate_depreciation=1, submit=1)
initial_asset_value = get_asset_value(asset)
initial_asset_value = get_asset_value_after_depreciation(asset.name)
asset_repair = create_asset_repair(asset=asset, capitalize_repair_cost=1, submit=1)
asset.reload()
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
increase_in_asset_value = get_asset_value_after_depreciation(asset.name) - initial_asset_value
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
def test_purchase_invoice(self):
@@ -143,10 +144,6 @@ class TestAssetRepair(unittest.TestCase):
)
def get_asset_value(asset):
return asset.finance_books[0].value_after_depreciation
def num_of_depreciations(asset):
return asset.finance_books[0].total_number_of_depreciations

View File

@@ -47,7 +47,7 @@ frappe.ui.form.on('Asset Value Adjustment', {
set_current_asset_value: function(frm) {
if (frm.doc.asset) {
frm.call({
method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_current_asset_value",
method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation",
args: {
asset: frm.doc.asset,
finance_book: frm.doc.finance_book

View File

@@ -10,7 +10,10 @@ from frappe.utils import cint, date_diff, flt, formatdate, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.assets.doctype.asset.asset import get_depreciation_amount
from erpnext.assets.doctype.asset.asset import (
get_asset_value_after_depreciation,
get_depreciation_amount,
)
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from erpnext.regional.india.utils import (
get_depreciation_amount as get_depreciation_amount_for_india,
@@ -45,7 +48,7 @@ class AssetValueAdjustment(Document):
def set_current_asset_value(self):
if not self.current_asset_value and self.asset:
self.current_asset_value = get_current_asset_value(self.asset, self.finance_book)
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
def make_depreciation_entry(self):
asset = frappe.get_doc("Asset", self.asset)
@@ -148,12 +151,3 @@ class AssetValueAdjustment(Document):
for asset_data in asset.schedules:
if not asset_data.journal_entry:
asset_data.db_update()
@frappe.whitelist()
def get_current_asset_value(asset, finance_book=None):
cond = {"parent": asset, "parenttype": "Asset"}
if finance_book:
cond.update({"finance_book": finance_book})
return frappe.db.get_value("Asset Finance Book", cond, "value_after_depreciation")

View File

@@ -6,10 +6,8 @@ import unittest
import frappe
from frappe.utils import add_days, get_last_day, nowdate
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value,
)
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -43,7 +41,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
)
asset_doc.submit()
current_value = get_current_asset_value(asset_doc.name)
current_value = get_asset_value_after_depreciation(asset_doc.name)
self.assertEqual(current_value, 100000.0)
def test_asset_depreciation_value_adjustment(self):
@@ -73,7 +71,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
)
asset_doc.submit()
current_value = get_current_asset_value(asset_doc.name)
current_value = get_asset_value_after_depreciation(asset_doc.name)
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
)

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.utils import cstr, flt, formatdate, getdate
from erpnext.accounts.report.financial_statements import (
@@ -11,6 +12,8 @@ from erpnext.accounts.report.financial_statements import (
get_period_list,
validate_fiscal_year,
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
def execute(filters=None):
@@ -85,7 +88,9 @@ def get_data(filters):
"asset_name",
"status",
"department",
"company",
"cost_center",
"calculate_depreciation",
"purchase_receipt",
"asset_category",
"purchase_date",
@@ -97,12 +102,21 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
for asset in assets_record:
asset_value = (
asset.gross_purchase_amount
- flt(asset.opening_accumulated_depreciation)
- flt(depreciation_amount_map.get(asset.name))
)
if filters.finance_book:
if asset.asset_id not in assets_linked_to_fb:
continue
else:
if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
continue
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
@@ -113,7 +127,7 @@ def get_data(filters):
or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
@@ -170,6 +184,15 @@ def prepare_chart_data(data, filters):
}
def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
if asset.calculate_depreciation:
depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
else:
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
return flt(depr_amount, 2)
def get_finance_book_value_map(filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
@@ -189,6 +212,31 @@ def get_finance_book_value_map(filters):
)
def get_manual_depreciation_amount_of_asset(asset, filters):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
gle = frappe.qb.DocType("GL Entry")
result = (
frappe.qb.from_(gle)
.select(Sum(gle.debit))
.where(gle.against_voucher == asset.asset_id)
.where(gle.account == depreciation_expense_account)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(gle.posting_date <= date)
).run()
if result and result[0] and result[0][0]:
depr_amount = result[0][0]
else:
depr_amount = 0
return depr_amount
def get_purchase_receipt_supplier_map():
return frappe._dict(
frappe.db.sql(

View File

@@ -1239,6 +1239,11 @@ class TestPurchaseOrder(FrappeTestCase):
automatically_fetch_payment_terms(enable=0)
def test_variant_item_po(self):
po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
self.assertRaises(frappe.ValidationError, po.save)
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
@@ -1342,8 +1347,8 @@ def create_purchase_order(**args):
},
)
po.set_missing_values()
if not args.do_not_save:
po.set_missing_values()
po.insert()
if not args.do_not_submit:
if po.is_subcontracted == "Yes":

View File

@@ -383,7 +383,7 @@ class AccountsController(TransactionBase):
self.get("inter_company_reference")
or self.get("inter_company_invoice_reference")
or self.get("inter_company_order_reference")
):
) and not self.get("is_return"):
msg = _("Internal Sale or Delivery Reference missing.")
msg += _("Please create purchase from internal sale or delivery document itself")
frappe.throw(msg, title=_("Internal Sales Reference Missing"))

View File

@@ -755,6 +755,8 @@ class BuyingController(StockController, Subcontracting):
asset.purchase_date = self.posting_date
asset.supplier = self.supplier
elif self.docstatus == 2:
if asset.docstatus == 2:
continue
if asset.docstatus == 0:
asset.set(field, None)
asset.supplier = None

View File

@@ -25,7 +25,7 @@ class SellingController(StockController):
def onload(self):
super(SellingController, self).onload()
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
for item in self.get("items"):
for item in self.get("items") + (self.get("packed_items") or []):
item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True))
def validate(self):

View File

@@ -58,7 +58,7 @@ status_map = {
"eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1",
],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
["On Hold", "eval:self.status=='On Hold'"],
],
"Purchase Order": [
@@ -79,7 +79,7 @@ status_map = {
["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Delivery Note": [
["Draft", None],
@@ -87,7 +87,7 @@ status_map = {
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Purchase Receipt": [
["Draft", None],
@@ -95,7 +95,7 @@ status_map = {
["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"],
["Cancelled", "eval:self.docstatus==2"],
["Closed", "eval:self.status=='Closed'"],
["Closed", "eval:self.status=='Closed' and self.docstatus != 2"],
],
"Material Request": [
["Draft", None],

View File

@@ -102,7 +102,7 @@
}
],
"links": [],
"modified": "2021-06-29 18:27:02.832979",
"modified": "2022-12-28 16:35:34.377575",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment",
@@ -121,16 +121,6 @@
"share": 1,
"write": 1
},
{
"create": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Guest",
"share": 1
},
{
"create": 1,
"delete": 1,

View File

@@ -5,7 +5,9 @@
from collections import Counter
import frappe
import frappe.share
from frappe import _
from frappe.desk.form.assign_to import add as add_assignment
from frappe.model.document import Document
from frappe.utils import get_url, getdate
from frappe.utils.verified_command import get_signed_params
@@ -118,21 +120,18 @@ class Appointment(Document):
self.party = lead.name
def auto_assign(self):
from frappe.desk.form.assign_to import add as add_assignemnt
existing_assignee = self.get_assignee_from_latest_opportunity()
if existing_assignee:
# If the latest opportunity is assigned to someone
# Assign the appointment to the same
add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [existing_assignee]})
self.assign_agent(existing_assignee)
return
if self._assign:
return
available_agents = _get_agents_sorted_by_asc_workload(getdate(self.scheduled_time))
for agent in available_agents:
if _check_agent_availability(agent, self.scheduled_time):
agent = agent[0]
add_assignemnt({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
self.assign_agent(agent[0])
break
def get_assignee_from_latest_opportunity(self):
@@ -187,9 +186,15 @@ class Appointment(Document):
params = {"email": self.customer_email, "appointment": self.name}
return get_url(verify_route + "?" + get_signed_params(params))
def assign_agent(self, agent):
if not frappe.has_permission(doc=self, user=agent):
frappe.share.add(self.doctype, self.name, agent, flags={"ignore_share_permission": True})
add_assignment({"doctype": self.doctype, "name": self.name, "assign_to": [agent]})
def _get_agents_sorted_by_asc_workload(date):
appointments = frappe.db.get_list("Appointment", fields="*")
appointments = frappe.get_all("Appointment", fields="*")
agent_list = _get_agent_list_as_strings()
if not appointments:
return agent_list
@@ -214,7 +219,7 @@ def _get_agent_list_as_strings():
def _check_agent_availability(agent_email, scheduled_time):
appointemnts_at_scheduled_time = frappe.get_list(
appointemnts_at_scheduled_time = frappe.get_all(
"Appointment", filters={"scheduled_time": scheduled_time}
)
for appointment in appointemnts_at_scheduled_time:

View File

@@ -1,4 +1,5 @@
{
"actions": [],
"creation": "2019-08-27 10:56:48.309824",
"doctype": "DocType",
"editable_grid": 1,
@@ -101,7 +102,8 @@
}
],
"issingle": 1,
"modified": "2019-11-26 12:14:17.669366",
"links": [],
"modified": "2022-12-28 16:41:28.773090",
"modified_by": "Administrator",
"module": "CRM",
"name": "Appointment Booking Settings",
@@ -117,13 +119,6 @@
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"role": "Guest",
"share": 1
},
{
"create": 1,
"email": 1,

View File

@@ -173,7 +173,10 @@ class TestWebsiteItem(unittest.TestCase):
# Website Item Portal Tests Begin
def test_website_item_breadcrumbs(self):
"Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group."
"""
Check if breadcrumbs include homepage, product listing navigation page,
parent item group(s) and item group
"""
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
item_code = "Test Breadcrumb Item"
@@ -196,7 +199,7 @@ class TestWebsiteItem(unittest.TestCase):
breadcrumbs = get_parent_item_groups(item.item_group)
self.assertEqual(breadcrumbs[0]["name"], "Home")
self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
self.assertEqual(breadcrumbs[1]["name"], "All Products")
self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")

View File

@@ -345,7 +345,8 @@
"image_field": "website_image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-06-28 17:10:30.613251",
"make_attachments_public": 1,
"modified": "2022-09-13 04:05:11.614087",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "Website Item",

View File

@@ -111,34 +111,30 @@ def get_data(filters: Filters) -> List:
employee.leave_approver
)
if (
(leave_approvers and len(leave_approvers) and user in leave_approvers)
or (user in ["Administrator", employee.user_id])
or ("HR Manager" in frappe.get_roles(user))
):
if len(active_employees) > 1:
row = frappe._dict()
row.employee = employee.name
row.employee_name = employee.employee_name
if len(active_employees) > 1:
row = frappe._dict()
leaves_taken = (
get_leaves_for_period(employee.name, leave_type, filters.from_date, filters.to_date) * -1
)
row.employee = employee.name
row.employee_name = employee.employee_name
new_allocation, expired_leaves, carry_forwarded_leaves = get_allocated_and_expired_leaves(
filters.from_date, filters.to_date, employee.name, leave_type
)
opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves)
leaves_taken = (
get_leaves_for_period(employee.name, leave_type, filters.from_date, filters.to_date) * -1
)
row.leaves_allocated = new_allocation
row.leaves_expired = expired_leaves
row.opening_balance = opening
row.leaves_taken = leaves_taken
new_allocation, expired_leaves, carry_forwarded_leaves = get_allocated_and_expired_leaves(
filters.from_date, filters.to_date, employee.name, leave_type
)
opening = get_opening_balance(employee.name, leave_type, filters, carry_forwarded_leaves)
# not be shown on the basis of days left it create in user mind for carry_forward leave
row.closing_balance = new_allocation + opening - (row.leaves_expired + leaves_taken)
row.indent = 1
data.append(row)
row.leaves_allocated = new_allocation
row.leaves_expired = expired_leaves
row.opening_balance = opening
row.leaves_taken = leaves_taken
# not be shown on the basis of days left it create in user mind for carry_forward leave
row.closing_balance = new_allocation + opening - (row.leaves_expired + leaves_taken)
row.indent = 1
data.append(row)
return data

View File

@@ -65,21 +65,16 @@ def get_data(filters, leave_types):
if employee.leave_approver:
leave_approvers.append(employee.leave_approver)
if (
(len(leave_approvers) and user in leave_approvers)
or (user in ["Administrator", employee.user_id])
or ("HR Manager" in frappe.get_roles(user))
):
row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:
remaining = 0
if leave_type in available_leave["leave_allocation"]:
# opening balance
remaining = available_leave["leave_allocation"][leave_type]["remaining_leaves"]
row = [employee.name, employee.employee_name, employee.department]
available_leave = get_leave_details(employee.name, filters.date)
for leave_type in leave_types:
remaining = 0
if leave_type in available_leave["leave_allocation"]:
# opening balance
remaining = available_leave["leave_allocation"][leave_type]["remaining_leaves"]
row += [remaining]
row += [remaining]
data.append(row)
data.append(row)
return data

View File

@@ -13,38 +13,24 @@ frappe.query_reports["Work Order Summary"] = {
reqd: 1
},
{
fieldname: "fiscal_year",
label: __("Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: frappe.defaults.get_user_default("fiscal_year"),
reqd: 1,
on_change: function(query_report) {
var fiscal_year = query_report.get_values().fiscal_year;
if (!fiscal_year) {
return;
}
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
frappe.query_report.set_filter_value({
from_date: fy.year_start_date,
to_date: fy.year_end_date
});
});
}
label: __("Based On"),
fieldname:"based_on",
fieldtype: "Select",
options: "Creation Date\nPlanned Date\nActual Date",
default: "Creation Date"
},
{
label: __("From Posting Date"),
fieldname:"from_date",
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
default: frappe.datetime.add_months(frappe.datetime.get_today(), -3),
reqd: 1
},
{
label: __("To Posting Date"),
fieldname:"to_date",
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_end_date"),
default: frappe.datetime.get_today(),
reqd: 1,
},
{

View File

@@ -31,6 +31,7 @@ def get_data(filters):
"sales_order",
"production_item",
"qty",
"creation",
"produced_qty",
"planned_start_date",
"planned_end_date",
@@ -47,8 +48,14 @@ def get_data(filters):
if filters.get(field):
query_filters[field] = filters.get(field)
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
if filters.get("based_on") == "Planned Date":
query_filters["planned_start_date"] = (">=", filters.get("from_date"))
query_filters["planned_end_date"] = ("<=", filters.get("to_date"))
elif filters.get("based_on") == "Actual Date":
query_filters["actual_start_date"] = (">=", filters.get("from_date"))
query_filters["actual_end_date"] = ("<=", filters.get("to_date"))
else:
query_filters["creation"] = ("between", [filters.get("from_date"), filters.get("to_date")])
data = frappe.get_all(
"Work Order", fields=fields, filters=query_filters, order_by="planned_start_date asc"
@@ -212,6 +219,12 @@ def get_columns(filters):
"options": "Sales Order",
"width": 90,
},
{
"label": _("Created On"),
"fieldname": "creation",
"fieldtype": "Date",
"width": 150,
},
{
"label": _("Planned Start Date"),
"fieldname": "planned_start_date",

View File

@@ -374,4 +374,5 @@ erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.show_hr_payroll_deprecation_warning
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
execute:frappe.db.set_value("Naming Series", "Naming Series", {"select_doc_for_series": "", "set_options": "", "prefix": "", "current_value": 0, "user_must_always_select": 0})
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.update_asset_value_for_manual_depr_entries

View File

@@ -7,6 +7,7 @@ from erpnext.stock.stock_ledger import update_entries_after
def execute():
doctypes_to_reload = [
("setup", "company"),
("stock", "repost_item_valuation"),
("stock", "stock_entry_detail"),
("stock", "purchase_receipt_item"),

View File

@@ -0,0 +1,38 @@
import frappe
from frappe.query_builder.functions import IfNull, Sum
def execute():
asset = frappe.qb.DocType("Asset")
gle = frappe.qb.DocType("GL Entry")
aca = frappe.qb.DocType("Asset Category Account")
company = frappe.qb.DocType("Company")
asset_total_depr_value_map = (
frappe.qb.from_(gle)
.join(asset)
.on(gle.against_voucher == asset.name)
.join(aca)
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
.join(company)
.on(company.name == asset.company)
.select(Sum(gle.debit).as_("value"), asset.name.as_("asset_name"))
.where(
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(asset.docstatus == 1)
.where(asset.calculate_depreciation == 0)
.groupby(asset.name)
)
frappe.qb.update(asset).join(asset_total_depr_value_map).on(
asset_total_depr_value_map.asset_name == asset.name
).set(
asset.value_after_depreciation, asset.value_after_depreciation - asset_total_depr_value_map.value
).where(
asset.docstatus == 1
).where(
asset.calculate_depreciation == 0
).run()

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import datetime
import math
@@ -316,6 +315,8 @@ class SalarySlip(TransactionBase):
)
working_days = date_diff(self.end_date, self.start_date) + 1
working_days_list = [add_days(self.start_date, i) for i in range(working_days)]
if for_preview:
self.total_working_days = working_days
self.payment_days = working_days
@@ -325,6 +326,8 @@ class SalarySlip(TransactionBase):
if not cint(include_holidays_in_total_working_days):
working_days -= len(holidays)
working_days_list = [cstr(day) for day in working_days_list if cstr(day) not in holidays]
if working_days < 0:
frappe.throw(_("There are more holidays than working days this month."))
@@ -335,7 +338,7 @@ class SalarySlip(TransactionBase):
actual_lwp, absent = self.calculate_lwp_ppl_and_absent_days_based_on_attendance(holidays)
self.absent_days = absent
else:
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days)
actual_lwp = self.calculate_lwp_or_ppl_based_on_leave_application(holidays, working_days_list)
if not lwp:
lwp = actual_lwp
@@ -458,16 +461,15 @@ class SalarySlip(TransactionBase):
def get_holidays_for_employee(self, start_date, end_date):
return get_holiday_dates_for_employee(self.employee, start_date, end_date)
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days):
def calculate_lwp_or_ppl_based_on_leave_application(self, holidays, working_days_list):
lwp = 0
holidays = "','".join(holidays)
daily_wages_fraction_for_half_day = (
flt(frappe.db.get_value("Payroll Settings", None, "daily_wages_fraction_for_half_day")) or 0.5
)
for d in range(working_days):
date = add_days(cstr(getdate(self.start_date)), d)
leave = get_lwp_or_ppl_for_date(date, self.employee, holidays)
for d in working_days_list:
leave = get_lwp_or_ppl_for_date(d, self.employee, holidays)
if leave:
equivalent_lwp_count = 0

View File

@@ -115,24 +115,16 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
calculate_item_values: function() {
let me = this;
if (!this.discount_amount_applied) {
$.each(this.frm.doc["items"] || [], function(i, item) {
for (const item of this.frm.doc.items || []) {
frappe.model.round_floats_in(item);
item.net_rate = item.rate;
if ((!item.qty) && me.frm.doc.is_return) {
item.amount = flt(item.rate * -1, precision("amount", item));
} else if ((!item.qty) && me.frm.doc.is_debit_note) {
item.amount = flt(item.rate, precision("amount", item));
} else {
item.amount = flt(item.rate * item.qty, precision("amount", item));
}
item.net_amount = item.amount;
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
item.item_tax_amount = 0.0;
item.total_weight = flt(item.weight_per_unit * item.stock_qty);
me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]);
});
}
}
},

View File

@@ -1781,6 +1781,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
var me = this;
var valid = true;
if (frappe.flags.ignore_company_party_validation) {
return valid;
}
$.each(["company", "customer"], function(i, fieldname) {
if(frappe.meta.has_field(me.frm.doc.doctype, fieldname) && me.frm.doc.doctype != "Purchase Order") {
if (!me.frm.doc[fieldname]) {

View File

@@ -466,7 +466,20 @@ erpnext.utils.update_child_items = function(opts) {
const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
this.data = [];
this.data = frm.doc[opts.child_docname].map((d) => {
return {
"docname": d.name,
"name": d.name,
"item_code": d.item_code,
"delivery_date": d.delivery_date,
"schedule_date": d.schedule_date,
"conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
"uom": d.uom
}
});
const fields = [{
fieldtype:'Data',
fieldname:"docname",
@@ -559,7 +572,7 @@ erpnext.utils.update_child_items = function(opts) {
})
}
const dialog = new frappe.ui.Dialog({
new frappe.ui.Dialog({
title: __("Update Items"),
fields: [
{
@@ -595,24 +608,7 @@ erpnext.utils.update_child_items = function(opts) {
refresh_field("items");
},
primary_action_label: __('Update')
});
frm.doc[opts.child_docname].forEach(d => {
dialog.fields_dict.trans_items.df.data.push({
"docname": d.name,
"name": d.name,
"item_code": d.item_code,
"delivery_date": d.delivery_date,
"schedule_date": d.schedule_date,
"conversion_factor": d.conversion_factor,
"qty": d.qty,
"rate": d.rate,
"uom": d.uom
});
this.data = dialog.fields_dict.trans_items.df.data;
dialog.fields_dict.trans_items.grid.refresh();
})
dialog.show();
}).show();
}
erpnext.utils.map_current_doc = function(opts) {

View File

@@ -71,7 +71,11 @@ def validate_eligibility(doc):
# if export invoice, then taxes can be empty
# invoice can only be ineligible if no taxes applied and is not an export invoice
no_taxes_applied = not doc.get("taxes") and not doc.get("gst_category") == "Overseas"
no_taxes_applied = (
not doc.get("taxes")
and not doc.get("gst_category") == "Overseas"
and not doc.get("gst_category") == "SEZ"
)
has_non_gst_item = any(d for d in doc.get("items", []) if d.get("is_non_gst"))
if (

View File

@@ -84,11 +84,15 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
}
if (doc.docstatus == 1 && !["Lost", "Ordered"].includes(doc.status)) {
this.frm.add_custom_button(
__("Sales Order"),
this.frm.cscript["Make Sales Order"],
__("Create")
);
if (frappe.boot.sysdefaults.allow_sales_order_creation_for_expired_quotation
|| (!doc.valid_till)
|| frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
this.frm.add_custom_button(
__("Sales Order"),
this.frm.cscript["Make Sales Order"],
__("Create")
);
}
if(doc.status!=="Ordered") {
this.frm.add_custom_button(__('Set as Lost'), () => {

View File

@@ -191,14 +191,18 @@ def get_list_context(context=None):
@frappe.whitelist()
def make_sales_order(source_name, target_doc=None):
quotation = frappe.db.get_value(
"Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
)
if quotation.valid_till and (
quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
def make_sales_order(source_name: str, target_doc=None):
if not frappe.db.get_singles_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
):
frappe.throw(_("Validity period of this quotation has ended."))
quotation = frappe.db.get_value(
"Quotation", source_name, ["transaction_date", "valid_till"], as_dict=1
)
if quotation.valid_till and (
quotation.valid_till < quotation.transaction_date or quotation.valid_till < getdate(nowdate())
):
frappe.throw(_("Validity period of this quotation has ended."))
return _make_sales_order(source_name, target_doc)

View File

@@ -118,18 +118,31 @@ class TestQuotation(FrappeTestCase):
sales_order.payment_schedule[1].due_date, getdate(add_days(quotation.transaction_date, 30))
)
def test_valid_till(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
def test_valid_till_before_transaction_date(self):
quotation = frappe.copy_doc(test_records[0])
quotation.valid_till = add_days(quotation.transaction_date, -1)
self.assertRaises(frappe.ValidationError, quotation.validate)
def test_so_from_expired_quotation(self):
from erpnext.selling.doctype.quotation.quotation import make_sales_order
frappe.db.set_single_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation", 0
)
quotation = frappe.copy_doc(test_records[0])
quotation.valid_till = add_days(nowdate(), -1)
quotation.insert()
quotation.submit()
self.assertRaises(frappe.ValidationError, make_sales_order, quotation.name)
frappe.db.set_single_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation", 1
)
make_sales_order(quotation.name)
def test_shopping_cart_without_website_item(self):
if frappe.db.exists("Website Item", {"item_code": "_Test Item Home Desktop 100"}):
frappe.get_last_doc("Website Item", {"item_code": "_Test Item Home Desktop 100"}).delete()

View File

@@ -14,7 +14,6 @@ def get_data():
},
"internal_links": {
"Quotation": ["items", "prevdoc_docname"],
"Material Request": ["items", "material_request"],
},
"transactions": [
{

View File

@@ -546,6 +546,42 @@ class TestSalesOrder(FrappeTestCase):
workflow.is_active = 0
workflow.save()
def test_bin_details_of_packed_item(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
so = make_sales_order(
item_code="_Test Product Bundle Item New",
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
so.transaction_date = nowdate()
so.save()
packed_item = so.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_update_child_product_bundle(self):
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Product Bundle Item"):

View File

@@ -809,7 +809,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-04-27 03:15:34.366563",
"modified": "2022-12-25 02:51:10.247569",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
@@ -820,4 +820,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -32,7 +32,8 @@
"sales_update_frequency",
"allow_multiple_items",
"allow_against_multiple_purchase_orders",
"hide_tax_id"
"hide_tax_id",
"allow_sales_order_creation_for_expired_quotation"
],
"fields": [
{
@@ -199,6 +200,12 @@
"fieldtype": "Select",
"label": "Contract Naming By",
"options": "Party Name\nNaming Series"
},
{
"default": "0",
"fieldname": "allow_sales_order_creation_for_expired_quotation",
"fieldtype": "Check",
"label": "Allow Sales Order Creation For Expired Quotation"
}
],
"icon": "fa fa-cog",
@@ -206,7 +213,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-03-28 12:18:06.768403",
"modified": "2023-02-04 12:37:53.380857",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling Settings",

View File

@@ -41,8 +41,20 @@ def get_columns(filters):
{"label": _("Description"), "fieldtype": "Data", "fieldname": "description", "width": 150},
{"label": _("Quantity"), "fieldtype": "Float", "fieldname": "quantity", "width": 150},
{"label": _("UOM"), "fieldtype": "Link", "fieldname": "uom", "options": "UOM", "width": 100},
{"label": _("Rate"), "fieldname": "rate", "options": "Currency", "width": 120},
{"label": _("Amount"), "fieldname": "amount", "options": "Currency", "width": 120},
{
"label": _("Rate"),
"fieldname": "rate",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
},
{
"label": _("Sales Order"),
"fieldtype": "Link",
@@ -93,8 +105,9 @@ def get_columns(filters):
},
{
"label": _("Billed Amount"),
"fieldtype": "currency",
"fieldtype": "Currency",
"fieldname": "billed_amount",
"options": "currency",
"width": 120,
},
{
@@ -104,6 +117,13 @@ def get_columns(filters):
"options": "Company",
"width": 100,
},
{
"label": _("Currency"),
"fieldtype": "Link",
"fieldname": "currency",
"options": "Currency",
"hidden": 1,
},
]
@@ -141,31 +161,12 @@ def get_data(filters):
"billed_amount": flt(record.get("billed_amt")),
"company": record.get("company"),
}
row["currency"] = frappe.get_cached_value("Company", row["company"], "default_currency")
data.append(row)
return data
def get_conditions(filters):
conditions = ""
if filters.get("item_group"):
conditions += "AND so_item.item_group = %s" % frappe.db.escape(filters.item_group)
if filters.get("from_date"):
conditions += "AND so.transaction_date >= '%s'" % filters.from_date
if filters.get("to_date"):
conditions += "AND so.transaction_date <= '%s'" % filters.to_date
if filters.get("item_code"):
conditions += "AND so_item.item_code = %s" % frappe.db.escape(filters.item_code)
if filters.get("customer"):
conditions += "AND so.customer = %s" % frappe.db.escape(filters.customer)
return conditions
def get_customer_details():
details = frappe.get_all("Customer", fields=["name", "customer_name", "customer_group"])
customer_details = {}
@@ -187,29 +188,50 @@ def get_item_details():
def get_sales_order_details(company_list, filters):
conditions = get_conditions(filters)
db_so = frappe.qb.DocType("Sales Order")
db_so_item = frappe.qb.DocType("Sales Order Item")
return frappe.db.sql(
"""
SELECT
so_item.item_code, so_item.description, so_item.qty,
so_item.uom, so_item.base_rate, so_item.base_amount,
so.name, so.transaction_date, so.customer,so.territory,
so.project, so_item.delivered_qty,
so_item.billed_amt, so.company
FROM
`tabSales Order` so, `tabSales Order Item` so_item
WHERE
so.name = so_item.parent
AND so.company in ({0})
AND so.docstatus = 1 {1}
""".format(
",".join(["%s"] * len(company_list)), conditions
),
tuple(company_list),
as_dict=1,
query = (
frappe.qb.from_(db_so)
.inner_join(db_so_item)
.on(db_so_item.parent == db_so.name)
.select(
db_so.name,
db_so.customer,
db_so.transaction_date,
db_so.territory,
db_so.project,
db_so.company,
db_so_item.item_code,
db_so_item.description,
db_so_item.qty,
db_so_item.uom,
db_so_item.base_rate,
db_so_item.base_amount,
db_so_item.delivered_qty,
(db_so_item.billed_amt * db_so.conversion_rate).as_("billed_amt"),
)
.where(db_so.docstatus == 1)
.where(db_so.company.isin(tuple(company_list)))
)
if filters.get("item_group"):
query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_group))
if filters.get("from_date"):
query = query.where(db_so.transaction_date >= filters.from_date)
if filters.get("to_date"):
query = query.where(db_so.transaction_date <= filters.to_date)
if filters.get("item_code"):
query = query.where(db_so_item.item_group == frappe.db.escape(filters.item_code))
if filters.get("customer"):
query = query.where(db_so.customer == filters.customer)
return query.run(as_dict=1)
def get_chart_data(data):
item_wise_sales_map = {}

View File

@@ -13,9 +13,10 @@
"docstatus": 0,
"doctype": "Workspace",
"extends_another_page": 0,
"hide_custom": 1,
"hide_custom": 0,
"icon": "sell",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"label": "Selling",
"links": [
@@ -515,7 +516,7 @@
"type": "Link"
}
],
"modified": "2020-12-01 13:38:35.971277",
"modified": "2023-01-28 17:10:02.716760",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling",

View File

@@ -123,6 +123,7 @@
"fieldname": "route",
"fieldtype": "Data",
"label": "Route",
"no_copy": 1,
"unique": 1
},
{
@@ -232,11 +233,10 @@
"is_tree": 1,
"links": [],
"max_attachments": 3,
"modified": "2022-03-09 12:27:11.055782",
"modified": "2023-01-05 12:21:30.458628",
"modified_by": "Administrator",
"module": "Setup",
"name": "Item Group",
"name_case": "Title Case",
"naming_rule": "By fieldname",
"nsm_parent_field": "parent_item_group",
"owner": "Administrator",

View File

@@ -149,12 +149,12 @@ def get_item_for_list_in_html(context):
def get_parent_item_groups(item_group_name, from_item=False):
base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"}
base_nav_page = {"name": _("All Products"), "route": "/all-products"}
if from_item and frappe.request.environ.get("HTTP_REFERER"):
# base page after 'Home' will vary on Item page
last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
if last_page and last_page in ("shop-by-category", "all-products"):
if last_page and last_page == "shop-by-category":
base_nav_page_title = " ".join(last_page.split("-")).title()
base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}

View File

@@ -26,6 +26,12 @@ def boot_session(bootinfo):
frappe.db.get_single_value("Selling Settings", "default_valid_till")
)
bootinfo.sysdefaults.allow_sales_order_creation_for_expired_quotation = cint(
frappe.db.get_single_value(
"Selling Settings", "allow_sales_order_creation_for_expired_quotation"
)
)
# if no company, show a dialog box to create a new company
bootinfo.customer_count = frappe.db.sql("""SELECT count(*) FROM `tabCustomer`""")[0][0]

View File

@@ -42,7 +42,7 @@ erpnext.stock.ItemDashboard = Class.extend({
let warehouse = unescape(element.attr('data-warehouse'));
let actual_qty = unescape(element.attr('data-actual_qty'));
let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
let entry_type = action === "Move" ? "Material Transfer" : null;
let entry_type = action === "Move" ? "Material Transfer" : "Material Receipt";
if (disable_quick_entry) {
open_stock_entry(item, warehouse, entry_type);
@@ -63,11 +63,19 @@ erpnext.stock.ItemDashboard = Class.extend({
function open_stock_entry(item, warehouse, entry_type) {
frappe.model.with_doctype('Stock Entry', function () {
var doc = frappe.model.get_new_doc('Stock Entry');
if (entry_type) doc.stock_entry_type = entry_type;
if (entry_type) {
doc.stock_entry_type = entry_type;
}
var row = frappe.model.add_child(doc, 'items');
row.item_code = item;
row.s_warehouse = warehouse;
if (entry_type === "Material Transfer") {
row.s_warehouse = warehouse;
}
else {
row.t_warehouse = warehouse;
}
frappe.set_route('Form', doc.doctype, doc.name);
});

View File

@@ -150,12 +150,17 @@ def update_qty(bin_name, args):
last_sle_qty = (
frappe.qb.from_(sle)
.select(sle.qty_after_transaction)
.where((sle.item_code == args.get("item_code")) & (sle.warehouse == args.get("warehouse")))
.where(
(sle.item_code == args.get("item_code"))
& (sle.warehouse == args.get("warehouse"))
& (sle.is_cancelled == 0)
)
.orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=Order.desc)
.orderby(sle.creation, order=Order.desc)
.run()
)
actual_qty = 0.0
if last_sle_qty:
actual_qty = last_sle_qty[0][0]

View File

@@ -490,6 +490,46 @@ class TestDeliveryNote(FrappeTestCase):
self.assertEqual(gle_warehouse_amount, 1400)
def test_bin_details_of_packed_item(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.stock.doctype.item.test_item import make_item
# test Update Items with product bundle
if not frappe.db.exists("Item", "_Test Product Bundle Item New"):
bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0})
bundle_item.append(
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
)
bundle_item.save(ignore_permissions=True)
make_item("_Packed Item New 1", {"is_stock_item": 1})
make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2)
si = create_delivery_note(
item_code="_Test Product Bundle Item New",
update_stock=1,
warehouse="_Test Warehouse - _TC",
transaction_date=add_days(nowdate(), -1),
do_not_submit=1,
)
make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100)
bin_details = frappe.db.get_value(
"Bin",
{"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"},
["actual_qty", "projected_qty", "ordered_qty"],
as_dict=1,
)
si.transaction_date = nowdate()
si.save()
packed_item = si.packed_items[0]
self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty))
self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty))
self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty))
def test_return_for_serialized_items(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
@@ -650,6 +690,11 @@ class TestDeliveryNote(FrappeTestCase):
update_delivery_note_status(dn.name, "Closed")
self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed")
# Check cancelling closed delivery note
dn.load_from_db()
dn.cancel()
self.assertEqual(dn.status, "Cancelled")
def test_dn_billing_status_case1(self):
# SO -> DN -> SI
so = make_sales_order()

View File

@@ -849,6 +849,12 @@ function open_form(frm, doctype, child_doctype, parentfield) {
new_child_doc.uom = frm.doc.stock_uom;
new_child_doc.description = frm.doc.description;
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
frappe.run_serially([
() => frappe.ui.form.make_quick_entry(doctype, null, null, new_doc),
() => {
frappe.flags.ignore_company_party_validation = true;
frappe.model.trigger("item_code", frm.doc.name, new_child_doc);
}
])
});
}

View File

@@ -955,7 +955,8 @@
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-04-28 04:52:10.272256",
"make_attachments_public": 1,
"modified": "2022-09-13 04:08:17.431731",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",

View File

@@ -104,7 +104,6 @@ class TestItem(FrappeTestCase):
"conversion_factor": 1.0,
"reserved_qty": 1,
"actual_qty": 5,
"ordered_qty": 10,
"projected_qty": 14,
}

View File

@@ -74,11 +74,10 @@ class ItemAttribute(Document):
def validate_duplication(self):
values, abbrs = [], []
for d in self.item_attribute_values:
d.abbr = d.abbr.upper()
if d.attribute_value in values:
frappe.throw(_("{0} must appear only once").format(d.attribute_value))
if d.attribute_value.lower() in map(str.lower, values):
frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title()))
values.append(d.attribute_value)
if d.abbr in abbrs:
frappe.throw(_("{0} must appear only once").format(d.abbr))
if d.abbr.lower() in map(str.lower, abbrs):
frappe.throw(_("Abbreviation: {0} must appear only once").format(d.abbr.title()))
abbrs.append(d.abbr)

View File

@@ -366,10 +366,11 @@ frappe.ui.form.on('Material Request', {
frappe.ui.form.on("Material Request Item", {
qty: function (frm, doctype, name) {
var d = locals[doctype][name];
if (flt(d.qty) < flt(d.min_order_qty)) {
const item = locals[doctype][name];
if (flt(item.qty) < flt(item.min_order_qty)) {
frappe.msgprint(__("Warning: Material Requested Qty is less than Minimum Order Qty"));
}
frm.events.get_item_data(frm, item, false);
},
from_warehouse: function(frm, doctype, name) {

View File

@@ -83,8 +83,8 @@ def reset_packing_list(doc):
# 1. items were deleted
# 2. if bundle item replaced by another item (same no. of items but different items)
# we maintain list to track recurring item rows as well
items_before_save = [item.item_code for item in doc_before_save.get("items")]
items_after_save = [item.item_code for item in doc.get("items")]
items_before_save = [(item.name, item.item_code) for item in doc_before_save.get("items")]
items_after_save = [(item.name, item.item_code) for item in doc.get("items")]
reset_table = items_before_save != items_after_save
else:
# reset: if via Update Items OR

View File

@@ -198,7 +198,8 @@ class PickList(Document):
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
def before_print(self, settings=None):
self.group_similar_items()
if self.group_same_items:
self.group_similar_items()
def group_similar_items(self):
group_item_qty = defaultdict(float)

View File

@@ -1,7 +1,10 @@
def get_data():
return {
"fieldname": "pick_list",
"internal_links": {
"Sales Order": ["locations", "sales_order"],
},
"transactions": [
{"items": ["Stock Entry", "Delivery Note"]},
{"items": ["Stock Entry", "Sales Order", "Delivery Note"]},
],
}

View File

@@ -432,10 +432,10 @@ class TestPickList(FrappeTestCase):
pl.before_print()
self.assertEqual(len(pl.locations), 4)
# grouping should halve the number of items
# grouping should not happen if group_same_items is False
pl = frappe.get_doc(
doctype="Pick List",
group_same_items=True,
group_same_items=False,
locations=[
_dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
_dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
@@ -444,6 +444,11 @@ class TestPickList(FrappeTestCase):
],
)
pl.before_print()
self.assertEqual(len(pl.locations), 4)
# grouping should halve the number of items
pl.group_same_items = True
pl.before_print()
self.assertEqual(len(pl.locations), 2)
expected_items = [

View File

@@ -221,7 +221,7 @@ class QualityInspection(Document):
def item_query(doctype, txt, searchfield, start, page_len, filters):
from frappe.desk.reportview import get_match_cond
from_doctype = cstr(filters.get("doctype"))
from_doctype = cstr(filters.get("from"))
if not from_doctype or not frappe.db.exists("DocType", from_doctype):
return []

View File

@@ -2440,7 +2440,7 @@ def get_uom_details(item_code, uom, qty):
if not conversion_factor:
frappe.msgprint(
_("UOM coversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
_("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
)
ret = {"uom": ""}
else:

View File

@@ -1571,6 +1571,48 @@ class TestStockEntry(FrappeTestCase):
self.assertRaises(BatchExpiredError, se.save)
def test_negative_stock_reco(self):
from erpnext.controllers.stock_controller import BatchExpiredError
from erpnext.stock.doctype.batch.test_batch import make_new_batch
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0)
item_code = "Test Negative Item - 001"
item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
make_stock_entry(
item_code=item_code,
posting_date=add_days(today(), -3),
posting_time="00:00:00",
purpose="Material Receipt",
qty=10,
to_warehouse="_Test Warehouse - _TC",
do_not_save=True,
)
make_stock_entry(
item_code=item_code,
posting_date=today(),
posting_time="00:00:00",
purpose="Material Receipt",
qty=8,
from_warehouse="_Test Warehouse - _TC",
do_not_save=True,
)
sr_doc = create_stock_reconciliation(
purpose="Stock Reconciliation",
posting_date=add_days(today(), -3),
posting_time="00:00:00",
item_code=item_code,
warehouse="_Test Warehouse - _TC",
valuation_rate=10,
qty=7,
do_not_submit=True,
)
self.assertRaises(frappe.ValidationError, sr_doc.submit)
def make_serialized_item(**args):
args = frappe._dict(args)

View File

@@ -226,8 +226,10 @@ def validate_item_details(args, item):
validate_end_of_life(item.name, item.end_of_life, item.disabled)
if args.transaction_type == "selling" and cint(item.has_variants):
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
if cint(item.has_variants):
msg = f"Item {item.name} is a template, please select one of its variants"
throw(_(msg), title=_("Template Item Selected"))
elif args.transaction_type == "buying" and args.doctype != "Material Request":
if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1:
@@ -1161,7 +1163,7 @@ def get_projected_qty(item_code, warehouse):
@frappe.whitelist()
def get_bin_details(item_code, warehouse, company=None, include_child_warehouses=False):
bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0, "ordered_qty": 0}
bin_details = {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
if warehouse:
from frappe.query_builder.functions import Coalesce, Sum
@@ -1177,7 +1179,6 @@ def get_bin_details(item_code, warehouse, company=None, include_child_warehouses
Coalesce(Sum(bin.projected_qty), 0).as_("projected_qty"),
Coalesce(Sum(bin.actual_qty), 0).as_("actual_qty"),
Coalesce(Sum(bin.reserved_qty), 0).as_("reserved_qty"),
Coalesce(Sum(bin.ordered_qty), 0).as_("ordered_qty"),
)
.where((bin.item_code == item_code) & (bin.warehouse.isin(warehouses)))
).run(as_dict=True)[0]

View File

@@ -41,7 +41,7 @@ def get_data(report_filters):
key = (d.voucher_type, d.voucher_no)
gl_data = voucher_wise_gl_data.get(key) or {}
d.account_value = gl_data.get("account_value", 0)
d.difference_value = abs(d.stock_value - d.account_value)
d.difference_value = d.stock_value - d.account_value
if abs(d.difference_value) > 0.1:
data.append(d)

View File

@@ -62,7 +62,7 @@ def execute(filters=None):
continue
total_stock_value = sum(item_value[(item, item_group)])
row = [item, item_group, total_stock_value]
row = [item, item_map[item]["item_name"], item_group, total_stock_value]
fifo_queue = item_ageing[item]["fifo_queue"]
average_age = 0.00
@@ -89,10 +89,11 @@ def get_columns(filters):
"""return columns"""
columns = [
_("Item") + ":Link/Item:180",
_("Item Group") + "::100",
_("Value") + ":Currency:100",
_("Age") + ":Float:60",
_("Item") + ":Link/Item:150",
_("Item Name") + ":Link/Item:150",
_("Item Group") + "::120",
_("Value") + ":Currency:120",
_("Age") + ":Float:120",
]
return columns
@@ -132,7 +133,7 @@ def get_warehouse_list(filters):
def add_warehouse_column(columns, warehouse_list):
if len(warehouse_list) > 1:
columns += [_("Total Qty") + ":Int:50"]
columns += [_("Total Qty") + ":Int:120"]
for wh in warehouse_list:
columns += [_(wh.name) + ":Int:54"]
columns += [_(wh.name) + ":Int:100"]

View File

@@ -1021,7 +1021,7 @@ class update_entries_after(object):
frappe.db.set_value("Bin", bin_name, updated_values)
def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_voucher=False):
"""get stock ledger entries filtered by specific posting datetime conditions"""
args["time_format"] = "%H:%i:%s"
@@ -1043,11 +1043,17 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
and warehouse = %(warehouse)s
and is_cancelled = 0
{voucher_condition}
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
and (
posting_date < %(posting_date)s or
(
posting_date = %(posting_date)s and
time_format(posting_time, %(time_format)s) {operator} time_format(%(posting_time)s, %(time_format)s)
)
)
order by timestamp(posting_date, posting_time) desc, creation desc
limit 1
for update""".format(
voucher_condition=voucher_condition
operator=operator, voucher_condition=voucher_condition
),
args,
as_dict=1,
@@ -1144,7 +1150,7 @@ def get_stock_ledger_entries(
def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
return frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle]},
{"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle], "is_cancelled": 0},
[
"item_code",
"warehouse",
@@ -1184,20 +1190,6 @@ def get_valuation_rate(
(item_code, warehouse, voucher_no, voucher_type),
)
if not last_valuation_rate:
# Get valuation rate from last sle for the item against any warehouse
last_valuation_rate = frappe.db.sql(
"""select valuation_rate
from `tabStock Ledger Entry` force index (item_code)
where
item_code = %s
AND valuation_rate > 0
AND is_cancelled = 0
AND NOT(voucher_no = %s AND voucher_type = %s)
order by posting_date desc, posting_time desc, name desc limit 1""",
(item_code, voucher_no, voucher_type),
)
if last_valuation_rate:
return flt(last_valuation_rate[0][0])
@@ -1299,7 +1291,7 @@ def get_stock_reco_qty_shift(args):
stock_reco_qty_shift = flt(args.actual_qty)
else:
# reco is being submitted
last_balance = get_previous_sle_of_current_voucher(args, exclude_current_voucher=True).get(
last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get(
"qty_after_transaction"
)

View File

@@ -2,7 +2,6 @@ frappe.ready(async () => {
initialise_select_date();
})
window.holiday_list = [];
async function initialise_select_date() {
navigate_to_page(1);
@@ -20,7 +19,6 @@ async function get_global_variables() {
window.timezones = (await frappe.call({
method:'erpnext.www.book_appointment.index.get_timezones'
})).message;
window.holiday_list = window.appointment_settings.holiday_list;
}
function setup_timezone_selector() {

View File

@@ -26,8 +26,12 @@ def get_context(context):
@frappe.whitelist(allow_guest=True)
def get_appointment_settings():
settings = frappe.get_doc("Appointment Booking Settings")
settings.holiday_list = frappe.get_doc("Holiday List", settings.holiday_list)
settings = frappe.get_cached_value(
"Appointment Booking Settings",
None,
["advance_booking_days", "appointment_duration", "success_redirect_url"],
as_dict=True,
)
return settings
@@ -106,7 +110,7 @@ def create_appointment(date, time, tz, contact):
appointment.customer_details = contact.get("notes", None)
appointment.customer_email = contact.get("email", None)
appointment.status = "Open"
appointment.insert()
appointment.insert(ignore_permissions=True)
return appointment

View File

@@ -2,7 +2,6 @@ import frappe
from frappe.utils.verified_command import verify_request
@frappe.whitelist(allow_guest=True)
def get_context(context):
if not verify_request():
context.success = False