Compare commits

...

204 Commits

Author SHA1 Message Date
Frappe PR Bot
d9008779de chore(release): Bumped to Version 14.27.12
## [14.27.12](https://github.com/frappe/erpnext/compare/v14.27.11...v14.27.12) (2023-06-23)

### Bug Fixes

* show non-depreciable assets in fixed asset register ([#35858](https://github.com/frappe/erpnext/issues/35858)) ([2240aeb](2240aeb307))
2023-06-23 02:55:10 +00:00
Anand Baburajan
c66c4e3aab Merge pull request #35859 from frappe/mergify/bp/version-14/pr-35858
fix: show non-depreciable assets in fixed asset register (backport #35858)
2023-06-23 08:22:43 +05:30
Anand Baburajan
2240aeb307 fix: show non-depreciable assets in fixed asset register (#35858)
fix: show non-depr assets in fixed asset register
(cherry picked from commit 42d09448ee)
2023-06-23 02:52:01 +00:00
Anand Baburajan
8049582652 Merge pull request #35853 from frappe/mergify/bp/version-14/pr-35851
chore: asset scrap and restore fixes [v14] (backport #35851)
2023-06-22 22:46:05 +05:30
Anand Baburajan
1d5415f39d chore: asset scrap and restore fixes [v14] (#35851)
chore: better err msg on cancelling JE for asset scrap and allow restoring non-depr assets
(cherry picked from commit 69780da099)
2023-06-22 16:53:00 +00:00
ruthra kumar
23dacbe9b2 Merge pull request #35840 from frappe/mergify/bp/version-14/pr-35837
refactor: increase precision for current exc rate in Exchange Rate Revaluation (backport #35837)
2023-06-22 14:34:52 +05:30
ruthra kumar
f45e8b9c16 refactor: increase precision for current exc rate in ERR
(cherry picked from commit b4db25dd18)
2023-06-22 08:37:20 +00:00
Frappe PR Bot
71207a7dae chore(release): Bumped to Version 14.27.10
## [14.27.10](https://github.com/frappe/erpnext/compare/v14.27.9...v14.27.10) (2023-06-22)

### Bug Fixes

* incorrect cost center error in bank reconciliation ([4be554d](4be554d8b4))
* no permission for accounts settings on payment reconciliation ([3194e3a](3194e3a020))
2023-06-22 06:26:14 +00:00
ruthra kumar
71e14b3dbb Merge pull request #35835 from frappe/mergify/bp/version-14/pr-35825
fix: multiple fixes in reconciliation tools (backport #35825)
2023-06-22 11:54:51 +05:30
ruthra kumar
4be554d8b4 fix: incorrect cost center error in bank reconciliation
(cherry picked from commit 41b9e92868)
2023-06-22 05:58:00 +00:00
ruthra kumar
3194e3a020 fix: no permission for accounts settings on payment reconciliation
(cherry picked from commit ad758b8d85)
2023-06-22 05:58:00 +00:00
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
Anoop
5b4b71d941 Merge branch 'version-14-hotfix' into gross-and-net-profit-fix 2023-05-22 17:23:03 +05:30
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
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
145 changed files with 4555 additions and 2402 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.25.0"
__version__ = "14.27.12"
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

@@ -10,6 +10,7 @@ from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
@@ -140,6 +141,9 @@ def create_journal_entry_bts(
second_account
)
)
company = frappe.get_value("Account", company_account, "company")
accounts = []
# Multi Currency?
accounts.append(
@@ -149,6 +153,7 @@ def create_journal_entry_bts(
"debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type,
"party": party,
"cost_center": get_default_cost_center(company),
}
)
@@ -158,11 +163,10 @@ def create_journal_entry_bts(
"bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company),
}
)
company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
"voucher_type": entry_type,
"company": company,

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

@@ -73,6 +73,7 @@
"fieldname": "current_exchange_rate",
"fieldtype": "Float",
"label": "Current Exchange Rate",
"precision": "9",
"read_only": 1
},
{
@@ -92,6 +93,7 @@
"fieldtype": "Float",
"in_list_view": 1,
"label": "New Exchange Rate",
"precision": "9",
"reqd": 1
},
{
@@ -147,7 +149,7 @@
],
"istable": 1,
"links": [],
"modified": "2022-12-29 19:38:52.915295",
"modified": "2023-06-22 12:39:56.446722",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation Account",

View File

@@ -326,12 +326,10 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
for d in self.get("accounts"):
if (
d.reference_type == "Asset"
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and d.debit
@@ -358,6 +356,15 @@ class JournalEntry(AccountsController):
else:
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
asset.set_status()
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
journal_entry_for_scrap = frappe.db.get_value(
"Asset", d.reference_name, "journal_entry_for_scrap"
)
if journal_entry_for_scrap == self.name:
frappe.throw(
_("Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset.")
)
def unlink_inter_company_jv(self):
if (
@@ -940,6 +947,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({

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

@@ -85,25 +85,29 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
// check for any running reconciliation jobs
if (this.frm.doc.receivable_payable_account) {
frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments").then((enabled) => {
if(enabled) {
this.frm.call({
'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running",
"args": {
for_filter: {
company: this.frm.doc.company,
party_type: this.frm.doc.party_type,
party: this.frm.doc.party,
receivable_payable_account: this.frm.doc.receivable_payable_account
this.frm.call({
doc: this.frm.doc,
method: 'is_auto_process_enabled',
callback: (r) => {
if (r.message) {
this.frm.call({
'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running",
"args": {
for_filter: {
company: this.frm.doc.company,
party_type: this.frm.doc.party_type,
party: this.frm.doc.party,
receivable_payable_account: this.frm.doc.receivable_payable_account
}
}
}
}).then(r => {
if (r.message) {
let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true);
let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]);
this.frm.dashboard.add_comment(msg, "yellow");
}
});
}).then(r => {
if (r.message) {
let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true);
let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]);
this.frm.dashboard.add_comment(msg, "yellow");
}
});
}
}
});
}

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]
@@ -236,6 +252,10 @@ class PaymentReconciliation(Document):
return difference_amount
@frappe.whitelist()
def is_auto_process_enabled(self):
return frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments")
@frappe.whitelist()
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)

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):

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):
@@ -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

@@ -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,10 +338,6 @@ 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_for_disposal(finance_book)
@@ -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

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
@@ -334,6 +343,9 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
def reverse_depreciation_entry_made_after_disposal(asset, date):
if not asset.calculate_depreciation:
return
row = -1
finance_book = asset.get("schedules")[0].get("finance_book")
for schedule in asset.get("schedules"):
@@ -474,18 +486,22 @@ def get_gl_entries_on_asset_disposal(
},
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": 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(
@@ -591,3 +607,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

@@ -766,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

@@ -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,31 @@ 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:
if (
assets_linked_to_fb
and asset.calculate_depreciation
and asset.asset_id not in assets_linked_to_fb
):
continue
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
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 +136,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 +150,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 +203,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

@@ -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": "2023-05-23 09:56:43.826602",
"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."
@@ -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

@@ -1534,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)
@@ -1552,6 +1552,9 @@ 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

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -1,31 +1,467 @@
{
"charts": [],
"content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Projects Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"HR Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Support Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Shopping Cart Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Portal Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Domain Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Products Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Naming Series\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Manufacturing Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Education Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Hotel Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"CRM Settings\",\"col\":3}}]",
"content": "[{\"id\":\"NO5yYHJopc\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t</b></span>\",\"col\":12}},{\"id\":\"CDxIM-WuZ9\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"System Settings\",\"col\":3}},{\"id\":\"-Uh7DKJNJX\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Accounts Settings\",\"col\":3}},{\"id\":\"K9ST9xcDXh\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Stock Settings\",\"col\":3}},{\"id\":\"27IdVHVQMb\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Selling Settings\",\"col\":3}},{\"id\":\"Rwp5zff88b\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Buying Settings\",\"col\":3}},{\"id\":\"hkfnQ2sevf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Global Defaults\",\"col\":3}},{\"id\":\"jjxI_PDawD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Print Settings\",\"col\":3}},{\"id\":\"R3CoYYFXye\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yynbm1J_VO\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Settings</b></span>\",\"col\":12}},{\"id\":\"KDCv2MvSg3\",\"type\":\"card\",\"data\":{\"card_name\":\"Module Settings\",\"col\":4}},{\"id\":\"Q0_bqT7cxQ\",\"type\":\"card\",\"data\":{\"card_name\":\"Email / Notifications\",\"col\":4}},{\"id\":\"UnqK5haBnh\",\"type\":\"card\",\"data\":{\"card_name\":\"Website\",\"col\":4}},{\"id\":\"kp7u1H5hCd\",\"type\":\"card\",\"data\":{\"card_name\":\"Core\",\"col\":4}},{\"id\":\"Ufc3jycgy9\",\"type\":\"card\",\"data\":{\"card_name\":\"Printing\",\"col\":4}},{\"id\":\"89bSNzv3Yh\",\"type\":\"card\",\"data\":{\"card_name\":\"Workflow\",\"col\":4}}]",
"creation": "2022-01-27 13:14:47.349433",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "setting",
"idx": 0,
"is_hidden": 0,
"label": "ERPNext Settings",
"links": [],
"modified": "2022-06-27 16:53:07.056620",
"links": [
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Import Data",
"link_count": 0,
"link_to": "Data Import",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Export Data",
"link_count": 0,
"link_to": "Data Export",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bulk Update",
"link_count": 0,
"link_to": "Bulk Update",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Download Backups",
"link_count": 0,
"link_to": "backups",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Deleted Documents",
"link_count": 0,
"link_to": "Deleted Document",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email / Notifications",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Account",
"link_count": 0,
"link_to": "Email Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Domain",
"link_count": 0,
"link_to": "Email Domain",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Notification",
"link_count": 0,
"link_to": "Notification",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Template",
"link_count": 0,
"link_to": "Email Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Auto Email Report",
"link_count": 0,
"link_to": "Auto Email Report",
"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": "Notification Settings",
"link_count": 0,
"link_to": "Notification Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Website",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Settings",
"link_count": 0,
"link_to": "Website Settings",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Theme",
"link_count": 0,
"link_to": "Website Theme",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Script",
"link_count": 0,
"link_to": "Website Script",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "About Us Settings",
"link_count": 0,
"link_to": "About Us Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contact Us Settings",
"link_count": 0,
"link_to": "Contact Us Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Printing",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Format Builder",
"link_count": 0,
"link_to": "print-format-builder",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Settings",
"link_count": 0,
"link_to": "Print Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Format",
"link_count": 0,
"link_to": "Print Format",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Style",
"link_count": 0,
"link_to": "Print Style",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"link_count": 0,
"link_to": "Workflow",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow State",
"link_count": 0,
"link_to": "Workflow State",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow Action",
"link_count": 0,
"link_to": "Workflow Action",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Core",
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "System Settings",
"link_count": 0,
"link_to": "System Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Domain Settings",
"link_count": 0,
"link_to": "Domain Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Global Defaults",
"link_count": 0,
"link_to": "Global Defaults",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Settings",
"link_count": 8,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Accounts Settings",
"link_count": 0,
"link_to": "Accounts Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Stock Settings",
"link_count": 0,
"link_to": "Stock Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Selling Settings",
"link_count": 0,
"link_to": "Selling Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Buying Settings",
"link_count": 0,
"link_to": "Buying Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Manufacturing Settings",
"link_count": 0,
"link_to": "Manufacturing Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "CRM Settings",
"link_count": 0,
"link_to": "CRM Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Projects Settings",
"link_count": 0,
"link_to": "Projects Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Support Settings",
"link_count": 0,
"link_to": "Support Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-05-24 14:47:25.356531",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 12.0,
"sequence_id": 19.0,
"shortcuts": [
{
"icon": "project",
"label": "Projects Settings",
"link_to": "Projects Settings",
"color": "Grey",
"doc_view": "List",
"label": "Print Settings",
"link_to": "Print Settings",
"type": "DocType"
},
{
"color": "Grey",
"doc_view": "List",
"label": "System Settings",
"link_to": "System Settings",
"type": "DocType"
},
{
@@ -34,6 +470,13 @@
"link_to": "Accounts Settings",
"type": "DocType"
},
{
"color": "Grey",
"doc_view": "List",
"label": "Global Defaults",
"link_to": "Global Defaults",
"type": "DocType"
},
{
"icon": "stock",
"label": "Stock Settings",
@@ -51,44 +494,6 @@
"label": "Buying Settings",
"link_to": "Buying Settings",
"type": "DocType"
},
{
"icon": "support",
"label": "Support Settings",
"link_to": "Support Settings",
"type": "DocType"
},
{
"icon": "retail",
"label": "E Commerce Settings",
"link_to": "E Commerce Settings",
"type": "DocType"
},
{
"icon": "website",
"label": "Portal Settings",
"link_to": "Portal Settings",
"type": "DocType"
},
{
"icon": "organization",
"label": "Manufacturing Settings",
"link_to": "Manufacturing Settings",
"restrict_to_domain": "Manufacturing",
"type": "DocType"
},
{
"icon": "setting",
"label": "Domain Settings",
"link_to": "Domain Settings",
"type": "DocType"
},
{
"doc_view": "",
"icon": "crm",
"label": "CRM Settings",
"link_to": "CRM Settings",
"type": "DocType"
}
],
"title": "ERPNext Settings"

View File

@@ -1,13 +1,15 @@
{
"charts": [],
"content": "[{\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"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\":\"Customer\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"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\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Human Resources\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
"content": "[{\"id\":\"aCk49ShVRs\",\"type\":\"onboarding\",\"data\":{\"onboarding_name\":\"Home\",\"col\":12}},{\"id\":\"kb3XPLg8lb\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"nWd2KJPW8l\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":3}},{\"id\":\"snrzfbFr5Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Customer\",\"col\":3}},{\"id\":\"SHJKakmLLf\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Supplier\",\"col\":3}},{\"id\":\"CPxEyhaf3G\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Sales Invoice\",\"col\":3}},{\"id\":\"WU4F-HUcIQ\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Leaderboard\",\"col\":3}},{\"id\":\"d_KVM1gsf9\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"JVu8-FJZCu\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"JiuSi0ubOg\",\"type\":\"card\",\"data\":{\"card_name\":\"Accounting\",\"col\":4}},{\"id\":\"ji2Jlm3Q8i\",\"type\":\"card\",\"data\":{\"card_name\":\"Stock\",\"col\":4}},{\"id\":\"N61oiXpuwK\",\"type\":\"card\",\"data\":{\"card_name\":\"CRM\",\"col\":4}},{\"id\":\"6J0CVl1mPo\",\"type\":\"card\",\"data\":{\"card_name\":\"Data Import and Settings\",\"col\":4}}]",
"creation": "2020-01-23 13:46:38.833076",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "getting-started",
"idx": 0,
"is_hidden": 0,
"label": "Home",
"links": [
{
@@ -230,10 +232,11 @@
"type": "Link"
}
],
"modified": "2022-06-27 16:54:35.462176",
"modified": "2023-05-24 14:47:18.765388",
"modified_by": "Administrator",
"module": "Setup",
"name": "Home",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on("Closing Stock Balance", {
refresh(frm) {
frm.trigger("generate_closing_balance");
frm.trigger("regenerate_closing_balance");
},
generate_closing_balance(frm) {
if (in_list(["Queued", "Failed"], frm.doc.status)) {
frm.add_custom_button(__("Generate Closing Stock Balance"), () => {
frm.call({
method: "enqueue_job",
doc: frm.doc,
freeze: true,
callback: () => {
frm.reload_doc();
}
})
})
}
},
regenerate_closing_balance(frm) {
if (frm.doc.status == "Completed") {
frm.add_custom_button(__("Regenerate Closing Stock Balance"), () => {
frm.call({
method: "regenerate_closing_balance",
doc: frm.doc,
freeze: true,
callback: () => {
frm.reload_doc();
}
})
})
}
}
});

View File

@@ -0,0 +1,148 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2023-05-17 09:58:42.086911",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"company",
"status",
"column_break_p0s0",
"from_date",
"to_date",
"filters_section",
"item_code",
"item_group",
"include_uom",
"column_break_rm5w",
"warehouse",
"warehouse_type",
"amended_from"
],
"fields": [
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "CBAL-.#####"
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"default": "Draft",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"in_preview": 1,
"label": "Status",
"options": "Draft\nQueued\nIn Progress\nCompleted\nFailed\nCanceled",
"read_only": 1
},
{
"fieldname": "column_break_p0s0",
"fieldtype": "Column Break"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"label": "To Date"
},
{
"collapsible": 1,
"fieldname": "filters_section",
"fieldtype": "Section Break",
"label": "Filters"
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"label": "Item Code",
"options": "Item"
},
{
"fieldname": "item_group",
"fieldtype": "Link",
"label": "Item Group",
"options": "Item Group"
},
{
"fieldname": "column_break_rm5w",
"fieldtype": "Column Break"
},
{
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
"options": "Warehouse"
},
{
"fieldname": "warehouse_type",
"fieldtype": "Link",
"label": "Warehouse Type",
"options": "Warehouse Type"
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Closing Stock Balance",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Closing Stock Balance",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "include_uom",
"fieldtype": "Link",
"label": "Include UOM",
"options": "UOM"
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-05-17 11:46:04.448220",
"modified_by": "Administrator",
"module": "Stock",
"name": "Closing Stock Balance",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -0,0 +1,133 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _
from frappe.core.doctype.prepared_report.prepared_report import create_json_gz_file
from frappe.desk.form.load import get_attachments
from frappe.model.document import Document
from frappe.utils import get_link_to_form, gzip_decompress, parse_json
from frappe.utils.background_jobs import enqueue
from erpnext.stock.report.stock_balance.stock_balance import execute
class ClosingStockBalance(Document):
def before_save(self):
self.set_status()
def set_status(self, save=False):
self.status = "Queued"
if self.docstatus == 2:
self.status = "Canceled"
if self.docstatus == 0:
self.status = "Draft"
if save:
self.db_set("status", self.status)
def validate(self):
self.validate_duplicate()
def validate_duplicate(self):
table = frappe.qb.DocType("Closing Stock Balance")
query = (
frappe.qb.from_(table)
.select(table.name)
.where(
(table.docstatus == 1)
& (table.company == self.company)
& (
(table.from_date.between(self.from_date, self.to_date))
| (table.to_date.between(self.from_date, self.to_date))
| (table.from_date >= self.from_date and table.to_date <= self.to_date)
)
)
)
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
if self.get(fieldname):
query = query.where(table[fieldname] == self.get(fieldname))
query = query.run(as_dict=True)
if query and query[0].name:
name = get_link_to_form("Closing Stock Balance", query[0].name)
msg = f"Closing Stock Balance {name} already exists for the selected date range"
frappe.throw(_(msg), title=_("Duplicate Closing Stock Balance"))
def on_submit(self):
self.set_status(save=True)
self.enqueue_job()
def on_cancel(self):
self.set_status(save=True)
self.clear_attachment()
@frappe.whitelist()
def enqueue_job(self):
self.db_set("status", "In Progress")
self.clear_attachment()
enqueue(prepare_closing_stock_balance, name=self.name, queue="long", timeout=1500)
@frappe.whitelist()
def regenerate_closing_balance(self):
self.enqueue_job()
def clear_attachment(self):
if attachments := get_attachments(self.doctype, self.name):
attachment = attachments[0]
frappe.delete_doc("File", attachment.name)
def create_closing_stock_balance_entries(self):
columns, data = execute(
filters=frappe._dict(
{
"company": self.company,
"from_date": self.from_date,
"to_date": self.to_date,
"warehouse": self.warehouse,
"item_code": self.item_code,
"item_group": self.item_group,
"warehouse_type": self.warehouse_type,
"include_uom": self.include_uom,
"ignore_closing_balance": 1,
"show_variant_attributes": 1,
"show_stock_ageing_data": 1,
}
)
)
create_json_gz_file({"columns": columns, "data": data}, self.doctype, self.name)
def get_prepared_data(self):
if attachments := get_attachments(self.doctype, self.name):
attachment = attachments[0]
attached_file = frappe.get_doc("File", attachment.name)
data = gzip_decompress(attached_file.get_content())
if data := json.loads(data.decode("utf-8")):
data = data
return parse_json(data)
return frappe._dict({})
def prepare_closing_stock_balance(name):
doc = frappe.get_doc("Closing Stock Balance", name)
doc.db_set("status", "In Progress")
try:
doc.create_closing_stock_balance_entries()
doc.db_set("status", "Completed")
except Exception as e:
doc.db_set("status", "Failed")
traceback = frappe.get_traceback()
frappe.log_error("Closing Stock Balance Failed", traceback, doc.doctype, doc.name)

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestClosingStockBalance(FrappeTestCase):
pass

View File

@@ -185,11 +185,14 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends erpn
}
if(doc.docstatus==0 && !doc.__islocal) {
this.frm.add_custom_button(__('Packing Slip'), function() {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip",
frm: me.frm
}) }, __('Create'));
if (doc.__onload && doc.__onload.has_unpacked_items) {
this.frm.add_custom_button(__('Packing Slip'), function() {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip",
frm: me.frm
}) }, __('Create')
);
}
}
if (!doc.__islocal && doc.docstatus==1) {

View File

@@ -1222,7 +1222,8 @@
"hidden": 1,
"label": "Pick List",
"options": "Pick List",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"default": "0",
@@ -1398,7 +1399,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2023-04-21 11:15:23.931084",
"modified": "2023-06-16 14:58:55.066602",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

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