Compare commits

..

230 Commits

Author SHA1 Message Date
Frappe PR Bot
2561cf2072 chore(release): Bumped to Version 14.27.9
## [14.27.9](https://github.com/frappe/erpnext/compare/v14.27.8...v14.27.9) (2023-06-20)

### Bug Fixes

* `Process Loss Report` (backport [#35712](https://github.com/frappe/erpnext/issues/35712)) ([#35719](https://github.com/frappe/erpnext/issues/35719)) ([55a8be5](55a8be5cad))
* add validation for QI in PR (backport [#35677](https://github.com/frappe/erpnext/issues/35677)) ([#35757](https://github.com/frappe/erpnext/issues/35757)) ([59ab13c](59ab13c34f))
* Allocated amount validation for other party types ([#35741](https://github.com/frappe/erpnext/issues/35741)) ([5541d68](5541d68477))
* cannot start / stop jobs ([53ec2a9](53ec2a9268))
* conflicts ([697fcef](697fcef98b))
* consider field precision while setting sle actual_qty ([#35717](https://github.com/frappe/erpnext/issues/35717)) ([3f62e85](3f62e854e5))
* date and finance book fixes in fixed asset register (backport [#35751](https://github.com/frappe/erpnext/issues/35751)) ([#35799](https://github.com/frappe/erpnext/issues/35799)) ([8b57ecd](8b57ecd8ef))
* don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport [#35714](https://github.com/frappe/erpnext/issues/35714)) ([#35715](https://github.com/frappe/erpnext/issues/35715)) ([77b0c5f](77b0c5f722))
* Duplicate addresses are creating while using the E-commerce ([703e4f4](703e4f4f5d))
* fix get outstanding invoices btn and add get outstanding orders btn (backport [#35776](https://github.com/frappe/erpnext/issues/35776)) ([#35787](https://github.com/frappe/erpnext/issues/35787)) ([42e25d4](42e25d4cdf))
* for zero bal accounts, dr/cr only on currency that has balance ([7da461b](7da461b862))
* incorrect gl entries for standalone debit note with update stock ([3355dc2](3355dc2a41))
* incorrect stock value for purchase returned with rejected qty (backport [#35747](https://github.com/frappe/erpnext/issues/35747)) ([#35752](https://github.com/frappe/erpnext/issues/35752)) ([c11d950](c11d950fc5))
* keyerror while checking the stock balance report ([baf014f](baf014fc61))
* loan interest accrual date ([#35695](https://github.com/frappe/erpnext/issues/35695)) ([070df97](070df97663))
* **patch:** enable existing serial no in stock settings ([#35762](https://github.com/frappe/erpnext/issues/35762)) ([3c790c1](3c790c12f2))
* stock error for service item ([2bbea63](2bbea63de1))
* test case ([4af0a9b](4af0a9b192))
* unsupported operand type(s) for //: 'float' and 'NoneType' for POS Barcode search ([#35710](https://github.com/frappe/erpnext/issues/35710)) ([58a6bbc](58a6bbcf6d))
* update `Stock Reconciliation` diff qty while reposting ([bdb5cc8](bdb5cc8ad4))
* **ux:** set route options for new `Batch` ([b261242](b261242792))
* validation of job card in stock entry ([ce2bf5f](ce2bf5fb1c))
* work order serial no issue ([50a8907](50a8907e8c))

### Performance Improvements

* Duplicate queries for UOM (backport [#35744](https://github.com/frappe/erpnext/issues/35744)) ([#35745](https://github.com/frappe/erpnext/issues/35745)) ([632b67c](632b67cbc8))
* duplicate queries while checking prevdoc (backport [#35746](https://github.com/frappe/erpnext/issues/35746)) ([#35749](https://github.com/frappe/erpnext/issues/35749)) ([a0fc8e2](a0fc8e252c))
* Ignore cancelled pick lists while fetching picked items (backport [#35737](https://github.com/frappe/erpnext/issues/35737)) ([#35740](https://github.com/frappe/erpnext/issues/35740)) ([01ac54d](01ac54d65d))
* index `purpose` in `Stock Entry` (backport [#35782](https://github.com/frappe/erpnext/issues/35782)) ([#35783](https://github.com/frappe/erpnext/issues/35783)) ([3bac2a8](3bac2a88bd))
* Index pick list field in stock entry and DN (backport [#35738](https://github.com/frappe/erpnext/issues/35738)) ([#35742](https://github.com/frappe/erpnext/issues/35742)) ([b875de6](b875de6fb7))
* Index sales_order_item in Pick list item (backport [#35735](https://github.com/frappe/erpnext/issues/35735)) ([#35736](https://github.com/frappe/erpnext/issues/35736)) ([0e57f4d](0e57f4dd3c))
2023-06-20 16:06:29 +00:00
Deepesh Garg
abbbfe6240 Merge pull request #35807 from frappe/version-14-hotfix
chore: release v14
2023-06-20 21:34:30 +05:30
rohitwaghchaure
4e1b2c6f8d Merge branch 'version-14' into version-14-hotfix 2023-06-20 19:41:56 +05:30
rohitwaghchaure
c669dba691 Merge pull request #35813 from frappe/mergify/bp/version-14-hotfix/pr-35810
fix: stock error for service item (backport #35810)
2023-06-20 17:49:32 +05:30
rohitwaghchaure
8c183741bd Merge pull request #35812 from frappe/mergify/bp/version-14-hotfix/pr-35809
fix: key error while checking the stock balance report (backport #35809)
2023-06-20 17:30:51 +05:30
Rohit Waghchaure
2bbea63de1 fix: stock error for service item
(cherry picked from commit 32965f1af9)
2023-06-20 11:51:27 +00:00
Rohit Waghchaure
baf014fc61 fix: keyerror while checking the stock balance report
(cherry picked from commit a627d2a38c)
2023-06-20 11:32:06 +00:00
rohitwaghchaure
95e3dc9b81 Merge pull request #35805 from rohitwaghchaure/fixed-address-issue-from-e-cart
fix: Duplicate addresses are creating while using the E-commerce
2023-06-20 15:43:58 +05:30
Rohit Waghchaure
703e4f4f5d fix: Duplicate addresses are creating while using the E-commerce 2023-06-20 14:50:40 +05:30
ruthra kumar
bf2ebce6f4 Merge pull request #35803 from frappe/mergify/bp/version-14-hotfix/pr-35794
fix: Exchange Rate Revaluation should only post on the currency that has balance in a 'zero' balance account (backport #35794)
2023-06-20 14:49:06 +05:30
ruthra kumar
65d24ea9ea refactor: higher precision for rounding loss and allow '0'
(cherry picked from commit 6694175a51)
2023-06-20 14:02:45 +05:30
ruthra kumar
6c9c3426f8 refactor: allow '0' rounding allowance
(cherry picked from commit 4567474418)
2023-06-20 14:02:40 +05:30
ruthra kumar
146d41ee81 refactor: allow higher precision for new exchange rate
(cherry picked from commit 9d04af9ecc)
2023-06-20 08:29:53 +00:00
ruthra kumar
7da461b862 fix: for zero bal accounts, dr/cr only on currency that has balance
(cherry picked from commit 1b33afd699)
2023-06-20 08:29:53 +00:00
mergify[bot]
8b57ecd8ef fix: date and finance book fixes in fixed asset register (backport #35751) (#35799)
* fix: date and finance book fixes in fixed asset register (#35751)

* fix: handle finance books properly and show all assets by default in fixed asset register

* chore: rename value to depr amount

* chore: get asset value for correct fb properly

* chore: rename include_default_book_entries to include_default_book_assets

(cherry picked from commit 0d12588583)

# Conflicts:
#	erpnext/assets/report/fixed_asset_register/fixed_asset_register.py

* chore: resolving conflicts and renaming entries to assets

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-20 12:52:18 +05:30
rohitwaghchaure
7d010adcd4 Merge pull request #35786 from rohitwaghchaure/fixed-work-order-serial-no-issue
fix: work order serial no issue
2023-06-20 12:13:17 +05:30
Rohit Waghchaure
4af0a9b192 fix: test case 2023-06-20 11:42:02 +05:30
mergify[bot]
3bac2a88bd perf: index purpose in Stock Entry (backport #35782) (#35783)
perf: index `purpose` in `Stock Entry`

(cherry picked from commit 4f941ac5c0)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-19 22:22:50 +05:30
Rohit Waghchaure
50a8907e8c fix: work order serial no issue 2023-06-19 21:29:34 +05:30
mergify[bot]
42e25d4cdf fix: fix get outstanding invoices btn and add get outstanding orders btn (backport #35776) (#35787)
fix: fix get outstanding invoices btn and add get outstanding orders btn (#35776)

* fix: fix get outstanding invoices btn and add get outstanding orders btn

* chore: remove unnecessary arg

(cherry picked from commit c1da3ddbbf)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-19 20:17:56 +05:30
Frappe PR Bot
4c1befab8f chore(release): Bumped to Version 14.27.8
## [14.27.8](https://github.com/frappe/erpnext/compare/v14.27.7...v14.27.8) (2023-06-19)

### Bug Fixes

* Allocated amount validation for other party types ([#35741](https://github.com/frappe/erpnext/issues/35741)) ([3bf7115](3bf7115cdc))
2023-06-19 11:38:53 +00:00
Deepesh Garg
d928a5c3aa Merge pull request #35774 from frappe/mergify/bp/version-14/pr-35770
fix: Allocated amount validation for other party types (#35741)
2023-06-19 17:07:06 +05:30
mergify[bot]
3bf7115cdc fix: Allocated amount validation for other party types (#35741)
fix: Allocated amount validation for other party types (#35741)

* fix: Allocated amount validation for other party types

* chore: Validation for return allocations

* chore: minor typo

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 9d27a25e5f)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 5541d68477)
2023-06-19 07:22:55 +00:00
mergify[bot]
5541d68477 fix: Allocated amount validation for other party types (#35741)
fix: Allocated amount validation for other party types (#35741)

* fix: Allocated amount validation for other party types

* chore: Validation for return allocations

* chore: minor typo

---------

Co-authored-by: anandbaburajan <anandbaburajan@gmail.com>
(cherry picked from commit 9d27a25e5f)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-19 12:52:06 +05:30
mergify[bot]
070df97663 fix: loan interest accrual date (#35695)
fix: loan interest accrual date (#35695)

fix: loan interest accrual date

---------

Co-authored-by: Abhinav Raut <abhinav.raut@zerodha.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 2a24423ad2)

Co-authored-by: Abhinav Raut <abhinavrautcs@gmail.com>
2023-06-19 09:14:21 +05:30
Vishal Dhayagude
58a6bbcf6d fix: unsupported operand type(s) for //: 'float' and 'NoneType' for POS Barcode search (#35710) 2023-06-18 23:00:01 +05:30
Sagar Sharma
6650373c9f Merge pull request #35743 from s-aga-r/FIX-35493-V14
fix(ux): set route options for new `Batch`
2023-06-18 15:12:12 +05:30
Sagar Sharma
3c790c12f2 fix(patch): enable existing serial no in stock settings (#35762) 2023-06-17 23:21:30 +05:30
mergify[bot]
b875de6fb7 perf: Index pick list field in stock entry and DN (backport #35738) (#35742)
* perf: Index pick list field in stock entry and DN (#35738)

We check if pick list is created against them but there's no index so we
end up reading entire table.

```
+------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+
| id   | select_type | table            | type  | possible_keys | key      | key_len | ref  | rows   | r_rows    | filtered | r_filtered | Extra       |
+------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+
|    1 | SIMPLE      | tabDelivery Note | index | NULL          | modified | 9       | NULL | 207015 | 348940.00 |   100.00 |       0.00 | Using where |
+------+-------------+------------------+-------+---------------+----------+---------+------+--------+-----------+----------+------------+-------------+
```

After

```
+------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+------------------------------->
| id   | select_type | table            | type | possible_keys   | key             | key_len | ref   | rows | r_rows | filtered | r_filtered | Extra                         >
+------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+------------------------------->
|    1 | SIMPLE      | tabDelivery Note | ref  | pick_list_index | pick_list_index | 563     | const | 1    | 0.00   |   100.00 |     100.00 | Using index condition; Using w>
+------+-------------+------------------+------+-----------------+-----------------+---------+-------+------+--------+----------+------------+------------------------------->
```

(cherry picked from commit 433489a9e6)

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

* chore: conflict

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-17 21:25:30 +05:30
Frappe PR Bot
58e304f64b chore(release): Bumped to Version 14.27.7
## [14.27.7](https://github.com/frappe/erpnext/compare/v14.27.6...v14.27.7) (2023-06-17)

### Bug Fixes

* validation of job card in stock entry ([e7abdc3](e7abdc34d0))
2023-06-17 08:35:04 +00:00
rohitwaghchaure
46171738c0 Merge pull request #35761 from frappe/mergify/bp/version-14/pr-35759
fix: validation of job card in stock entry (backport #35756) (backport #35759)
2023-06-17 14:03:29 +05:30
Rohit Waghchaure
e7abdc34d0 fix: validation of job card in stock entry
(cherry picked from commit df8c3f0888)
(cherry picked from commit ce2bf5fb1c)
2023-06-17 08:32:59 +00:00
rohitwaghchaure
fc103ab4ce Merge pull request #35759 from frappe/mergify/bp/version-14-hotfix/pr-35756
fix: validation of job card in stock entry (backport #35756)
2023-06-17 14:02:11 +05:30
Rohit Waghchaure
ce2bf5fb1c fix: validation of job card in stock entry
(cherry picked from commit df8c3f0888)
2023-06-17 08:03:08 +00:00
mergify[bot]
59ab13c34f fix: add validation for QI in PR (backport #35677) (#35757)
fix: add validation for QI in PR

(cherry picked from commit 2c1ab569a7)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-17 12:52:18 +05:30
Frappe PR Bot
7ffa6b4fbd chore(release): Bumped to Version 14.27.6
## [14.27.6](https://github.com/frappe/erpnext/compare/v14.27.5...v14.27.6) (2023-06-17)

### Bug Fixes

* cannot start / stop Job Card (backport [#35753](https://github.com/frappe/erpnext/issues/35753)) ([#35754](https://github.com/frappe/erpnext/issues/35754)) ([19203bb](19203bb87d))
2023-06-17 06:58:24 +00:00
mergify[bot]
19203bb87d fix: cannot start / stop Job Card (backport #35753) (#35754)
fix: cannot start / stop jobs

(cherry picked from commit 53ec2a9268)

Co-authored-by: Anoop Kurungadam <anoop@earthianslive.com>
2023-06-17 12:18:01 +05:30
rohitwaghchaure
9792099cea Merge pull request #35753 from akurungadam/jobcard-fix
fix: cannot start / stop Job Card
2023-06-17 12:04:49 +05:30
mergify[bot]
c11d950fc5 fix: incorrect stock value for purchase returned with rejected qty (backport #35747) (#35752)
fix: incorrect stock value for purchase returned with rejected qty

(cherry picked from commit 28dd758aa3)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2023-06-17 11:33:17 +05:30
Anoop Kurungadam
53ec2a9268 fix: cannot start / stop jobs 2023-06-17 11:21:03 +05:30
mergify[bot]
a0fc8e252c perf: duplicate queries while checking prevdoc (backport #35746) (#35749)
perf: duplicate queries while checking prevdoc (#35746)

These values can't change durning DB transaction AFAIK

(cherry picked from commit 6086d1a99d)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 18:45:23 +05:30
mergify[bot]
632b67cbc8 perf: Duplicate queries for UOM (backport #35744) (#35745)
perf: Duplicate queries for UOM (#35744)

This query repeats for every item, UOMs rarely if ever change

(cherry picked from commit 29da1db516)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 16:40:40 +05:30
s-aga-r
b261242792 fix(ux): set route options for new Batch 2023-06-16 16:28:49 +05:30
mergify[bot]
01ac54d65d perf: Ignore cancelled pick lists while fetching picked items (backport #35737) (#35740)
perf: Ignore cancelled pick lists while fetching picked items (#35737)

(cherry picked from commit 81f916b7d3)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 15:29:20 +05:30
mergify[bot]
0e57f4dd3c perf: Index sales_order_item in Pick list item (backport #35735) (#35736)
* perf: Index sales_order_item in Pick list item (#35735)

- `get_picked_items_qty` does full table scan
- because it also locks, it does full table lock.

(cherry picked from commit 07d748c290)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-16 15:22:44 +05:30
rohitwaghchaure
7339e447bb Merge pull request #35718 from frappe/mergify/bp/version-14-hotfix/pr-35711
fix: incorrect gl entries for standalone debit note (backport #35711)
2023-06-16 13:46:16 +05:30
mergify[bot]
55a8be5cad fix: Process Loss Report (backport #35712) (#35719)
fix: `Process Loss Report`

(cherry picked from commit d176d86e2c)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-15 19:42:53 +05:30
Sagar Sharma
3f62e854e5 fix: consider field precision while setting sle actual_qty (#35717) 2023-06-15 19:26:38 +05:30
rohitwaghchaure
697fcef98b fix: conflicts 2023-06-15 19:14:41 +05:30
Rohit Waghchaure
e2c4e16d72 test: added test case
(cherry picked from commit f9f662679f)
2023-06-15 13:43:20 +00:00
Rohit Waghchaure
3355dc2a41 fix: incorrect gl entries for standalone debit note with update stock
(cherry picked from commit 6e198188ff)

# Conflicts:
#	erpnext/controllers/buying_controller.py
2023-06-15 13:43:20 +00:00
mergify[bot]
77b0c5f722 fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (backport #35714) (#35715)
fix: don't add GL Entry for Acc. Depr. while scrapping non-depreciable assets (#35714)

fix: on asset scrap, don't add gl entry for acc. depr. if no acc. depr.
(cherry picked from commit bb39a2cac7)

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-15 17:37:12 +05:30
Frappe PR Bot
138133e11d chore(release): Bumped to Version 14.27.5
## [14.27.5](https://github.com/frappe/erpnext/compare/v14.27.4...v14.27.5) (2023-06-15)

### Bug Fixes

* typeerror on exchange rate revaluation ([d934dda](d934dda410))
2023-06-15 08:10:29 +00:00
ruthra kumar
582ed2c7f1 Merge pull request #35706 from frappe/mergify/bp/version-14/pr-35701
fix: typeerror on exchange rate revaluation (backport #35701)
2023-06-15 13:38:59 +05:30
ruthra kumar
d934dda410 fix: typeerror on exchange rate revaluation
(cherry picked from commit f8273f7db6)
2023-06-15 06:55:03 +00:00
Frappe PR Bot
c774c14b13 chore(release): Bumped to Version 14.27.4
## [14.27.4](https://github.com/frappe/erpnext/compare/v14.27.3...v14.27.4) (2023-06-15)

### Bug Fixes

* update `Stock Reconciliation` diff qty while reposting (backport [#35700](https://github.com/frappe/erpnext/issues/35700)) (backport [#35702](https://github.com/frappe/erpnext/issues/35702)) ([#35703](https://github.com/frappe/erpnext/issues/35703)) ([f3b7eed](f3b7eedb25))
2023-06-15 06:34:31 +00:00
mergify[bot]
f3b7eedb25 fix: update Stock Reconciliation diff qty while reposting (backport #35700) (backport #35702) (#35703)
fix: update `Stock Reconciliation` diff qty while reposting

(cherry picked from commit 6a1b0a2fab)
(cherry picked from commit bdb5cc8ad4)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-06-15 11:46:28 +05:30
Sagar Sharma
9d1fac19e5 Merge pull request #35702 from frappe/mergify/bp/version-14-hotfix/pr-35700
fix: update `Stock Reconciliation` diff qty while reposting (backport #35700)
2023-06-15 11:44:14 +05:30
s-aga-r
bdb5cc8ad4 fix: update Stock Reconciliation diff qty while reposting
(cherry picked from commit 6a1b0a2fab)
2023-06-15 06:13:10 +00:00
Frappe PR Bot
975010c987 chore(release): Bumped to Version 14.27.3
## [14.27.3](https://github.com/frappe/erpnext/compare/v14.27.2...v14.27.3) (2023-06-14)

### Bug Fixes

* reference error while using exchange rate revaluation ([aa7c81a](aa7c81a0bc))
2023-06-14 15:21:49 +00:00
ruthra kumar
1f6352532e Merge pull request #35696 from frappe/mergify/bp/version-14/pr-35694
fix: reference error while using exchange rate revaluation (backport #35694)
2023-06-14 20:50:16 +05:30
ruthra kumar
aa7c81a0bc fix: reference error while using exchange rate revaluation
(cherry picked from commit cd538e138a)
2023-06-14 15:16:16 +00:00
Frappe PR Bot
08729b49e9 chore(release): Bumped to Version 14.27.2
## [14.27.2](https://github.com/frappe/erpnext/compare/v14.27.1...v14.27.2) (2023-06-14)

### Bug Fixes

* `enqueue_after_commit` wherever it makes sense (backport [#35588](https://github.com/frappe/erpnext/issues/35588)) ([#35590](https://github.com/frappe/erpnext/issues/35590)) ([e505516](e5055160fb))
* `TypeError` in Closing Stock Balance ([32e5bbb](32e5bbbb46))
* **accounts:** validate payment entry references with latest data. ([#31166](https://github.com/frappe/erpnext/issues/31166)) ([4add1b4](4add1b4374))
* added process loss in job card ([6a21d61](6a21d617ce))
* allow user to set rounding loss allowance for accounts balance ([cf14858](cf14858909))
* attribute error on payment reconciliation tool ([25b3c77](25b3c7736b))
* based on status_update.py update opportunity status to converted… ([#35145](https://github.com/frappe/erpnext/issues/35145)) ([dee8275](dee82754ab))
* calculate wdv depr schedule properly for existing assets [v14] ([#35613](https://github.com/frappe/erpnext/issues/35613)) ([feb5d00](feb5d0089b))
* conflicts ([2060a00](2060a003c8))
* CSS not applied to product title ([#35630](https://github.com/frappe/erpnext/issues/35630)) ([2cf871c](2cf871c21e))
* don't set default payment amount in case of invoice return ([#35645](https://github.com/frappe/erpnext/issues/35645)) ([79483cc](79483cc90e))
* Lower deduction certificate not getting applied ([#35667](https://github.com/frappe/erpnext/issues/35667)) ([6f59fa9](6f59fa9e5b))
* Make difference entry button not working ([#35622](https://github.com/frappe/erpnext/issues/35622)) ([043815e](043815e745))
* make showing taxes as table in print configurable (backport [#35672](https://github.com/frappe/erpnext/issues/35672)) ([#35678](https://github.com/frappe/erpnext/issues/35678)) ([f39ae9d](f39ae9dbb1))
* Payment against credit notes will be considered as payment against parent invoice in Accounts Receivable/Payable report ([#35642](https://github.com/frappe/erpnext/issues/35642)) ([81ef2ba](81ef2babe9))
* Project in item-wise sales register ([#35596](https://github.com/frappe/erpnext/issues/35596)) ([7737b90](7737b9061f))
* Stock Reconciliation document update while reposting ([8b617fb](8b617fb75e))
* test case ([7af0380](7af03800c9))
* Update de.csv ([#35278](https://github.com/frappe/erpnext/issues/35278)) ([2077f6e](2077f6e89c))
* Validation for delivery date in Sales Order ([#35597](https://github.com/frappe/erpnext/issues/35597)) ([4a8ce22](4a8ce226f6))
2023-06-14 05:06:16 +00:00
Deepesh Garg
a02469f09c Merge pull request #35664 from frappe/version-14-hotfix
chore: release v14
2023-06-14 10:34:24 +05:30
mergify[bot]
4a8ce226f6 fix: Validation for delivery date in Sales Order (#35597)
fix: Validation for delivery date in Sales Order (#35597)

* fix: Validation for delivery date in Sales Order

* chore: update utils

* chore: revert

* chore: Add default delivery date

(cherry picked from commit 984f89d274)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-14 09:01:38 +05:30
mergify[bot]
6f59fa9e5b fix: Lower deduction certificate not getting applied (#35667)
* fix: Lower deduction certificate not getting applied (#35667)

(cherry picked from commit 937c0feefe)

# Conflicts:
#	erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py

* chore: resolve conflicts

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-14 09:01:04 +05:30
mergify[bot]
f39ae9dbb1 fix: make showing taxes as table in print configurable (backport #35672) (#35678)
* fix: make showing taxes as table in print configurable (#35672)

(cherry picked from commit 491a50a027)

# Conflicts:
#	erpnext/accounts/doctype/accounts_settings/accounts_settings.json

* chore: fix conflict

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
2023-06-13 20:44:05 +05:30
mergify[bot]
4add1b4374 fix(accounts): validate payment entry references with latest data. (#31166)
fix(accounts): validate payment entry references with latest data. (#31166)

* test: payment entry over allocation.

* fix: validate allocated_amount against latest outstanding amount.

* fix: payment entry get outstanding documents for advance payments

* fix: only fetch latest outstanding_amount.

* fix: throw if reference is allocated

* test: throw error if a reference has been partially allocated after inital creation.

* chore: test name

* fix: remove unused part of test

* chore: linter

* chore: more user friendly error messages

* fix: only validate outstanding amount if partly paid and don't filter by cost center

* chore: minor refactor for doc.cost_center

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>

---------

Co-authored-by: Anand Baburajan <anandbaburajan@gmail.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 20de27d480)

Co-authored-by: Devin Slauenwhite <devin.slauenwhite@gmail.com>
2023-06-13 19:47:20 +05:30
mergify[bot]
dffa682b80 Stock aging report fix when called in dashboard chart (backport #35671) (#35675)
fix: get_range_age conditions fixed (#35671)

see https://github.com/frappe/erpnext/issues/35669

(cherry picked from commit 9f669d4c2f)

Co-authored-by: Hossein Yousefian <86075967+ihosseinu@users.noreply.github.com>
2023-06-13 19:22:25 +05:30
rohitwaghchaure
74ffb1d59c Merge pull request #35658 from frappe/mergify/bp/version-14-hotfix/pr-35629
fix: added process loss in job card (backport #35629)
2023-06-13 11:52:52 +05:30
rohitwaghchaure
2060a003c8 fix: conflicts 2023-06-13 11:22:59 +05:30
ruthra kumar
9d18b40fb0 Merge pull request #35661 from frappe/mergify/bp/version-14-hotfix/pr-35659
fix: attribute error on payment reconciliation tool (backport #35659)
2023-06-13 10:44:51 +05:30
ruthra kumar
25b3c7736b fix: attribute error on payment reconciliation tool
(cherry picked from commit bada5796fa)
2023-06-13 04:47:20 +00:00
ruthra kumar
6a08d04706 Merge pull request #35660 from frappe/mergify/bp/version-14-hotfix/pr-35620
fix: allow higher debit-credit diff tolerance in Exchange Rate Revaluation (backport #35620)
2023-06-13 10:16:47 +05:30
ruthra kumar
cf14858909 fix: allow user to set rounding loss allowance for accounts balance
(cherry picked from commit 96a0132501)
2023-06-13 04:16:55 +00:00
Rohit Waghchaure
7af03800c9 fix: test case
(cherry picked from commit 0382eecff4)
2023-06-12 17:59:56 +00:00
Rohit Waghchaure
6a21d617ce fix: added process loss in job card
(cherry picked from commit e9a6191af9)

# Conflicts:
#	erpnext/manufacturing/doctype/job_card/job_card.json
2023-06-12 17:59:56 +00:00
Sagar Sharma
48c2c82dc4 Merge pull request #35649 from frappe/mergify/bp/version-14-hotfix/pr-35646
fix: Stock Reconciliation document update while reposting (backport #35646)
2023-06-12 18:45:12 +05:30
s-aga-r
8b617fb75e fix: Stock Reconciliation document update while reposting
(cherry picked from commit db159dd11f)
2023-06-12 13:13:17 +00:00
Anand Baburajan
79483cc90e fix: don't set default payment amount in case of invoice return (#35645) 2023-06-12 18:34:13 +05:30
mergify[bot]
81ef2babe9 fix: Payment against credit notes will be considered as payment against parent invoice in Accounts Receivable/Payable report (#35642)
fix: Payment against credit notes will be considered as payment against parent invoice in Accounts Receivable/Payable report (#35642)

* fix: payment against credit note should be linked to parent invoice

* test: AR/AP report for payment against cr note scenario

* fix: cr_note shows up as outstanding invoice

Payment made against cr_note causes it be reported as outstanding invoice

(cherry picked from commit 42f4f80e0c)

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-06-12 18:06:26 +05:30
mergify[bot]
043815e745 fix: Make difference entry button not working (#35622)
fix: Make difference entry button not working (#35622)

(cherry picked from commit 2f24546b21)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-12 15:35:48 +05:30
Trusted Computer
2cf871c21e fix: CSS not applied to product title (#35630) 2023-06-11 19:35:24 +05:30
Frappe PR Bot
0ccb5a34bd chore(release): Bumped to Version 14.27.1
## [14.27.1](https://github.com/frappe/erpnext/compare/v14.27.0...v14.27.1) (2023-06-09)

### Bug Fixes

* calculate wdv depr schedule properly for existing assets [v14] ([#35613](https://github.com/frappe/erpnext/issues/35613)) ([a630db1](a630db1b21))
2023-06-09 16:55:54 +00:00
Anand Baburajan
8372f038c0 Merge pull request #35628 from frappe/mergify/bp/version-14/pr-35613
fix: calculate wdv depr schedule properly for existing assets [v14] (backport #35613)
2023-06-09 22:24:24 +05:30
Anand Baburajan
a630db1b21 fix: calculate wdv depr schedule properly for existing assets [v14] (#35613)
* fix: calculate wdv depr schedule properly for existing assets

* fix: calculate wdv depr schedule properly for existing assets properly

(cherry picked from commit feb5d0089b)
2023-06-09 16:28:14 +00:00
Sagar Sharma
a59dffe42a Merge pull request #35619 from frappe/mergify/bp/version-14-hotfix/pr-35617
fix: `TypeError` in Closing Stock Balance (backport #35617)
2023-06-09 12:24:27 +05:30
s-aga-r
32e5bbbb46 fix: TypeError in Closing Stock Balance
(cherry picked from commit 446253ff39)
2023-06-09 06:07:07 +00:00
Anand Baburajan
feb5d0089b fix: calculate wdv depr schedule properly for existing assets [v14] (#35613)
* fix: calculate wdv depr schedule properly for existing assets

* fix: calculate wdv depr schedule properly for existing assets properly
2023-06-08 23:16:28 +05:30
mergify[bot]
059141df52 refactor: get default contact or address (#35248)
refactor: get default contact or address (#35248)

* refactor: get_party_shipping_address

* refactor: get_default_contact

* chore: adding docstrings

* fix: keep original order

* fix: use get_all instead of get_list

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
(cherry picked from commit b91bb17779)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-06-08 22:04:13 +05:30
mergify[bot]
29e079d5d6 refactor: use delete_contact_and_address (#34497)
refactor: use delete_contact_and_address (#34497)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
(cherry picked from commit 0dde4d4c69)

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
2023-06-08 19:59:22 +05:30
mergify[bot]
7737b9061f fix: Project in item-wise sales register (#35596)
fix: Project in item-wise sales register (#35596)

(cherry picked from commit f732cac678)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-07 22:36:31 +05:30
mergify[bot]
2077f6e89c fix: Update de.csv (#35278)
fix: Update de.csv (#35278)

added many fixes on the base of 'No' which is often wrongly translated to 'Kein'.

Also removed translation of Naming Seris like ACC-INV-.YYYY.- to ACC-INV-.YYYY.-

(cherry picked from commit c236979508)

Co-authored-by: Wolfram Schmidt <wolfram.schmidt@phamos.eu>
2023-06-07 22:04:14 +05:30
mergify[bot]
dee82754ab fix: based on status_update.py update opportunity status to converted… (#35145)
fix: based on status_update.py update opportunity status to converted… (#35145)

fix: based on status_update.py update opportunity status to converted on sales submit
(cherry picked from commit a9a47a51e4)

Co-authored-by: HarryPaulo <paulo_fabris@hotmail.com>
2023-06-07 22:02:03 +05:30
mergify[bot]
e5055160fb fix: enqueue_after_commit wherever it makes sense (backport #35588) (#35590)
fix: `enqueue_after_commit` wherever it makes sense (#35588)

(cherry picked from commit 4507cb3cd7)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-06-07 15:27:45 +05:30
Ankush Menat
a8ac2a088d chore: extend default role profiles
(cherry picked from commit 0166f69b31)
2023-06-07 15:10:20 +05:30
Frappe PR Bot
0dd16fc97d chore(release): Bumped to Version 14.27.0
# [14.27.0](https://github.com/frappe/erpnext/compare/v14.26.0...v14.27.0) (2023-06-07)

### Bug Fixes

* add validation for existing Serial No Manufactured/Received again ([#35534](https://github.com/frappe/erpnext/issues/35534)) ([4ed701b](4ed701b477))
* get party details ([c7391c8](c7391c8a0f))
* higher precision makes ERR to misjudge zero bal acc as non-zero ([456c336](456c336899))
* ignore `Non-Stock Item` mapping in Pick List ([200166b](200166bc24))
* ignore `Non-Stock Item` while calculating `% Picked` in Sales Order ([c0d8271](c0d82710b7))
* Interest Accrual on Loan Topup ([#35555](https://github.com/frappe/erpnext/issues/35555)) ([6140f13](6140f13f3a))
* missing bom details in the stock entry ([2ee0229](2ee0229751))
* **regional:** allow regional override for updating gl_dict ([#35550](https://github.com/frappe/erpnext/issues/35550)) ([57b502b](57b502b9de))
* Task gantt popup style ([20e2242](20e224224b))
* update `Stock Reconciliation` document while reposting ([cc95ced](cc95cedfee))
* **ux:** throw if no row selected to create repost entries (backport [#35503](https://github.com/frappe/erpnext/issues/35503)) ([#35504](https://github.com/frappe/erpnext/issues/35504)) ([5ba5fb1](5ba5fb1b1c))

### Features

* ability to create quotation against a prospect ([d82d159](d82d1597ac))
2023-06-07 06:18:29 +00:00
Deepesh Garg
35d488d909 Merge pull request #35571 from frappe/version-14-hotfix
chore: release v14
2023-06-07 11:46:26 +05:30
Deepesh Garg
0e82932de1 Merge branch 'version-14' into version-14-hotfix 2023-06-07 10:45:44 +05:30
mergify[bot]
7798bab0d8 chore: Default role profiles (#35584)
chore: Default role profiles (#35584)

(cherry picked from commit 76197cc437)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-07 10:30:28 +05:30
mergify[bot]
6140f13f3a fix: Interest Accrual on Loan Topup (#35555)
fix: Interest Accrual on Loan Topup (#35555)

* fix: Interest Accrual on Loan Topup

* chore: CI

* chore: Ignore test

(cherry picked from commit 2ffcca6f10)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-06-07 10:12:12 +05:30
Nabin Hait
c7c1a6e501 Merge pull request #35579 from frappe/mergify/bp/version-14-hotfix/pr-35572
feat: ability to create quotation against a prospect (backport #35572)
2023-06-06 21:55:48 +05:30
Nabin Hait
c7391c8a0f fix: get party details
(cherry picked from commit 5a0aacc0b6)
2023-06-06 16:25:11 +00:00
Nabin Hait
d82d1597ac feat: ability to create quotation against a prospect
(cherry picked from commit 47ce6de57d)
2023-06-06 16:25:11 +00:00
Sagar Vora
307fadc8e2 Merge pull request #35560 from frappe/mergify/bp/version-14-hotfix/pr-35550
fix(regional): allow regional override for updating gl_dict (backport #35550)
2023-06-05 13:58:03 +05:30
Smit Vora
57b502b9de fix(regional): allow regional override for updating gl_dict (#35550)
(cherry picked from commit b1ef19a0cd)
2023-06-05 08:27:44 +00:00
Sagar Sharma
5b92334e95 Merge pull request #35544 from frappe/mergify/bp/version-14-hotfix/pr-35510
fix: ignore `Non-Stock Item` while calculating `% Picked` in Sales Order (backport #35510)
2023-06-03 11:52:19 +05:30
s-aga-r
c0d82710b7 fix: ignore Non-Stock Item while calculating % Picked in Sales Order
(cherry picked from commit 0305a925fe)
2023-06-03 05:48:53 +00:00
s-aga-r
200166bc24 fix: ignore Non-Stock Item mapping in Pick List
(cherry picked from commit 03d7742737)
2023-06-03 05:48:52 +00:00
Sagar Sharma
4ed701b477 fix: add validation for existing Serial No Manufactured/Received again (#35534)
* feat: add new field `Allow existing Serial No` in `Stock Settings`

* fix: add validation for existing Serial No to be Manufactured/Received again
2023-06-03 11:15:55 +05:30
Suraj Shetty
9e71f3263d Merge pull request #35531 from frappe/mergify/bp/version-14-hotfix/pr-35530
fix: Task gantt popup style and info (backport #35530)
2023-06-02 11:10:46 +05:30
Suraj Shetty
20e224224b fix: Task gantt popup style
(cherry picked from commit f7b2d103e7)
2023-06-02 05:39:53 +00:00
rohitwaghchaure
90c79cb24f Merge pull request #35481 from s-aga-r/FIX-ISS-23-24-01138
fix: update `Stock Reconciliation` document while reposting
2023-06-01 21:23:14 +05:30
ruthra kumar
114cb98b8e Merge pull request #35362 from frappe/mergify/bp/version-14-hotfix/pr-35299
refactor: replace join with subquery in PCV (backport #35299)
2023-06-01 15:43:37 +05:30
ruthra kumar
4a62252e7e Merge pull request #35521 from frappe/mergify/bp/version-14-hotfix/pr-35518
fix:higher precision causes ERR to misjudge zero bal acc as non-zero (backport #35518)
2023-06-01 15:39:50 +05:30
ruthra kumar
a6a7cc24a4 Merge pull request #35519 from frappe/mergify/bp/version-14-hotfix/pr-35112
refactor(Gross Profit): simplify group by invoice logic (backport #35112)
2023-06-01 15:12:48 +05:30
ruthra kumar
456c336899 fix: higher precision makes ERR to misjudge zero bal acc as non-zero
(cherry picked from commit 0cd47f07a6)
2023-06-01 09:29:46 +00:00
mergify[bot]
caf886d4a6 chore: typo in pricing rule schema (#35457)
chore: typo in pricing rule schema (#35457)
2023-06-01 14:41:43 +05:30
ruthra kumar
e29ce12a58 refactor: simplify group by invoice logic
(cherry picked from commit 092c4b4c58)
2023-06-01 09:05:35 +00:00
mergify[bot]
1e09a020a9 refactor: Workspace cleanup (backport #35409) (#35512)
refactor: Workspace cleanup (backport #35409) (#35505)

* refactor: Workspace cleanup

(cherry picked from commit 243c49c550)

# Conflicts:
#	erpnext/accounts/workspace/accounting/accounting.json
#	erpnext/assets/workspace/assets/assets.json

* fix: Delete Retail and Utilities worspaces amd hide default Settings and Integration workspaces

(cherry picked from commit 0b28f641ad)

* fix: Added pos links in Selling workspace

(cherry picked from commit 86f88817a9)

* fix: removed duplicate links of manufacturing workspace

(cherry picked from commit 5cf4c8c8b7)

* fix: Rearranged accounting module links

(cherry picked from commit e78a7de1e5)

# Conflicts:
#	erpnext/accounts/workspace/accounting/accounting.json

* chore: typo

(cherry picked from commit bb67cc03df)

* chore: conflicts

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit 841d2e4b2c)

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
2023-06-01 11:57:07 +05:30
rohitwaghchaure
6835efa132 Merge pull request #35506 from frappe/mergify/bp/version-14-hotfix/pr-35482
fix: missing bom details in the stock entry (backport #35482)
2023-05-31 18:02:46 +05:30
Rohit Waghchaure
2ee0229751 fix: missing bom details in the stock entry
(cherry picked from commit 2fc7d82324)
2023-05-31 12:00:05 +00:00
s-aga-r
5c9506c8ca test: add test case for update stock reconciliation doc 2023-05-31 16:59:53 +05:30
s-aga-r
cc95cedfee fix: update Stock Reconciliation document while reposting 2023-05-31 16:59:53 +05:30
mergify[bot]
841d2e4b2c refactor: Workspace cleanup (backport #35409) (#35505)
* refactor: Workspace cleanup

(cherry picked from commit 243c49c550)

# Conflicts:
#	erpnext/accounts/workspace/accounting/accounting.json
#	erpnext/assets/workspace/assets/assets.json

* fix: Delete Retail and Utilities worspaces amd hide default Settings and Integration workspaces

(cherry picked from commit 0b28f641ad)

* fix: Added pos links in Selling workspace

(cherry picked from commit 86f88817a9)

* fix: removed duplicate links of manufacturing workspace

(cherry picked from commit 5cf4c8c8b7)

* fix: Rearranged accounting module links

(cherry picked from commit e78a7de1e5)

# Conflicts:
#	erpnext/accounts/workspace/accounting/accounting.json

* chore: typo

(cherry picked from commit bb67cc03df)

* chore: conflicts

---------

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
Co-authored-by: Ankush Menat <ankushmenat@gmail.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-31 16:51:54 +05:30
mergify[bot]
5ba5fb1b1c fix(ux): throw if no row selected to create repost entries (backport #35503) (#35504)
fix(ux): throw if no row selected to create repost entries

(cherry picked from commit 1905239ec2)

Co-authored-by: s-aga-r <sagarsharma.s312@gmail.com>
2023-05-31 16:37:52 +05:30
Sagar Vora
9fc798efc0 Merge pull request #35501 from frappe/mergify/bp/version-14-hotfix/pr-35500 2023-05-31 14:46:06 +05:30
Sagar Vora
b10dfba931 chore: remove whitelisting for method not accessed from UI
(cherry picked from commit 517d8a03ec)
2023-05-31 09:15:46 +00:00
Frappe PR Bot
69b214b85b chore(release): Bumped to Version 14.26.0
# [14.26.0](https://github.com/frappe/erpnext/compare/v14.25.1...v14.26.0) (2023-05-31)

### Bug Fixes

* account group totals calculation to consider include_in_gross ([8dcb930](8dcb9302b4))
* add total col for gross and net profit ([cb9b4fb](cb9b4fbb91))
* available qty not fetching for raw material in PP ([746a734](746a734257))
* balance quantity ([56ba7d6](56ba7d6a8a))
* Billing Address display in buying transactions ([#35451](https://github.com/frappe/erpnext/issues/35451)) ([33e8d05](33e8d05718))
* don't map items twice ([6f0c7cf](6f0c7cf9a4))
* Error while validating budget ([#35487](https://github.com/frappe/erpnext/issues/35487)) ([5fd00e7](5fd00e7113))
* **Gross Profit:** 'company' column is ambiguous in filter ([a59c205](a59c205d2e))
* incorrect `POS Reserved Qty` in `Stock Projected Qty` Report ([71e4f34](71e4f34b86))
* incorrect available quantity in BIN ([5a9452f](5a9452f4a3))
* incorrect transferred qty in the job card ([#35478](https://github.com/frappe/erpnext/issues/35478)) ([86801c2](86801c29cb))
* make DN item reference mandatory for Packing Slip Item ([b4e481a](b4e481a390))
* map `Packed Items` while creating `Packing Slip` ([984e32c](984e32c34a))
* monthly WDV depr schedule for existing assets [v14] ([#35458](https://github.com/frappe/erpnext/issues/35458)) ([37d437a](37d437a33c))
* Negative value in Reserved Qty for Production Plan ([6fe42c9](6fe42c937c))
* Packing Slip Item Qty ([5345ebe](5345ebe242))
* **patch:** add patch to set `packed_qty` in draft DN ([b3da2f7](b3da2f7c26))
* rate not fetching properly for inter transfer purchase order ([7b75f45](7b75f454d3))
* remove duplicate items validation ([c7628c9](c7628c98c5))
* retention stock entry: grab conversion factor from source ([bd75584](bd75584c27))
* Show future payments in accounts receivable summary ([#35416](https://github.com/frappe/erpnext/issues/35416)) ([11440cc](11440cca4c))
* Stock Analytics and Warehouse wise Item Balance Age and Value issue ([2058993](205899348a))
* stock onboarding (backport [#35453](https://github.com/frappe/erpnext/issues/35453)) ([#35480](https://github.com/frappe/erpnext/issues/35480)) ([d231b19](d231b19b9f))
* tab-uniformity (backport [#35400](https://github.com/frappe/erpnext/issues/35400)) ([#35402](https://github.com/frappe/erpnext/issues/35402)) ([989052c](989052c075))
* travis ([fe1e2fe](fe1e2fec7a))
* update `Packed Qty` in DN on submit and cancel of `Packing Slip` ([0bed062](0bed06284e))
* **ux:** don't show `Create > Packing Slip` button if items are already packed ([9854c84](9854c84ad8))
* **ux:** get items on selecting DN in Packing Slip ([b96aa75](b96aa75ded))
* **ux:** remove `Get Items` button from `Packing Slip` ([4017342](4017342c15))
* validate Packing Slip Item Qty with DN Items ([cc7e267](cc7e267c35))

### Features

* add field `Packed Qty` in `Delivery Note Item` and `Packed Item` ([509b684](509b68404c))
* add field `pi_detail` in `Packing Slip` ([2b75474](2b75474649))
2023-05-31 06:16:00 +00:00
Deepesh Garg
10afde75da Merge pull request #35473 from frappe/version-14-hotfix
chore: release v14
2023-05-31 11:44:01 +05:30
mergify[bot]
33e8d05718 fix: Billing Address display in buying transactions (#35451)
fix: Billing Address display in buying transactions (#35451)

(cherry picked from commit bb21c044f6)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-05-31 11:03:49 +05:30
mergify[bot]
5fd00e7113 fix: Error while validating budget (#35487)
fix: Error while validating budget (#35487)

* fix: Error while validating budget

* chore: remove print statement

(cherry picked from commit 27d5e6a99b)

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-05-31 11:02:14 +05:30
mergify[bot]
86801c29cb fix: incorrect transferred qty in the job card (#35478)
fix: incorrect transfer quantity in the job card

(cherry picked from commit d3a5e49db9)

Co-authored-by: Rohit Waghchaure <rohitw1991@gmail.com>
2023-05-31 08:45:02 +05:30
Ankush Menat
c17e537bb6 chore: trigger ci 2023-05-30 19:00:40 +05:30
mergify[bot]
d231b19b9f fix: stock onboarding (backport #35453) (#35480)
* fix: stock settings tour

- remove dead fields
- Keep only essential fields in tour

(cherry picked from commit 2e13fbab5e)

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

* fix: hide ledger button on new warehouse

(cherry picked from commit 7a18db561f)

* fix: warehouse form cleanup

- organize fields
- group transit fields and move them lower
- enable/disable should be button
- hide pointless fields from listview

(cherry picked from commit 40ce33dff1)

* fix: disable/enable with button

(cherry picked from commit 81e901ba62)

* fix: warehouse tour

- remove warehouse type, it doesn't do what it says. Misleading.

(cherry picked from commit aa9f926298)

* fix: filter parent warehouses by company

(cherry picked from commit 3341cd6b80)

* fix: reorder stock reco tour

(cherry picked from commit dd245ccc7f)

* fix: replace stock projected with ledger

Ledger is much more widely used report, better to show that first?

(cherry picked from commit 8fe8f80033)

* chore: docs for stock settings

[skip ci]

(cherry picked from commit 964bb1d948)

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

* chore: conflicts

---------

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-30 18:42:07 +05:30
rohitwaghchaure
09d3a98873 Merge pull request #35466 from frappe/mergify/bp/version-14-hotfix/pr-35465
fix: rate not fetching properly for inter transfer purchase order (backport #35465)
2023-05-30 11:49:30 +05:30
Sagar Sharma
a6f334a15e Merge pull request #35467 from frappe/mergify/bp/version-14-hotfix/pr-35459
fix: retention stock entry: grab conversion factor from source (backport #35459)
2023-05-30 11:35:55 +05:30
Marc de Lima Lucio
bd75584c27 fix: retention stock entry: grab conversion factor from source
(cherry picked from commit 6954f538c9)
2023-05-30 05:36:33 +00:00
Rohit Waghchaure
7b75f454d3 fix: rate not fetching properly for inter transfer purchase order
(cherry picked from commit 2931c657f4)
2023-05-30 03:45:03 +00:00
Anand Baburajan
37d437a33c fix: monthly WDV depr schedule for existing assets [v14] (#35458)
* fix: monthly wdv depr schedule for existing assets

* fix: monthly wdv depr schedule for existing assets properly
2023-05-29 23:17:59 +05:30
mergify[bot]
11440cca4c fix: Show future payments in accounts receivable summary (#35416)
fix: Show future payments in accounts receivable summary (#35416)

(cherry picked from commit 3504bf7f62)

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2023-05-29 09:52:48 +05:30
Sagar Sharma
3673104949 Merge pull request #35443 from frappe/mergify/bp/version-14-hotfix/pr-35437
fix: incorrect `POS Reserved Qty` in `Stock Projected Qty` Report (backport #35437)
2023-05-28 16:18:29 +05:30
Sagar Sharma
71e4f34b86 fix: incorrect POS Reserved Qty in Stock Projected Qty Report
(cherry picked from commit 027de41600)
2023-05-28 09:35:34 +00:00
rohitwaghchaure
0a9c764f31 Merge pull request #35436 from frappe/mergify/bp/version-14-hotfix/pr-35426
fix: incorrect available quantity in BIN (backport #35426)
2023-05-27 12:46:02 +05:30
Rohit Waghchaure
fe1e2fec7a fix: travis
(cherry picked from commit 718ad3f240)
2023-05-26 17:18:40 +00:00
Rohit Waghchaure
5a9452f4a3 fix: incorrect available quantity in BIN
(cherry picked from commit 9e5e2de5d5)
2023-05-26 17:18:39 +00:00
Sagar Sharma
e251180c1a Merge pull request #35427 from frappe/mergify/bp/version-14-hotfix/pr-35108
refactor: Packing Slip (backport #35108)
2023-05-26 00:55:06 +05:30
Sagar Sharma
f5b2b80707 chore: conflict 2023-05-26 00:26:14 +05:30
Sagar Sharma
b3da2f7c26 fix(patch): add patch to set packed_qty in draft DN
(cherry picked from commit 196e18187f)
2023-05-25 18:24:56 +00:00
s-aga-r
ca6607e800 refactor: use get_product_bundle_list() to get all product bundle at once
(cherry picked from commit bbcb65894b)
2023-05-25 18:24:56 +00:00
s-aga-r
70857bf157 test: add test case for packed qty validation on DN submit
(cherry picked from commit ba61292dfc)
2023-05-25 18:24:56 +00:00
s-aga-r
dd7e5e019f refactor: validate_packed_qty()
(cherry picked from commit 699532647d)
2023-05-25 18:24:55 +00:00
s-aga-r
7105648468 refactor(minor): use set_onload to get unpacked items details
(cherry picked from commit b0eb9ea7bd)
2023-05-25 18:24:55 +00:00
s-aga-r
1ead0a3fef test: add test cases for Packing Slip
(cherry picked from commit 7742c592c5)
2023-05-25 18:24:55 +00:00
s-aga-r
9854c84ad8 fix(ux): don't show Create > Packing Slip button if items are already packed
(cherry picked from commit da00fc0f16)
2023-05-25 18:24:55 +00:00
s-aga-r
4017342c15 fix(ux): remove Get Items button from Packing Slip
(cherry picked from commit 8d1bccada4)
2023-05-25 18:24:54 +00:00
s-aga-r
b96aa75ded fix(ux): get items on selecting DN in Packing Slip
(cherry picked from commit e75aa4e291)
2023-05-25 18:24:54 +00:00
s-aga-r
1412c63158 refactor: move js validations to py
(cherry picked from commit 269cc96c41)
2023-05-25 18:24:54 +00:00
s-aga-r
cc7e267c35 fix: validate Packing Slip Item Qty with DN Items
(cherry picked from commit 90701c7ae9)
2023-05-25 18:24:54 +00:00
s-aga-r
b4e481a390 fix: make DN item reference mandatory for Packing Slip Item
(cherry picked from commit 9e5b102768)
2023-05-25 18:24:54 +00:00
s-aga-r
5345ebe242 fix: Packing Slip Item Qty
(cherry picked from commit 372bce4567)
2023-05-25 18:24:54 +00:00
s-aga-r
19713f9f6f chore: enable no_copy for dn_detail and pi_detail in Packing Slip Item
(cherry picked from commit 0add90e7ec)
2023-05-25 18:24:53 +00:00
s-aga-r
0bed06284e fix: update Packed Qty in DN on submit and cancel of Packing Slip
(cherry picked from commit 77f1e8ce78)
2023-05-25 18:24:53 +00:00
s-aga-r
509b68404c feat: add field Packed Qty in Delivery Note Item and Packed Item
(cherry picked from commit e6fc281acf)

# Conflicts:
#	erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
2023-05-25 18:24:53 +00:00
s-aga-r
6f0c7cf9a4 fix: don't map items twice
* don't explicitly map Delivery Note Item custom fields to Packing Slip Item, get auto-mapped while mapping the doc.
* call Packing List `set_missing_values` after mapping the doc.
* refactor `get_recommended_case_no`, use `frappe.db.get_value` instead of `frappe.db.sql`.

(cherry picked from commit 75fe9dd3ea)
2023-05-25 18:24:53 +00:00
s-aga-r
c7628c98c5 fix: remove duplicate items validation
(cherry picked from commit ee9f97ca7c)
2023-05-25 18:24:52 +00:00
s-aga-r
0c7efae858 refactor: packing_slip.js
(cherry picked from commit b62bf78814)
2023-05-25 18:24:52 +00:00
s-aga-r
984e32c34a fix: map Packed Items while creating Packing Slip
(cherry picked from commit 380dd73065)
2023-05-25 18:24:52 +00:00
s-aga-r
2b75474649 feat: add field pi_detail in Packing Slip
(cherry picked from commit eca77134ae)
2023-05-25 18:24:52 +00:00
Sagar Sharma
eb5cc2cf66 Merge pull request #35424 from frappe/mergify/bp/version-14-hotfix/pr-35422
chore: typo in stock balance (backport #35422)
2023-05-25 17:23:18 +05:30
Sagar Sharma
10a1681e87 chore: typo in stock balance
(cherry picked from commit c4e1f927ee)
2023-05-25 11:52:50 +00:00
ruthra kumar
54fac5786a Merge pull request #35419 from frappe/mergify/bp/version-14-hotfix/pr-35417
fix(Gross Profit): 'company' column is ambiguous in filter (backport #35417)
2023-05-25 16:44:45 +05:30
ruthra kumar
a59c205d2e fix(Gross Profit): 'company' column is ambiguous in filter
(cherry picked from commit 448712f719)
2023-05-25 09:37:24 +00:00
Deepesh Garg
151b599808 Merge pull request #35320 from akurungadam/gross-and-net-profit-fix
fix: Gross and Net Profit Report - incorrect calculation of totals
2023-05-25 14:43:10 +05:30
Frappe PR Bot
eb723637d3 chore(release): Bumped to Version 14.25.1
## [14.25.1](https://github.com/frappe/erpnext/compare/v14.25.0...v14.25.1) (2023-05-25)

### Bug Fixes

* Negative value in Reserved Qty for Production Plan ([20ceb6c](20ceb6c617))
2023-05-25 06:54:14 +00:00
rohitwaghchaure
aba322e086 Merge pull request #35414 from frappe/mergify/bp/version-14/pr-35411
fix: Negative value in Reserved Qty for Production Plan (backport #35410) (backport #35411)
2023-05-25 12:21:59 +05:30
Rohit Waghchaure
20ceb6c617 fix: Negative value in Reserved Qty for Production Plan
(cherry picked from commit a37608a36c)
(cherry picked from commit 6fe42c937c)
2023-05-25 04:37:18 +00:00
rohitwaghchaure
591b7705ac Merge pull request #35411 from frappe/mergify/bp/version-14-hotfix/pr-35410
fix: Negative value in Reserved Qty for Production Plan (backport #35410)
2023-05-24 22:57:21 +05:30
rohitwaghchaure
7163d7d27f Merge pull request #35413 from frappe/mergify/bp/version-14-hotfix/pr-35348
refactor: stock balance report (backport #35348)
2023-05-24 22:56:59 +05:30
Rohit Waghchaure
205899348a fix: Stock Analytics and Warehouse wise Item Balance Age and Value issue
(cherry picked from commit 3f548ac910)
2023-05-24 14:16:56 +00:00
Rohit Waghchaure
56ba7d6a8a fix: balance quantity
(cherry picked from commit 545b2d32cd)
2023-05-24 14:16:56 +00:00
Rohit Waghchaure
9fcfab219c refactor: stock balance report
(cherry picked from commit d9979b2ffb)
2023-05-24 14:16:56 +00:00
Rohit Waghchaure
6fe42c937c fix: Negative value in Reserved Qty for Production Plan
(cherry picked from commit a37608a36c)
2023-05-24 13:39:03 +00:00
rohitwaghchaure
1fe22f0fcf Merge pull request #35406 from frappe/mergify/bp/version-14-hotfix/pr-35405
fix: available qty not fetching for raw material in PP (backport #35405)
2023-05-24 17:29:39 +05:30
Rohit Waghchaure
746a734257 fix: available qty not fetching for raw material in PP
(cherry picked from commit 8e3463c4ef)
2023-05-24 08:55:40 +00:00
mergify[bot]
989052c075 fix: tab-uniformity (backport #35400) (#35402)
fix: tab-uniformity (#35400)

Made Doctype tabs uniform

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

Co-authored-by: Gursheen Kaur Anand <40693548+GursheenK@users.noreply.github.com>
2023-05-24 12:47:38 +05:30
Frappe PR Bot
cd3991dd14 chore(release): Bumped to Version 14.25.0
# [14.25.0](https://github.com/frappe/erpnext/compare/v14.24.3...v14.25.0) (2023-05-24)

### Bug Fixes

* consider 0 if rate/qty are null (backport [#35338](https://github.com/frappe/erpnext/issues/35338)) ([#35340](https://github.com/frappe/erpnext/issues/35340)) ([15c1af3](15c1af3d8a))
* Creating landed cost voucher from connections ([2631224](2631224e49))
* depreciation schedule for existing assets [v14] ([#35255](https://github.com/frappe/erpnext/issues/35255)) ([0a080ef](0a080efce2))
* don't recalculate rate for SCR rejected warehouse SLE ([633a170](633a1703dc))
* error while saving job card ([fb7d3b7](fb7d3b7878))
* incorrect depr schedule and posting dates on selling of existing assets [v14] ([#35396](https://github.com/frappe/erpnext/issues/35396)) ([8af6a11](8af6a113d1))
* Pick List Status ([4888436](48884366ea))
* Pick List TypeError ([6df9b53](6df9b53682))
* possible type error on quotation -> sales order creation ([d23b93a](d23b93a462))
* replace quotation with invoice in first onboarding (backport [#35389](https://github.com/frappe/erpnext/issues/35389)) ([#35394](https://github.com/frappe/erpnext/issues/35394)) ([9b9772e](9b9772eb14))
* tds incorrectly calculated for invoice that are below threshold ([bdf81a4](bdf81a43c6))
* **test:** cumulative threshold checks ([879946e](879946e2c8))
* use flt instead of mandatory field ([668b092](668b092f6b))
* **ux:** SCR consumed-qty read-only property ([aa7fede](aa7fede0dc))

### Features

* provision to make reposting entries from Stock and Account Value Comparison Report ([3a0cdf3](3a0cdf30ce))
* provision to skip available sub assembly items in the production plan ([ce601af](ce601afc4e))
2023-05-24 03:05:06 +00:00
Deepesh Garg
e586c07bdd Merge pull request #35392 from frappe/version-14-hotfix
chore: release v14
2023-05-24 08:31:31 +05:30
Anand Baburajan
8af6a113d1 fix: incorrect depr schedule and posting dates on selling of existing assets [v14] (#35396)
* fix: use date in asset get gl entries functions

* fix: calc depr amount properly on selling of existing assets

* fix: calc depr amount properly on selling of existing assets again

* chore: remove unnecessary line breaks
2023-05-23 23:04:04 +05:30
mergify[bot]
9b9772eb14 fix: replace quotation with invoice in first onboarding (backport #35389) (#35394)
fix: replace quotation with invoice in first onboarding (#35389)

(cherry picked from commit b0eb72ffac)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-23 15:36:56 +05:30
Sagar Sharma
88ecc933f7 Merge pull request #35390 from frappe/mergify/bp/version-14-hotfix/pr-35303
fix: Pick List TypeError (backport #35303)
2023-05-23 15:06:05 +05:30
Sagar Sharma
6df9b53682 fix: Pick List TypeError
(cherry picked from commit a111917114)
2023-05-23 08:51:32 +00:00
rohitwaghchaure
47e97e0b3d Merge pull request #35382 from frappe/mergify/bp/version-14-hotfix/pr-35381
feat: provision to skip available sub assembly items in the production plan (backport #35381)
2023-05-23 12:29:18 +05:30
Sagar Sharma
fec6e3724e Merge pull request #35383 from frappe/mergify/bp/version-14-hotfix/pr-35380
fix: TypeError while saving Job card (backport #35380)
2023-05-23 10:52:00 +05:30
vishnu
668b092f6b fix: use flt instead of mandatory field
(cherry picked from commit 8c34cc0e00)
2023-05-23 05:20:22 +00:00
vishnu
fb7d3b7878 fix: error while saving job card
(cherry picked from commit a209fb4b64)
2023-05-23 05:20:22 +00:00
Rohit Waghchaure
f6857b4698 test: test case to skip available qty for sub-assembly items
(cherry picked from commit 9dd566c1e8)
2023-05-23 04:09:40 +00:00
Rohit Waghchaure
ce601afc4e feat: provision to skip available sub assembly items in the production plan
(cherry picked from commit 64751ec4d9)
2023-05-23 04:09:40 +00:00
Anoop
5b4b71d941 Merge branch 'version-14-hotfix' into gross-and-net-profit-fix 2023-05-22 17:23:03 +05:30
Sagar Sharma
1b827f61f7 Merge pull request #35377 from frappe/mergify/bp/version-14-hotfix/pr-35376
fix: don't recalculate rate for SCR rejected warehouse SLE (backport #35376)
2023-05-22 16:08:25 +05:30
Sagar Sharma
633a1703dc fix: don't recalculate rate for SCR rejected warehouse SLE
(cherry picked from commit 57ee473fa4)
2023-05-22 10:16:09 +00:00
Ankush Menat
abfd975eb6 chore: drop outdated navigation video
new stuff is in work, this one is actually counter-productive rn.

(cherry picked from commit ba61865ee6)
2023-05-20 19:47:17 +05:30
rohitwaghchaure
9106b01c04 Merge pull request #35368 from frappe/mergify/bp/version-14-hotfix/pr-35365
feat: provision to make reposting entries from Stock and Account Value Comparison Report (backport #35365)
2023-05-20 18:31:00 +05:30
Rohit Waghchaure
3a0cdf30ce feat: provision to make reposting entries from Stock and Account Value Comparison Report
(cherry picked from commit 7b818e9d34)
2023-05-20 11:37:02 +00:00
ruthra kumar
4620ed6e42 refactor: replace join with sub-query for fetching accounts
(cherry picked from commit e6a9252f79)
2023-05-19 05:32:43 +00:00
Frappe PR Bot
756ac6c587 chore(release): Bumped to Version 14.24.3
## [14.24.3](https://github.com/frappe/erpnext/compare/v14.24.2...v14.24.3) (2023-05-18)

### Bug Fixes

* tds incorrectly calculated for invoice that are below threshold ([14565ed](14565ed8b1))
* **test:** cumulative threshold checks ([cdd378c](cdd378c518))
2023-05-18 14:07:04 +00:00
ruthra kumar
b5b1b2da32 Merge pull request #35359 from frappe/mergify/bp/version-14/pr-35335
fix: tds incorrectly calculated for invoice that are below threshold (backport #35335)
2023-05-18 19:35:11 +05:30
ruthra kumar
cdd378c518 fix(test): cumulative threshold checks
(cherry picked from commit 132846bbd1)
2023-05-18 13:30:03 +00:00
ruthra kumar
14565ed8b1 fix: tds incorrectly calculated for invoice that are below threshold
Two purchase invoices for the same supplier, using different tax
withholding categories have this issue.

| Category | single | cumulative |
|----------+--------+------------|
| cat1     |    100 |        500 |
| cat2     |   1000 |       5000 |

1. PINV1 of net total: 105/- uses cat1. TDS is calculated as it
breached single threshold
2. PINV2 of net total: 200/- uses cat2. TDS incorrectly calculated as
PINV1 already has TDS calculated and 'consider_party_ledger_amount' is enabled.

(cherry picked from commit 84b7c1bba0)
2023-05-18 13:30:02 +00:00
ruthra kumar
44b290f58e Merge pull request #35357 from frappe/mergify/bp/version-14-hotfix/pr-35355
fix: possible type error on quotation -> sales order creation (backport #35355)
2023-05-18 14:35:53 +05:30
ruthra kumar
d23b93a462 fix: possible type error on quotation -> sales order creation
(cherry picked from commit b2290c6f57)
2023-05-18 13:39:58 +05:30
ruthra kumar
80f4a11d60 Merge pull request #35353 from frappe/mergify/bp/version-14-hotfix/pr-35335
fix: tds incorrectly calculated for invoice that are below threshold (backport #35335)
2023-05-18 13:03:40 +05:30
Sagar Sharma
9caa39195c Merge pull request #35344 from frappe/mergify/bp/version-14-hotfix/pr-35306
fix: Pick List Status (backport #35306)
2023-05-18 12:52:25 +05:30
ruthra kumar
879946e2c8 fix(test): cumulative threshold checks
(cherry picked from commit 132846bbd1)
2023-05-18 07:09:20 +00:00
ruthra kumar
bdf81a43c6 fix: tds incorrectly calculated for invoice that are below threshold
Two purchase invoices for the same supplier, using different tax
withholding categories have this issue.

| Category | single | cumulative |
|----------+--------+------------|
| cat1     |    100 |        500 |
| cat2     |   1000 |       5000 |

1. PINV1 of net total: 105/- uses cat1. TDS is calculated as it
breached single threshold
2. PINV2 of net total: 200/- uses cat2. TDS incorrectly calculated as
PINV1 already has TDS calculated and 'consider_party_ledger_amount' is enabled.

(cherry picked from commit 84b7c1bba0)
2023-05-18 07:09:19 +00:00
Sagar Sharma
cb681e0b96 Merge branch 'version-14-hotfix' into mergify/bp/version-14-hotfix/pr-35306 2023-05-18 12:30:40 +05:30
ruthra kumar
65554bdabf Merge pull request #35336 from frappe/mergify/bp/version-14-hotfix/pr-35239
fix: in payment_entry  difference amount cal is broken (backport #35239)
2023-05-18 12:15:44 +05:30
rohitwaghchaure
0d2d45f32f Merge pull request #35351 from frappe/mergify/bp/version-14-hotfix/pr-35298
fix: Creating landed cost voucher from connections (backport #35298)
2023-05-18 06:16:11 +05:30
vishnu
2631224e49 fix: Creating landed cost voucher from connections
(cherry picked from commit f2ceb00379)
2023-05-18 00:43:36 +00:00
Anand Baburajan
0a080efce2 fix: depreciation schedule for existing assets [v14] (#35255)
* fix: depreciation schedule for existing assets

* chore: correct logic for existing assets and fix test
2023-05-17 22:21:00 +05:30
Sagar Sharma
48884366ea fix: Pick List Status
(cherry picked from commit 9fb8b1827d)
2023-05-17 12:43:08 +00:00
mergify[bot]
15c1af3d8a fix: consider 0 if rate/qty are null (backport #35338) (#35340)
fix: consider 0 if rate/qty are null (#35338)

[skip ci]

(cherry picked from commit e5c86bc2e8)

Co-authored-by: Ankush Menat <ankush@frappe.io>
2023-05-17 16:17:21 +05:30
Sagar Sharma
350f75877a Merge pull request #35339 from frappe/mergify/bp/version-14-hotfix/pr-35337
fix(ux): SCR consumed-qty read-only property (backport #35337)
2023-05-17 15:58:29 +05:30
Sagar Sharma
aa7fede0dc fix(ux): SCR consumed-qty read-only property
(cherry picked from commit adf2474d9d)
2023-05-17 10:28:03 +00:00
Ashish Shah
e9f2ab5caf refactor: use 'flt' for base_total_taxes_and_charges
difference_amount calculation is broken, as calculation gives NaN. Fix is make frm.doc.base_total_taxes_and_charges as flt(frm.doc.base_total_taxes_and_charges)

(cherry picked from commit ae4e56747c)
2023-05-17 09:40:55 +00:00
Anoop
fb0f82eed3 Merge branch 'version-14-hotfix' into gross-and-net-profit-fix 2023-05-16 20:10:23 +05:30
Anoop Kurungadam
cb9b4fbb91 fix: add total col for gross and net profit 2023-05-16 12:08:42 +05:30
Anoop Kurungadam
1a3b9c5bdf refactor: merge separate loops for calculating group / leaf node totals
rename function
remove return statement as the list is  mutated
2023-05-16 12:08:32 +05:30
Anoop Kurungadam
50822f207e refactor: remove unused parameters 2023-05-16 12:08:24 +05:30
Anoop Kurungadam
8dcb9302b4 fix: account group totals calculation to consider include_in_gross 2023-05-16 12:08:17 +05:30
161 changed files with 5015 additions and 2458 deletions

2
.github/stale.yml vendored
View File

@@ -13,7 +13,7 @@ exemptProjects: true
exemptMilestones: true
pulls:
daysUntilStale: 15
daysUntilStale: 14
daysUntilClose: 3
exemptLabels:
- hotfix

View File

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

View File

@@ -50,13 +50,15 @@ class AccountingDimension(Document):
if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self)
else:
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
frappe.enqueue(
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
)
def on_trash(self):
if frappe.flags.in_test:
delete_accounting_dimension(doc=self)
else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True)
def set_fieldname_and_label(self):
if not self.label:

View File

@@ -36,6 +36,7 @@
"book_tax_discount_loss",
"print_settings",
"show_inclusive_tax_in_print",
"show_taxes_as_table_in_print",
"column_break_12",
"show_payment_schedule_in_print",
"currency_exchange_section",
@@ -378,6 +379,12 @@
"fieldname": "auto_reconcile_payments",
"fieldtype": "Check",
"label": "Auto Reconcile Payments"
},
{
"default": "0",
"fieldname": "show_taxes_as_table_in_print",
"fieldtype": "Check",
"label": "Show Taxes as Table in Print"
}
],
"icon": "icon-cog",
@@ -385,7 +392,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2023-04-21 13:11:37.130743",
"modified": "2023-06-13 18:47:46.430291",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -414,4 +421,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -125,14 +125,27 @@ def validate_expense_against_budget(args, expense_amount=0):
if not args.account:
return
for budget_against in ["project", "cost_center"] + get_accounting_dimensions():
default_dimensions = [
{
"fieldname": "project",
"document_type": "Project",
},
{
"fieldname": "cost_center",
"document_type": "Cost Center",
},
]
for dimension in default_dimensions + get_accounting_dimensions(as_list=False):
budget_against = dimension.get("fieldname")
if (
args.get(budget_against)
and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
):
doctype = frappe.unscrub(budget_against)
doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])

View File

@@ -35,6 +35,21 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
}
},
validate_rounding_loss: function(frm) {
let allowance = frm.doc.rounding_loss_allowance;
if (!(allowance >= 0 && allowance < 1)) {
frappe.throw(__("Rounding Loss Allowance should be between 0 and 1"));
}
},
rounding_loss_allowance: function(frm) {
frm.events.validate_rounding_loss(frm);
},
validate: function(frm) {
frm.events.validate_rounding_loss(frm);
},
get_entries: function(frm, account) {
frappe.call({
method: "get_accounts_data",
@@ -126,7 +141,8 @@ var get_account_details = function(frm, cdt, cdn) {
company: frm.doc.company,
posting_date: frm.doc.posting_date,
party_type: row.party_type,
party: row.party
party: row.party,
rounding_loss_allowance: frm.doc.rounding_loss_allowance
},
callback: function(r){
$.extend(row, r.message);

View File

@@ -8,6 +8,7 @@
"engine": "InnoDB",
"field_order": [
"posting_date",
"rounding_loss_allowance",
"column_break_2",
"company",
"section_break_4",
@@ -96,11 +97,19 @@
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"default": "0.05",
"description": "Only values between [0,1) are allowed. Like {0.00, 0.04, 0.09, ...}\nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account",
"fieldname": "rounding_loss_allowance",
"fieldtype": "Float",
"label": "Rounding Loss Allowance",
"precision": "9"
}
],
"is_submittable": 1,
"links": [],
"modified": "2022-12-29 19:38:24.416529",
"modified": "2023-06-20 07:29:06.972434",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation",

View File

@@ -12,13 +12,19 @@ from frappe.utils import flt, get_link_to_form
import erpnext
from erpnext.accounts.doctype.journal_entry.journal_entry import get_balance_on
from erpnext.accounts.utils import get_currency_precision
from erpnext.setup.utils import get_exchange_rate
class ExchangeRateRevaluation(Document):
def validate(self):
self.validate_rounding_loss_allowance()
self.set_total_gain_loss()
def validate_rounding_loss_allowance(self):
if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1):
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
def set_total_gain_loss(self):
total_gain_loss = 0
@@ -91,7 +97,12 @@ class ExchangeRateRevaluation(Document):
def get_accounts_data(self):
self.validate_mandatory()
account_details = self.get_account_balance_from_gle(
company=self.company, posting_date=self.posting_date, account=None, party_type=None, party=None
company=self.company,
posting_date=self.posting_date,
account=None,
party_type=None,
party=None,
rounding_loss_allowance=self.rounding_loss_allowance,
)
accounts_with_new_balance = self.calculate_new_account_balance(
self.company, self.posting_date, account_details
@@ -103,7 +114,9 @@ class ExchangeRateRevaluation(Document):
return accounts_with_new_balance
@staticmethod
def get_account_balance_from_gle(company, posting_date, account, party_type, party):
def get_account_balance_from_gle(
company, posting_date, account, party_type, party, rounding_loss_allowance
):
account_details = []
if company and posting_date:
@@ -170,6 +183,23 @@ class ExchangeRateRevaluation(Document):
.run(as_dict=True)
)
# round off balance based on currency precision
# and consider debit-credit difference allowance
currency_precision = get_currency_precision()
rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
for acc in account_details:
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
acc.balance_in_account_currency = 0
acc.balance = flt(acc.balance, currency_precision)
if abs(acc.balance) <= rounding_loss_allowance:
acc.balance = 0
acc.zero_balance = (
True if (acc.balance == 0 or acc.balance_in_account_currency == 0) else False
)
return account_details
@staticmethod
@@ -343,6 +373,24 @@ class ExchangeRateRevaluation(Document):
"credit": 0,
}
)
journal_entry_accounts.append(journal_account)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit": 0,
"credit": 0,
"debit_in_account_currency": abs(d.gain_loss) if d.gain_loss < 0 else 0,
"credit_in_account_currency": abs(d.gain_loss) if d.gain_loss > 0 else 0,
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
elif d.get("balance_in_base_currency") and not d.get("new_balance_in_base_currency"):
# Base currency has balance
dr_or_cr = "credit" if d.get("balance_in_base_currency") > 0 else "debit"
@@ -358,22 +406,22 @@ class ExchangeRateRevaluation(Document):
}
)
journal_entry_accounts.append(journal_account)
journal_entry_accounts.append(journal_account)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0,
"credit": abs(self.gain_loss_booked) if self.gain_loss_booked > 0 else 0,
"debit_in_account_currency": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0,
"credit_in_account_currency": self.gain_loss_booked if self.gain_loss_booked > 0 else 0,
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
journal_entry_accounts.append(
{
"account": unrealized_exchange_gain_loss_account,
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
"debit": abs(d.gain_loss) if d.gain_loss < 0 else 0,
"credit": abs(d.gain_loss) if d.gain_loss > 0 else 0,
"debit_in_account_currency": 0,
"credit_in_account_currency": 0,
"cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": 1,
"reference_type": "Exchange Rate Revaluation",
"reference_name": self.name,
}
)
journal_entry.set("accounts", journal_entry_accounts)
journal_entry.set_total_debit_credit()
@@ -521,7 +569,9 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
@frappe.whitelist()
def get_account_details(company, posting_date, account, party_type=None, party=None):
def get_account_details(
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None
):
if not (company and posting_date):
frappe.throw(_("Company and Posting Date is mandatory"))
@@ -539,7 +589,12 @@ def get_account_details(company, posting_date, account, party_type=None, party=N
"account_currency": account_currency,
}
account_balance = ExchangeRateRevaluation.get_account_balance_from_gle(
company=company, posting_date=posting_date, account=account, party_type=party_type, party=party
company=company,
posting_date=posting_date,
account=account,
party_type=party_type,
party=party,
rounding_loss_allowance=rounding_loss_allowance,
)
if account_balance and (

View File

@@ -92,6 +92,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "New Exchange Rate",
"precision": "9",
"reqd": 1
},
{
@@ -147,7 +148,7 @@
],
"istable": 1,
"links": [],
"modified": "2022-12-29 19:38:52.915295",
"modified": "2023-06-20 07:21:40.743460",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation Account",

View File

@@ -940,6 +940,7 @@ class JournalEntry(AccountsController):
blank_row.debit_in_account_currency = abs(diff)
blank_row.debit = abs(diff)
self.set_total_debit_credit()
self.validate_total_debit_and_credit()
@frappe.whitelist()

View File

@@ -613,7 +613,7 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_unallocated_amount(frm);
},
get_outstanding_invoice: function(frm) {
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
const today = frappe.datetime.get_today();
const fields = [
{fieldtype:"Section Break", label: __("Posting Date")},
@@ -643,12 +643,29 @@ frappe.ui.form.on('Payment Entry', {
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
];
let btn_text = "";
if (get_outstanding_invoices) {
btn_text = "Get Outstanding Invoices";
}
else if (get_orders_to_be_billed) {
btn_text = "Get Outstanding Orders";
}
frappe.prompt(fields, function(filters){
frappe.flags.allocate_payment_amount = true;
frm.events.validate_filters_data(frm, filters);
frm.doc.cost_center = filters.cost_center;
frm.events.get_outstanding_documents(frm, filters);
}, __("Filters"), __("Get Outstanding Documents"));
frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed);
}, __("Filters"), __(btn_text));
},
get_outstanding_invoices: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, true, false);
},
get_outstanding_orders: function(frm) {
frm.events.get_outstanding_invoices_or_orders(frm, false, true);
},
validate_filters_data: function(frm, filters) {
@@ -674,7 +691,7 @@ frappe.ui.form.on('Payment Entry', {
}
},
get_outstanding_documents: function(frm, filters) {
get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) {
frm.clear_table("references");
if(!frm.doc.party) {
@@ -698,6 +715,13 @@ frappe.ui.form.on('Payment Entry', {
args[key] = filters[key];
}
if (get_outstanding_invoices) {
args["get_outstanding_invoices"] = true;
}
else if (get_orders_to_be_billed) {
args["get_orders_to_be_billed"] = true;
}
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
return frappe.call({
@@ -905,7 +929,7 @@ frappe.ui.form.on('Payment Entry', {
function(d) { return flt(d.amount) }));
frm.set_value("difference_amount", difference_amount - total_deductions +
frm.doc.base_total_taxes_and_charges);
flt(frm.doc.base_total_taxes_and_charges));
frm.events.hide_unhide_fields(frm);
},

View File

@@ -48,7 +48,8 @@
"base_received_amount",
"base_received_amount_after_tax",
"section_break_14",
"get_outstanding_invoice",
"get_outstanding_invoices",
"get_outstanding_orders",
"references",
"section_break_34",
"total_allocated_amount",
@@ -355,12 +356,6 @@
"fieldtype": "Section Break",
"label": "Reference"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoice",
"fieldtype": "Button",
"label": "Get Outstanding Invoice"
},
{
"fieldname": "references",
"fieldtype": "Table",
@@ -728,12 +723,24 @@
"fieldname": "section_break_60",
"fieldtype": "Section Break",
"hide_border": 1
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_invoices",
"fieldtype": "Button",
"label": "Get Outstanding Invoices"
},
{
"depends_on": "eval:doc.docstatus==0",
"fieldname": "get_outstanding_orders",
"fieldtype": "Button",
"label": "Get Outstanding Orders"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-02-14 04:52:30.478523",
"modified": "2023-06-19 11:38:04.387219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -148,19 +148,68 @@ class PaymentEntry(AccountsController):
)
def validate_allocated_amount(self):
if self.payment_type == "Internal Transfer":
return
if self.party_type in ("Customer", "Supplier"):
self.validate_allocated_amount_with_latest_data()
else:
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def validate_allocated_amount_with_latest_data(self):
latest_references = get_outstanding_reference_documents(
{
"posting_date": self.posting_date,
"company": self.company,
"party_type": self.party_type,
"payment_type": self.payment_type,
"party": self.party,
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
"get_outstanding_invoices": True,
"get_orders_to_be_billed": True,
}
)
# Group latest_references by (voucher_type, voucher_no)
latest_lookup = {}
for d in latest_references:
d = frappe._dict(d)
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
for d in self.get("references"):
if (flt(d.allocated_amount)) > 0:
if flt(d.allocated_amount) > flt(d.outstanding_amount):
frappe.throw(
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
)
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
# The reference has already been fully paid
if not latest:
frappe.throw(
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
)
# The reference has already been partly paid
elif (
latest.outstanding_amount < latest.invoice_amount
and d.outstanding_amount != latest.outstanding_amount
):
frappe.throw(
_(
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
).format(d.reference_doctype, d.reference_name)
)
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
# Check for negative outstanding invoices as well
if flt(d.allocated_amount) < 0:
if flt(d.allocated_amount) < flt(d.outstanding_amount):
frappe.throw(
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
)
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
frappe.throw(fail_message.format(d.idx))
def delink_advance_entry_references(self):
for reference in self.references:
@@ -373,7 +422,7 @@ class PaymentEntry(AccountsController):
for k, v in no_oustanding_refs.items():
frappe.msgprint(
_(
"{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
).format(
_(k),
frappe.bold(", ".join(d.reference_name for d in v)),
@@ -1309,62 +1358,75 @@ def get_outstanding_reference_documents(args):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company"))
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
args.get("party"),
args.get("company"),
party_account_currency,
company_currency,
filters=args,
)
# Get negative outstanding sales /purchase invoices
outstanding_invoices = []
negative_outstanding_invoices = []
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
if args.get("get_outstanding_invoices"):
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
accounting_dimensions=accounting_dimensions_filter,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
party_account_currency, company_currency, d.posting_date
)
if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get negative outstanding sales /purchase invoices
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
negative_outstanding_invoices = get_negative_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
party_account_currency,
company_currency,
condition=condition,
)
# Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = []
if args.get("get_orders_to_be_billed"):
orders_to_be_billed = get_orders_to_be_billed(
args.get("posting_date"),
args.get("party_type"),
args.get("party"),
args.get("company"),
party_account_currency,
company_currency,
condition=condition,
filters=args,
)
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data:
if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"):
ref_document_type = "invoices or orders"
elif args.get("get_outstanding_invoices"):
ref_document_type = "invoices"
elif args.get("get_orders_to_be_billed"):
ref_document_type = "orders"
frappe.msgprint(
_(
"No outstanding invoices found for the {0} {1} which qualify the filters you have specified."
).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))
"No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
).format(
ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
)
)
return data
@@ -1449,7 +1511,7 @@ def get_orders_to_be_billed(
if voucher_type:
doc = frappe.get_doc({"doctype": voucher_type})
condition = ""
if doc and hasattr(doc, "cost_center"):
if doc and hasattr(doc, "cost_center") and doc.cost_center:
condition = " and cost_center='%s'" % cost_center
orders = []
@@ -1495,9 +1557,15 @@ def get_orders_to_be_billed(
order_list = []
for d in orders:
if not (
flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
if (
filters
and filters.get("outstanding_amt_greater_than")
and filters.get("outstanding_amt_less_than")
and not (
flt(filters.get("outstanding_amt_greater_than"))
<= flt(d.outstanding_amount)
<= flt(filters.get("outstanding_amt_less_than"))
)
):
continue

View File

@@ -1013,6 +1013,30 @@ class TestPaymentEntry(FrappeTestCase):
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
create_payment_entry(party_type="Employee", party=employee, save=True)
def test_duplicate_payment_entry_allocate_amount(self):
si = create_sales_invoice()
pe_draft = get_payment_entry("Sales Invoice", si.name)
pe_draft.insert()
pe = get_payment_entry("Sales Invoice", si.name)
pe.submit()
self.assertRaises(frappe.ValidationError, pe_draft.submit)
def test_duplicate_payment_entry_partial_allocate_amount(self):
si = create_sales_invoice()
pe_draft = get_payment_entry("Sales Invoice", si.name)
pe_draft.insert()
pe = get_payment_entry("Sales Invoice", si.name)
pe.received_amount = si.total / 2
pe.references[0].allocated_amount = si.total / 2
pe.submit()
self.assertRaises(frappe.ValidationError, pe_draft.submit)
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -6,7 +6,6 @@ import frappe
from frappe import _, msgprint, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull
from frappe.utils import flt, get_link_to_form, getdate, nowdate, today
import erpnext
@@ -127,12 +126,29 @@ class PaymentReconciliation(Document):
return list(journal_entries)
def get_return_invoices(self):
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
doc = qb.DocType(voucher_type)
self.return_invoices = (
qb.from_(doc)
.select(
ConstantColumn(voucher_type).as_("voucher_type"),
doc.name.as_("voucher_no"),
doc.return_against,
)
.where(
(doc.docstatus == 1)
& (doc[frappe.scrub(self.party_type)] == self.party)
& (doc.is_return == 1)
)
.run(as_dict=True)
)
def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry")
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
if erpnext.get_party_account_type(self.party_type) == "Receivable":
self.common_filter_conditions.append(ple.account_type == "Receivable")
@@ -140,19 +156,10 @@ class PaymentReconciliation(Document):
self.common_filter_conditions.append(ple.account_type == "Payable")
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
# get return invoices
doc = qb.DocType(voucher_type)
return_invoices = (
qb.from_(doc)
.select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no"))
.where(
(doc.docstatus == 1)
& (doc[frappe.scrub(self.party_type)] == self.party)
& (doc.is_return == 1)
& (IfNull(doc.return_against, "") == "")
)
.run(as_dict=True)
)
self.get_return_invoices()
return_invoices = [
x for x in self.return_invoices if x.return_against == None or x.return_against == ""
]
outstanding_dr_or_cr = []
if return_invoices:
@@ -204,6 +211,15 @@ class PaymentReconciliation(Document):
accounting_dimensions=self.accounting_dimension_filter_conditions,
)
cr_dr_notes = (
[x.voucher_no for x in self.return_invoices]
if self.party_type in ["Customer", "Supplier"]
else []
)
# Filter out cr/dr notes from outstanding invoices list
# Happens when non-standalone cr/dr notes are linked with another invoice through journal entry
non_reconciled_invoices = [x for x in non_reconciled_invoices if x.voucher_no not in cr_dr_notes]
if self.invoice_limit:
non_reconciled_invoices = non_reconciled_invoices[: self.invoice_limit]

View File

@@ -35,6 +35,7 @@ class PeriodClosingVoucher(AccountsController):
voucher_type="Period Closing Voucher",
voucher_no=self.name,
queue="long",
enqueue_after_commit=True,
)
frappe.msgprint(
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
@@ -169,21 +170,18 @@ class PeriodClosingVoucher(AccountsController):
return frappe.db.sql(
"""
select
t2.account_currency,
t1.account_currency,
{dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
from `tabGL Entry` t1
where
t1.is_cancelled = 0
and t1.account = t2.name
and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2
and t2.company = %s
and t1.account in (select name from `tabAccount` where report_type = 'Profit and Loss' and docstatus < 2 and company = %s)
and t1.posting_date between %s and %s
group by {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields)
dimension_fields=", ".join(dimension_fields),
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
@@ -673,18 +674,22 @@ def get_bin_qty(item_code, warehouse):
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql(
"""select sum(p_item.stock_qty) as qty
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
where p.name = p_item.parent
and ifnull(p.consolidated_invoice, '') = ''
and p_item.docstatus = 1
and p_item.item_code = %s
and p_item.warehouse = %s
""",
(item_code, warehouse),
as_dict=1,
)
p_inv = frappe.qb.DocType("POS Invoice")
p_item = frappe.qb.DocType("POS Invoice Item")
reserved_qty = (
frappe.qb.from_(p_inv)
.from_(p_item)
.select(Sum(p_item.qty).as_("qty"))
.where(
(p_inv.name == p_item.parent)
& (IfNull(p_inv.consolidated_invoice, "") == "")
& (p_inv.is_return == 0)
& (p_item.docstatus == 1)
& (p_item.item_code == item_code)
& (p_item.warehouse == warehouse)
)
).run(as_dict=True)
return reserved_qty[0].qty or 0 if reserved_qty else 0

View File

@@ -469,7 +469,7 @@
"options": "UOM"
},
{
"description": "If rate is zero them item will be treated as \"Free Item\"",
"description": "If rate is zero then item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"label": "Free Item Rate"
@@ -670,4 +670,4 @@
"sort_order": "DESC",
"states": [],
"title_field": "title"
}
}

View File

@@ -637,13 +637,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
gle_filters={"account": "Stock In Hand - TCP1"},
)
# assert loss booked in COGS
self.assertGLEs(
return_pi,
[{"credit": 0, "debit": 200}],
gle_filters={"account": "Cost of Goods Sold - TCP1"},
)
def test_return_with_lcv(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
@@ -1662,6 +1655,21 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
self.assertTrue(return_pi.docstatus == 1)
def test_gl_entries_for_standalone_debit_note(self):
make_purchase_invoice(qty=5, rate=500, update_stock=True)
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
# override the rate with valuation rate
sle = frappe.get_all(
"Stock Ledger Entry",
fields=["stock_value_difference", "actual_qty"],
filters={"voucher_no": returned_inv.name},
)[0]
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(

View File

@@ -1012,10 +1012,16 @@ class SalesInvoice(SellingController):
def check_prev_docstatus(self):
for d in self.get("items"):
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
if (
d.sales_order
and frappe.db.get_value("Sales Order", d.sales_order, "docstatus", cache=True) != 1
):
frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
if (
d.delivery_note
and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus", cache=True) != 1
):
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
def make_gl_entries(self, gl_entries=None, from_repost=False):
@@ -1179,7 +1185,12 @@ class SalesInvoice(SellingController):
if self.is_return:
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", None)
@@ -1194,7 +1205,12 @@ class SalesInvoice(SellingController):
asset.reload()
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
asset,
item.base_net_amount,
item.finance_book,
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", self.posting_date)

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, getdate
from frappe.utils import cint, flt, getdate
class TaxWithholdingCategory(Document):
@@ -302,7 +302,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
"docstatus": 1,
}
if not tax_details.get("consider_party_ledger_amount") and doctype != "Sales Invoice":
if doctype != "Sales Invoice":
filters.update(
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
)
@@ -569,7 +569,12 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
tds_amount = 0
limit_consumed = frappe.db.get_value(
"Purchase Invoice",
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
{
"supplier": ("in", parties),
"apply_tds": 1,
"docstatus": 1,
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
},
"sum(tax_withholding_net_total)",
)
@@ -584,10 +589,10 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
if current_amount < (certificate_limit - deducted_amount):
if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
return current_amount * rate / 100
else:
ltds_amount = certificate_limit - deducted_amount
ltds_amount = certificate_limit - flt(deducted_amount)
tds_amount = current_amount - ltds_amount
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
@@ -598,9 +603,9 @@ def is_valid_certificate(
):
valid = False
if (
getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)
) and certificate_limit > deducted_amount:
available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount)
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
valid = True
return valid

View File

@@ -110,9 +110,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices.append(pi1)
# Cumulative threshold is 30000
# Threshold calculation should be on both the invoices
# TDS should be applied only on 1000
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
# Threshold calculation should be only on the Second invoice
# Second didn't breach, no TDS should be applied
self.assertEqual(pi1.taxes, [])
for d in reversed(invoices):
d.cancel()

View File

@@ -0,0 +1,41 @@
{
"creation": "2023-05-23 09:58:17.235916",
"docstatus": 0,
"doctype": "Form Tour",
"first_document": 0,
"idx": 0,
"include_name_field": 0,
"is_standard": 1,
"modified": "2023-05-23 13:10:56.227127",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
"owner": "Administrator",
"reference_doctype": "Sales Invoice",
"save_on_complete": 1,
"steps": [
{
"description": "Select a customer for whom this invoice is being prepared.",
"fieldname": "customer",
"fieldtype": "Link",
"has_next_condition": 1,
"is_table_field": 0,
"label": "Customer",
"next_step_condition": "eval: doc.customer",
"position": "Right",
"title": "Select Customer"
},
{
"child_doctype": "Sales Invoice Item",
"description": "Select item that you have sold along with quantity and rate.",
"fieldname": "items",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"parent_fieldname": "items",
"position": "Top",
"title": "Select Item"
}
],
"title": "Sales Invoice"
}

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
from typing import Optional
import frappe
from frappe import _, msgprint, scrub
from frappe.contacts.doctype.address.address import (
@@ -647,12 +649,12 @@ def set_taxes(
else:
args.update(get_party_details(party, party_type))
if party_type in ("Customer", "Lead"):
if party_type in ("Customer", "Lead", "Prospect"):
args.update({"tax_type": "Sales"})
if party_type == "Lead":
if party_type in ["Lead", "Prospect"]:
args["customer"] = None
del args["lead"]
del args[frappe.scrub(party_type)]
else:
args.update({"tax_type": "Purchase"})
@@ -850,7 +852,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
return company_wise_info
def get_party_shipping_address(doctype, name):
def get_party_shipping_address(doctype: str, name: str) -> Optional[str]:
"""
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
and/or `is_shipping_address = 1`.
@@ -861,37 +863,41 @@ def get_party_shipping_address(doctype, name):
:param name: Party name
:return: String
"""
out = frappe.db.sql(
"SELECT dl.parent "
"from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name "
"where "
"dl.link_doctype=%s "
"and dl.link_name=%s "
"and dl.parenttype='Address' "
"and ifnull(ta.disabled, 0) = 0 and"
"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
(doctype, name),
shipping_addresses = frappe.get_all(
"Address",
filters=[
["Dynamic Link", "link_doctype", "=", doctype],
["Dynamic Link", "link_name", "=", name],
["disabled", "=", 0],
],
or_filters=[
["is_shipping_address", "=", 1],
["address_type", "=", "Shipping"],
],
pluck="name",
limit=1,
order_by="is_shipping_address DESC",
)
if out:
return out[0][0]
else:
return ""
return shipping_addresses[0] if shipping_addresses else None
def get_partywise_advanced_payment_amount(
party_type, posting_date=None, future_payment=0, company=None
party_type, posting_date=None, future_payment=0, company=None, party=None
):
cond = "1=1"
if posting_date:
if future_payment:
cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' " "".format(posting_date)
cond = "(posting_date <= '{0}' OR DATE(creation) <= '{0}')" "".format(posting_date)
else:
cond = "posting_date <= '{0}'".format(posting_date)
if company:
cond += "and company = {0}".format(frappe.db.escape(company))
if party:
cond += "and party = {0}".format(frappe.db.escape(party))
data = frappe.db.sql(
""" SELECT party, sum({0}) as amount
FROM `tabGL Entry`
@@ -903,36 +909,36 @@ def get_partywise_advanced_payment_amount(
),
party_type,
)
if data:
return frappe._dict(data)
def get_default_contact(doctype, name):
def get_default_contact(doctype: str, name: str) -> Optional[str]:
"""
Returns default contact for the given doctype and name.
Can be ordered by `contact_type` to either is_primary_contact or is_billing_contact.
Returns contact name only if there is a primary contact for given doctype and name.
Else returns None
:param doctype: Party Doctype
:param name: Party name
:return: String
"""
out = frappe.db.sql(
"""
SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
FROM `tabDynamic Link` dl
INNER JOIN `tabContact` c ON c.name = dl.parent
WHERE
dl.link_doctype=%s AND
dl.link_name=%s AND
dl.parenttype = 'Contact'
ORDER BY is_primary_contact DESC, is_billing_contact DESC
""",
(doctype, name),
contacts = frappe.get_all(
"Contact",
filters=[
["Dynamic Link", "link_doctype", "=", doctype],
["Dynamic Link", "link_name", "=", name],
],
or_filters=[
["is_primary_contact", "=", 1],
["is_billing_contact", "=", 1],
],
pluck="name",
limit=1,
order_by="is_primary_contact DESC, is_billing_contact DESC",
)
if out:
try:
return out[0][0]
except Exception:
return None
else:
return None
return contacts[0] if contacts else None
def add_party_account(party_type, party, company, account):

View File

@@ -181,6 +181,16 @@ class ReceivablePayableReport(object):
return
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
# If payment is made against credit note
# and credit note is made against a Sales Invoice
# then consider the payment against original sales invoice.
if ple.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
if ple.against_voucher_no in self.return_entries:
return_against = self.return_entries.get(ple.against_voucher_no)
if return_against:
key = (ple.against_voucher_type, return_against, ple.party)
row = self.voucher_balance.get(key)
if not row:
@@ -610,7 +620,7 @@ class ReceivablePayableReport(object):
def get_return_entries(self):
doctype = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
filters = {"is_return": 1, "docstatus": 1}
filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
party_field = scrub(self.filters.party_type)
if self.filters.get(party_field):
filters.update({party_field: self.filters.get(party_field)})

View File

@@ -210,6 +210,67 @@ class TestAccountsReceivable(FrappeTestCase):
],
)
def test_payment_against_credit_note(self):
"""
Payment against credit/debit note should be considered against the parent invoice
"""
company = "_Test Company 2"
customer = "_Test Customer 2"
si1 = make_sales_invoice()
pe = get_payment_entry("Sales Invoice", si1.name, bank_account="Cash - _TC2")
pe.paid_from = "Debtors - _TC2"
pe.insert()
pe.submit()
cr_note = make_credit_note(si1.name)
si2 = make_sales_invoice()
# manually link cr_note with si2 using journal entry
je = frappe.new_doc("Journal Entry")
je.company = company
je.voucher_type = "Credit Note"
je.posting_date = today()
debit_account = "Debtors - _TC2"
debit_entry = {
"account": debit_account,
"party_type": "Customer",
"party": customer,
"debit": 100,
"debit_in_account_currency": 100,
"reference_type": cr_note.doctype,
"reference_name": cr_note.name,
"cost_center": "Main - _TC2",
}
credit_entry = {
"account": debit_account,
"party_type": "Customer",
"party": customer,
"credit": 100,
"credit_in_account_currency": 100,
"reference_type": si2.doctype,
"reference_name": si2.name,
"cost_center": "Main - _TC2",
}
je.append("accounts", debit_entry)
je.append("accounts", credit_entry)
je = je.save().submit()
filters = {
"company": company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
report = execute(filters)
self.assertEqual(report[1], [])
def make_sales_invoice(no_payment_schedule=False, do_not_submit=False):
frappe.set_user("Administrator")
@@ -256,7 +317,7 @@ def make_payment(docname):
def make_credit_note(docname):
create_sales_invoice(
credit_note = create_sales_invoice(
company="_Test Company 2",
customer="_Test Customer 2",
currency="EUR",
@@ -269,3 +330,5 @@ def make_credit_note(docname):
is_return=1,
return_against=docname,
)
return credit_note

View File

@@ -31,7 +31,6 @@ class AccountsReceivableSummary(ReceivablePayableReport):
def get_data(self, args):
self.data = []
self.receivables = ReceivablePayableReport(self.filters).run(args)[1]
self.get_party_total(args)
@@ -42,6 +41,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.filters.report_date,
self.filters.show_future_payments,
self.filters.company,
party=self.filters.get(scrub(self.party_type)),
)
or {}
)
@@ -74,6 +74,9 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row.gl_balance = gl_balance_map.get(party)
row.diff = flt(row.outstanding) - flt(row.gl_balance)
if self.filters.show_future_payments:
row.remaining_balance = flt(row.outstanding) - flt(row.future_amount)
self.data.append(row)
def get_party_total(self, args):
@@ -106,6 +109,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"range4": 0.0,
"range5": 0.0,
"total_due": 0.0,
"future_amount": 0.0,
"sales_person": [],
}
),
@@ -151,6 +155,10 @@ class AccountsReceivableSummary(ReceivablePayableReport):
self.setup_ageing_columns()
if self.filters.show_future_payments:
self.add_column(label=_("Future Payment Amount"), fieldname="future_amount")
self.add_column(label=_("Remaining Balance"), fieldname="remaining_balance")
if self.party_type == "Customer":
self.add_column(
label=_("Territory"), fieldname="territory", fieldtype="Link", options="Territory"

View File

@@ -125,12 +125,14 @@ def get_revenue(data, period_list, include_in_gross=1):
data_to_be_removed = True
while data_to_be_removed:
revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list)
revenue = adjust_account(revenue, period_list)
revenue, data_to_be_removed = remove_parent_with_no_child(revenue)
adjust_account_totals(revenue, period_list)
return copy.deepcopy(revenue)
def remove_parent_with_no_child(data, period_list):
def remove_parent_with_no_child(data):
data_to_be_removed = False
for parent in data:
if "is_group" in parent and parent.get("is_group") == 1:
@@ -147,16 +149,19 @@ def remove_parent_with_no_child(data, period_list):
return data, data_to_be_removed
def adjust_account(data, period_list, consolidated=False):
leaf_nodes = [item for item in data if item["is_group"] == 0]
def adjust_account_totals(data, period_list):
totals = {}
for node in leaf_nodes:
set_total(node, node["total"], data, totals)
for d in data:
for period in period_list:
key = period if consolidated else period.key
d["total"] = totals[d["account"]]
return data
for d in reversed(data):
if d.get("is_group"):
for period in period_list:
# reset totals for group accounts as totals set by get_data doesn't consider include_in_gross check
d[period.key] = sum(
item[period.key] for item in data if item.get("parent_account") == d.get("account")
)
else:
set_total(d, d["total"], data, totals)
d["total"] = totals[d["account"]]
def set_total(node, value, complete_list, totals):
@@ -191,6 +196,9 @@ def get_profit(
if profit_loss[key]:
has_value = True
if not profit_loss.get("total"):
profit_loss["total"] = 0
profit_loss["total"] += profit_loss[key]
if has_value:
return profit_loss
@@ -229,6 +237,9 @@ def get_net_profit(
if profit_loss[key]:
has_value = True
if not profit_loss.get("total"):
profit_loss["total"] = 0
profit_loss["total"] += profit_loss[key]
if has_value:
return profit_loss

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from collections import OrderedDict
import frappe
from frappe import _, qb, scrub
@@ -736,7 +737,7 @@ class GrossProfitGenerator(object):
def load_invoice_items(self):
conditions = ""
if self.filters.company:
conditions += " and company = %(company)s"
conditions += " and `tabSales Invoice`.company = %(company)s"
if self.filters.from_date:
conditions += " and posting_date >= %(from_date)s"
if self.filters.to_date:
@@ -849,30 +850,30 @@ class GrossProfitGenerator(object):
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
"""
parents = []
grouped = OrderedDict()
for row in self.si_list:
if row.parent not in parents:
parents.append(row.parent)
# initialize list with a header row for each new parent
grouped.setdefault(row.parent, [self.get_invoice_row(row)]).append(
row.update(
{"indent": 1.0, "parent_invoice": row.parent, "invoice_or_item": row.item_code}
) # descendant rows will have indent: 1.0 or greater
)
parents_index = 0
for index, row in enumerate(self.si_list):
if parents_index < len(parents) and row.parent == parents[parents_index]:
invoice = self.get_invoice_row(row)
self.si_list.insert(index, invoice)
parents_index += 1
# if item is a bundle, add it's components as seperate rows
if frappe.db.exists("Product Bundle", row.item_code):
bundled_items = self.get_bundle_items(row)
for x in bundled_items:
bundle_item = self.get_bundle_item_row(row, x)
grouped.get(row.parent).append(bundle_item)
else:
# skipping the bundle items rows
if not row.indent:
row.indent = 1.0
row.parent_invoice = row.parent
row.invoice_or_item = row.item_code
self.si_list.clear()
if frappe.db.exists("Product Bundle", row.item_code):
self.add_bundle_items(row, index)
for items in grouped.values():
self.si_list.extend(items)
def get_invoice_row(self, row):
# header row format
return frappe._dict(
{
"parent_invoice": "",
@@ -901,13 +902,6 @@ class GrossProfitGenerator(object):
}
)
def add_bundle_items(self, product_bundle, index):
bundle_items = self.get_bundle_items(product_bundle)
for i, item in enumerate(bundle_items):
bundle_item = self.get_bundle_item_row(product_bundle, item)
self.si_list.insert((index + i + 1), bundle_item)
def get_bundle_items(self, product_bundle):
return frappe.get_all(
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]

View File

@@ -399,8 +399,9 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.is_internal_customer,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.project,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,

View File

@@ -5,8 +5,9 @@
"label": "Profit and Loss"
}
],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}}]",
"content": "[{\"id\":\"MmUf9abwxg\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Accounts\",\"col\":12}},{\"id\":\"i0EtSjDAXq\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Profit and Loss\",\"col\":12}},{\"id\":\"X78jcbq1u3\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"vikWSkNm6_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"pMywM0nhlj\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chart of Accounts\",\"col\":3}},{\"id\":\"_pRdD6kqUG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"G984SgVRJN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Invoice\",\"col\":3}},{\"id\":\"1ArNvt9qhz\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Journal Entry\",\"col\":3}},{\"id\":\"F9f4I1viNr\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Payment Entry\",\"col\":3}},{\"id\":\"4IBBOIxfqW\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Receivable\",\"col\":3}},{\"id\":\"El2anpPaFY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"General Ledger\",\"col\":3}},{\"id\":\"1nwcM9upJo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Trial Balance\",\"col\":3}},{\"id\":\"OF9WOi1Ppc\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"B7-uxs8tkU\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"tHb3yxthkR\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"DnNtsmxpty\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting Masters\",\"col\":4}},{\"id\":\"nKKr6fjgjb\",\"type\":\"card\",\"data\":{\"card_name\":\"General Ledger\",\"col\":4}},{\"id\":\"xOHTyD8b5l\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Receivable\",\"col\":4}},{\"id\":\"_Cb7C8XdJJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounts Payable\",\"col\":4}},{\"id\":\"p7NY6MHe2Y\",\"type\":\"card\",\"data\":{\"card_name\":\"Financial Statements\",\"col\":4}},{\"id\":\"KlqilF5R_V\",\"type\":\"card\",\"data\":{\"card_name\":\"Taxes\",\"col\":4}},{\"id\":\"jTUy8LB0uw\",\"type\":\"card\",\"data\":{\"card_name\":\"Cost Center and Budgeting\",\"col\":4}},{\"id\":\"Wn2lhs7WLn\",\"type\":\"card\",\"data\":{\"card_name\":\"Multi Currency\",\"col\":4}},{\"id\":\"PAQMqqNkBM\",\"type\":\"card\",\"data\":{\"card_name\":\"Bank Statement\",\"col\":4}},{\"id\":\"Q_hBCnSeJY\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"3AK1Zf0oew\",\"type\":\"card\",\"data\":{\"card_name\":\"Profitability\",\"col\":4}},{\"id\":\"kxhoaiqdLq\",\"type\":\"card\",\"data\":{\"card_name\":\"Opening and Closing\",\"col\":4}},{\"id\":\"q0MAlU2j_Z\",\"type\":\"card\",\"data\":{\"card_name\":\"Subscription Management\",\"col\":4}},{\"id\":\"ptm7T6Hwu-\",\"type\":\"card\",\"data\":{\"card_name\":\"Share Management\",\"col\":4}},{\"id\":\"OX7lZHbiTr\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:41:59.515192",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
@@ -1093,10 +1094,11 @@
"type": "Link"
}
],
"modified": "2022-06-24 05:41:09.236458",
"modified": "2023-05-30 13:23:29.316711",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,

View File

@@ -26,6 +26,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_first_day_of_the_month,
is_last_day_of_the_month,
)
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -337,13 +338,9 @@ class Asset(AccountsController):
if should_get_last_day:
schedule_date = get_last_day(schedule_date)
# schedule date will be a year later from start date
# so monthly schedule date is calculated by removing 11 months from it
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
# if asset is being sold
if date_of_disposal:
from_date = self.get_from_date(finance_book.finance_book)
from_date = self.get_from_date_for_disposal(finance_book)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
@@ -364,9 +361,9 @@ class Asset(AccountsController):
# For first row
if (
(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
n == 0
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
and not self.opening_accumulated_depreciation
and n == 0
):
from_date = add_days(
self.available_for_use_date, -1
@@ -378,10 +375,26 @@ class Asset(AccountsController):
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
# For first depr schedule date will be the start date
# so monthly schedule date is calculated by removing month difference between use date and start date
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
if not is_first_day_of_the_month(getdate(self.available_for_use_date)):
from_date = get_last_day(
add_months(
getdate(self.available_for_use_date),
((self.number_of_depreciations_booked - 1) * finance_book.frequency_of_depreciation),
)
)
else:
from_date = add_months(
getdate(add_days(self.available_for_use_date, -1)),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
depreciation_amount, days, months = self.get_pro_rata_amt(
finance_book,
depreciation_amount,
from_date,
finance_book.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
@@ -406,9 +419,7 @@ class Asset(AccountsController):
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
)
monthly_schedule_date = add_months(schedule_date, 1)
schedule_date = add_days(schedule_date, days)
last_schedule_date = schedule_date
if not depreciation_amount:
continue
@@ -425,7 +436,7 @@ class Asset(AccountsController):
depreciation_amount += value_after_depreciation - finance_book.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:
self._add_depreciation_row(
schedule_date,
depreciation_amount,
@@ -500,16 +511,19 @@ class Asset(AccountsController):
return start
def get_from_date(self, finance_book):
def get_from_date_for_disposal(self, finance_book):
if not self.get("schedules"):
return self.available_for_use_date
return add_months(
getdate(self.available_for_use_date),
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
)
if len(self.finance_books) == 1:
return self.schedules[-1].schedule_date
from_date = ""
for schedule in self.get("schedules"):
if schedule.finance_book == finance_book:
if schedule.finance_book == finance_book.finance_book:
from_date = schedule.schedule_date
if from_date:
@@ -1287,9 +1301,11 @@ def get_straight_line_or_manual_depr_amount(asset, row):
)
# if the Depreciation Schedule is being prepared for the first time
else:
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
row.total_number_of_depreciations
)
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
def get_wdv_or_dd_depr_amount(

View File

@@ -4,7 +4,16 @@
import frappe
from frappe import _
from frappe.utils import add_months, cint, flt, get_last_day, getdate, nowdate, today
from frappe.utils import (
add_months,
cint,
flt,
get_first_day,
get_last_day,
getdate,
nowdate,
today,
)
from frappe.utils.data import get_link_to_form
from frappe.utils.user import get_users_with_role
@@ -272,7 +281,7 @@ def scrap_asset(asset_name):
je.company = asset.company
je.remark = "Scrap Entry for asset {0}".format(asset_name)
for entry in get_gl_entries_on_asset_disposal(asset):
for entry in get_gl_entries_on_asset_disposal(asset, date):
entry.update({"reference_type": "Asset", "reference_name": asset_name})
je.append("accounts", entry)
@@ -395,8 +404,11 @@ def disposal_happens_in_the_future(posting_date_of_disposal):
def get_gl_entries_on_asset_regain(
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None
):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -414,7 +426,7 @@ def get_gl_entries_on_asset_regain(
"debit_in_account_currency": asset.gross_purchase_amount,
"debit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
@@ -424,7 +436,7 @@ def get_gl_entries_on_asset_regain(
"credit_in_account_currency": accumulated_depr_amount,
"credit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
@@ -433,7 +445,7 @@ def get_gl_entries_on_asset_regain(
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
if profit_amount:
get_profit_gl_entries(
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
if voucher_type and voucher_no:
@@ -445,8 +457,11 @@ def get_gl_entries_on_asset_regain(
def get_gl_entries_on_asset_disposal(
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None, date=None
):
if not date:
date = getdate()
(
fixed_asset_account,
asset,
@@ -464,26 +479,30 @@ def get_gl_entries_on_asset_disposal(
"credit_in_account_currency": asset.gross_purchase_amount,
"credit": asset.gross_purchase_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
},
item=asset,
),
asset.get_gl_dict(
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
),
]
if accumulated_depr_amount:
gl_entries.append(
asset.get_gl_dict(
{
"account": accumulated_depr_account,
"debit_in_account_currency": accumulated_depr_amount,
"debit": accumulated_depr_amount,
"cost_center": depreciation_cost_center,
"posting_date": date,
},
item=asset,
),
)
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
if profit_amount:
get_profit_gl_entries(
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
)
if voucher_type and voucher_no:
@@ -517,8 +536,12 @@ def get_asset_details(asset, finance_book=None):
def get_profit_gl_entries(
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center, date=None
):
if not date:
date = getdate()
debit_or_credit = "debit" if profit_amount < 0 else "credit"
gl_entries.append(
asset.get_gl_dict(
@@ -527,7 +550,7 @@ def get_profit_gl_entries(
"cost_center": depreciation_cost_center,
debit_or_credit: abs(profit_amount),
debit_or_credit + "_in_account_currency": abs(profit_amount),
"posting_date": getdate(),
"posting_date": date,
},
item=asset,
)
@@ -581,3 +604,9 @@ 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)
def is_first_day_of_the_month(date):
first_day_of_the_month = get_first_day(date)
return getdate(first_day_of_the_month) == getdate(date)

View File

@@ -327,6 +327,79 @@ class TestAsset(AssetSetup):
si.cancel()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_gle_made_by_asset_sale_for_existing_asset(self):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
asset = create_asset(
calculate_depreciation=1,
available_for_use_date="2020-04-01",
purchase_date="2020-04-01",
expected_value_after_useful_life=0,
total_number_of_depreciations=5,
number_of_depreciations_booked=2,
frequency_of_depreciation=12,
depreciation_start_date="2023-03-31",
opening_accumulated_depreciation=24000,
gross_purchase_amount=60000,
submit=1,
)
expected_depr_values = [
["2023-03-31", 12000, 36000],
["2024-03-31", 12000, 48000],
["2025-03-31", 12000, 60000],
]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_depr_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_depr_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_depr_values[i][2], schedule.accumulated_depreciation_amount)
post_depreciation_entries(date="2023-03-31")
si = create_sales_invoice(
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
)
asset.load_from_db()
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
for i, schedule in enumerate(asset.schedules):
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
self.assertTrue(schedule.journal_entry)
expected_gle = (
(
"_Test Accumulated Depreciations - _TC",
37742.47,
0.0,
),
(
"_Test Fixed Asset - _TC",
0.0,
60000.0,
),
(
"_Test Gain/Loss on Asset Disposal - _TC",
0.0,
17742.47,
),
("Debtors - _TC", 40000.0, 0.0),
)
gle = frappe.db.sql(
"""select account, debit, credit from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no = %s
order by account""",
si.name,
)
self.assertSequenceEqual(gle, expected_gle)
def test_asset_with_maintenance_required_status_after_sale(self):
asset = create_asset(
calculate_depreciation=1,
@@ -649,7 +722,7 @@ class TestDepreciationMethods(AssetSetup):
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")
@@ -693,14 +766,14 @@ class TestDepreciationMethods(AssetSetup):
number_of_depreciations_booked=1,
opening_accumulated_depreciation=50000,
expected_value_after_useful_life=10000,
depreciation_start_date="2030-12-31",
depreciation_start_date="2031-12-31",
total_number_of_depreciations=3,
frequency_of_depreciation=12,
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]

View File

@@ -436,6 +436,7 @@ class AssetCapitalization(StockController):
item.get("finance_book") or self.get("finance_book"),
self.get("doctype"),
self.get("name"),
self.get("posting_date"),
)
asset.db_set("disposal_date", self.posting_date)

View File

@@ -96,7 +96,6 @@ class AssetCategory(Document):
frappe.throw(msg, title=_("Missing Account"))
@frappe.whitelist()
def get_asset_category_account(
fieldname, item=None, asset=None, account=None, asset_category=None, company=None
):

View File

@@ -19,56 +19,6 @@ frappe.query_reports["Fixed Asset Register"] = {
options: "\nIn Location\nDisposed",
default: 'In Location'
},
{
"fieldname":"filter_based_on",
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["Fiscal Year", "Date Range"],
"default": "Fiscal Year",
"reqd": 1
},
{
"fieldname":"from_date",
"label": __("Start Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
"reqd": 1
},
{
"fieldname":"to_date",
"label": __("End Date"),
"fieldtype": "Date",
"default": frappe.datetime.nowdate(),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
"reqd": 1
},
{
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
"reqd": 1
},
{
"fieldname":"to_fiscal_year",
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
"reqd": 1
},
{
"fieldname":"date_based_on",
"label": __("Date Based On"),
"fieldtype": "Select",
"options": ["Purchase Date", "Available For Use Date"],
"default": "Purchase Date",
"reqd": 1
},
{
fieldname:"asset_category",
label: __("Asset Category"),
@@ -89,22 +39,67 @@ frappe.query_reports["Fixed Asset Register"] = {
default: "--Select a group--",
reqd: 1
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book",
depends_on: "eval: doc.filter_by_finance_book == 1",
},
{
fieldname:"filter_by_finance_book",
label: __("Filter by Finance Book"),
fieldtype: "Check"
},
{
fieldname:"only_existing_assets",
label: __("Only existing assets"),
fieldtype: "Check"
},
{
fieldname:"finance_book",
label: __("Finance Book"),
fieldtype: "Link",
options: "Finance Book",
},
{
"fieldname": "include_default_book_assets",
"label": __("Include Default Book Assets"),
"fieldtype": "Check",
"default": 1
},
{
"fieldname":"filter_based_on",
"label": __("Period Based On"),
"fieldtype": "Select",
"options": ["--Select a period--", "Fiscal Year", "Date Range"],
"default": "--Select a period--",
},
{
"fieldname":"from_date",
"label": __("Start Date"),
"fieldtype": "Date",
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
},
{
"fieldname":"to_date",
"label": __("End Date"),
"fieldtype": "Date",
"default": frappe.datetime.nowdate(),
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
},
{
"fieldname":"from_fiscal_year",
"label": __("Start Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
"fieldname":"to_fiscal_year",
"label": __("End Year"),
"fieldtype": "Link",
"options": "Fiscal Year",
"default": frappe.defaults.get_user_default("fiscal_year"),
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
},
{
"fieldname":"date_based_on",
"label": __("Date Based On"),
"fieldtype": "Select",
"options": ["Purchase Date", "Available For Use Date"],
"default": "Purchase Date",
"depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'",
},
]
};

View File

@@ -2,9 +2,11 @@
# For license information, please see license.txt
from itertools import chain
import frappe
from frappe import _
from frappe.query_builder.functions import Sum
from frappe.query_builder.functions import IfNull, Sum
from frappe.utils import cstr, flt, formatdate, getdate
from erpnext.accounts.report.financial_statements import (
@@ -13,7 +15,6 @@ from erpnext.accounts.report.financial_statements import (
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):
@@ -64,11 +65,9 @@ def get_conditions(filters):
def get_data(filters):
data = []
conditions = get_conditions(filters)
depreciation_amount_map = get_finance_book_value_map(filters)
pr_supplier_map = get_purchase_receipt_supplier_map()
pi_supplier_map = get_purchase_invoice_supplier_map()
@@ -102,20 +101,27 @@ def get_data(filters):
]
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
assets_linked_to_fb = None
assets_linked_to_fb = get_assets_linked_to_fb(filters)
if filters.filter_by_finance_book:
assets_linked_to_fb = frappe.db.get_all(
doctype="Asset Finance Book",
filters={"finance_book": filters.finance_book or ("is", "not set")},
pluck="parent",
)
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.include_default_book_assets and company_fb:
finance_book = company_fb
elif filters.finance_book:
finance_book = filters.finance_book
else:
finance_book = None
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
for asset in assets_record:
if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb:
continue
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
asset_value = get_asset_value_after_depreciation(
asset.asset_id, finance_book
) or get_asset_value_after_depreciation(asset.asset_id)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
@@ -126,7 +132,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": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map),
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
@@ -140,14 +146,23 @@ def get_data(filters):
def prepare_chart_data(data, filters):
labels_values_map = {}
date_field = frappe.scrub(filters.date_based_on)
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
filters_filter_based_on = "Date Range"
date_field = "purchase_date"
filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
else:
filters_filter_based_on = filters.filter_based_on
date_field = frappe.scrub(filters.date_based_on)
filters_from_date = filters.from_date
filters_to_date = filters.to_date
period_list = get_period_list(
filters.from_fiscal_year,
filters.to_fiscal_year,
filters.from_date,
filters.to_date,
filters.filter_based_on,
filters_from_date,
filters_to_date,
filters_filter_based_on,
"Monthly",
company=filters.company,
ignore_fiscal_year=True,
@@ -184,57 +199,76 @@ 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)
def get_assets_linked_to_fb(filters):
afb = frappe.qb.DocType("Asset Finance Book")
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
return frappe._dict(
frappe.db.sql(
""" Select
parent, SUM(depreciation_amount)
FROM `tabDepreciation Schedule`
WHERE
parentfield='schedules'
AND schedule_date<=%s
AND journal_entry IS NOT NULL
AND ifnull(finance_book, '')=%s
GROUP BY parent""",
(date, cstr(filters.finance_book or "")),
)
query = frappe.qb.from_(afb).select(
afb.parent,
)
if filters.include_default_book_assets:
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
def get_manual_depreciation_amount_of_asset(asset, filters):
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'"))
query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (afb.finance_book.isnull())
)
else:
query = query.where(
(afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull())
)
assets_linked_to_fb = list(chain(*query.run(as_list=1)))
return assets_linked_to_fb
def get_depreciation_amount_of_asset(asset, depreciation_amount_map):
return depreciation_amount_map.get(asset.asset_id) or 0.0
def get_asset_depreciation_amount_map(filters, finance_book):
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
asset = frappe.qb.DocType("Asset")
gle = frappe.qb.DocType("GL Entry")
aca = frappe.qb.DocType("Asset Category Account")
company = frappe.qb.DocType("Company")
result = (
query = (
frappe.qb.from_(gle)
.select(Sum(gle.debit))
.where(gle.against_voucher == asset.asset_id)
.where(gle.account == depreciation_expense_account)
.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(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount"))
.where(
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.where(gle.posting_date <= date)
).run()
.where(asset.docstatus == 1)
.groupby(asset.name)
)
if result and result[0] and result[0][0]:
depr_amount = result[0][0]
if finance_book:
query = query.where(
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
)
else:
depr_amount = 0
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
return depr_amount
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
query = query.where(gle.posting_date <= date)
asset_depr_amount_map = query.run()
return dict(asset_depr_amount_map)
def get_purchase_receipt_supplier_map():

View File

@@ -7,12 +7,14 @@
],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Assets\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Asset Value Analytics\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Asset Category\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Fixed Asset Register\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Assets\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-02 15:43:27.634865",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "assets",
"idx": 0,
"is_hidden": 0,
"label": "Assets",
"links": [
{
@@ -183,13 +185,15 @@
"type": "Link"
}
],
"modified": "2021-10-04 12:15:54.839454",
"modified": "2023-05-24 14:47:20.243146",
"modified_by": "Administrator",
"module": "Assets",
"name": "Assets",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"parent_page": "Accounting",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 4.0,
@@ -216,4 +220,4 @@
}
],
"title": "Assets"
}
}

View File

@@ -157,7 +157,7 @@
"party_account_currency",
"inter_company_order_reference",
"is_old_subcontracting_flow",
"dashboard"
"connections_tab"
],
"fields": [
{
@@ -1171,7 +1171,6 @@
"depends_on": "is_internal_supplier",
"fieldname": "set_from_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Set From Warehouse",
"options": "Warehouse"
},
@@ -1185,12 +1184,6 @@
"fieldtype": "Tab Break",
"label": "More Info"
},
{
"fieldname": "dashboard",
"fieldtype": "Tab Break",
"label": "Dashboard",
"show_dashboard": 1
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
@@ -1266,13 +1259,19 @@
"fieldname": "shipping_address_section",
"fieldtype": "Section Break",
"label": "Shipping Address"
},
{
"fieldname": "connections_tab",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2023-05-07 20:18:09.196799",
"modified": "2023-05-24 11:16:41.195340",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -7,12 +7,14 @@
],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Buying\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Purchase Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Material Request\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Purchase Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Buying\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items & Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Supplier Scorecard\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Regional\",\"col\":4}}]",
"creation": "2020-01-28 11:50:26.195467",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "buying",
"idx": 0,
"is_hidden": 0,
"label": "Buying",
"links": [
{
@@ -509,16 +511,18 @@
"type": "Link"
}
],
"modified": "2022-01-13 17:26:39.090190",
"modified": "2023-05-24 14:47:20.535772",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 6.0,
"sequence_id": 5.0,
"shortcuts": [
{
"color": "Green",

View File

@@ -758,6 +758,7 @@ class AccountsController(TransactionBase):
}
)
update_gl_dict_with_regional_fields(self, gl_dict)
accounting_dimensions = get_accounting_dimensions()
dimension_dict = frappe._dict()
@@ -916,6 +917,9 @@ class AccountsController(TransactionBase):
return is_inclusive
def should_show_taxes_as_table_in_print(self):
return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print"))
def validate_advance_entries(self):
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
@@ -2835,3 +2839,8 @@ def validate_regional(doc):
@erpnext.allow_regional
def validate_einvoice_fields(doc):
pass
@erpnext.allow_regional
def update_gl_dict_with_regional_fields(doc, gl_dict):
pass

View File

@@ -30,6 +30,8 @@ class BuyingController(SubcontractingController):
return _("From {0} | {1} {2}").format(self.supplier_name, self.currency, self.grand_total)
def validate(self):
self.set_rate_for_standalone_debit_note()
super(BuyingController, self).validate()
if getattr(self, "supplier", None) and not self.supplier_name:
self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name")
@@ -72,6 +74,30 @@ class BuyingController(SubcontractingController):
),
)
def set_rate_for_standalone_debit_note(self):
if self.get("is_return") and self.get("update_stock") and not self.return_against:
for row in self.items:
# override the rate with valuation rate
row.rate = get_incoming_rate(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"posting_date": self.get("posting_date"),
"posting_time": self.get("posting_time"),
"qty": row.qty,
"serial_and_batch_bundle": row.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
},
raise_error_if_no_rate=False,
)
row.discount_percentage = 0.0
row.discount_amount = 0.0
row.margin_rate_or_amount = 0.0
def set_missing_values(self, for_validate=False):
super(BuyingController, self).set_missing_values(for_validate)
@@ -184,6 +210,7 @@ class BuyingController(SubcontractingController):
address_dict = {
"supplier_address": "address_display",
"shipping_address": "shipping_address_display",
"billing_address": "billing_address_display",
}
for address_field, address_display_field in address_dict.items():
@@ -444,7 +471,7 @@ class BuyingController(SubcontractingController):
continue
if d.warehouse:
pr_qty = flt(d.qty) * flt(d.conversion_factor)
pr_qty = flt(flt(d.qty) * flt(d.conversion_factor), d.precision("stock_qty"))
if pr_qty:
@@ -506,7 +533,7 @@ class BuyingController(SubcontractingController):
d,
{
"warehouse": d.rejected_warehouse,
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
"actual_qty": flt(flt(d.rejected_qty) * flt(d.conversion_factor), d.precision("stock_qty")),
"serial_no": cstr(d.rejected_serial_no).strip(),
"incoming_rate": 0.0,
},

View File

@@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings):
doc.print_templates.update(
{
"total": "templates/print_formats/includes/total.html",
"taxes": "templates/print_formats/includes/taxes.html",
}
)
if not doc.should_show_taxes_as_table_in_print():
doc.print_templates.update(
{
"taxes": "templates/print_formats/includes/taxes.html",
}
)
def format_columns(display_columns, compact_fields):
compact_fields = compact_fields + ["image", "item_code", "item_name"]

View File

@@ -617,6 +617,9 @@ def get_filters(
if reference_voucher_detail_no:
filters["voucher_detail_no"] = reference_voucher_detail_no
if item_row and item_row.get("warehouse"):
filters["warehouse"] = item_row.get("warehouse")
return filters

View File

@@ -43,7 +43,6 @@ class SellingController(StockController):
self.validate_auto_repeat_subscription_dates()
def set_missing_values(self, for_validate=False):
super(SellingController, self).set_missing_values(for_validate)
# set contact and address details for customer, if they are not mentioned
@@ -62,7 +61,7 @@ class SellingController(StockController):
elif self.doctype == "Quotation" and self.party_name:
if self.quotation_to == "Customer":
customer = self.party_name
else:
elif self.quotation_to == "Lead":
lead = self.party_name
if customer:

View File

@@ -689,7 +689,6 @@ class SubcontractingController(StockController):
"actual_qty": flt(item.rejected_qty) * flt(item.conversion_factor),
"serial_no": cstr(item.rejected_serial_no).strip(),
"incoming_rate": 0.0,
"recalculate_rate": 1,
},
)
)

View File

@@ -3,7 +3,10 @@
import frappe
from frappe import _
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
)
from frappe.email.inbox import link_communication_to_document
from frappe.model.mapper import get_mapped_doc
from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
@@ -43,9 +46,8 @@ class Lead(SellingController, CRMNote):
self.update_prospect()
def on_trash(self):
frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
self.unlink_dynamic_links()
frappe.db.set_value("Issue", {"lead": self.name}, "lead", None)
delete_contact_and_address(self.doctype, self.name)
self.remove_link_from_prospect()
def set_full_name(self):
@@ -122,27 +124,6 @@ class Lead(SellingController, CRMNote):
)
lead_row.db_update()
def unlink_dynamic_links(self):
links = frappe.get_all(
"Dynamic Link",
filters={"link_doctype": self.doctype, "link_name": self.name},
fields=["parent", "parenttype"],
)
for link in links:
linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
if len(linked_doc.get("links")) == 1:
linked_doc.delete(ignore_permissions=True)
else:
to_remove = None
for d in linked_doc.get("links"):
if d.link_doctype == self.doctype and d.link_name == self.name:
to_remove = d
if to_remove:
linked_doc.remove(to_remove)
linked_doc.save(ignore_permissions=True)
def remove_link_from_prospect(self):
prospects = self.get_linked_prospects()

View File

@@ -5,7 +5,7 @@ frappe.ui.form.on('LinkedIn Settings', {
onload: function(frm) {
if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret) {
frappe.confirm(
__('Session not valid, Do you want to login?'),
__('Session not valid. Do you want to login?'),
function(){
frm.trigger("login");
},
@@ -14,11 +14,11 @@ frappe.ui.form.on('LinkedIn Settings', {
}
);
}
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings'>${__('Click here')}</a>`]));
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings'>${__('click here')}</a>`]));
},
refresh: function(frm) {
if (frm.doc.session_status=="Expired"){
let msg = __("Session Not Active. Save doc to login.");
let msg = __("Session not active. Save document to login.");
frm.dashboard.set_headline_alert(
`<div class="row">
<div class="col-xs-12">
@@ -37,7 +37,7 @@ frappe.ui.form.on('LinkedIn Settings', {
let msg,color;
if (days>0){
msg = __("Your Session will be expire in {0} days.", [days]);
msg = __("Your session will be expire in {0} days.", [days]);
color = "green";
}
else {

View File

@@ -2,7 +2,10 @@
# For license information, please see license.txt
import frappe
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.contacts.address_and_contact import (
delete_contact_and_address,
load_address_and_contact,
)
from frappe.model.mapper import get_mapped_doc
from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
@@ -16,7 +19,7 @@ class Prospect(CRMNote):
self.link_with_lead_contact_and_address()
def on_trash(self):
self.unlink_dynamic_links()
delete_contact_and_address(self.doctype, self.name)
def after_insert(self):
carry_forward_communication_and_comments = frappe.db.get_single_value(
@@ -54,27 +57,6 @@ class Prospect(CRMNote):
linked_doc.append("links", {"link_doctype": self.doctype, "link_name": self.name})
linked_doc.save(ignore_permissions=True)
def unlink_dynamic_links(self):
links = frappe.get_all(
"Dynamic Link",
filters={"link_doctype": self.doctype, "link_name": self.name},
fields=["parent", "parenttype"],
)
for link in links:
linked_doc = frappe.get_doc(link["parenttype"], link["parent"])
if len(linked_doc.get("links")) == 1:
linked_doc.delete(ignore_permissions=True)
else:
to_remove = None
for d in linked_doc.get("links"):
if d.link_doctype == self.doctype and d.link_name == self.name:
to_remove = d
if to_remove:
linked_doc.remove(to_remove)
linked_doc.save(ignore_permissions=True)
@frappe.whitelist()
def make_customer(source_name, target_doc=None):

View File

@@ -14,7 +14,7 @@ frappe.ui.form.on('Twitter Settings', {
}
);
}
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings'>${__('Click here')}</a>`]));
frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings'>${__('click here')}</a>`]));
},
refresh: function(frm) {
let msg, color, flag=false;

View File

@@ -5,177 +5,18 @@
"label": "Territory Wise Sales"
}
],
"content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Territory Wise Sales\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Opportunity\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Sales Pipeline\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Campaign\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}}]",
"content": "[{\"id\":\"Cj2TyhgiWy\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Territory Wise Sales\",\"col\":12}},{\"id\":\"LAKRmpYMRA\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"XGIwEUStw_\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"69RN0XsiJK\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Lead\",\"col\":3}},{\"id\":\"t6PQ0vY-Iw\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Opportunity\",\"col\":3}},{\"id\":\"VOFE0hqXRD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"id\":\"0ik53fuemG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"id\":\"wdROEmB_XG\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"-I9HhcgUKE\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"ttpROKW9vk\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"-76QPdbBHy\",\"type\":\"card\",\"data\":{\"card_name\":\"Sales Pipeline\",\"col\":4}},{\"id\":\"_YmGwzVWRr\",\"type\":\"card\",\"data\":{\"card_name\":\"Masters\",\"col\":4}},{\"id\":\"Bma1PxoXk3\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"80viA0R83a\",\"type\":\"card\",\"data\":{\"card_name\":\"Campaign\",\"col\":4}},{\"id\":\"Buo5HtKRFN\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"sLS_x4FMK2\",\"type\":\"card\",\"data\":{\"card_name\":\"Maintenance\",\"col\":4}}]",
"creation": "2020-01-23 14:48:30.183272",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "crm",
"idx": 0,
"is_hidden": 0,
"label": "CRM",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Pipeline",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Lead",
"link_count": 0,
"link_to": "Lead",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Opportunity",
"link_count": 0,
"link_to": "Opportunity",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Prospect",
"link_count": 0,
"link_to": "Prospect",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contact",
"link_count": 0,
"link_to": "Contact",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Communication",
"link_count": 0,
"link_to": "Communication",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contract",
"link_count": 0,
"link_to": "Contract",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Appointment",
"link_count": 0,
"link_to": "Appointment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 0,
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Lead Source",
"link_count": 0,
"link_to": "Lead Source",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Territory",
"link_count": 0,
"link_to": "Territory",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer Group",
"link_count": 0,
"link_to": "Customer Group",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Sales Person",
"link_count": 0,
"link_to": "Sales Person",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Stage",
"link_count": 0,
"link_to": "Sales Stage",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
@@ -446,19 +287,183 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Masters",
"link_count": 7,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Territory",
"link_count": 0,
"link_to": "Territory",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Customer Group",
"link_count": 0,
"link_to": "Customer Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Contact",
"link_count": 0,
"link_to": "Contact",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Prospect",
"link_count": 0,
"link_to": "Prospect",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Person",
"link_count": 0,
"link_to": "Sales Person",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Stage",
"link_count": 0,
"link_to": "Sales Stage",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Lead Source",
"link_count": 0,
"link_to": "Lead Source",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Sales Pipeline",
"link_count": 7,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Lead",
"link_count": 0,
"link_to": "Lead",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Opportunity",
"link_count": 0,
"link_to": "Opportunity",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Customer",
"link_count": 0,
"link_to": "Customer",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contract",
"link_count": 0,
"link_to": "Contract",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Appointment",
"link_count": 0,
"link_to": "Appointment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_count": 0,
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Communication",
"link_count": 0,
"link_to": "Communication",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2022-07-22 15:03:30.755417",
"modified": "2023-05-26 16:49:04.298122",
"modified_by": "Administrator",
"module": "CRM",
"name": "CRM",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 7.0,
"sequence_id": 10.0,
"shortcuts": [
{
"color": "Blue",

View File

@@ -78,9 +78,10 @@ erpnext.ProductList = class {
let title_html = `<div style="display: flex; margin-left: -15px;">`;
title_html += `
<div class="col-8" style="margin-right: -15px;">
<a class="" href="/${ item.route || '#' }"
style="color: var(--gray-800); font-weight: 500;">
<a href="/${ item.route || '#' }">
<div class="product-title">
${ title }
</div>
</a>
</div>
`;
@@ -201,4 +202,4 @@ erpnext.ProductList = class {
}
}
};
};

View File

@@ -3,7 +3,7 @@
import frappe
import frappe.defaults
from frappe import _, throw
from frappe import _, bold, throw
from frappe.contacts.doctype.address.address import get_address_display
from frappe.contacts.doctype.contact.contact import get_contact_name
from frappe.utils import cint, cstr, flt, get_fullname
@@ -201,6 +201,11 @@ def get_shopping_cart_menu(context=None):
@frappe.whitelist()
def add_new_address(doc):
doc = frappe.parse_json(doc)
address_title = doc.get("address_title")
if frappe.db.exists("Address", {"address_title": address_title}):
msg = f"The address with the title {bold(address_title)} already exists. Please change the title accordingly."
frappe.throw(_(msg), title=_("Address Already Exists"))
doc.update({"doctype": "Address"})
address = frappe.get_doc(doc)
address.save(ignore_permissions=True)

View File

@@ -162,6 +162,7 @@ def get_next_attribute_and_values(item_code, selected_attributes):
product_info = get_item_variant_price_dict(exact_match[0], cart_settings)
if product_info:
product_info["is_stock_item"] = frappe.get_cached_value("Item", exact_match[0], "is_stock_item")
product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock)
else:
product_info = None

View File

@@ -1,30 +1,185 @@
{
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Marketplace\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"content": "[{\"id\":\"e88ADOJ7WC\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Integrations</b></span>\",\"col\":12}},{\"id\":\"G0tyx9WOfm\",\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"id\":\"nu4oSjH5Rd\",\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"id\":\"nG8cdkpzoc\",\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"id\":\"4hwuQn6E95\",\"type\":\"card\",\"data\":{\"card_name\":\"Communication Channels\",\"col\":4}},{\"id\":\"sEGAzTJRmq\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"id\":\"ZC6xu-cLBR\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-08-20 19:30:48.138801",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "integration",
"idx": 0,
"is_hidden": 0,
"label": "ERPNext Integrations",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Marketplace",
"link_count": 0,
"label": "Backup",
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Woocommerce Settings",
"label": "Dropbox Settings",
"link_count": 0,
"link_to": "Woocommerce Settings",
"link_to": "Dropbox Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "S3 Backup Settings",
"link_count": 0,
"link_to": "S3 Backup Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Drive",
"link_count": 0,
"link_to": "Google Drive",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Authentication",
"link_count": 4,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Social Login",
"link_count": 0,
"link_to": "Social Login Key",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "LDAP Settings",
"link_count": 0,
"link_to": "LDAP Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "OAuth Client",
"link_count": 0,
"link_to": "OAuth Client",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "OAuth Provider Settings",
"link_count": 0,
"link_to": "OAuth Provider Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Communication Channels",
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Webhook",
"link_count": 0,
"link_to": "Webhook",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "SMS Settings",
"link_count": 0,
"link_to": "SMS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Slack Webhook URL",
"link_count": 0,
"link_to": "Slack Webhook URL",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Services",
"link_count": 4,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Settings",
"link_count": 0,
"link_to": "Google Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Contacts",
"link_count": 0,
"link_to": "Google Contacts",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Calendar",
"link_count": 0,
"link_to": "Google Calendar",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Drive",
"link_count": 0,
"link_to": "Google Drive",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
@@ -33,12 +188,11 @@
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 0,
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "GoCardless Settings",
@@ -49,10 +203,9 @@
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "M-Pesa Settings",
"label": "Mpesa Settings",
"link_count": 0,
"link_to": "Mpesa Settings",
"link_type": "DocType",
@@ -60,15 +213,6 @@
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Plaid Settings",
@@ -78,6 +222,14 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings",
"link_count": 2,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
@@ -88,18 +240,30 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Woocommerce Settings",
"link_count": 0,
"link_to": "Woocommerce Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2022-01-13 17:35:35.508718",
"modified": "2023-05-24 14:47:25.984717",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 10.0,
"sequence_id": 21.0,
"shortcuts": [],
"title": "ERPNext Integrations"
}
}

View File

@@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase):
interest = per_day_interest * 15
self.assertEqual(amounts["pending_principal_amount"], 1500000)
self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))

View File

@@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController):
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
if not self.last_accrual_date:
self.last_accrual_date = get_last_accrual_date(self.loan)
self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date)
def on_submit(self):
self.make_gl_entries()
@@ -271,14 +271,14 @@ def make_loan_interest_accrual_entry(args):
def get_no_of_days_for_interest_accural(loan, posting_date):
last_interest_accrual_date = get_last_accrual_date(loan.name)
last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date)
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
return no_of_days
def get_last_accrual_date(loan):
def get_last_accrual_date(loan, posting_date):
last_posting_date = frappe.db.sql(
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
WHERE loan = %s and docstatus = 1""",
@@ -286,12 +286,30 @@ def get_last_accrual_date(loan):
)
if last_posting_date[0][0]:
last_interest_accrual_date = last_posting_date[0][0]
# interest for last interest accrual date is already booked, so add 1 day
return add_days(last_posting_date[0][0], 1)
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
if last_disbursement_date and getdate(last_disbursement_date) > add_days(
getdate(last_interest_accrual_date), 1
):
last_interest_accrual_date = last_disbursement_date
return add_days(last_interest_accrual_date, 1)
else:
return frappe.db.get_value("Loan", loan, "disbursement_date")
def get_last_disbursement_date(loan, posting_date):
last_disbursement_date = frappe.db.get_value(
"Loan Disbursement",
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
"MAX(posting_date)",
)
return last_disbursement_date
def days_in_year(year):
days = 365

View File

@@ -101,7 +101,7 @@ class LoanRepayment(AccountsController):
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
if not self.is_term_loan:
# get last loan interest accrual date
last_accrual_date = get_last_accrual_date(self.against_loan)
last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date)
# get posting date upto which interest has to be accrued
per_day_interest = get_per_day_interest(
@@ -722,7 +722,7 @@ def get_amounts(amounts, against_loan, posting_date):
if due_date:
pending_days = date_diff(posting_date, due_date) + 1
else:
last_accrual_date = get_last_accrual_date(against_loan_doc.name)
last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date)
pending_days = date_diff(posting_date, last_accrual_date) + 1
if pending_days > 0:

View File

@@ -2,6 +2,7 @@
"charts": [],
"content": "[{\"id\":\"_38WStznya\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"t7o_K__1jB\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan Application\",\"col\":3}},{\"id\":\"IRiNDC6w1p\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Loan\",\"col\":3}},{\"id\":\"xbbo0FYbq0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"7ZL4Bro-Vi\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yhyioTViZ3\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"oYFn4b1kSw\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan\",\"col\":4}},{\"id\":\"vZepJF5tl9\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Processes\",\"col\":4}},{\"id\":\"k-393Mjhqe\",\"type\":\"card\",\"data\":{\"card_name\":\"Disbursement and Repayment\",\"col\":4}},{\"id\":\"6crJ0DBiBJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Loan Security\",\"col\":4}},{\"id\":\"Um5YwxVLRJ\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}}]",
"creation": "2020-03-12 16:35:55.299820",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
@@ -279,17 +280,18 @@
"type": "Link"
}
],
"modified": "2023-01-31 19:47:13.114415",
"modified": "2023-05-24 14:47:24.109945",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loans",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 16.0,
"sequence_id": 15.0,
"shortcuts": [
{
"color": "Green",

View File

@@ -88,12 +88,14 @@ class BOMUpdateLog(Document):
boms=boms,
timeout=40000,
now=frappe.flags.in_test,
enqueue_after_commit=True,
)
else:
frappe.enqueue(
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
update_doc=self,
now=frappe.flags.in_test,
enqueue_after_commit=True,
)

View File

@@ -83,7 +83,7 @@ frappe.ui.form.on('Job Card', {
// and if stock mvt for WIP is required
if (frm.doc.work_order) {
frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => {
if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0) {
if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0 || !frm.doc.items.length) {
frm.trigger("prepare_timer_buttons");
}
});
@@ -411,6 +411,16 @@ frappe.ui.form.on('Job Card', {
}
});
if (frm.doc.total_completed_qty && frm.doc.for_quantity > frm.doc.total_completed_qty) {
let flt_precision = precision('for_quantity', frm.doc);
let process_loss_qty = (
flt(frm.doc.for_quantity, flt_precision)
- flt(frm.doc.total_completed_qty, flt_precision)
);
frm.set_value('process_loss_qty', process_loss_qty);
}
refresh_field("total_completed_qty");
}
});

View File

@@ -38,6 +38,7 @@
"time_logs",
"section_break_13",
"total_completed_qty",
"process_loss_qty",
"column_break_15",
"total_time_in_mins",
"section_break_8",
@@ -435,11 +436,17 @@
"fieldname": "expected_end_date",
"fieldtype": "Datetime",
"label": "Expected End Date"
},
{
"fieldname": "process_loss_qty",
"fieldtype": "Float",
"label": "Process Loss Qty",
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2022-11-09 15:02:44.490731",
"modified": "2023-06-09 12:04:55.534264",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card",
@@ -497,4 +504,4 @@
"states": [],
"title_field": "operation",
"track_changes": 1
}
}

View File

@@ -451,6 +451,9 @@ class JobCard(Document):
},
)
def before_save(self):
self.set_process_loss()
def on_submit(self):
self.validate_transfer_qty()
self.validate_job_card()
@@ -487,19 +490,35 @@ class JobCard(Document):
)
)
if self.for_quantity and self.total_completed_qty != self.for_quantity:
precision = self.precision("total_completed_qty")
total_completed_qty = flt(
flt(self.total_completed_qty, precision) + flt(self.process_loss_qty, precision)
)
if self.for_quantity and flt(total_completed_qty, precision) != flt(
self.for_quantity, precision
):
total_completed_qty = bold(_("Total Completed Qty"))
qty_to_manufacture = bold(_("Qty to Manufacture"))
frappe.throw(
_("The {0} ({1}) must be equal to {2} ({3})").format(
total_completed_qty,
bold(self.total_completed_qty),
bold(flt(total_completed_qty, precision)),
qty_to_manufacture,
bold(self.for_quantity),
)
)
def set_process_loss(self):
precision = self.precision("total_completed_qty")
self.process_loss_qty = 0.0
if self.total_completed_qty and self.for_quantity > self.total_completed_qty:
self.process_loss_qty = flt(self.for_quantity, precision) - flt(
self.total_completed_qty, precision
)
def update_work_order(self):
if not self.work_order:
return
@@ -511,7 +530,7 @@ class JobCard(Document):
):
return
for_quantity, time_in_mins = 0, 0
for_quantity, time_in_mins, process_loss_qty = 0, 0, 0
from_time_list, to_time_list = [], []
field = "operation_id"
@@ -519,6 +538,7 @@ class JobCard(Document):
if data and len(data) > 0:
for_quantity = flt(data[0].completed_qty)
time_in_mins = flt(data[0].time_in_mins)
process_loss_qty = flt(data[0].process_loss_qty)
wo = frappe.get_doc("Work Order", self.work_order)
@@ -526,8 +546,8 @@ class JobCard(Document):
self.update_corrective_in_work_order(wo)
elif self.operation_id:
self.validate_produced_quantity(for_quantity, wo)
self.update_work_order_data(for_quantity, time_in_mins, wo)
self.validate_produced_quantity(for_quantity, process_loss_qty, wo)
self.update_work_order_data(for_quantity, process_loss_qty, time_in_mins, wo)
def update_corrective_in_work_order(self, wo):
wo.corrective_operation_cost = 0.0
@@ -542,11 +562,11 @@ class JobCard(Document):
wo.flags.ignore_validate_update_after_submit = True
wo.save()
def validate_produced_quantity(self, for_quantity, wo):
def validate_produced_quantity(self, for_quantity, process_loss_qty, wo):
if self.docstatus < 2:
return
if wo.produced_qty > for_quantity:
if wo.produced_qty > for_quantity + process_loss_qty:
first_part_msg = _(
"The {0} {1} is used to calculate the valuation cost for the finished good {2}."
).format(
@@ -561,7 +581,7 @@ class JobCard(Document):
_("{0} {1}").format(first_part_msg, second_part_msg), JobCardCancelError, title=_("Error")
)
def update_work_order_data(self, for_quantity, time_in_mins, wo):
def update_work_order_data(self, for_quantity, process_loss_qty, time_in_mins, wo):
workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate")
jc = frappe.qb.DocType("Job Card")
jctl = frappe.qb.DocType("Job Card Time Log")
@@ -582,6 +602,7 @@ class JobCard(Document):
for data in wo.operations:
if data.get("name") == self.operation_id:
data.completed_qty = for_quantity
data.process_loss_qty = process_loss_qty
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
@@ -599,7 +620,11 @@ class JobCard(Document):
def get_current_operation_data(self):
return frappe.get_all(
"Job Card",
fields=["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
fields=[
"sum(total_time_in_mins) as time_in_mins",
"sum(total_completed_qty) as completed_qty",
"sum(process_loss_qty) as process_loss_qty",
],
filters={
"docstatus": 1,
"work_order": self.work_order,
@@ -653,23 +678,19 @@ class JobCard(Document):
exc=JobCardOverTransferError,
)
job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc)
job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc) or {}
allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
if job_card_items_transferred_qty:
allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
for row in ste_doc.items:
if not row.job_card_item:
continue
for row in ste_doc.items:
if not row.job_card_item:
continue
transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item, 0.0))
transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item))
if not allow_excess:
_validate_over_transfer(row, transferred_qty)
if not allow_excess:
_validate_over_transfer(row, transferred_qty)
frappe.db.set_value(
"Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)
)
frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
def set_transferred_qty(self, update_status=False):
"Set total FG Qty in Job Card for which RM was transferred."
@@ -730,7 +751,7 @@ class JobCard(Document):
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
if self.docstatus < 2:
if self.for_quantity <= self.transferred_qty:
if flt(self.for_quantity) <= flt(self.transferred_qty):
self.status = "Material Transferred"
if self.time_logs:
@@ -781,7 +802,7 @@ class JobCard(Document):
data = frappe.get_all(
"Work Order Operation",
fields=["operation", "status", "completed_qty"],
fields=["operation", "status", "completed_qty", "sequence_id"],
filters={"docstatus": 1, "parent": self.work_order, "sequence_id": ("<", self.sequence_id)},
order_by="sequence_id, idx",
)
@@ -799,6 +820,16 @@ class JobCard(Document):
OperationSequenceError,
)
if row.completed_qty < current_operation_qty:
msg = f"""The completed quantity {bold(current_operation_qty)}
of an operation {bold(self.operation)} cannot be greater
than the completed quantity {bold(row.completed_qty)}
of a previous operation
{bold(row.operation)}.
"""
frappe.throw(_(msg))
def validate_work_order(self):
if self.is_work_order_closed():
frappe.throw(_("You can't make any changes to Job Card since Work Order is closed."))

View File

@@ -5,15 +5,17 @@
from typing import Literal
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import random_string
from frappe.utils.data import add_to_date, now
from frappe.utils.data import add_to_date, now, today
from erpnext.manufacturing.doctype.job_card.job_card import (
JobCardOverTransferError,
OperationMismatchError,
OverlapError,
make_corrective_job_card,
make_material_request,
)
from erpnext.manufacturing.doctype.job_card.job_card import (
make_stock_entry as make_stock_entry_from_jc,
@@ -342,6 +344,12 @@ class TestJobCard(FrappeTestCase):
job_card.reload()
self.assertEqual(job_card.transferred_qty, 2)
transfer_entry_2.cancel()
transfer_entry.cancel()
job_card.reload()
self.assertEqual(job_card.transferred_qty, 0.0)
def test_job_card_material_transfer_correctness(self):
"""
1. Test if only current Job Card Items are pulled in a Stock Entry against a Job Card
@@ -443,6 +451,138 @@ class TestJobCard(FrappeTestCase):
jc.docstatus = 2
assertStatus("Cancelled")
def test_job_card_material_request_and_bom_details(self):
from erpnext.stock.doctype.material_request.material_request import make_stock_entry
create_bom_with_multiple_operations()
work_order = make_wo_with_transfer_against_jc()
job_card_name = frappe.db.get_value("Job Card", {"work_order": work_order.name}, "name")
mr = make_material_request(job_card_name)
mr.schedule_date = today()
mr.submit()
ste = make_stock_entry(mr.name)
self.assertEqual(ste.purpose, "Material Transfer for Manufacture")
self.assertEqual(ste.work_order, work_order.name)
self.assertEqual(ste.job_card, job_card_name)
self.assertEqual(ste.from_bom, 1.0)
self.assertEqual(ste.bom_no, work_order.bom_no)
def test_job_card_proccess_qty_and_completed_qty(self):
from erpnext.manufacturing.doctype.routing.test_routing import (
create_routing,
setup_bom,
setup_operations,
)
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as make_stock_entry_for_wo,
)
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
operations = [
{"operation": "Test Operation A1", "workstation": "Test Workstation A", "time_in_mins": 30},
{"operation": "Test Operation B1", "workstation": "Test Workstation A", "time_in_mins": 20},
]
make_test_records("UOM")
warehouse = create_warehouse("Test Warehouse 123 for Job Card")
setup_operations(operations)
item_code = "Test Job Card Process Qty Item"
for item in [item_code, item_code + "RM 1", item_code + "RM 2"]:
if not frappe.db.exists("Item", item):
make_item(
item,
{
"item_name": item,
"stock_uom": "Nos",
"is_stock_item": 1,
},
)
routing_doc = create_routing(routing_name="Testing Route", operations=operations)
bom_doc = setup_bom(
item_code=item_code,
routing=routing_doc.name,
raw_materials=[item_code + "RM 1", item_code + "RM 2"],
source_warehouse=warehouse,
)
for row in bom_doc.items:
make_stock_entry(
item_code=row.item_code,
target=row.source_warehouse,
qty=10,
basic_rate=100,
)
wo_doc = make_wo_order_test_record(
production_item=item_code,
bom_no=bom_doc.name,
skip_transfer=1,
wip_warehouse=warehouse,
source_warehouse=warehouse,
)
for row in routing_doc.operations:
self.assertEqual(row.sequence_id, row.idx)
first_job_card = frappe.get_all(
"Job Card",
filters={"work_order": wo_doc.name, "sequence_id": 1},
fields=["name"],
order_by="sequence_id",
limit=1,
)[0].name
jc = frappe.get_doc("Job Card", first_job_card)
jc.time_logs[0].completed_qty = 8
jc.save()
jc.submit()
self.assertEqual(jc.process_loss_qty, 2)
self.assertEqual(jc.for_quantity, 10)
second_job_card = frappe.get_all(
"Job Card",
filters={"work_order": wo_doc.name, "sequence_id": 2},
fields=["name"],
order_by="sequence_id",
limit=1,
)[0].name
jc2 = frappe.get_doc("Job Card", second_job_card)
jc2.time_logs[0].completed_qty = 10
self.assertRaises(frappe.ValidationError, jc2.save)
jc2.load_from_db()
jc2.time_logs[0].completed_qty = 8
jc2.save()
jc2.submit()
self.assertEqual(jc2.for_quantity, 10)
self.assertEqual(jc2.process_loss_qty, 2)
s = frappe.get_doc(make_stock_entry_for_wo(wo_doc.name, "Manufacture", 10))
s.submit()
self.assertEqual(s.process_loss_qty, 2)
wo_doc.reload()
for row in wo_doc.operations:
self.assertEqual(row.completed_qty, 8)
self.assertEqual(row.process_loss_qty, 2)
self.assertEqual(wo_doc.produced_qty, 8)
self.assertEqual(wo_doc.process_loss_qty, 2)
self.assertEqual(wo_doc.status, "Completed")
def create_bom_with_multiple_operations():
"Create a BOM with multiple operations and Material Transfer against Job Card"

View File

@@ -451,10 +451,14 @@ frappe.ui.form.on("Material Request Plan Item", {
for_warehouse: row.warehouse
},
callback: function(r) {
let {projected_qty, actual_qty} = r.message;
if (r.message) {
let {projected_qty, actual_qty} = r.message[0];
frappe.model.set_value(cdt, cdn, 'projected_qty', projected_qty);
frappe.model.set_value(cdt, cdn, 'actual_qty', actual_qty);
frappe.model.set_value(cdt, cdn, {
'projected_qty': projected_qty,
'actual_qty': actual_qty
});
}
}
})
}

View File

@@ -35,8 +35,12 @@
"section_break_25",
"prod_plan_references",
"section_break_24",
"get_sub_assembly_items",
"combine_sub_items",
"section_break_ucc4",
"skip_available_sub_assembly_item",
"column_break_igxl",
"get_sub_assembly_items",
"section_break_g4ip",
"sub_assembly_items",
"download_materials_request_plan_section_section",
"download_materials_required",
@@ -351,12 +355,12 @@
{
"fieldname": "section_break_24",
"fieldtype": "Section Break",
"hide_border": 1
"hide_border": 1,
"label": "Sub Assembly Items"
},
{
"fieldname": "sub_assembly_items",
"fieldtype": "Table",
"label": "Sub Assembly Items",
"no_copy": 1,
"options": "Production Plan Sub Assembly Item"
},
@@ -392,13 +396,33 @@
"fieldname": "download_materials_request_plan_section_section",
"fieldtype": "Section Break",
"label": "Download Materials Request Plan Section"
},
{
"default": "0",
"description": "System consider the projected quantity to check available or will be available sub-assembly items ",
"fieldname": "skip_available_sub_assembly_item",
"fieldtype": "Check",
"label": "Skip Available Sub Assembly Items"
},
{
"fieldname": "section_break_ucc4",
"fieldtype": "Column Break",
"hide_border": 1
},
{
"fieldname": "section_break_g4ip",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_igxl",
"fieldtype": "Column Break"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-03-31 10:30:48.118932",
"modified": "2023-05-22 23:36:31.770517",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",

View File

@@ -718,7 +718,9 @@ class ProductionPlan(Document):
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
warehouse = row.warehouse if self.skip_available_sub_assembly_item else None
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty, self.company, warehouse=warehouse)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
@@ -894,7 +896,9 @@ def download_raw_materials(doc, warehouses=None):
build_csv_response(item_list, doc.name)
def get_exploded_items(item_details, company, bom_no, include_non_stock_items, planned_qty=1):
def get_exploded_items(
item_details, company, bom_no, include_non_stock_items, planned_qty=1, doc=None
):
bei = frappe.qb.DocType("BOM Explosion Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
@@ -1271,6 +1275,12 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
include_safety_stock = doc.get("include_safety_stock")
so_item_details = frappe._dict()
sub_assembly_items = {}
if doc.get("skip_available_sub_assembly_item"):
for d in doc.get("sub_assembly_items"):
sub_assembly_items.setdefault((d.get("production_item"), d.get("bom_no")), d.get("qty"))
for data in po_items:
if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
data["include_exploded_items"] = 1
@@ -1296,10 +1306,24 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
frappe.throw(_("For row {0}: Enter Planned Qty").format(data.get("idx")))
if bom_no:
if data.get("include_exploded_items") and include_subcontracted_items:
if (
data.get("include_exploded_items")
and doc.get("sub_assembly_items")
and doc.get("skip_available_sub_assembly_item")
):
item_details = get_raw_materials_of_sub_assembly_items(
item_details,
company,
bom_no,
include_non_stock_items,
sub_assembly_items,
planned_qty=planned_qty,
)
elif data.get("include_exploded_items") and include_subcontracted_items:
# fetch exploded items from BOM
item_details = get_exploded_items(
item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty
item_details, company, bom_no, include_non_stock_items, planned_qty=planned_qty, doc=doc
)
else:
item_details = get_subitems(
@@ -1456,12 +1480,22 @@ def get_item_data(item_code):
}
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, company, warehouse=None, indent=0):
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
if warehouse:
bin_dict = get_bin_details(d, company, for_warehouse=warehouse)
if bin_dict and bin_dict[0].projected_qty > 0:
if bin_dict[0].projected_qty > stock_qty:
continue
else:
stock_qty = stock_qty - bin_dict[0].projected_qty
bom_data.append(
frappe._dict(
{
@@ -1481,7 +1515,7 @@ def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
)
if d.value:
get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent + 1)
get_sub_assembly_items(d.value, bom_data, stock_qty, company, warehouse, indent=indent + 1)
def set_default_warehouses(row, default_warehouses):
@@ -1500,7 +1534,7 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
frappe.qb.from_(table)
.inner_join(child)
.on(table.name == child.parent)
.select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
.select(Sum(child.required_bom_qty * IfNull(child.conversion_factor, 1.0)))
.where(
(table.docstatus == 1)
& (child.item_code == item_code)
@@ -1518,4 +1552,72 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True)
)
if reserved_qty_for_production > reserved_qty_for_production_plan:
return 0.0
return reserved_qty_for_production_plan - reserved_qty_for_production
def get_raw_materials_of_sub_assembly_items(
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
):
bei = frappe.qb.DocType("BOM Item")
bom = frappe.qb.DocType("BOM")
item = frappe.qb.DocType("Item")
item_default = frappe.qb.DocType("Item Default")
item_uom = frappe.qb.DocType("UOM Conversion Detail")
items = (
frappe.qb.from_(bei)
.join(bom)
.on(bom.name == bei.parent)
.join(item)
.on(item.name == bei.item_code)
.left_join(item_default)
.on((item_default.parent == item.name) & (item_default.company == company))
.left_join(item_uom)
.on((item.name == item_uom.parent) & (item_uom.uom == item.purchase_uom))
.select(
(IfNull(Sum(bei.stock_qty / IfNull(bom.quantity, 1)), 0) * planned_qty).as_("qty"),
item.item_name,
item.name.as_("item_code"),
bei.description,
bei.stock_uom,
bei.bom_no,
item.min_order_qty,
bei.source_warehouse,
item.default_material_request_type,
item.min_order_qty,
item_default.default_warehouse,
item.purchase_uom,
item_uom.conversion_factor,
item.safety_stock,
)
.where(
(bei.docstatus == 1)
& (bom.name == bom_no)
& (item.is_stock_item.isin([0, 1]) if include_non_stock_items else item.is_stock_item == 1)
)
.groupby(bei.item_code, bei.stock_uom)
).run(as_dict=True)
for item in items:
key = (item.item_code, item.bom_no)
if item.bom_no and key in sub_assembly_items:
planned_qty = flt(sub_assembly_items[key])
get_raw_materials_of_sub_assembly_items(
item_details,
company,
item.bom_no,
include_non_stock_items,
sub_assembly_items,
planned_qty=planned_qty,
)
else:
if not item.conversion_factor and item.purchase_uom:
item.conversion_factor = get_uom_conversion_factor(item.item_code, item.purchase_uom)
item_details.setdefault(item.get("item_code"), item)
return item_details

View File

@@ -926,6 +926,50 @@ class TestProductionPlan(FrappeTestCase):
self.assertEqual(after_qty, before_qty)
def test_skip_available_qty_for_sub_assembly_items(self):
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
bom_tree = {
"Fininshed Goods1 For SUB Test": {
"SubAssembly1 For SUB Test": {"ChildPart1 For SUB Test": {}},
"SubAssembly2 For SUB Test": {},
}
}
parent_bom = create_nested_bom(bom_tree, prefix="")
plan = create_production_plan(
item_code=parent_bom.item,
planned_qty=10,
ignore_existing_ordered_qty=1,
do_not_submit=1,
skip_available_sub_assembly_item=1,
warehouse="_Test Warehouse - _TC",
)
make_stock_entry(
item_code="SubAssembly1 For SUB Test",
qty=5,
rate=100,
target="_Test Warehouse - _TC",
)
self.assertTrue(plan.skip_available_sub_assembly_item)
plan.get_sub_assembly_items()
for row in plan.sub_assembly_items:
if row.production_item == "SubAssembly1 For SUB Test":
self.assertEqual(row.qty, 5)
mr_items = get_items_for_material_requests(plan.as_dict())
for row in mr_items:
row = frappe._dict(row)
if row.item_code == "ChildPart1 For SUB Test":
self.assertEqual(row.quantity, 5)
if row.item_code == "SubAssembly2 For SUB Test":
self.assertEqual(row.quantity, 10)
def create_production_plan(**args):
"""
@@ -945,6 +989,7 @@ def create_production_plan(**args):
"include_subcontracted_items": args.include_subcontracted_items or 0,
"ignore_existing_ordered_qty": args.ignore_existing_ordered_qty or 0,
"get_items_from": "Sales Order",
"skip_available_sub_assembly_item": args.skip_available_sub_assembly_item or 0,
}
)
@@ -958,6 +1003,7 @@ def create_production_plan(**args):
"planned_qty": args.planned_qty or 1,
"planned_start_date": args.planned_start_date or now_datetime(),
"stock_uom": args.stock_uom or "Nos",
"warehouse": args.warehouse,
},
)

View File

@@ -28,7 +28,11 @@
"uom",
"stock_uom",
"column_break_22",
"description"
"description",
"section_break_4rxf",
"actual_qty",
"column_break_xfhm",
"projected_qty"
],
"fields": [
{
@@ -183,12 +187,34 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Schedule Date"
},
{
"fieldname": "section_break_4rxf",
"fieldtype": "Section Break"
},
{
"fieldname": "actual_qty",
"fieldtype": "Float",
"label": "Actual Qty",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "column_break_xfhm",
"fieldtype": "Column Break"
},
{
"fieldname": "projected_qty",
"fieldtype": "Float",
"label": "Projected Qty",
"no_copy": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-11-28 13:50:15.116082",
"modified": "2023-05-22 17:52:34.708879",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Sub Assembly Item",

View File

@@ -141,6 +141,7 @@ def setup_bom(**args):
routing=args.routing,
with_operations=1,
currency=args.currency,
source_warehouse=args.source_warehouse,
)
else:
bom_doc = frappe.get_doc("BOM", name)

View File

@@ -891,7 +891,7 @@ class TestWorkOrder(FrappeTestCase):
self.assertEqual(se.process_loss_qty, 1)
wo.load_from_db()
self.assertEqual(wo.status, "In Process")
self.assertEqual(wo.status, "Completed")
@timeout(seconds=60)
def test_job_card_scrap_item(self):
@@ -1657,6 +1657,61 @@ class TestWorkOrder(FrappeTestCase):
job_card2.time_logs = []
job_card2.save()
def test_make_serial_no_batch_from_work_order_for_serial_no(self):
item_code = "Test Serial No Item For Work Order"
warehouse = "_Test Warehouse - _TC"
raw_materials = [
"Test RM Item 1 for Serial No Item In Work Order",
]
make_item(
item_code,
{
"has_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "TSNIFWO-.#####",
},
)
for rm_item in raw_materials:
make_item(
rm_item,
{
"has_stock_item": 1,
},
)
test_stock_entry.make_stock_entry(item_code=rm_item, target=warehouse, qty=10, basic_rate=100)
bom = make_bom(item=item_code, raw_materials=raw_materials)
frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 1)
wo_order = make_wo_order_test_record(
item=item_code,
bom_no=bom.name,
qty=5,
skip_transfer=1,
from_wip_warehouse=1,
)
serial_nos = frappe.get_all(
"Serial No",
filters={"item_code": item_code, "work_order": wo_order.name},
)
serial_nos = [d.name for d in serial_nos]
self.assertEqual(len(serial_nos), 5)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
stock_entry.submit()
for row in stock_entry.items:
if row.is_finished_item:
self.assertEqual(sorted(get_serial_nos(row.serial_no)), sorted(get_serial_nos(serial_nos)))
frappe.db.set_single_value("Manufacturing Settings", "make_serial_no_batch_from_work_order", 0)
def prepare_data_for_workstation_type_check():
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
@@ -1886,6 +1941,7 @@ def make_wo_order_test_record(**args):
wo_order.sales_order = args.sales_order or None
wo_order.planned_start_date = args.planned_start_date or now()
wo_order.transfer_material_against = args.transfer_material_against or "Work Order"
wo_order.from_wip_warehouse = args.from_wip_warehouse or None
if args.source_warehouse:
for item in wo_order.get("required_items"):

View File

@@ -139,7 +139,7 @@ frappe.ui.form.on("Work Order", {
}
if (frm.doc.status != "Closed") {
if (frm.doc.docstatus === 1
if (frm.doc.docstatus === 1 && frm.doc.status !== "Completed"
&& frm.doc.operations && frm.doc.operations.length) {
const not_completed = frm.doc.operations.filter(d => {
@@ -256,6 +256,12 @@ frappe.ui.form.on("Work Order", {
label: __('Batch Size'),
read_only: 1
},
{
fieldtype: 'Int',
fieldname: 'sequence_id',
label: __('Sequence Id'),
read_only: 1
},
],
data: operations_data,
in_place_edit: true,
@@ -280,8 +286,8 @@ frappe.ui.form.on("Work Order", {
var pending_qty = 0;
frm.doc.operations.forEach(data => {
if(data.completed_qty != frm.doc.qty) {
pending_qty = frm.doc.qty - flt(data.completed_qty);
if(data.completed_qty + data.process_loss_qty != frm.doc.qty) {
pending_qty = frm.doc.qty - flt(data.completed_qty) - flt(data.process_loss_qty);
if (pending_qty) {
dialog.fields_dict.operations.df.data.push({
@@ -290,7 +296,8 @@ frappe.ui.form.on("Work Order", {
'workstation': data.workstation,
'batch_size': data.batch_size,
'qty': pending_qty,
'pending_qty': pending_qty
'pending_qty': pending_qty,
'sequence_id': data.sequence_id
});
}
}

View File

@@ -47,8 +47,8 @@
"required_items_section",
"materials_and_operations_tab",
"operations_section",
"operations",
"transfer_material_against",
"operations",
"time",
"planned_start_date",
"planned_end_date",
@@ -331,7 +331,6 @@
"label": "Expected Delivery Date"
},
{
"collapsible": 1,
"fieldname": "operations_section",
"fieldtype": "Section Break",
"label": "Operations",
@@ -599,7 +598,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2023-04-06 12:35:12.149827",
"modified": "2023-06-09 13:20:09.154362",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",

View File

@@ -249,7 +249,9 @@ class WorkOrder(Document):
status = "Not Started"
if flt(self.material_transferred_for_manufacturing) > 0:
status = "In Process"
if flt(self.produced_qty) >= flt(self.qty):
total_qty = flt(self.produced_qty) + flt(self.process_loss_qty)
if flt(total_qty) >= flt(self.qty):
status = "Completed"
else:
status = "Cancelled"
@@ -736,13 +738,15 @@ class WorkOrder(Document):
max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage / 100 * flt(self.qty))
for d in self.get("operations"):
if not d.completed_qty:
precision = d.precision("completed_qty")
qty = flt(d.completed_qty, precision) + flt(d.process_loss_qty, precision)
if not qty:
d.status = "Pending"
elif flt(d.completed_qty) < flt(self.qty):
elif flt(qty) < flt(self.qty):
d.status = "Work in Progress"
elif flt(d.completed_qty) == flt(self.qty):
elif flt(qty) == flt(self.qty):
d.status = "Completed"
elif flt(d.completed_qty) <= max_allowed_qty_for_wo:
elif flt(qty) <= max_allowed_qty_for_wo:
d.status = "Completed"
else:
frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'"))

View File

@@ -2,12 +2,14 @@
"actions": [],
"creation": "2014-10-16 14:35:41.950175",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"details",
"operation",
"status",
"completed_qty",
"process_loss_qty",
"column_break_4",
"bom",
"workstation_type",
@@ -36,6 +38,7 @@
"fieldtype": "Section Break"
},
{
"columns": 2,
"fieldname": "operation",
"fieldtype": "Link",
"in_list_view": 1,
@@ -46,6 +49,7 @@
"reqd": 1
},
{
"columns": 2,
"fieldname": "bom",
"fieldtype": "Link",
"in_list_view": 1,
@@ -62,7 +66,7 @@
"oldfieldtype": "Text"
},
{
"columns": 1,
"columns": 2,
"description": "Operation completed for how many finished goods?",
"fieldname": "completed_qty",
"fieldtype": "Float",
@@ -80,6 +84,7 @@
"options": "Pending\nWork in Progress\nCompleted"
},
{
"columns": 1,
"fieldname": "workstation",
"fieldtype": "Link",
"in_list_view": 1,
@@ -115,7 +120,7 @@
"fieldname": "time_in_mins",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Operation Time",
"label": "Time",
"oldfieldname": "time_in_mins",
"oldfieldtype": "Currency",
"reqd": 1
@@ -203,12 +208,21 @@
"fieldtype": "Link",
"label": "Workstation Type",
"options": "Workstation Type"
},
{
"columns": 2,
"fieldname": "process_loss_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Process Loss Qty",
"no_copy": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-11-09 01:37:56.563068",
"modified": "2023-06-09 14:03:01.612909",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Operation",

View File

@@ -33,10 +33,9 @@ def get_data(filters: Filters) -> Data:
wo.name,
wo.status,
wo.production_item,
wo.qty,
wo.produced_qty,
wo.process_loss_qty,
(wo.produced_qty - wo.process_loss_qty).as_("actual_produced_qty"),
wo.qty.as_("qty_to_manufacture"),
Sum(se.total_incoming_value).as_("total_fg_value"),
Sum(se.total_outgoing_value).as_("total_rm_value"),
)
@@ -44,6 +43,7 @@ def get_data(filters: Filters) -> Data:
(wo.process_loss_qty > 0)
& (wo.company == filters.company)
& (se.docstatus == 1)
& (se.purpose == "Manufacture")
& (se.posting_date.between(filters.from_date, filters.to_date))
)
.groupby(se.work_order)
@@ -79,20 +79,30 @@ def get_columns() -> Columns:
"width": "100",
},
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": "100"},
{
"label": _("Qty To Manufacture"),
"fieldname": "qty_to_manufacture",
"fieldtype": "Float",
"width": "150",
},
{
"label": _("Manufactured Qty"),
"fieldname": "produced_qty",
"fieldtype": "Float",
"width": "150",
},
{"label": _("Loss Qty"), "fieldname": "process_loss_qty", "fieldtype": "Float", "width": "150"},
{
"label": _("Actual Manufactured Qty"),
"fieldname": "actual_produced_qty",
"label": _("Process Loss Qty"),
"fieldname": "process_loss_qty",
"fieldtype": "Float",
"width": "150",
},
{
"label": _("Process Loss Value"),
"fieldname": "total_pl_value",
"fieldtype": "Float",
"width": "150",
},
{"label": _("Loss Value"), "fieldname": "total_pl_value", "fieldtype": "Float", "width": "150"},
{"label": _("FG Value"), "fieldname": "total_fg_value", "fieldtype": "Float", "width": "150"},
{
"label": _("Raw Material Value"),
@@ -105,5 +115,5 @@ def get_columns() -> Columns:
def update_data_with_total_pl_value(data: Data) -> None:
for row in data:
value_per_unit_fg = row["total_fg_value"] / row["actual_produced_qty"]
value_per_unit_fg = row["total_fg_value"] / row["qty_to_manufacture"]
row["total_pl_value"] = row["process_loss_qty"] * value_per_unit_fg

View File

@@ -1,13 +1,15 @@
{
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 17:11:37.032604",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "organization",
"idx": 0,
"is_hidden": 0,
"label": "Manufacturing",
"links": [
{
@@ -243,7 +245,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Bill of Materials",
"link_count": 15,
"link_count": 6,
"onboard": 0,
"type": "Card Break"
},
@@ -312,117 +314,20 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Production Planning Report",
"link_count": 0,
"link_to": "Production Planning Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Quality Inspection",
"hidden": 0,
"is_query_report": 1,
"label": "Work Order Summary",
"link_count": 0,
"link_to": "Work Order Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Downtime Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Quality Inspection Summary",
"link_count": 0,
"link_to": "Quality Inspection Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Job Card",
"hidden": 0,
"is_query_report": 1,
"label": "Downtime Analysis",
"link_count": 0,
"link_to": "Downtime Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "Job Card Summary",
"link_count": 0,
"link_to": "Job Card Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Search",
"link_count": 0,
"link_to": "BOM Search",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Stock Report",
"link_count": 0,
"link_to": "BOM Stock Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "Production Analytics",
"link_count": 0,
"link_to": "Production Analytics",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "BOM Operations Time",
"link_count": 0,
"link_to": "BOM Operations Time",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2022-11-14 14:53:34.616862",
"modified": "2023-05-27 16:41:04.776115",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 17.0,
"sequence_id": 8.0,
"shortcuts": [
{
"color": "Grey",

View File

@@ -331,3 +331,6 @@ execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_h
# below migration patches should always run last
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.patches.v14_0.update_company_in_ldc
erpnext.patches.v14_0.set_packed_qty_in_draft_delivery_notes
erpnext.patches.v14_0.cleanup_workspaces
erpnext.patches.v14_0.enable_allow_existing_serial_no

View File

@@ -7,4 +7,6 @@ def execute():
frappe.reload_doc("manufacturing", "doctype", "work_order")
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
frappe.db.sql(
"""UPDATE `tabWork Order Item` SET amount = ifnull(rate, 0.0) * ifnull(required_qty, 0.0)"""
)

View File

@@ -0,0 +1,9 @@
import frappe
def execute():
for ws in ["Retail", "Utilities"]:
frappe.delete_doc_if_exists("Workspace", ws)
for ws in ["Integrations", "Settings"]:
frappe.db.set_value("Workspace", ws, "public", 0)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
def execute():
frappe.reload_doc("stock", "doctype", frappe.scrub("Stock Settings"))
frappe.db.set_single_value("Stock Settings", "allow_existing_serial_no", 1, update_modified=False)

View File

@@ -0,0 +1,60 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.query_builder.functions import Sum
def execute():
ps = frappe.qb.DocType("Packing Slip")
dn = frappe.qb.DocType("Delivery Note")
ps_item = frappe.qb.DocType("Packing Slip Item")
ps_details = (
frappe.qb.from_(ps)
.join(ps_item)
.on(ps.name == ps_item.parent)
.join(dn)
.on(ps.delivery_note == dn.name)
.select(
dn.name.as_("delivery_note"),
ps_item.item_code.as_("item_code"),
Sum(ps_item.qty).as_("packed_qty"),
)
.where((ps.docstatus == 1) & (dn.docstatus == 0))
.groupby(dn.name, ps_item.item_code)
).run(as_dict=True)
if ps_details:
dn_list = set()
item_code_list = set()
for ps_detail in ps_details:
dn_list.add(ps_detail.delivery_note)
item_code_list.add(ps_detail.item_code)
dn_item = frappe.qb.DocType("Delivery Note Item")
dn_item_query = (
frappe.qb.from_(dn_item)
.select(
dn.parent.as_("delivery_note"),
dn_item.name,
dn_item.item_code,
dn_item.qty,
)
.where((dn_item.parent.isin(dn_list)) & (dn_item.item_code.isin(item_code_list)))
)
dn_details = frappe._dict()
for r in dn_item_query.run(as_dict=True):
dn_details.setdefault((r.delivery_note, r.item_code), frappe._dict()).setdefault(r.name, r.qty)
for ps_detail in ps_details:
dn_items = dn_details.get((ps_detail.delivery_note, ps_detail.item_code))
if dn_items:
remaining_qty = ps_detail.packed_qty
for name, qty in dn_items.items():
if remaining_qty > 0:
row_packed_qty = min(qty, remaining_qty)
frappe.db.set_value("Delivery Note Item", name, "packed_qty", row_packed_qty)
remaining_qty -= row_packed_qty

View File

@@ -25,20 +25,38 @@ frappe.listview_settings['Task'] = {
}
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
},
gantt_custom_popup_html: function(ganttobj, task) {
var html = `<h5><a style="text-decoration:underline"\
href="/app/task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
gantt_custom_popup_html: function (ganttobj, task) {
let html = `
<a class="text-white mb-2 inline-block cursor-pointer"
href="/app/task/${ganttobj.id}"">
${ganttobj.name}
</a>
`;
if(task.project) html += `<p>Project: ${task.project}</p>`;
html += `<p>Progress: ${ganttobj.progress}</p>`;
if (task.project) {
html += `<p class="mb-1">${__("Project")}:
<a class="text-white inline-block"
href="/app/project/${task.project}"">
${task.project}
</a>
</p>`;
}
html += `<p class="mb-1">
${__("Progress")}:
<span class="text-white">${ganttobj.progress}%</span>
</p>`;
if(task._assign_list) {
html += task._assign_list.reduce(
(html, user) => html + frappe.avatar(user)
, '');
if (task._assign) {
const assign_list = JSON.parse(task._assign);
const assignment_wrapper = `
<span>Assigned to:</span>
<span class="text-white">
${assign_list.map((user) => frappe.user_info(user).fullname).join(", ")}
</span>
`;
html += assignment_wrapper;
}
return html;
}
return `<div class="p-3" style="min-width: 220px">${html}</div>`;
},
};

View File

@@ -7,12 +7,14 @@
],
"content": "[{\"type\":\"chart\",\"data\":{\"chart_name\":\"Open Projects\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Task\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Timesheet\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Project Billing Summary\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Projects\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Time Tracking\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:46:04.874669",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "project",
"idx": 0,
"is_hidden": 0,
"label": "Projects",
"links": [
{
@@ -190,17 +192,18 @@
"type": "Link"
}
],
"modified": "2022-10-11 22:39:10.436311",
"modified": "2023-05-24 14:47:23.179860",
"modified_by": "Administrator",
"module": "Projects",
"name": "Projects",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 20.0,
"sequence_id": 11.0,
"shortcuts": [
{
"color": "Blue",

View File

@@ -91,6 +91,12 @@ frappe.ui.form.on("Sales Invoice", {
});
frappe.ui.form.on('Purchase Invoice', {
setup: (frm) => {
frm.make_methods = {
'Landed Cost Voucher': function () { frm.trigger('create_landedcost_voucher') },
}
},
mode_of_payment: function(frm) {
get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){
frm.set_value('cash_bank_account', account);
@@ -99,6 +105,20 @@ frappe.ui.form.on('Purchase Invoice', {
payment_terms_template: function() {
cur_frm.trigger("disable_due_date");
},
create_landedcost_voucher: function (frm) {
let lcv = frappe.model.get_new_doc('Landed Cost Voucher');
lcv.company = frm.doc.company;
let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice');
lcv_receipt.receipt_document_type = 'Purchase Invoice';
lcv_receipt.receipt_document = frm.doc.name;
lcv_receipt.supplier = frm.doc.supplier;
lcv_receipt.grand_total = frm.doc.grand_total;
lcv.purchase_receipts = [lcv_receipt];
frappe.set_route("Form", lcv.doctype, lcv.name);
}
});

View File

@@ -805,11 +805,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
);
}
this.frm.doc.payments.find(pay => {
if (pay.default) {
pay.amount = total_amount_to_pay;
}
});
if(!this.frm.doc.is_return){
this.frm.doc.payments.find(payment => {
if (payment.default) {
payment.amount = total_amount_to_pay;
}
});
}
this.frm.refresh_fields();
}

View File

@@ -123,6 +123,15 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
return me.set_query_for_batch(doc, cdt, cdn);
});
let batch_field = this.frm.get_docfield('items', 'batch_no');
if (batch_field) {
batch_field.get_route_options_for_new_doc = (row) => {
return {
'item': row.doc.item_code
}
};
}
}
if(
@@ -494,7 +503,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
},
() => {
// for internal customer instead of pricing rule directly apply valuation rate on item
if (me.frm.doc.is_internal_customer || me.frm.doc.is_internal_supplier) {
if ((me.frm.doc.is_internal_customer || me.frm.doc.is_internal_supplier) && me.frm.doc.represents_company === me.frm.doc.company) {
me.get_incoming_rate(item, me.frm.posting_date, me.frm.posting_time,
me.frm.doc.doctype, me.frm.doc.company);
} else {

View File

@@ -16,8 +16,8 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
|| (frm.doc.party_name && in_list(['Quotation', 'Opportunity'], frm.doc.doctype))) {
let party_type = "Customer";
if (frm.doc.quotation_to && frm.doc.quotation_to === "Lead") {
party_type = "Lead";
if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
party_type = frm.doc.quotation_to;
}
args = {

View File

@@ -2,12 +2,14 @@
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Goal\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Procedure\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Inspection\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Review\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Quality Action\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Non Conformance\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Goal and Procedure\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Feedback\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Meeting\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Review and Action\",\"col\":4}}]",
"creation": "2020-03-02 15:49:28.632014",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "quality",
"idx": 0,
"is_hidden": 0,
"label": "Quality",
"links": [
{
@@ -142,16 +144,18 @@
"type": "Link"
}
],
"modified": "2022-01-13 17:42:20.105187",
"modified": "2023-05-24 14:47:22.597974",
"modified_by": "Administrator",
"module": "Quality Management",
"name": "Quality",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 21.0,
"sequence_id": 9.0,
"shortcuts": [
{
"color": "Grey",

View File

@@ -13,7 +13,7 @@ frappe.ui.form.on('Quotation', {
frm.set_query("quotation_to", function() {
return{
"filters": {
"name": ["in", ["Customer", "Lead"]],
"name": ["in", ["Customer", "Lead", "Prospect"]],
}
}
});
@@ -160,19 +160,16 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
}
set_dynamic_field_label(){
if (this.frm.doc.quotation_to == "Customer")
{
if (this.frm.doc.quotation_to == "Customer") {
this.frm.set_df_property("party_name", "label", "Customer");
this.frm.fields_dict.party_name.get_query = null;
}
if (this.frm.doc.quotation_to == "Lead")
{
} else if (this.frm.doc.quotation_to == "Lead") {
this.frm.set_df_property("party_name", "label", "Lead");
this.frm.fields_dict.party_name.get_query = function() {
return{ query: "erpnext.controllers.queries.lead_query" }
}
} else if (this.frm.doc.quotation_to == "Prospect") {
this.frm.set_df_property("party_name", "label", "Prospect");
}
}

View File

@@ -288,7 +288,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
)
# sales team
for d in customer.get("sales_team"):
for d in customer.get("sales_team") or []:
target.append(
"sales_team",
{
@@ -299,6 +299,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
)
target.flags.ignore_permissions = ignore_permissions
target.delivery_date = nowdate()
target.run_method("set_missing_values")
target.run_method("calculate_taxes_and_totals")
@@ -306,6 +307,7 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
balance_qty = obj.qty - ordered_items.get(obj.item_code, 0.0)
target.qty = balance_qty if balance_qty > 0 else 0
target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
target.delivery_date = nowdate()
if obj.against_blanket_order:
target.against_blanket_order = obj.against_blanket_order

View File

@@ -60,9 +60,9 @@ class TestQuotation(FrappeTestCase):
sales_order = make_sales_order(quotation.name)
sales_order.currency = "USD"
sales_order.conversion_rate = 20.0
sales_order.delivery_date = "2019-01-01"
sales_order.naming_series = "_T-Quotation-"
sales_order.transaction_date = nowdate()
sales_order.delivery_date = nowdate()
sales_order.insert()
self.assertEqual(sales_order.currency, "USD")
@@ -644,8 +644,6 @@ def make_quotation(**args):
},
)
qo.delivery_date = add_days(qo.transaction_date, 10)
if not args.do_not_save:
qo.insert()
if not args.do_not_submit:

View File

@@ -158,7 +158,8 @@ class SalesOrder(SellingController):
frappe.msgprint(
_("Expected Delivery Date should be after Sales Order Date"),
indicator="orange",
title=_("Warning"),
title=_("Invalid Delivery Date"),
raise_exception=True,
)
else:
frappe.throw(_("Please enter Delivery Date"))
@@ -217,6 +218,7 @@ class SalesOrder(SellingController):
frappe.throw(_("Quotation {0} is cancelled").format(quotation))
doc.set_status(update=True)
doc.update_opportunity("Converted" if flag == "submit" else "Quotation")
def validate_drop_ship(self):
for d in self.get("items"):
@@ -398,10 +400,17 @@ class SalesOrder(SellingController):
def update_picking_status(self):
total_picked_qty = 0.0
total_qty = 0.0
per_picked = 0.0
for so_item in self.items:
total_picked_qty += flt(so_item.picked_qty)
total_qty += flt(so_item.stock_qty)
per_picked = total_picked_qty / total_qty * 100
if cint(
frappe.get_cached_value("Item", so_item.item_code, "is_stock_item")
) or self.has_product_bundle(so_item.item_code):
total_picked_qty += flt(so_item.picked_qty)
total_qty += flt(so_item.stock_qty)
if total_picked_qty and total_qty:
per_picked = total_picked_qty / total_qty * 100
self.db_set("per_picked", flt(per_picked), update_modified=False)

View File

@@ -2,9 +2,11 @@
"creation": "2021-11-23 12:00:36.138824",
"docstatus": 0,
"doctype": "Form Tour",
"first_document": 0,
"idx": 0,
"include_name_field": 0,
"is_standard": 1,
"modified": "2021-11-23 12:02:48.010298",
"modified": "2023-05-23 12:51:48.684517",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
@@ -14,51 +16,43 @@
"steps": [
{
"description": "Select a customer or lead for whom this quotation is being prepared. Let's select a Customer.",
"field": "",
"fieldname": "quotation_to",
"fieldtype": "Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Quotation To",
"parent_field": "",
"position": "Right",
"title": "Quotation To"
},
{
"description": "Select a specific Customer to whom this quotation will be sent.",
"field": "",
"fieldname": "party_name",
"fieldtype": "Dynamic Link",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Party",
"parent_field": "",
"position": "Right",
"title": "Party"
},
{
"child_doctype": "Quotation Item",
"description": "Select an item for which you will be quoting a price.",
"field": "",
"fieldname": "items",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Items",
"parent_field": "",
"parent_fieldname": "items",
"position": "Bottom",
"title": "Items"
},
{
"description": "You can select pre-populated Sales Taxes and Charges from here.",
"field": "",
"fieldname": "taxes",
"fieldtype": "Table",
"has_next_condition": 0,
"is_table_field": 0,
"label": "Sales Taxes and Charges",
"parent_field": "",
"position": "Bottom",
"title": "Sales Taxes and Charges"
}

View File

@@ -49,7 +49,6 @@ def search_by_term(search_term, warehouse, price_list):
)
item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
item_stock_qty = item_stock_qty // item.get("conversion_factor")
item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
item.update({"actual_qty": item_stock_qty})
@@ -59,7 +58,7 @@ def search_by_term(search_term, warehouse, price_list):
"price_list": price_list,
"item_code": item_code,
},
fields=["uom", "stock_uom", "currency", "price_list_rate"],
fields=["uom", "currency", "price_list_rate"],
)
def __sort(p):

View File

@@ -1,123 +0,0 @@
{
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Point Of Sale\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings & Configurations\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loyalty Program\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Opening & Closing\",\"col\":4}}]",
"creation": "2020-03-02 17:18:32.505616",
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "retail",
"idx": 0,
"label": "Retail",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Settings & Configurations",
"link_count": 2,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Point-of-Sale Profile",
"link_count": 0,
"link_to": "POS Profile",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "POS Settings",
"link_count": 0,
"link_to": "POS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Loyalty Program",
"link_count": 2,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loyalty Program",
"link_count": 0,
"link_to": "Loyalty Program",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loyalty Point Entry",
"link_count": 0,
"link_to": "Loyalty Point Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Opening & Closing",
"link_count": 2,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "POS Opening Entry",
"link_count": 0,
"link_to": "POS Opening Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "POS Closing Entry",
"link_count": 0,
"link_to": "POS Closing Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2022-01-13 18:07:56.711095",
"modified_by": "Administrator",
"module": "Selling",
"name": "Retail",
"owner": "Administrator",
"parent_page": "",
"public": 1,
"restrict_to_domain": "Retail",
"roles": [],
"sequence_id": 22.0,
"shortcuts": [
{
"doc_view": "",
"label": "Point Of Sale",
"link_to": "point-of-sale",
"type": "Page"
}
],
"title": "Retail"
}

View File

@@ -5,14 +5,16 @@
"label": "Sales Order Trends"
}
],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Selling\",\"col\":12}},{\"type\":\"chart\",\"data\":{\"chart_name\":\"Sales Order Trends\",\"col\":12}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order Analysis\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Selling\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"content": "[{\"id\":\"ow595dYDrI\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Selling\",\"col\":12}},{\"id\":\"vBSf8Vi9U8\",\"type\":\"chart\",\"data\":{\"chart_name\":\"Sales Order Trends\",\"col\":12}},{\"id\":\"aW2i5R5GRP\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"1it3dCOnm6\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Quick Access</b></span>\",\"col\":12}},{\"id\":\"x7pLl-spS4\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"SSGrXWmY-H\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Order\",\"col\":3}},{\"id\":\"-5J_yLxDaS\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Analytics\",\"col\":3}},{\"id\":\"6YEYpnIBKV\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Point of Sale\",\"col\":3}},{\"id\":\"c_GjZuZ2oN\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":3}},{\"id\":\"oNjjNbnUHp\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"0BcePLg0g1\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"uze5dJ1ipL\",\"type\":\"card\",\"data\":{\"card_name\":\"Selling\",\"col\":4}},{\"id\":\"3j2fYwMAkq\",\"type\":\"card\",\"data\":{\"card_name\":\"Point of Sale\",\"col\":4}},{\"id\":\"xImm8NepFt\",\"type\":\"card\",\"data\":{\"card_name\":\"Items and Pricing\",\"col\":4}},{\"id\":\"6MjIe7KCQo\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}},{\"id\":\"lBu2EKgmJF\",\"type\":\"card\",\"data\":{\"card_name\":\"Key Reports\",\"col\":4}},{\"id\":\"1ARHrjg4kI\",\"type\":\"card\",\"data\":{\"card_name\":\"Other Reports\",\"col\":4}}]",
"creation": "2020-01-28 11:49:12.092882",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "sell",
"idx": 0,
"is_hidden": 0,
"label": "Selling",
"links": [
{
@@ -317,140 +319,68 @@
{
"hidden": 0,
"is_query_report": 0,
"label": "Other Reports",
"link_count": 12,
"label": "Point of Sale",
"link_count": 6,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Lead",
"hidden": 0,
"is_query_report": 1,
"label": "Lead Details",
"is_query_report": 0,
"label": "Point-of-Sale Profile",
"link_count": 0,
"link_to": "Lead Details",
"link_type": "Report",
"link_to": "POS Profile",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Address",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Addresses And Contacts",
"is_query_report": 0,
"label": "POS Settings",
"link_count": 0,
"link_to": "Address And Contacts",
"link_type": "Report",
"link_to": "POS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Item",
"hidden": 0,
"is_query_report": 1,
"label": "Available Stock for Packing Items",
"is_query_report": 0,
"label": "POS Opening Entry",
"link_count": 0,
"link_to": "Available Stock for Packing Items",
"link_type": "Report",
"link_to": "POS Opening Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Order",
"hidden": 0,
"is_query_report": 1,
"label": "Pending SO Items For Purchase Request",
"is_query_report": 0,
"label": "POS Closing Entry",
"link_count": 0,
"link_to": "Pending SO Items For Purchase Request",
"link_type": "Report",
"link_to": "POS Closing Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Delivery Note",
"hidden": 0,
"is_query_report": 1,
"label": "Delivery Note Trends",
"is_query_report": 0,
"label": "Loyalty Program",
"link_count": 0,
"link_to": "Delivery Note Trends",
"link_type": "Report",
"link_to": "Loyalty Program",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Invoice",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Invoice Trends",
"is_query_report": 0,
"label": "Loyalty Point Entry",
"link_count": 0,
"link_to": "Sales Invoice Trends",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Customer",
"hidden": 0,
"is_query_report": 1,
"label": "Customer Credit Balance",
"link_count": 0,
"link_to": "Customer Credit Balance",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Customer",
"hidden": 0,
"is_query_report": 1,
"label": "Customers Without Any Sales Transactions",
"link_count": 0,
"link_to": "Customers Without Any Sales Transactions",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Customer",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Partners Commission",
"link_count": 0,
"link_to": "Sales Partners Commission",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Order",
"hidden": 0,
"is_query_report": 1,
"label": "Territory Target Variance Based On Item Group",
"link_count": 0,
"link_to": "Territory Target Variance Based On Item Group",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Order",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Person Target Variance Based On Item Group",
"link_count": 0,
"link_to": "Sales Person Target Variance Based On Item Group",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Sales Order",
"hidden": 0,
"is_query_report": 1,
"label": "Sales Partner Target Variance Based On Item Group",
"link_count": 0,
"link_to": "Sales Partner Target Variance based on Item Group",
"link_type": "Report",
"link_to": "Loyalty Point Entry",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
@@ -458,7 +388,7 @@
"hidden": 0,
"is_query_report": 0,
"label": "Key Reports",
"link_count": 22,
"link_count": 9,
"onboard": 0,
"type": "Card Break"
},
@@ -562,15 +492,12 @@
"type": "Link"
},
{
"dependencies": "Lead",
"hidden": 0,
"is_query_report": 1,
"label": "Lead Details",
"link_count": 0,
"link_to": "Lead Details",
"link_type": "Report",
"is_query_report": 0,
"label": "Other Reports",
"link_count": 11,
"onboard": 0,
"type": "Link"
"type": "Card Break"
},
{
"dependencies": "Address",
@@ -692,29 +619,26 @@
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Payment Terms Status for Sales Order",
"link_count": 0,
"link_to": "Payment Terms Status for Sales Order",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-04-16 13:29:55.087240",
"modified": "2023-05-26 16:31:53.634851",
"modified_by": "Administrator",
"module": "Selling",
"name": "Selling",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 23.0,
"sequence_id": 6.0,
"shortcuts": [
{
"label": "Point of Sale",
"link_to": "point-of-sale",
"type": "Page"
},
{
"color": "Grey",
"format": "{} Available",
@@ -739,11 +663,6 @@
"stats_filter": "{ \"Status\": \"Open\" }",
"type": "Report"
},
{
"label": "Sales Order Analysis",
"link_to": "Sales Order Analysis",
"type": "Report"
},
{
"label": "Dashboard",
"link_to": "Selling",

View File

@@ -27,10 +27,12 @@ def after_install():
create_default_success_action()
create_default_energy_point_rules()
create_incoterms()
create_default_role_profiles()
add_company_to_session_defaults()
add_standard_navbar_items()
add_app_name()
setup_log_settings()
hide_workspaces()
frappe.db.commit()
@@ -205,3 +207,47 @@ def setup_log_settings():
log_settings.append("logs_to_clear", {"ref_doctype": "Repost Item Valuation", "days": 60})
log_settings.save(ignore_permissions=True)
def hide_workspaces():
for ws in ["Integration", "Settings"]:
frappe.db.set_value("Workspace", ws, "public", 0)
def create_default_role_profiles():
for role_profile_name, roles in DEFAULT_ROLE_PROFILES.items():
role_profile = frappe.new_doc("Role Profile")
role_profile.role_profile = role_profile_name
for role in roles:
role_profile.append("roles", {"role": role})
role_profile.insert(ignore_permissions=True)
DEFAULT_ROLE_PROFILES = {
"Inventory": [
"Stock User",
"Stock Manager",
"Item Manager",
],
"Manufacturing": [
"Stock User",
"Manufacturing User",
"Manufacturing Manager",
],
"Accounts": [
"Accounts User",
"Accounts Manager",
],
"Sales": [
"Sales User",
"Stock User",
"Sales Manager",
],
"Purchase": [
"Item Manager",
"Stock User",
"Purchase User",
"Purchase Manager",
],
}

View File

@@ -25,15 +25,12 @@
"documentation_url": "https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/company-setup",
"idx": 0,
"is_complete": 0,
"modified": "2023-05-16 13:13:24.043792",
"modified": "2023-05-23 13:20:19.703506",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
"owner": "Administrator",
"steps": [
{
"step": "Navigation Help"
},
{
"step": "Create an Item"
},
@@ -41,13 +38,10 @@
"step": "Create a Customer"
},
{
"step": "Create a Supplier"
},
{
"step": "Create a Quotation"
"step": "Create Your First Sales Invoice"
}
],
"subtitle": "Item, Customer, Supplier, Navigation Help and Quotation",
"subtitle": "Item, Customer, Supplier and Quotation",
"success_message": "You're ready to start your journey with ERPNext",
"title": "Let's begin your journey with ERPNext"
}

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