Compare commits

...

310 Commits

Author SHA1 Message Date
Sagar Sharma
320f3c1ac0 Merge branch 'develop' into update-codeowners 2023-02-16 14:25:19 +05:30
HENRY Florian
4d79ffe3c6 fix: rename duplicate field name with same type into a DocType to avoid import Error (#34053)
* fix: Delivery Note field label set

* fix: Item field label de-duplicate name

* fix: Payment Entry field label de-duplicate name

* fix: Pruicing Rule field label de-duplicate name

* fix: Project field label de-duplicate name

* fix: Timesheet field label de-duplicate name

* Update erpnext/accounts/doctype/pricing_rule/pricing_rule.json

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>

* Update erpnext/stock/doctype/item/item.json

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>

---------

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2023-02-16 13:46:19 +05:30
Sagar Sharma
e8b8c51d82 chore: update CODEOWNERS 2023-02-16 12:55:37 +05:30
Tej Pochiraju
3f44ef8790 Allow bulk import of party specific item (#34083)
Co-authored-by: Tej Pochiraju <tej@iotready.co>
2023-02-16 10:11:25 +05:30
Sagar Sharma
c793fb0bb4 Merge pull request #34086 from s-aga-r/github-issue-33930
fix: create `Delivery Trip` from `Delivery Note` list
2023-02-15 22:40:10 +05:30
Sagar Sharma
c7a540b7e2 Merge branch 'develop' into github-issue-33930 2023-02-15 22:37:52 +05:30
s-aga-r
6c6195bae6 fix: create Delivery Trip from Delivery Note list 2023-02-15 22:37:26 +05:30
Sagar Sharma
0cd69c8700 Merge pull request #34080 from s-aga-r/batch-no-item
chore: copy `item_code` to `Batch` while creating new batch from SCR
2023-02-15 21:39:07 +05:30
s-aga-r
ffa9c6e4d9 chore: copy item_code to Batch while creating new batch from SCR 2023-02-15 20:16:42 +05:30
Anand Baburajan
d3ff289779 Merge pull request #34057 from AnandBaburajan/more_asset_bug_fixes
fix: manual depr entries in asset_depreciations_and_balances report and some misc bugs [develop]
2023-02-15 12:32:35 +05:30
Anand Baburajan
ef56060166 Merge branch 'develop' into more_asset_bug_fixes 2023-02-15 11:55:55 +05:30
rohitwaghchaure
2e43123048 Merge pull request #34065 from barredterra/print-rfq-letterhead
fix: change parameter name for letter head
2023-02-15 09:31:06 +05:30
rohitwaghchaure
9235918d65 Merge pull request #34061 from rohitwaghchaure/feat-allow-to-make-in-transit-entry
feat: allow to make in transit transfer entry from material request
2023-02-15 09:08:22 +05:30
barredterra
d5b7f2e49e test: download RFQ PDF 2023-02-14 19:53:39 +01:00
barredterra
8e40c04494 refactor: download RFQ PDF 2023-02-14 19:53:03 +01:00
barredterra
f7fd30fecf fix: change parameter name for letter head
To match changes from https://github.com/frappe/frappe/pull/19627
2023-02-14 19:34:36 +01:00
Deepesh Garg
e69ea17f59 Merge pull request #34059 from AnandBaburajan/anand_codeowners
chore: add anand to asset's codeowner
2023-02-14 19:38:46 +05:30
Rohit Waghchaure
5b6128848f feat: allow to make in transit transfer entry from material request 2023-02-14 19:03:07 +05:30
Anand Baburajan
662ebc5c1a Merge branch 'develop' into anand_codeowners 2023-02-14 18:35:05 +05:30
anandbaburajan
d003370f61 chore: add anand to asset's codeowner 2023-02-14 18:33:01 +05:30
Anand Baburajan
b0cfb0b9ef Merge branch 'develop' into more_asset_bug_fixes 2023-02-14 18:09:55 +05:30
anandbaburajan
a58f2e6c03 fix: asset_depreciation_and_balances report doesn't reflect manual depr entries 2023-02-14 17:59:16 +05:30
Sagar Sharma
75d9dbca43 Merge pull request #34018 from s-aga-r/fix-so-on-hold
fix: update `reserved_qty` when `Sales Order` marked as `Hold`
2023-02-14 16:51:56 +05:30
Sagar Sharma
447310702e Merge branch 'develop' into fix-so-on-hold 2023-02-14 16:01:43 +05:30
ruthra kumar
960085853a Merge pull request #34048 from ruthra-kumar/typeerror_on_sales_order_analysis
fix: typeerror on Sales Order analysis report
2023-02-14 15:27:36 +05:30
Sagar Sharma
5faa62c49a Merge branch 'develop' into fix-so-on-hold 2023-02-14 11:06:10 +05:30
rohitwaghchaure
3014bac293 Merge pull request #34046 from FHenry/dev_fix_BOM_import
fix: BOM import failed as use same label field for Raw Materials Item table and Scrap Item table
2023-02-14 10:29:26 +05:30
ruthra kumar
00a4191966 fix: typeerror on Sales Order analysis report 2023-02-14 09:56:35 +05:30
Raffael Meyer
770369e5c1 feat: translate fixtures during runtime, not installation (#33996)
feat: install untranslated fixtures from files

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-14 09:29:27 +05:30
Florian HENRY
86be259341 fix: BOM import failed as importer use same label field for Raw MaterialsItem table and Scrap Item table 2023-02-13 23:34:20 +01:00
Deepesh Garg
ce748cec3a fix: Amount validation in Payment Request against Purchase Order (#34042) 2023-02-13 20:56:24 +05:30
HENRY Florian
231fe4156f fix: should never get cutomer price on purchase document (#34002)
* fix: never get cutomer price on purchase document

chores: syntax

chore: typo in stock_entry get_uom_details (#33998)

fix: typo in stock_entry get_uom_details

chores: syntax

* feat: add test for get_item_detail price list oriented

* feat: add test for get_item_detail price price oriented

* feat: add test for get_item_detail price price oriented

* chore: clean test code
2023-02-13 20:56:05 +05:30
anandbaburajan
c533213225 fix: opening_accumulated_depreciation and precision in charts 2023-02-13 17:22:31 +05:30
Deepesh Garg
a67284e96d fix: Concurrency issues in Sales and Purchase returns (#34019) 2023-02-13 15:31:19 +05:30
ruthra kumar
4f2553e7b6 Merge pull request #34021 from ruthra-kumar/handle_rare_cases_of_null_in_outstanding_calculation
fix: rare instances of IntegrityError while cancelling journals against cr note
2023-02-13 14:14:27 +05:30
ruthra kumar
192a3395a5 refactor: filter only immediate upcoming payment term for each SO (#33923)
* fix: ignore closed or 'on hold' orders

* refactor: filter immediate upcoming term

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-13 12:36:40 +05:30
ruthra kumar
e5a2b15fba fix: unwanted difference amount calculation on cr note and invoice with same currency (#34020)
* fix: incorrect difference amount while reconiling cr/dr notes

* fix(test): catch incorrect difference amount calculation

Fixed issues where difference amount was calculated for Cr Notes and Invoices of
the same currency.

---------

Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-13 12:08:42 +05:30
Deepesh Garg
148703bfc2 feat: Setting to allow Sales Order creation against expired quotation (#33952)
* feat: Setting to allow Sales Order creation against expired quotation

* chore: linting issues
2023-02-13 10:27:55 +05:30
Deepesh Garg
ce3f9622cb Merge branch 'develop' into handle_rare_cases_of_null_in_outstanding_calculation 2023-02-13 09:42:41 +05:30
Deepesh Garg
48bb2c942b fix: Ignore Payment Ledger Entry on dunning cancel (#34025)
* fix: Ignore Payment Ledger Entry on dunning cancel

* chore: fix translation issue
2023-02-13 09:42:01 +05:30
Deepesh Garg
77f6789706 Revert "Update tr.csv" (#34026)
Revert "chore: Update Turkish translations (#33985)"

This reverts commit 6ea0a69d3a.
2023-02-13 08:27:00 +05:30
ruthra kumar
b9a7ff7c3d fix: IntegrityError while cancelling journals against cr note 2023-02-12 14:06:40 +05:30
Sagar Sharma
b0e8dfc1d0 Merge branch 'develop' into fix-so-on-hold 2023-02-12 13:37:21 +05:30
s-aga-r
d76759e066 fix: update reserved_qty when Sales Order marked as Hold 2023-02-12 13:36:26 +05:30
Richard Case
a0eb5e5535 fix: add payment hook to point of sale JS (#33988) 2023-02-11 20:40:15 +05:30
Raffael Meyer
e4953df4a3 fix: set per_billed based on hours when amounts are zero (#33984)
* fix: set per_billed based on hours when amounts are zero

* test: calculate_percentage_billed
2023-02-11 12:36:37 +05:30
Sabu Siyad
0df28c7174 fix(ecommerce): throw invalid doctype error in shop by category (#33901)
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-11 12:28:42 +05:30
Raffael Meyer
201573ab9a refactor: install fixtures (#33964)
* refactor: install fixtures

* style: disable semgrep for install_defaults signature
2023-02-11 12:15:42 +05:30
Deepesh Garg
0efdc6c13a fix: Ignore mandatory fields while creating tax templates for new companies (#34005) 2023-02-11 12:03:48 +05:30
Raffael Meyer
ab7293bcd3 fix: list view for Terms and Conditions (#33925)
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-02-11 11:09:26 +05:30
Mehmet Demirel
6ea0a69d3a chore: Update Turkish translations (#33985)
We are the ERPNext Turkey Team.
We request confirmation of the translation we sent.
2023-02-11 09:27:18 +05:30
Deepesh Garg
e478a5d0ce feat: Add filters in Loan Interest Report (#33907) 2023-02-11 09:17:24 +05:30
Deepesh Garg
47c91324b1 fix: Amount for debit and credit notes with 0 qty line items (#33902) 2023-02-11 09:04:00 +05:30
Akshay
185c543b73 chore: typo in stock_entry get_uom_details (#33998)
fix: typo in stock_entry get_uom_details
2023-02-10 20:42:35 +05:30
Anand Baburajan
9d8502f406 Merge pull request #33946 from AnandBaburajan/cancel_pi_cancelled_asset
fix: allow cancelling purchase invoice if linked asset is already cancelled
2023-02-05 16:06:47 +05:30
Anand Baburajan
4686217d3c Merge branch 'develop' into cancel_pi_cancelled_asset 2023-02-05 14:14:39 +05:30
Anand Baburajan
52435a3a7f Merge pull request #33945 from AnandBaburajan/manual_depr_entries_develop
fix: manual depr entry not updating asset value [develop]
2023-02-05 14:14:19 +05:30
Anand Baburajan
396f455258 Merge branch 'develop' into cancel_pi_cancelled_asset 2023-02-05 12:19:21 +05:30
Anand Baburajan
f8270f093d Merge branch 'develop' into manual_depr_entries_develop 2023-02-05 12:19:17 +05:30
Sagar Sharma
05f4b659a7 Merge pull request #33942 from s-aga-r/github-issue-33533
fix: stock entry from item dashboard (stock levels)
2023-02-05 09:46:27 +05:30
Sagar Sharma
2fedd595a9 Merge branch 'develop' into github-issue-33533 2023-02-05 09:36:17 +05:30
rohitwaghchaure
0352e1dace Merge pull request #33936 from rohitwaghchaure/fixed-negative-stock-error
fix: negative stock error
2023-02-04 23:34:56 +05:30
rohitwaghchaure
7d794bc70a Merge branch 'develop' into fixed-negative-stock-error 2023-02-04 22:35:58 +05:30
rohitwaghchaure
67b5471d09 Merge pull request #33941 from developsessions/fix_wrong_due_date_calculation
fix: default due_date was wrong calculated on template "_Test Payment Term Template 1" (last day of next month)
2023-02-04 22:34:47 +05:30
Anand Baburajan
82490d9f55 Merge branch 'develop' into cancel_pi_cancelled_asset 2023-02-04 18:37:34 +05:30
Anand Baburajan
07a0e2b7a8 Merge branch 'develop' into manual_depr_entries_develop 2023-02-04 18:37:34 +05:30
developsessions
c8cd351b39 style: apply results of lint run 2023-02-04 09:12:29 +01:00
Sagar Sharma
4a6a7593c3 Merge pull request #33940 from s-aga-r/github-issue-33917
chore: report `Warehouse wise Item Balance Age and Value`
2023-02-04 13:27:57 +05:30
anandbaburajan
3380dc5dea chore: use continue, not break 2023-02-04 11:20:26 +05:30
developsessions
9d0096ad9e fix: failed test, convert date time to string 2023-02-03 21:21:43 +01:00
Anand Baburajan
bb424965ee Merge branch 'develop' into cancel_pi_cancelled_asset 2023-02-03 23:00:40 +05:30
anandbaburajan
b961321de5 fix: allow PI cancel if linked asset is cancelled 2023-02-03 22:58:22 +05:30
Anand Baburajan
2d54bf7199 Merge branch 'develop' into manual_depr_entries_develop 2023-02-03 22:25:27 +05:30
anandbaburajan
e5da0d7a63 chore: refactor AssetDepreciationSchedule 2023-02-03 22:23:17 +05:30
anandbaburajan
8ea9e4576f fix: asset value for manual depr entries 2023-02-03 21:43:18 +05:30
developsessions
be1f941996 fix: Add missing 1 required positional argument: 'bill_date' 2023-02-03 14:50:44 +01:00
developsessions
c80aaad437 style: lint wrong from position 2023-02-03 13:55:36 +01:00
s-aga-r
dc0ddf8d7e fix: stock entry from item dashboard (stock levels) 2023-02-03 18:08:34 +05:30
developsessions
ce8a1086a7 fix: default due_date was wrong calculated on template "_Test Payment Term Template 1" (last day of next month) 2023-02-03 11:30:29 +01:00
s-aga-r
56356ffbb9 chore: add Item Name column in Warehouse wise Item Balance Age and Value report 2023-02-03 12:28:23 +05:30
s-aga-r
d7a665cb84 chore: column width in Warehouse wise Item Balance Age and Value report 2023-02-03 12:27:41 +05:30
Rohit Waghchaure
9ae7578b07 test: test case 2023-02-02 18:54:42 +05:30
Rohit Waghchaure
6d513e2519 fix: negative stock error 2023-02-02 18:40:15 +05:30
ruthra kumar
2afed7de03 Merge pull request #33777 from ruthra-kumar/performance_tuning_remarks_migration
fix(patch): reduce memory usage while migrating remarks
2023-02-02 16:47:15 +05:30
ruthra kumar
ae2de3cf65 Merge pull request #33776 from ruthra-kumar/performance_tuning_ple_migration
patch: reduce memory usage by paging through records
2023-02-02 16:46:51 +05:30
ruthra kumar
cb1d33880c Merge pull request #33502 from ruthra-kumar/toggle_account_balances_in_coa
feat: Toggle display of Account Balance in Chart of Accounts
2023-02-02 09:20:53 +05:30
Gokulnath
2884738864 feat: adding warehouse filter for gross profit report (#33397)
Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2023-02-01 18:44:15 +05:30
rohitwaghchaure
fbf2d7e9de Merge pull request #33918 from rohitwaghchaure/fixed-incorrect-bin-qty
fix: incorrect actual qty in Bin
2023-02-01 18:22:56 +05:30
Rohit Waghchaure
f8c852c54c fix: incorrect actual qty in Bin 2023-02-01 17:49:11 +05:30
Suraj Shetty
ffeda0cee9 Merge pull request #33916 from gavindsouza/permlevel-apis 2023-02-01 16:24:18 +05:30
Dany Robert
2cc7239dd5 fix: currency formatting in item-wise sales history (#33903)
* fix(item-sales-history): currency formatting

* chore: linting issues

* fix: convert raw sql to qb
2023-02-01 14:27:38 +05:30
Gavin D'souza
940ad6e3f2 fix: Fetch all fields via get_returned_qty_map_for_row 2023-02-01 13:53:37 +05:30
Gavin D'souza
3518d4be1d fix(update_billing_percentage): Remove permlevel checks on aggregated
value
2023-02-01 12:58:08 +05:30
Raffael Meyer
3c7b460fd8 fix: german chart of accounts "SKR03" (#33909)
* fix: german chart of accounts "SKR03"

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

* style: convert indentation to tabs

* fix: space before percentage sign

* feat: add some expense accounts

* refactor: replace unicode characters with utf-8

for better readability

* revert: add back groups for Bank and Cash accounts

Removed in 7d0d9c6900
2023-02-01 12:16:58 +05:30
Deepesh Garg
103a91ed40 Merge pull request #33808 from dj12djdjs/fix-lead-rename
fix: make title field readonly
2023-02-01 12:02:33 +05:30
ruthra kumar
9bb64107c5 perf: reduce memory usage while migrating remarks
Page through records using primary key
2023-02-01 10:44:44 +05:30
Sagar Sharma
4ac983cf09 Merge pull request #33910 from barredterra/named-place-rfq
feat: add incoterm named place to RFQ
2023-02-01 10:40:09 +05:30
barredterra
7156184933 feat: add incoterm named place to RFQ 2023-01-31 23:23:54 +01:00
rohitwaghchaure
c5834c1db4 Merge pull request #33896 from rohitwaghchaure/inventory-dimension-mandatory
feat: mandatory and mandatory depends on in inventory dimension
2023-01-31 15:18:14 +05:30
rohitwaghchaure
ddb4396117 Merge pull request #33715 from s-aga-r/fix-pick-list
fix: consider existing pick-list
2023-01-31 15:10:19 +05:30
Sagar Sharma
510b265830 Merge pull request #33869 from s-aga-r/fix-mr-rate-and-amount
fix: `amount` in `Material Request`
2023-01-31 14:44:11 +05:30
s-aga-r
6ffdeb1af8 fix: consider stock_qty if picked_qty is zero 2023-01-31 14:39:57 +05:30
Rohit Waghchaure
22d0e1373b test: added test case 2023-01-31 14:36:26 +05:30
Rohit Waghchaure
423f2b5627 feat: mandatory and mandatory depends on in inventory dimension 2023-01-31 14:19:14 +05:30
Sagar Sharma
6e015fd6be Merge branch 'develop' into fix-mr-rate-and-amount 2023-01-31 12:39:38 +05:30
Sagar Sharma
57c8871de0 Merge branch 'develop' into fix-pick-list 2023-01-31 12:38:58 +05:30
s-aga-r
0b76a26c8a refactor: test_consider_existing_pick_list() 2023-01-31 12:38:30 +05:30
s-aga-r
bb7fe795fe test: add test cases 2023-01-31 12:38:30 +05:30
s-aga-r
207eeefc85 fix(test): test_pick_list_for_items_with_multiple_UOM() 2023-01-31 12:38:30 +05:30
s-aga-r
5138ef0160 fix: pymysql.err.ProgrammingError 2023-01-31 12:38:30 +05:30
s-aga-r
7b3d496ce0 fix: get_picked_items_details 2023-01-31 12:38:30 +05:30
s-aga-r
be41052dc8 chore: add status field in Pick List 2023-01-31 12:38:30 +05:30
s-aga-r
b642718f08 fix: consider existing pick list 2023-01-31 12:38:27 +05:30
Deepesh Garg
b3dc22de29 fix: Fetch commission rate from sales partner (#33851) 2023-01-31 09:33:33 +05:30
Anand Baburajan
e007776ada Merge pull request #33888 from barredterra/get-asset-value
fix(Fixed Asset Register): error when selecting more than one fiscal …
2023-01-31 01:59:53 +05:30
barredterra
11d165c274 fix(Fixed Asset Register): error when selecting more than one fiscal year 2023-01-30 19:33:58 +01:00
Anand Baburajan
3e5f8323b0 Merge pull request #33883 from AnandBaburajan/fix_disposal_was_made_on_original_schedule_date_develop
fix: disposal_was_made_on_original_schedule_date [develop]
2023-01-30 22:02:34 +05:30
Anand Baburajan
5d8a2d3d3b Merge branch 'develop' into fix_disposal_was_made_on_original_schedule_date_develop 2023-01-30 21:36:51 +05:30
anandbaburajan
f487660a7c fix: disposal_was_made_on_original_schedule_date 2023-01-30 21:33:43 +05:30
Deepesh Garg
2bad86d8d8 ci: bump isort to 5.12.0 (#33875)
[skip ci]
2023-01-30 19:58:23 +05:30
ruthra kumar
5f38859a04 Merge pull request #33736 from rtdany10/gp-report-fix
fix(gp): fetch buying amount from dn related to so
2023-01-30 19:04:36 +05:30
Dany Robert
d69c839369 chore: linting issues 2023-01-30 10:45:08 +00:00
Dany Robert
1f6ab86a65 feat(gp): test for inv and dn related via so 2023-01-30 10:35:43 +00:00
s-aga-r
6b781d78e0 fix: amount in Material Request 2023-01-30 12:51:59 +05:30
Deepesh Garg
a34a1f8fd2 fix: Amount validation in Payment Request against Purchase Order (#33855)
fix: Amount validation in Payment Request againt Purchase Order
2023-01-29 21:34:28 +05:30
Deepesh Garg
faecf3ee40 fix: Lead to customer creation (#33859) 2023-01-29 21:25:49 +05:30
Deepesh Garg
6674edface chore: Resize numeric and date columns (#33858) 2023-01-29 21:25:26 +05:30
Deepesh Garg
428b099f63 fix: Ignore linked JE on JE cancellation (#33852) 2023-01-29 17:27:17 +05:30
Deepesh Garg
8abe0ce0ec fix: Currency symbol for tax withholding net total field (#33850)
* fix: Currency symbol for tax withholding net total field

* chore: Update display depends on property
2023-01-29 17:08:11 +05:30
ruthra kumar
fee0ca8cd9 perf: reduce memory usage by paging through records
While migrating GL entries to Payment Ledger, page through records using
primary key to reduce memory usage.
2023-01-29 15:27:01 +05:30
ruthra kumar
f270880735 fix: double salutation on quotation print (#33834)
'lead_name' always has salutation.
2023-01-28 18:17:25 +05:30
rohitwaghchaure
1b967f37af Merge pull request #33845 from rohitwaghchaure/fixed-item-not-fetching-issue
fix: item rate not fetching
2023-01-28 15:10:02 +05:30
Rohit Waghchaure
0d7f98b496 fix: item rate not fetching 2023-01-28 13:51:33 +05:30
Sabu Siyad
9e50aa4833 feat(pos): multiple item prices (#33005)
* fix(pos): multiple item prices

feat: show uom with product price
feat: multiple item (variant) depending on uom

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* feat(pos): consider uom for new item

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* feat(pos): uom based stock display

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* use `//` instead of `math.floor()`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* feat(pos): uom by barcode

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* fix: replace `is not` with `!=`

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* fix(pos): barcode_info `next()`: fallback

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* chore: format

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

* Update erpnext/selling/page/point_of_sale/point_of_sale.py

Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>

* chore: un-ignore unused local variable

Signed-off-by: Sabu Siyad <hello@ssiyad.com>

---------

Signed-off-by: Sabu Siyad <hello@ssiyad.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Deepesh Garg <deepeshgarg6@gmail.com>
2023-01-28 10:28:31 +05:30
Deepesh Garg
3598bcc9a8 chore: ERPNext setup wizard cleanup (#33675)
* chore: ERPNext setup wizard cleanup

* chore: Remove default website

* chore: Remove flaky tests

* chore: remove unwanted tests
2023-01-27 14:40:39 +05:30
ruthra kumar
9dd6b3c950 Merge pull request #33835 from ruthra-kumar/skip_validation_on_migration
fix(patch): validation error on cost center allocation migration
2023-01-27 13:40:57 +05:30
ruthra kumar
de10f2dc00 fix(patch): validation error on cost center allocation migration
If Distributed cost centers have GL postings on patch run date,
patch failes with valiation error.
2023-01-27 12:08:18 +05:30
Sagar Vora
7e592fdf91 Merge pull request #33831 from dj12djdjs/perf-update-items
perf: show update items dialog
2023-01-27 04:17:39 +00:00
Devin Slauenwhite
a835c1a418 perf: show update items dialog 2023-01-26 16:53:05 -05:00
ruthra kumar
b31b850db8 fix: disfuctional cost center filter on Journal Entries (#33815)
* fix: missing cost_center filter for journal entries

* test: cost center filter on invoices, journals and payments
2023-01-26 20:26:21 +05:30
Vishal Dhayagude
c5ca8d74c4 fix: GST Category validation broken for pos unregistered customer who dont have address. (#33800)
* fix: GST Category validation is given for pos customer
2023-01-26 15:45:51 +05:30
Anand Baburajan
e3d7426b47 Merge pull request #33823 from AnandBaburajan/fix_translations
chore: remove broken translation
2023-01-26 13:46:16 +05:30
anandbaburajan
21f425660d chore: remove broken translation 2023-01-26 13:15:01 +05:30
Ankush Menat
33b68552f1 chore: remove broken translation
[skip ci]
2023-01-25 17:35:23 +05:30
Daizy Modi
da323cbb40 fix: use correct filter name in item_query (#33814) 2023-01-25 17:32:21 +05:30
Sagar Vora
41222659a6 Merge pull request #33813 from frappe/revert-33810-fix-quality-inspection-item-query
Revert "fix: use correct filter name in reports"
2023-01-25 10:50:03 +00:00
Sagar Vora
1f4fa90072 Revert "fix: use correct filter name in reports" 2023-01-25 16:19:44 +05:30
Sagar Sharma
58dab9ca8b Merge pull request #33810 from resilient-tech/fix-quality-inspection-item-query
fix: use correct filter name in reports
2023-01-25 16:15:04 +05:30
Daizy Modi
def759d2e8 fix: use correct filter name in reports 2023-01-25 15:57:39 +05:30
Devin Slauenwhite
a504b6eac0 fix: make title field readonly 2023-01-24 18:21:08 -05:00
Sagar Sharma
a067a904a1 Merge pull request #33797 from s-aga-r/fix-sco-status
chore: add `Cancelled` status in SCO `get_indicator`
2023-01-24 17:33:57 +05:30
Sagar Sharma
302f5e27be Merge branch 'develop' into fix-sco-status 2023-01-24 17:33:29 +05:30
s-aga-r
840f9d8987 chore: add Cancelled status in SCO get_indicator 2023-01-24 17:30:37 +05:30
rohitwaghchaure
a5478c61af Merge pull request #33790 from rohitwaghchaure/fix-web-supplier-quotation-from-rfq
fix: web supplier quotation
2023-01-24 15:59:04 +05:30
Raffael Meyer
d155042edd ci: documentation helper (#33757)
refactor: documentation helper
2023-01-24 11:42:44 +05:30
Anand Baburajan
84383087a6 Merge pull request #33789 from AnandBaburajan/update_translations_for_naming_series_error
chore: update translations for naming series error
2023-01-23 18:48:10 +05:30
anandbaburajan
4e6066f929 chore: fix translations 2023-01-23 18:07:32 +05:30
Rohit Waghchaure
7800db7c0d fix: web supplier quotation 2023-01-23 14:21:10 +05:30
Anand Baburajan
7d5df407c4 Merge branch 'develop' into update_translations_for_naming_series_error 2023-01-23 14:19:39 +05:30
anandbaburajan
bbde1f611f chore: update translations for naming series error 2023-01-23 14:16:31 +05:30
s-aga-r
9ae3a54ce9 chore: add method get_picked_items_details() 2023-01-23 13:48:26 +05:30
rohitwaghchaure
b3a216c8f8 Merge pull request #33785 from rohitwaghchaure/get-items-from-transit-entry
feat: get items from Transit Stock Entry
2023-01-23 13:03:53 +05:30
Rohit Waghchaure
62141b0b63 feat: get items from Transit Stock Entry 2023-01-23 12:01:57 +05:30
Deepesh Garg
a3c89dbc10 fix: TDS deduction in payment entry (#33747)
* fix: TDS deduction in payment entry
2023-01-22 23:31:19 +05:30
Xeanth
ad437fd5ba fix: use stock qty to calculate POS reserved stock (#33735) 2023-01-22 18:06:01 +05:30
Sagar Sharma
7a8e89e2fe fix: incorrect rate and amount in MR Item (#33547)
* fix: incorrect `rate` and `amount` in MR Item
2023-01-22 18:04:13 +05:30
Sagar Sharma
72be489b1b Merge pull request #33778 from barredterra/fix-missing-const
fix: missing constant definition
2023-01-21 22:28:49 +05:30
barredterra
547d37b1db fix: missing constant definition 2023-01-21 17:29:43 +01:00
Deepesh Garg
2f03dcb5b6 fix: Better budget exceeding validation messages (#33713)
* fix: Better budget exceeding validation messages

* chore: remove unwanted changes
2023-01-21 19:33:14 +05:30
s-aga-r
57c3216683 refactor: rewrite get_available_item_locations_for_serial_and_batched_item query in QB 2023-01-21 14:05:51 +05:30
s-aga-r
5b76e8b193 refactor: rewrite get_available_item_locations_for_serialized_item query in QB 2023-01-21 12:45:39 +05:30
s-aga-r
58dd40a2d7 refactor: rewrite get_available_item_locations_for_other_item query in QB 2023-01-21 12:15:45 +05:30
rohitwaghchaure
f6f64083d2 Merge pull request #33760 from rohitwaghchaure/allow-to-change-bom-in-sco
fix: not able to change default BOM in the Subcontracting Order
2023-01-21 12:04:50 +05:30
s-aga-r
29bf787313 refactor: rewrite get_picked_items_qty query in QB 2023-01-21 11:28:23 +05:30
ruthra kumar
4cbd63edbf Merge pull request #33710 from openrefactory/patch-2
Removed an unnecessary check in code which always evaluates to true
2023-01-21 09:41:04 +05:30
OpenRefactory, Inc
49aed7ff69 fix: removed an unnecessary check which always evaluates to true 2023-01-21 06:15:51 +05:30
Kevin Shenk
e4bceaaf66 feat: Copy project_name, from_time, to_time from timesheet details to sales invoice (#33726)
feat: Copy project_name, from_time, to_time from timesheet details to sales invoice
2023-01-20 23:39:37 +05:30
rohitwaghchaure
b5d2eedfa6 Merge pull request #33759 from rohitwaghchaure/fix-incorrect-actual-qty-for-packed-item
fix: incorrect actual qty for the packed item
2023-01-20 23:38:55 +05:30
Rohit Waghchaure
11b2994fe8 fix: not able to change default BOM in the Subcontracting Order 2023-01-20 23:27:49 +05:30
Rohit Waghchaure
02566a02a8 fix: incorrect actual qty for the packed item 2023-01-20 22:38:56 +05:30
rohitwaghchaure
d462d6de06 Merge pull request #33595 from vishdha/fg_based_operating_cost
feat: Add operating cost based on bom quanity without creating job card
2023-01-20 22:36:42 +05:30
Dany Robert
1e607cd4c7 fix(pricing rule): free item duplication (#33746)
* fix(pricing rule): free item duplication

* chore: linting issues
2023-01-20 20:07:37 +05:30
Deepesh Garg
597ef0a498 fix: Short closed order, receipt and delivery note status on cancellation (#33743)
* fix: Short closed order, receipt and delivery note status on cancellation

* test: Cancelled status check
2023-01-20 19:44:06 +05:30
Raffael Meyer
327b6fdb32 fix: calculate correct amount for qty == 0 (#33739) 2023-01-20 19:35:27 +05:30
Sabu Siyad
a94aa7a79f fix(ecommerce): breadcrumb: fallback to /all-products (#33718)
* fix(ecommerce): breadcrumb: fallback to `/all-products`

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

* test(ecommerce): breadcrumb
2023-01-20 18:54:54 +05:30
Ritwik Puri
17045f88a1 fix: use hash based naming for tax withheld vouchers child table (#33643) 2023-01-20 18:53:39 +05:30
Dany Robert
ef90e24931 chore: linting issue 2023-01-19 10:25:05 +00:00
Dany Robert
e8e20da78e fix(gp): fetch buying amount from dn related to so 2023-01-19 09:39:43 +00:00
Sagar Sharma
cceb7922a5 Merge pull request #33619 from SvbZ3r0/item-attr-abbr-lowercase
fix: rewrite logic for duplicate check in Item Attribute
2023-01-19 13:27:43 +05:30
Sagar Sharma
333b2ae721 Merge branch 'develop' into item-attr-abbr-lowercase 2023-01-19 12:55:49 +05:30
rohitwaghchaure
ecfebfec57 Merge pull request #33723 from rohitwaghchaure/fixed-issue-of-item-variant
fix: don't add template item in sales/purchase transaction
2023-01-19 12:42:04 +05:30
Rohit Waghchaure
2c83fff1a1 fix: don't add template item in sales/purchase transaction 2023-01-18 23:29:06 +05:30
mergify[bot]
cc569c955f fix: the frappe throw message is corrected in the group task validation (#33698)
fix: the frappe throw message is corrected in the group task validation (#33698)
2023-01-18 21:47:22 +05:30
Sagar Sharma
60ec7b6cde Merge pull request #33679 from s-aga-r/refactor/qb/pick-list
refactor: rewrite `pick_list.py` queries in `QB`
2023-01-18 18:26:08 +05:30
Sagar Sharma
f89600aee8 Merge branch 'develop' into refactor/qb/pick-list 2023-01-18 17:52:23 +05:30
rohitwaghchaure
e8e21ea4b4 Merge pull request #33712 from rohitwaghchaure/fixed-stock_rbnb-error
fix: local variable 'stock_rbnb' referenced before assignment
2023-01-18 16:36:20 +05:30
Rohit Waghchaure
1de4742ffb fix: local variable 'stock_rbnb' referenced before assignment 2023-01-18 15:29:39 +05:30
Sagar Sharma
eee3bd2554 Merge branch 'develop' into refactor/qb/pick-list 2023-01-18 13:01:59 +05:30
Deepesh Garg
6b31c27ed6 fix: Missing constructor args in Bank Reco Tool (#33705) 2023-01-17 21:03:39 +05:30
Vishal Dhayagude
c4e1f81ccb Merge branch 'develop' into fg_based_operating_cost 2023-01-17 19:20:37 +05:30
Anand Baburajan
d7522d40c0 Merge pull request #33682 from AnandBaburajan/misc_asset_fixes
fix: handle asset depr entries posting failure, improve asset depr schedules cancel notes and fix asset repair link [develop]
2023-01-17 16:23:13 +05:30
Anand Baburajan
c6cfacc108 Merge branch 'develop' into misc_asset_fixes 2023-01-17 15:09:37 +05:30
Deepesh Garg
db9beb3cdd fix: Rate from LDC in TDS reports (#33699) 2023-01-17 14:53:24 +05:30
Vishal Dhayagude
f19212b5c8 Merge branch 'develop' into fg_based_operating_cost 2023-01-17 13:02:25 +05:30
Anand Baburajan
d97127bff2 Merge branch 'develop' into misc_asset_fixes 2023-01-17 12:57:22 +05:30
Sagar Sharma
eeed3356ac Merge branch 'develop' into refactor/qb/pick-list 2023-01-17 12:25:16 +05:30
rohitwaghchaure
e68c4e2aa8 Merge pull request #33695 from rohitwaghchaure/fixed-patch-item_reposting_for_incorrect_sl_and_gl
fix: patch item_reposting_for_incorrect_sl_and_gl
2023-01-17 12:18:34 +05:30
Rohit Waghchaure
dbde3a3421 fix: patch item_reposting_for_incorrect_sl_and_gl 2023-01-17 11:47:53 +05:30
Anand Baburajan
c4d3a8a600 Merge branch 'develop' into misc_asset_fixes 2023-01-17 11:21:37 +05:30
Ankush Menat
64f7f3a12a chore: ignore b028
Very high false positive count. 

[skip ci]
2023-01-17 10:56:55 +05:30
Sagar Sharma
1cadef97bf Merge branch 'develop' into refactor/qb/pick-list 2023-01-17 10:08:36 +05:30
Sagar Sharma
e4cf0dced8 Merge pull request #33690 from FHenry/dev_fix_bug_from_78b438f6
fix: Sales Order Connections Tabs do not show linked Material Request or "+" button  (intoduce by #33304)
2023-01-17 10:05:49 +05:30
Sagar Sharma
fb950417bf Merge branch 'develop' into dev_fix_bug_from_78b438f6 2023-01-17 10:04:36 +05:30
Deepesh Garg
0639d9e32a chore: Typo in payment reconciliation (#33686) 2023-01-17 08:06:30 +05:30
Florian HENRY
e19161a8ee fix: Sales ORder Connections on Material Request 2023-01-16 21:03:18 +01:00
anandbaburajan
86cf5c89ab chore: add depr_entry_posting_status in create_asset 2023-01-17 00:31:21 +05:30
anandbaburajan
cebb5a42f4 chore: add missing /n in options 2023-01-17 00:26:50 +05:30
Anand Baburajan
011a4c3faa Merge branch 'develop' into misc_asset_fixes 2023-01-17 00:00:09 +05:30
rohitwaghchaure
66ae807a26 Merge pull request #33684 from rohitwaghchaure/fixed-work-order-summary
feat: [minor] date type based on filter in Work Order Summary report
2023-01-16 23:57:43 +05:30
rohitwaghchaure
d6915df81d Merge pull request #33680 from rohitwaghchaure/refactor-picked-qty
refactor: picked qty in sales order item
2023-01-16 23:31:56 +05:30
Rohit Waghchaure
20c8873208 feat: provision to select date type based on filter 2023-01-16 23:29:13 +05:30
anandbaburajan
a144bb01fe chore: stying 2023-01-16 23:26:39 +05:30
Anand Baburajan
070bc8badd Merge branch 'develop' into misc_asset_fixes 2023-01-16 23:23:12 +05:30
anandbaburajan
cf7b43f7f0 fix: handle_post_depr_entries_fail, show error alert and send email 2023-01-16 23:10:02 +05:30
Rohit Waghchaure
1bcff80074 refactor: picked qty in sales order item 2023-01-16 22:50:16 +05:30
Devin Slauenwhite
179a31ed5e feat: disable currency exchange api. (#33593) 2023-01-16 21:00:10 +05:30
anandbaburajan
61cd0f98fd fix: asset repair link and improve notes 2023-01-16 18:35:40 +05:30
Vishal Dhayagude
352dcddd6d Merge branch 'develop' into fg_based_operating_cost 2023-01-16 17:02:31 +05:30
Sagar Sharma
1769b45d4b Merge branch 'develop' into refactor/qb/pick-list 2023-01-16 16:04:20 +05:30
s-aga-r
0ed6552655 refactor: rewrite pick_list.py queries in QB 2023-01-16 16:01:40 +05:30
Deepesh Garg
adaeba1554 fix(minor): Label updates in Statement of Accounts (#33639)
fix(minor): Label updates in Satement of Accounts
2023-01-16 14:03:54 +05:30
Deepesh Garg
312625fdc5 fix: Patch to update reference_due_date in Journal Entry (#33616) 2023-01-16 13:43:54 +05:30
ruthra kumar
9627b46ee7 Merge pull request #33661 from ruthra-kumar/performance_tuning_payment_reconciliation
perf: improve reconciliation performance for JE with 100s of accounts
2023-01-16 09:41:17 +05:30
ruthra kumar
56aa1866da Merge pull request #33663 from ruthra-kumar/attribute_error_on_repost_ple_tool
fix: attribute error while submitting Repost PLE
2023-01-16 09:36:22 +05:30
Raffael Meyer
dceef0397a fix: allow to create sales order from expired quotation (#33582) 2023-01-16 08:53:37 +05:30
ruthra kumar
be382054e5 revert: Reverting changes done on 33495 (#33662)
'ordered_qty' will not be fetched from `tabBin`
2023-01-16 08:50:39 +05:30
ruthra kumar
333907b7a5 Revert "fix: Updating SO throws ordered_qty not allowed to change after submission" (#33646) 2023-01-16 08:49:35 +05:30
Poruri Sai Rahul
8119442c94 FIX: Remove usage of "six.string_types" (#33603)
FIX: Remove usage of six.string_types

six is no longer a dependency
2023-01-16 08:47:28 +05:30
Sagar Sharma
9069643504 Merge pull request #33664 from s-aga-r/so-ref-in-pick-list
chore: `Sales Order` link in `Pick List`
2023-01-15 23:04:53 +05:30
s-aga-r
b3759890d7 chore: Sales Order link in Pick List 2023-01-15 21:09:16 +05:30
ruthra kumar
2c50f43cdd fix: attribute error while submitting Repost PLE 2023-01-15 20:51:54 +05:30
ruthra kumar
828eaf0930 fix: minor filter issue while reconciliation tool from bench console 2023-01-15 18:09:51 +05:30
ruthra kumar
11cf694d9a perf: improve reconciliation speed on JE's with 1000's of rows
1. No need to keep old PLE's on reconciliation.
2. Added Validation to catch debit-credit mismatch on JE's
3. Only update outstanding amount for newly reconciled invoices
2023-01-15 17:51:46 +05:30
Raffael Meyer
67cf7e1728 refactor: use DocStatus (#33594) 2023-01-15 17:34:16 +05:30
Deepesh Garg
906ad10d16 fix: Return against internal purchase invoice (#33635) 2023-01-15 17:32:57 +05:30
Sagar Sharma
bfbc9ea8f7 Merge branch 'develop' into item-attr-abbr-lowercase 2023-01-15 12:55:01 +05:30
Sagar Sharma
242af681e9 Merge pull request #33651 from s-aga-r/fix/scr-rm-cost
fix: zero rm-cost in SCR
2023-01-14 23:11:12 +05:30
s-aga-r
f70d757b82 fix: zero rm-cost in SCR 2023-01-14 22:24:18 +05:30
Vishal Dhayagude
736afdf85a Merge branch 'develop' into fg_based_operating_cost 2023-01-13 10:03:50 +05:30
Sagar Sharma
c2b3843d80 Merge branch 'develop' into item-attr-abbr-lowercase 2023-01-13 00:49:21 +05:30
Ritwik Puri
e22d56484d chore: reuse doc object in test_pick_list_grouping_before_print (#33636) 2023-01-13 00:47:26 +05:30
Deepesh Garg
75e52d7108 Merge pull request #33608 from AnandBaburajan/fixed_asset_report_asset_value
fix: asset value in fixed asset register
2023-01-12 22:59:19 +05:30
Vishal Dhayagude
d63258ee9b Merge branch 'develop' into fg_based_operating_cost 2023-01-12 21:50:09 +05:30
unknown
2ca4d3fb71 fix: linting 2023-01-12 20:53:12 +05:30
Anand Baburajan
29088e1777 Merge branch 'develop' into fixed_asset_report_asset_value 2023-01-12 20:30:39 +05:30
Deepesh Garg
afb33f2049 Merge pull request #33271 from sonali8848/filters-on-bank-reconciliation
feat: Date filters on bank reconciliation tool
2023-01-12 18:25:05 +05:30
Deepesh Garg
232726288a chore: fix fieldnames and order 2023-01-12 17:44:07 +05:30
Ritwik Puri
cfb0bb1eaa fix: only group similar items in print format if group_same_items is checked in pick list (#33627)
* fix: only group similar items if group same items is checked in pick list

* test: non grouping of locations if group_same_items is false

Co-authored-by: Sagar Sharma <sagarsharma.s312@gmail.com>
2023-01-12 17:25:50 +05:30
ruthra kumar
e0db2670f9 Merge pull request #33622 from ruthra-kumar/fix_change_after_submission_on_error
fix: Updating SO throws ordered_qty not allowed to change after submission
2023-01-12 16:59:08 +05:30
Sagar Sharma
9ed7594f6e Merge pull request #33621 from s-aga-r/fix/validation/scr
chore: subcontracting validations
2023-01-12 15:49:30 +05:30
s-aga-r
b26e96cdf4 chore: linter 2023-01-12 13:41:53 +05:30
ruthra kumar
391f42db04 fix: Updating SO throws ordered_qty not allowed to change after submission 2023-01-12 13:18:03 +05:30
s-aga-r
f028bd6e69 fix: validate accepted and rejected qty in SCR Item 2023-01-12 12:23:40 +05:30
s-aga-r
a0e2a93f3f chore: update error msgs for Subcontracted PO 2023-01-12 12:09:28 +05:30
s-aga-r
6878f40d1d chore: add row-index in error msgs 2023-01-12 12:02:00 +05:30
s-aga-r
434aa594d5 fix: ZeroDivisionError: float division by zero in SCR 2023-01-12 11:49:41 +05:30
unknown
974e12c837 fix: rewrite logic for duplicate check in Item Attribute
Previously, Item Attribute values were not checked for case-insensitive duplicates, and Item tttribute abbreviations were forced to be uppercase. This commit fixes both problems.
2023-01-12 07:44:57 +05:30
Vishal Dhayagude
467a2b5cb4 Merge branch 'develop' into fg_based_operating_cost 2023-01-11 22:30:36 +05:30
Vishal
694fc3e20c fix: hide with_operation on selection on fg_based and vice versa 2023-01-11 18:52:30 +05:30
Sagar Sharma
9ab8aa49d6 Merge pull request #33611 from vorasmit/fix-diff-value
fix: better comparision of `difference_value` of Stock and Account
2023-01-11 14:52:49 +05:30
Sagar Sharma
887ae1c434 Merge branch 'develop' into fix-diff-value 2023-01-11 14:01:51 +05:30
Smit Vora
be05aea101 fix: better comparision of difference value between stock and account 2023-01-11 05:21:13 +00:00
anandbaburajan
aa1f2a7297 fix: asset value in fixed asset register 2023-01-10 23:05:09 +05:30
Smit Vora
e0f5ecdad6 fix: RFQ emails not sent with pdf attachment (#33604) 2023-01-10 20:40:12 +05:30
vr-greycube
0f0bc9a462 fix: customer selection not mandatory in purchase invoice to fetch item details (#33572) 2023-01-10 20:26:33 +05:30
Deepesh Garg
0ed938a490 fix: Incorrect exchange rate in payment entries (#33481)
* fix: Incorrect exchange rate in payment entries

* test: Update failing tests
2023-01-10 20:12:43 +05:30
rohitwaghchaure
58ad79d3e8 Merge pull request #33597 from rohitwaghchaure/do-not-check-other-warehouse
fix: don't check other warehouse ledgers to calculate valuation rate
2023-01-10 13:56:10 +05:30
Vishal
a5cbdea8e4 fix: minor change in bom.js added 2023-01-10 13:40:12 +05:30
Rohit Waghchaure
ef2bf3c223 fix: don't check other warehouse ledgers to calculate valuation rate 2023-01-10 10:59:50 +05:30
Ankush Menat
8a56df695d perf: Drop name part from posting sort index (#33551) 2023-01-10 09:54:15 +05:30
Vishal
c51f9e0a97 fix: bom.json updated 2023-01-10 07:48:26 +05:30
Vishal
f0c0a64984 fix: test case added for FG_BASED OPERTING COST 2023-01-10 07:43:28 +05:30
Vishal
ddc0127e05 fix: minor changes added 2023-01-10 07:43:20 +05:30
Vishal
b559245f2a feat: Add operating cost based on bom quanity without creating job card 2023-01-10 07:43:05 +05:30
rohitwaghchaure
59628f1934 Merge pull request #33579 from rtdany10/repack-fg-valuation
fix(stock entry): wrong valuation rate in repack
2023-01-10 00:05:56 +05:30
rohitwaghchaure
a424acbfcb Merge pull request #33577 from rohitwaghchaure/timeout-error-in-purchase-invoice
fix: Timeout error while saving the purchase invoice
2023-01-10 00:01:15 +05:30
rohitwaghchaure
461f8e7724 Merge pull request #33590 from frappe/revert-33347-fb_based_operating_cost
Revert "feat: Add operating cost based on bom quanity without creating job card"
2023-01-09 23:37:13 +05:30
Rohit Waghchaure
ec171fc7c1 test: test case to check disable last purchase rate 2023-01-09 22:02:35 +05:30
sonali
917b2190aa fix: remove comments 2023-01-09 20:07:24 +05:30
safvanhuzain
99f5e869e0 fix(stock entry): wrong valuation rate in repack 2023-01-09 13:11:37 +00:00
Rohit Waghchaure
05df8579cd fix: linters issue 2023-01-09 18:23:22 +05:30
Rohit Waghchaure
d1d4671320 feat: provision to disable get last purchase rate
fix: set_incoming_rate condition
2023-01-09 18:10:52 +05:30
Rohit Waghchaure
7249657d15 fix: timeout error in the Purchase Invoice 2023-01-09 14:53:54 +05:30
ruthra kumar
23fbe86d51 refactor: Show Balance in COA based on Accounts Settings 2023-01-02 13:32:01 +05:30
ruthra kumar
1b78fae6fc refactor: show balance checkbox in Accounts Settings 2023-01-02 13:31:56 +05:30
sonali
12822f7c36 fix: data format 2022-12-30 14:06:19 +05:30
sonali
d65243eb65 feat: consolidated auto bank reconciliation
Added a button of Auto Reconcile, to reconcile the bank entries as per the matching reference number with the bank transaction and count of transactions reconciled message will be pop up on clicking the auto reconcile button.
2022-12-29 16:12:25 +05:30
sonali
c764f14f53 fix: pre-commit 2022-12-29 13:58:06 +05:30
sonali
35c29e0226 fix: pre-commit 2022-12-29 13:45:15 +05:30
sonali
f1810803e1 fix: passing from_date and to_date filters in test cases
passing from_date and to_date filters in  test_linked_payments and test_debit_credit_output  for unit testing
2022-12-29 13:07:06 +05:30
sonali
e2614b8a21 fix: pre-commit 2022-12-29 12:21:45 +05:30
sonali
3aaa2f5326 fix: filtered as per reference date
On bank reconciliation, transactions will be filtered as per date selected in 'from_date' and 'to_date' fields , In dialog, all the bank entries will  be fetched as per the posting date selected and if filtered by reference date checkbox is tick then then there will be two fields 'from_reference_date' and 'to_reference_date' then all bank entries in dialog box came as per reference date, selected. And by default journal entry checkbox is tick.
Also sorted the bank transactions and bank entries as per ascending order date wise.
2022-12-29 12:05:22 +05:30
sonali
81e5f71172 fix: json issue 2022-12-21 14:02:54 +05:30
sonali
6b5276398e fix: linters 2022-12-21 13:32:16 +05:30
sonali
645869e6ff feat: added arguments of posting date and reference date 2022-12-21 12:58:30 +05:30
sonali
05b6fce03d feat:filters on bank reconciliation
Added date filters on bank transactions, payment entries and journal entries and sorted list as per date in ascending order.
2022-12-17 17:15:28 +05:30
sonali
408c89df03 Feat:Filter on Payment Entries and Journal Entries
Applying filters on Payement entries and Journal Entries as per reference  date and posting date
2022-12-15 18:09:57 +05:30
sonali
8e7c8a6482 Update bank_reconciliation_tool.json
Adding fields in bank reconciliation tool
2022-12-08 17:23:08 +05:30
sonali
447272aa4d Filters on Bank Reconciliation
Applying date filter on transactions and all the bank entries and also gives the filter the bank entries as per reference date. Sorted all transactions and entries as per date in ascending order. Also added posting date columns in all bank entries and default checkbox tick of journal entry, hide the sales invoice and purchase invoice checkbox.
2022-12-08 16:11:53 +05:30
sonali
e5a1189bec Update bank_reconciliation_tool.py
Applying date filter on transactions and all the bank entries and also gives the filter the bank entries as per reference date. Sorted all transactions and entries as per date in ascending order.
Also added posting date columns in all bank entries and default checkbox tick of journal entry, hide the sales invoice and purchase invoice checkbox.
2022-12-08 13:29:28 +05:30
259 changed files with 5337 additions and 2966 deletions

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
@@ -16,6 +16,7 @@ erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev

View File

@@ -56,36 +56,41 @@ frappe.treeview_settings["Account"] = {
accounts = nodes;
}
const get_balances = frappe.call({
method: 'erpnext.accounts.utils.get_account_balances',
args: {
accounts: accounts,
company: cur_tree.args.company
},
});
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
if(value) {
get_balances.then(r => {
if (!r.message || r.message.length == 0) return;
const get_balances = frappe.call({
method: 'erpnext.accounts.utils.get_account_balances',
args: {
accounts: accounts,
company: cur_tree.args.company
},
});
for (let account of r.message) {
get_balances.then(r => {
if (!r.message || r.message.length == 0) return;
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
for (let account of r.message) {
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr": "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency);
const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
if (account.balance!==undefined) {
node.parent && node.parent.find('.balance-area').remove();
$('<span class="balance-area pull-right">'
+ (account.balance_in_account_currency ?
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ format(account.balance, account.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
// show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr": "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance!==undefined) {
node.parent && node.parent.find('.balance-area').remove();
$('<span class="balance-area pull-right">'
+ (account.balance_in_account_currency ?
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ format(account.balance, account.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
});
}
});
},

View File

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

View File

@@ -56,7 +56,9 @@
"acc_frozen_upto",
"column_break_25",
"frozen_accounts_modifier",
"report_settings_sb"
"report_settings_sb",
"tab_break_dpet",
"show_balance_in_coa"
],
"fields": [
{
@@ -347,6 +349,17 @@
"fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account "
},
{
"fieldname": "tab_break_dpet",
"fieldtype": "Tab Break",
"label": "Chart Of Accounts"
},
{
"default": "1",
"fieldname": "show_balance_in_coa",
"fieldtype": "Check",
"label": "Show Balances in Chart Of Accounts"
}
],
"icon": "icon-cog",
@@ -354,7 +367,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-11-27 21:49:52.538655",
"modified": "2023-01-02 12:07:42.434214",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -21,13 +21,22 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
frm.trigger('bank_account');
},
filter_by_reference_date: function (frm) {
if (frm.doc.filter_by_reference_date) {
frm.set_value("bank_statement_from_date", "");
frm.set_value("bank_statement_to_date", "");
} else {
frm.set_value("from_reference_date", "");
frm.set_value("to_reference_date", "");
}
},
refresh: function (frm) {
frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool")
);
frm.upload_statement_button = frm.page.set_secondary_action(
__("Upload Bank Statement"),
() =>
frm.add_custom_button(__("Upload Bank Statement"), () =>
frappe.call({
method:
"erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
@@ -49,6 +58,20 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
})
);
frm.add_custom_button(__('Auto Reconcile'), function() {
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
args: {
bank_account: frm.doc.bank_account,
from_date: frm.doc.bank_statement_from_date,
to_date: frm.doc.bank_statement_to_date,
filter_by_reference_date: frm.doc.filter_by_reference_date,
from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date,
},
})
});
},
after_save: function (frm) {
@@ -160,6 +183,9 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
).$wrapper,
bank_statement_from_date: frm.doc.bank_statement_from_date,
bank_statement_to_date: frm.doc.bank_statement_to_date,
filter_by_reference_date: frm.doc.filter_by_reference_date,
from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager,

View File

@@ -10,6 +10,9 @@
"column_break_1",
"bank_statement_from_date",
"bank_statement_to_date",
"from_reference_date",
"to_reference_date",
"filter_by_reference_date",
"column_break_2",
"account_opening_balance",
"bank_statement_closing_balance",
@@ -36,13 +39,13 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.bank_account",
"depends_on": "eval: doc.bank_account && !doc.filter_by_reference_date",
"fieldname": "bank_statement_from_date",
"fieldtype": "Date",
"label": "From Date"
},
{
"depends_on": "eval: doc.bank_statement_from_date",
"depends_on": "eval: doc.bank_account && !doc.filter_by_reference_date",
"fieldname": "bank_statement_to_date",
"fieldtype": "Date",
"label": "To Date"
@@ -81,14 +84,33 @@
},
{
"fieldname": "no_bank_transactions",
"fieldtype": "HTML"
"fieldtype": "HTML",
"options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
},
{
"depends_on": "eval:doc.filter_by_reference_date",
"fieldname": "from_reference_date",
"fieldtype": "Date",
"label": "From Reference Date"
},
{
"depends_on": "eval:doc.filter_by_reference_date",
"fieldname": "to_reference_date",
"fieldtype": "Date",
"label": "To Reference Date"
},
{
"default": "0",
"fieldname": "filter_by_reference_date",
"fieldtype": "Check",
"label": "Filter by Reference Date"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-04-21 11:13:49.831769",
"modified": "2023-01-13 13:00:02.022919",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",
@@ -107,5 +129,6 @@
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}
"sort_order": "DESC",
"states": []
}

View File

@@ -8,7 +8,7 @@ import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt
from frappe.utils import cint, flt
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
@@ -50,6 +50,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
"party",
],
filters=filters,
order_by="date",
)
return transactions
@@ -265,6 +266,80 @@ def create_payment_entry_bts(
return reconcile_vouchers(bank_transaction.name, vouchers)
@frappe.whitelist()
def auto_reconcile_vouchers(
bank_account,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
):
frappe.flags.auto_reconcile_vouchers = True
document_types = ["payment_entry", "journal_entry"]
bank_transactions = get_bank_transactions(bank_account)
matched_transaction = []
for transaction in bank_transactions:
linked_payments = get_linked_payments(
transaction.name,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
vouchers = []
for r in linked_payments:
vouchers.append(
{
"payment_doctype": r[1],
"payment_name": r[2],
"amount": r[4],
}
)
transaction = frappe.get_doc("Bank Transaction", transaction.name)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
matched_trans = 0
for voucher in vouchers:
gl_entry = frappe.db.get_value(
"GL Entry",
dict(
account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
),
["credit", "debit"],
as_dict=1,
)
gl_amount, transaction_amount = (
(gl_entry.credit, transaction.deposit)
if gl_entry.credit > 0
else (gl_entry.debit, transaction.withdrawal)
)
allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
transaction.append(
"payment_entries",
{
"payment_document": voucher["payment_doctype"],
"payment_entry": voucher["payment_name"],
"allocated_amount": allocated_amount,
},
)
matched_transaction.append(str(transaction.name))
transaction.save()
transaction.update_allocations()
matched_transaction_len = len(set(matched_transaction))
if matched_transaction_len == 0:
frappe.msgprint(_("No matching references found for auto reconciliation"))
elif matched_transaction_len == 1:
frappe.msgprint(_("{0} transaction is reconcilied").format(matched_transaction_len))
else:
frappe.msgprint(_("{0} transactions are reconcilied").format(matched_transaction_len))
frappe.flags.auto_reconcile_vouchers = False
return frappe.get_doc("Bank Transaction", transaction.name)
@frappe.whitelist()
def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
@@ -327,20 +402,58 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
@frappe.whitelist()
def get_linked_payments(bank_transaction_name, document_types=None):
def get_linked_payments(
bank_transaction_name,
document_types=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
):
# get all matching payments for a bank transaction
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
bank_account = frappe.db.get_values(
"Bank Account", transaction.bank_account, ["account", "company"], as_dict=True
)[0]
(account, company) = (bank_account.account, bank_account.company)
matching = check_matching(account, company, transaction, document_types)
matching = check_matching(
account,
company,
transaction,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
return matching
def check_matching(bank_account, company, transaction, document_types):
def check_matching(
bank_account,
company,
transaction,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
):
# combine all types of vouchers
subquery = get_queries(bank_account, company, transaction, document_types)
subquery = get_queries(
bank_account,
company,
transaction,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
filters = {
"amount": transaction.unallocated_amount,
"payment_type": "Receive" if transaction.deposit > 0 else "Pay",
@@ -361,11 +474,20 @@ def check_matching(bank_account, company, transaction, document_types):
filters,
)
)
return sorted(matching_vouchers, key=lambda x: x[0], reverse=True) if matching_vouchers else []
def get_queries(bank_account, company, transaction, document_types):
def get_queries(
bank_account,
company,
transaction,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
):
# get queries to get matching vouchers
amount_condition = "=" if "exact_match" in document_types else "<="
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
@@ -381,6 +503,11 @@ def get_queries(bank_account, company, transaction, document_types):
document_types,
amount_condition,
account_from_to,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
or []
)
@@ -389,15 +516,42 @@ def get_queries(bank_account, company, transaction, document_types):
def get_matching_queries(
bank_account, company, transaction, document_types, amount_condition, account_from_to
bank_account,
company,
transaction,
document_types,
amount_condition,
account_from_to,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
):
queries = []
if "payment_entry" in document_types:
pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
pe_amount_matching = get_pe_matching_query(
amount_condition,
account_from_to,
transaction,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
queries.extend([pe_amount_matching])
if "journal_entry" in document_types:
je_amount_matching = get_je_matching_query(amount_condition, transaction)
je_amount_matching = get_je_matching_query(
amount_condition,
transaction,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
)
queries.extend([je_amount_matching])
if transaction.deposit > 0 and "sales_invoice" in document_types:
@@ -504,47 +658,81 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
return vouchers
def get_pe_matching_query(amount_condition, account_from_to, transaction):
def get_pe_matching_query(
amount_condition,
account_from_to,
transaction,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
):
# get matching payment entries query
if transaction.deposit > 0:
currency_field = "paid_to_account_currency as currency"
else:
currency_field = "paid_from_account_currency as currency"
filter_by_date = f"AND posting_date between '{from_date}' and '{to_date}'"
order_by = " posting_date"
filter_by_reference_no = ""
if cint(filter_by_reference_date):
filter_by_date = f"AND reference_date between '{from_reference_date}' and '{to_reference_date}'"
order_by = " reference_date"
if frappe.flags.auto_reconcile_vouchers == True:
filter_by_reference_no = f"AND reference_no = '{transaction.reference_number}'"
return f"""
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Payment Entry' as doctype,
name,
paid_amount,
reference_no,
reference_date,
party,
party_type,
posting_date,
{currency_field}
FROM
`tabPayment Entry`
WHERE
paid_amount {amount_condition} %(amount)s
AND docstatus = 1
AND payment_type IN (%(payment_type)s, 'Internal Transfer')
AND ifnull(clearance_date, '') = ""
AND {account_from_to} = %(bank_account)s
SELECT
(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Payment Entry' as doctype,
name,
paid_amount,
reference_no,
reference_date,
party,
party_type,
posting_date,
{currency_field}
FROM
`tabPayment Entry`
WHERE
paid_amount {amount_condition} %(amount)s
AND docstatus = 1
AND payment_type IN (%(payment_type)s, 'Internal Transfer')
AND ifnull(clearance_date, '') = ""
AND {account_from_to} = %(bank_account)s
{filter_by_date}
{filter_by_reference_no}
order by{order_by}
"""
def get_je_matching_query(amount_condition, transaction):
def get_je_matching_query(
amount_condition,
transaction,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
):
# get matching journal entry query
# We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
filter_by_date = f"AND je.posting_date between '{from_date}' and '{to_date}'"
order_by = " je.posting_date"
filter_by_reference_no = ""
if cint(filter_by_reference_date):
filter_by_date = f"AND je.cheque_date between '{from_reference_date}' and '{to_reference_date}'"
order_by = " je.cheque_date"
if frappe.flags.auto_reconcile_vouchers == True:
filter_by_reference_no = f"AND je.cheque_no = '{transaction.reference_number}'"
return f"""
SELECT
(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END
+ 1) AS rank ,
@@ -568,6 +756,9 @@ def get_je_matching_query(amount_condition, transaction):
AND jea.account = %(bank_account)s
AND jea.{cr_or_dr}_in_account_currency {amount_condition} %(amount)s
AND je.docstatus = 1
{filter_by_date}
{filter_by_reference_no}
order by {order_by}
"""

View File

@@ -5,6 +5,7 @@ import json
import unittest
import frappe
from frappe import utils
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
@@ -40,7 +41,12 @@ class TestBankTransaction(FrappeTestCase):
"Bank Transaction",
dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"),
)
linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
linked_payments = get_linked_payments(
bank_transaction.name,
["payment_entry", "exact_match"],
from_date=bank_transaction.date,
to_date=utils.today(),
)
self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
@@ -81,7 +87,12 @@ class TestBankTransaction(FrappeTestCase):
"Bank Transaction",
dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"),
)
linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
linked_payments = get_linked_payments(
bank_transaction.name,
["payment_entry", "exact_match"],
from_date=bank_transaction.date,
to_date=utils.today(),
)
self.assertTrue(linked_payments[0][3])
# Check error if already reconciled

View File

@@ -184,6 +184,11 @@ def validate_budget_records(args, budget_records, expense_amount):
amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
if yearly_action in ("Stop", "Warn"):
compare_expense_with_budget(
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
)
if monthly_action in ["Stop", "Warn"]:
budget_amount = get_accumulated_monthly_budget(
budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount
@@ -195,28 +200,28 @@ def validate_budget_records(args, budget_records, expense_amount):
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
)
if (
yearly_action in ("Stop", "Warn")
and monthly_action != "Stop"
and yearly_action != monthly_action
):
compare_expense_with_budget(
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
actual_expense = amount or get_actual_expense(args)
if actual_expense > budget_amount:
diff = actual_expense - budget_amount
actual_expense = get_actual_expense(args)
total_expense = actual_expense + amount
if total_expense > budget_amount:
if actual_expense > budget_amount:
error_tense = _("is already")
diff = actual_expense - budget_amount
else:
error_tense = _("will be")
diff = total_expense - budget_amount
currency = frappe.get_cached_value("Company", args.company, "default_currency")
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It {5} exceed by {6}").format(
_(action_for),
frappe.bold(args.account),
args.budget_against_field,
frappe.unscrub(args.budget_against_field),
frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)),
error_tense,
frappe.bold(fmt_money(diff, currency=currency)),
)
@@ -227,9 +232,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
action = "Warn"
if action == "Stop":
frappe.throw(msg, BudgetError)
frappe.throw(msg, BudgetError, title=_("Budget Exceeded"))
else:
frappe.msgprint(msg, indicator="orange")
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
def get_actions(args, budget):
@@ -351,7 +356,9 @@ def get_actual_expense(args):
"""
select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle
where gle.account=%(account)s
where
is_cancelled = 0
and gle.account=%(account)s
{condition1}
and gle.fiscal_year=%(fiscal_year)s
and gle.company=%(company)s

View File

@@ -28,9 +28,14 @@ class InvalidDateError(frappe.ValidationError):
class CostCenterAllocation(Document):
def __init__(self, *args, **kwargs):
super(CostCenterAllocation, self).__init__(*args, **kwargs)
self._skip_from_date_validation = False
def validate(self):
self.validate_total_allocation_percentage()
self.validate_from_date_based_on_existing_gle()
if not self._skip_from_date_validation:
self.validate_from_date_based_on_existing_gle()
self.validate_backdated_allocation()
self.validate_main_cost_center()
self.validate_child_cost_centers()

View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"api_details_section",
"disabled",
"service_provider",
"api_endpoint",
"url",
@@ -77,12 +78,18 @@
"label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom",
"reqd": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-01-10 15:51:14.521174",
"modified": "2023-01-09 12:19:03.955906",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",

View File

@@ -40,7 +40,7 @@ class Dunning(AccountsController):
def on_cancel(self):
if self.dunning_amount:
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
def make_gl_entries(self):

View File

@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
frappe.ui.form.on("Journal Entry", {
setup: function(frm) {
frm.add_fetch("bank_account", "account", "account");
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry'];
},
refresh: function(frm) {

View File

@@ -137,8 +137,7 @@
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book",
"read_only": 1
"options": "Finance Book"
},
{
"fieldname": "2_add_edit_gl_entries",
@@ -539,7 +538,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2022-11-28 17:40:01.241908",
"modified": "2023-01-17 12:53:53.280620",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -81,6 +81,7 @@ class JournalEntry(AccountsController):
self.check_credit_limit()
self.make_gl_entries()
self.update_advance_paid()
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()
@@ -225,6 +226,34 @@ class JournalEntry(AccountsController):
for d in to_remove:
self.remove(d)
def update_asset_value(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
):
processed_assets.append(d.reference_name)
asset = frappe.db.get_value(
"Asset", d.reference_name, ["calculate_depreciation", "value_after_depreciation"], as_dict=1
)
if asset.calculate_depreciation:
continue
depr_value = d.debit or d.credit
frappe.db.set_value(
"Asset",
d.reference_name,
"value_after_depreciation",
asset.value_after_depreciation - depr_value,
)
def update_inter_company_jv(self):
if (
self.voucher_type == "Inter Company Journal Entry"
@@ -283,20 +312,48 @@ class JournalEntry(AccountsController):
d.db_update()
def unlink_asset_reference(self):
if self.voucher_type != "Depreciation Entry":
return
processed_assets = []
for d in self.get("accounts"):
if d.reference_type == "Asset" and d.reference_name:
if (
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
):
processed_assets.append(d.reference_name)
asset = frappe.get_doc("Asset", d.reference_name)
for row in asset.get("finance_books"):
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
for s in depr_schedule or []:
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
if asset.calculate_depreciation:
je_found = False
row.value_after_depreciation += s.depreciation_amount
row.db_update()
for row in asset.get("finance_books"):
if je_found:
break
asset.set_status()
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
for s in depr_schedule or []:
if s.journal_entry == self.name:
s.db_set("journal_entry", None)
row.value_after_depreciation += s.depreciation_amount
row.db_update()
asset.set_status()
je_found = True
break
else:
depr_value = d.debit or d.credit
frappe.db.set_value(
"Asset",
d.reference_name,
"value_after_depreciation",
asset.value_after_depreciation + depr_value,
)
def unlink_inter_company_jv(self):
if (

View File

@@ -239,7 +239,7 @@
"depends_on": "paid_from",
"fieldname": "paid_from_account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"label": "Account Currency (From)",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
@@ -249,7 +249,7 @@
"depends_on": "paid_from",
"fieldname": "paid_from_account_balance",
"fieldtype": "Currency",
"label": "Account Balance",
"label": "Account Balance (From)",
"options": "paid_from_account_currency",
"print_hide": 1,
"read_only": 1
@@ -272,7 +272,7 @@
"depends_on": "paid_to",
"fieldname": "paid_to_account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"label": "Account Currency (To)",
"options": "Currency",
"print_hide": 1,
"read_only": 1,
@@ -282,7 +282,7 @@
"depends_on": "paid_to",
"fieldname": "paid_to_account_balance",
"fieldtype": "Currency",
"label": "Account Balance",
"label": "Account Balance (To)",
"options": "paid_to_account_currency",
"print_hide": 1,
"read_only": 1
@@ -304,7 +304,7 @@
{
"fieldname": "source_exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
"label": "Source Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
@@ -334,7 +334,7 @@
{
"fieldname": "target_exchange_rate",
"fieldtype": "Float",
"label": "Exchange Rate",
"label": "Target Exchange Rate",
"precision": "9",
"print_hide": 1,
"reqd": 1
@@ -633,14 +633,14 @@
"depends_on": "eval:doc.party_type == 'Supplier'",
"fieldname": "purchase_taxes_and_charges_template",
"fieldtype": "Link",
"label": "Taxes and Charges Template",
"label": "Purchase Taxes and Charges Template",
"options": "Purchase Taxes and Charges Template"
},
{
"depends_on": "eval: doc.party_type == 'Customer'",
"fieldname": "sales_taxes_and_charges_template",
"fieldtype": "Link",
"label": "Taxes and Charges Template",
"label": "Sales Taxes and Charges Template",
"options": "Sales Taxes and Charges Template"
},
{
@@ -733,7 +733,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-12-08 16:25:43.824051",
"modified": "2023-02-14 04:52:30.478523",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -247,7 +247,7 @@ class PaymentEntry(AccountsController):
self.set_target_exchange_rate(ref_doc)
def set_source_exchange_rate(self, ref_doc=None):
if self.paid_from and not self.source_exchange_rate:
if self.paid_from:
if self.paid_from_account_currency == self.company_currency:
self.source_exchange_rate = 1
else:

View File

@@ -69,6 +69,10 @@ class PaymentReconciliation(Document):
def get_jv_entries(self):
condition = self.get_conditions()
if self.get("cost_center"):
condition += f" and t2.cost_center = '{self.cost_center}' "
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
@@ -230,7 +234,7 @@ class PaymentReconciliation(Document):
def allocate_entries(self, args):
self.validate_entries()
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"))
invoice_exchange_map = self.get_invoice_exchange_map(args.get("invoices"), args.get("payments"))
default_exchange_gain_loss_account = frappe.get_cached_value(
"Company", self.company, "exchange_gain_loss_account"
)
@@ -249,6 +253,9 @@ class PaymentReconciliation(Document):
pay["amount"] = 0
inv["exchange_rate"] = invoice_exchange_map.get(inv.get("invoice_number"))
if pay.get("reference_type") in ["Sales Invoice", "Purchase Invoice"]:
pay["exchange_rate"] = invoice_exchange_map.get(pay.get("reference_name"))
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
res.difference_account = default_exchange_gain_loss_account
res.exchange_rate = inv.get("exchange_rate")
@@ -334,7 +341,7 @@ class PaymentReconciliation(Document):
)
# Account Currency has balance
dr_or_cr = "debit" if self.party_type == "Customer" else "debit"
dr_or_cr = "debit" if self.party_type == "Customer" else "credit"
reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
journal_account = frappe._dict(
@@ -403,13 +410,21 @@ class PaymentReconciliation(Document):
if not self.get("payments"):
frappe.throw(_("No records found in the Payments table"))
def get_invoice_exchange_map(self, invoices):
def get_invoice_exchange_map(self, invoices, payments):
sales_invoices = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Sales Invoice"
]
sales_invoices.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Sales Invoice"]
)
purchase_invoices = [
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Purchase Invoice"
]
purchase_invoices.extend(
[d.get("reference_name") for d in payments if d.get("reference_type") == "Purchase Invoice"]
)
invoice_exchange_map = frappe._dict()
if sales_invoices:
@@ -471,6 +486,7 @@ class PaymentReconciliation(Document):
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()
self.accounting_dimension_filter_conditions.clear()
self.ple_posting_date_filter.clear()
ple = qb.DocType("Payment Ledger Entry")

View File

@@ -473,6 +473,11 @@ class TestPaymentReconciliation(FrappeTestCase):
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
pr.get_unreconciled_entries()
@@ -506,6 +511,11 @@ class TestPaymentReconciliation(FrappeTestCase):
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = allocated_amount
# Cr Note and Invoice are of the same currency. There shouldn't any difference amount.
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# assert outstanding
@@ -747,6 +757,73 @@ class TestPaymentReconciliation(FrappeTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_cost_center_filter_on_vouchers(self):
"""
Test Cost Center filter is applied on Invoices, Payment Entries and Journals
"""
transaction_date = nowdate()
rate = 100
# 'Main - PR' Cost Center
si1 = self.create_sales_invoice(
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si1.cost_center = self.main_cc.name
si1.submit()
pe1 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
pe1.cost_center = self.main_cc.name
pe1 = pe1.save().submit()
je1 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
je1.accounts[0].cost_center = self.main_cc.name
je1.accounts[1].cost_center = self.main_cc.name
je1.accounts[1].party_type = "Customer"
je1.accounts[1].party = self.customer
je1 = je1.save().submit()
# 'Sub - PR' Cost Center
si2 = self.create_sales_invoice(
qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True
)
si2.cost_center = self.sub_cc.name
si2.submit()
pe2 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
pe2.cost_center = self.sub_cc.name
pe2 = pe2.save().submit()
je2 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
je2.accounts[0].cost_center = self.sub_cc.name
je2.accounts[1].cost_center = self.sub_cc.name
je2.accounts[1].party_type = "Customer"
je2.accounts[1].party = self.customer
je2 = je2.save().submit()
pr = self.create_payment_reconciliation()
pr.cost_center = self.main_cc.name
pr.get_unreconciled_entries()
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(pr.get("invoices")[0].get("invoice_number"), si1.name)
self.assertEqual(len(pr.get("payments")), 2)
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
self.assertCountEqual(payment_vouchers, [pe1.name, je1.name])
# Change cost center
pr.cost_center = self.sub_cc.name
pr.get_unreconciled_entries()
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(pr.get("invoices")[0].get("invoice_number"), si2.name)
self.assertEqual(len(pr.get("payments")), 2)
payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -45,21 +45,20 @@ class PaymentRequest(Document):
frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request_amount(self):
existing_payment_request_amount = get_existing_payment_request_amount(
self.reference_doctype, self.reference_name
existing_payment_request_amount = flt(
get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
)
if existing_payment_request_amount:
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if not hasattr(ref_doc, "order_type") or getattr(ref_doc, "order_type") != "Shopping Cart":
ref_amount = get_amount(ref_doc, self.payment_account)
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
frappe.throw(
_("Total Payment Request amount cannot be greater than {0} amount").format(
self.reference_doctype
)
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
frappe.throw(
_("Total Payment Request amount cannot be greater than {0} amount").format(
self.reference_doctype
)
)
def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)

View File

@@ -675,7 +675,7 @@ def get_bin_qty(item_code, warehouse):
def get_pos_reserved_qty(item_code, warehouse):
reserved_qty = frappe.db.sql(
"""select sum(p_item.qty) as qty
"""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, '') = ''

View File

@@ -472,7 +472,7 @@
"description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"label": "Rate"
"label": "Free Item Rate"
},
{
"collapsible": 1,
@@ -608,7 +608,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2022-10-13 19:05:35.056304",
"modified": "2023-02-14 04:53:34.887358",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -687,11 +687,21 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
def apply_pricing_rule_for_free_items(doc, pricing_rule_args):
if pricing_rule_args:
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
args = {(d["item_code"], d["pricing_rules"]): d for d in pricing_rule_args}
for args in pricing_rule_args:
if not items or (args.get("item_code"), args.get("pricing_rules")) not in items:
doc.append("items", args)
for item in doc.items:
if not item.is_free_item:
continue
free_item_data = args.get((item.item_code, item.pricing_rules))
if free_item_data:
free_item_data.pop("item_name")
free_item_data.pop("description")
item.update(free_item_data)
args.pop((item.item_code, item.pricing_rules))
for free_item in args.values():
doc.append("items", free_item)
def get_pricing_rule_items(pr_doc, other_items=False) -> list:

View File

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

View File

@@ -1426,6 +1426,7 @@
},
{
"default": "0",
"depends_on": "apply_tds",
"fieldname": "tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
@@ -1435,12 +1436,13 @@
"read_only": 1
},
{
"depends_on": "apply_tds",
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Tax Withholding Net Total",
"no_copy": 1,
"options": "currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1554,7 +1556,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2022-12-12 18:37:38.142688",
"modified": "2023-01-28 19:18:56.586321",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -7,7 +7,6 @@ import frappe
from frappe import _, qb
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils.background_jobs import is_job_queued
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
@@ -27,7 +26,7 @@ def start_payment_ledger_repost(docname=None):
"""
if docname:
repost_doc = frappe.get_doc("Repost Payment Ledger", docname)
if repost_doc.docstatus == 1 and repost_doc.repost_status in ["Queued", "Failed"]:
if repost_doc.docstatus.is_submitted() and repost_doc.repost_status in ["Queued", "Failed"]:
try:
for entry in repost_doc.repost_vouchers:
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
@@ -102,10 +101,9 @@ def execute_repost_payment_ledger(docname):
job_name = "payment_ledger_repost_" + docname
if not is_job_queued(job_name):
frappe.enqueue(
method="erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.start_payment_ledger_repost",
docname=docname,
is_async=True,
job_name=job_name,
)
frappe.enqueue(
method="erpnext.accounts.doctype.repost_payment_ledger.repost_payment_ledger.start_payment_ledger_repost",
docname=docname,
is_async=True,
job_name=job_name,
)

View File

@@ -1776,6 +1776,8 @@
"width": "50%"
},
{
"fetch_from": "sales_partner.commission_rate",
"fetch_if_empty": 1,
"fieldname": "commission_rate",
"fieldtype": "Float",
"hide_days": 1,
@@ -2141,7 +2143,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2022-12-12 18:34:33.409895",
"modified": "2023-01-28 19:45:47.538163",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

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

View File

@@ -1,6 +1,6 @@
{
"actions": [],
"autoname": "autoincrement",
"autoname": "hash",
"creation": "2022-09-13 16:18:59.404842",
"doctype": "DocType",
"editable_grid": 1,
@@ -36,11 +36,11 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-13 23:40:41.479208",
"modified": "2023-01-13 13:40:41.479208",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withheld Vouchers",
"naming_rule": "Autoincrement",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",

View File

@@ -259,9 +259,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if tax_deducted:
net_total = inv.tax_withholding_net_total
if ldc:
tax_amount = get_tds_amount_from_ldc(
ldc, parties, pan_no, tax_details, posting_date, net_total
)
tax_amount = get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total)
else:
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
@@ -412,12 +410,26 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
## for TDS to be deducted on advances
payment_entry_filters = {
"party_type": "Supplier",
"party": ("in", parties),
"docstatus": 1,
"apply_tax_withholding_amount": 1,
"unallocated_amount": (">", 0),
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
"tax_withholding_category": tax_details.get("tax_withholding_category"),
}
field = "sum(tax_withholding_net_total)"
if cint(tax_details.consider_party_ledger_amount):
invoice_filters.pop("apply_tds", None)
field = "sum(grand_total)"
payment_entry_filters.pop("apply_tax_withholding_amount", None)
payment_entry_filters.pop("tax_withholding_category", None)
supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
supp_jv_credit_amt = (
@@ -429,14 +441,28 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
"party": ("in", parties),
"reference_type": ("!=", "Purchase Invoice"),
},
"sum(credit_in_account_currency)",
"sum(credit_in_account_currency - debit_in_account_currency)",
)
or 0.0
)
# Get Amount via payment entry
payment_entry_amounts = frappe.db.get_all(
"Payment Entry",
filters=payment_entry_filters,
fields=["sum(unallocated_amount) as amount", "payment_type"],
group_by="payment_type",
)
supp_credit_amt += supp_jv_credit_amt
supp_credit_amt += inv.tax_withholding_net_total
for type in payment_entry_amounts:
if type.payment_type == "Pay":
supp_credit_amt += type.amount
else:
supp_credit_amt -= type.amount
threshold = tax_details.get("threshold", 0)
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
@@ -538,7 +564,7 @@ def get_invoice_total_without_tcs(inv, tax_details):
return inv.grand_total - tcs_tax_row_amount
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
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",

View File

@@ -16,7 +16,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
def setUpClass(self):
# create relevant supplier, etc
create_records()
create_tax_with_holding_category()
create_tax_withholding_category_records()
def tearDown(self):
cancel_invoices()
@@ -38,7 +38,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
pi = create_purchase_invoice(supplier="Test TDS Supplier")
pi.submit()
# assert equal tax deduction on total invoice amount uptil now
# assert equal tax deduction on total invoice amount until now
self.assertEqual(pi.taxes_and_charges_deducted, 3000)
self.assertEqual(pi.grand_total, 7000)
invoices.append(pi)
@@ -47,7 +47,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
pi = create_purchase_invoice(supplier="Test TDS Supplier", rate=5000)
pi.submit()
# assert equal tax deduction on total invoice amount uptil now
# assert equal tax deduction on total invoice amount until now
self.assertEqual(pi.taxes_and_charges_deducted, 500)
invoices.append(pi)
@@ -130,7 +130,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices.append(si)
# create another invoice whose total when added to previously created invoice,
# surpasses cumulative threshhold
# surpasses cumulative threshold
si = create_sales_invoice(customer="Test TCS Customer", rate=12000)
si.submit()
@@ -329,6 +329,38 @@ class TestTaxWithholdingCategory(unittest.TestCase):
for d in reversed(invoices):
d.cancel()
def test_tax_withholding_via_payment_entry_for_advances(self):
frappe.db.set_value(
"Supplier", "Test TDS Supplier7", "tax_withholding_category", "Advance TDS Category"
)
# create payment entry
pe1 = create_payment_entry(
payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000
)
pe1.submit()
self.assertFalse(pe1.get("taxes"))
pe2 = create_payment_entry(
payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000
)
pe2.submit()
self.assertFalse(pe2.get("taxes"))
pe3 = create_payment_entry(
payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000
)
pe3.apply_tax_withholding_amount = 1
pe3.save()
pe3.submit()
self.assertEquals(pe3.get("taxes")[0].tax_amount, 1200)
pe1.cancel()
pe2.cancel()
pe3.cancel()
def cancel_invoices():
purchase_invoices = frappe.get_all(
@@ -450,6 +482,32 @@ def create_sales_invoice(**args):
return si
def create_payment_entry(**args):
# return payment entry doc object
args = frappe._dict(args)
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
"posting_date": today(),
"payment_type": args.payment_type,
"party_type": args.party_type,
"party": args.party,
"company": "_Test Company",
"paid_from": "Cash - _TC",
"paid_to": "Creditors - _TC",
"paid_amount": args.paid_amount or 10000,
"received_amount": args.paid_amount or 10000,
"reference_no": args.reference_no or "12345",
"reference_date": today(),
"paid_from_account_currency": "INR",
"paid_to_account_currency": "INR",
}
)
pe.save()
return pe
def create_records():
# create a new suppliers
for name in [
@@ -460,6 +518,7 @@ def create_records():
"Test TDS Supplier4",
"Test TDS Supplier5",
"Test TDS Supplier6",
"Test TDS Supplier7",
]:
if frappe.db.exists("Supplier", name):
continue
@@ -530,142 +589,129 @@ def create_records():
).insert()
def create_tax_with_holding_category():
def create_tax_withholding_category_records():
fiscal_year = get_fiscal_year(today(), company="_Test Company")
from_date = fiscal_year[1]
to_date = fiscal_year[2]
# Cumulative threshold
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Cumulative Threshold TDS",
"category_name": "10% TDS",
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 0,
"cumulative_threshold": 30000.00,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
create_tax_withholding_category(
category_name="Cumulative Threshold TDS",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=0,
cumulative_threshold=30000.00,
)
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Cumulative Threshold TCS",
"category_name": "10% TCS",
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 0,
"cumulative_threshold": 30000.00,
}
],
"accounts": [{"company": "_Test Company", "account": "TCS - _TC"}],
}
).insert()
# Category for TCS
create_tax_withholding_category(
category_name="Cumulative Threshold TCS",
rate=10,
from_date=from_date,
to_date=to_date,
account="TCS - _TC",
single_threshold=0,
cumulative_threshold=30000.00,
)
# Single thresold
if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Single Threshold TDS",
"category_name": "10% TDS",
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 20000.00,
"cumulative_threshold": 0,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
# Single threshold
create_tax_withholding_category(
category_name="Single Threshold TDS",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=20000,
cumulative_threshold=0,
)
if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "New TDS Category",
"category_name": "New TDS Category",
"round_off_tax_amount": 1,
"consider_party_ledger_amount": 1,
"tax_on_excess_amount": 1,
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 0,
"cumulative_threshold": 30000,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
create_tax_withholding_category(
category_name="New TDS Category",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=0,
cumulative_threshold=30000,
round_off_tax_amount=1,
consider_party_ledger_amount=1,
tax_on_excess_amount=1,
)
if not frappe.db.exists("Tax Withholding Category", "Test Service Category"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Test Service Category",
"category_name": "Test Service Category",
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 2000,
"cumulative_threshold": 2000,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
create_tax_withholding_category(
category_name="Test Service Category",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=2000,
cumulative_threshold=2000,
)
if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Test Goods Category",
"category_name": "Test Goods Category",
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 2000,
"cumulative_threshold": 2000,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
}
).insert()
create_tax_withholding_category(
category_name="Test Goods Category",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=2000,
cumulative_threshold=2000,
)
if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"):
create_tax_withholding_category(
category_name="Test Multi Invoice Category",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=5000,
cumulative_threshold=10000,
)
create_tax_withholding_category(
category_name="Advance TDS Category",
rate=10,
from_date=from_date,
to_date=to_date,
account="TDS - _TC",
single_threshold=5000,
cumulative_threshold=10000,
consider_party_ledger_amount=1,
)
def create_tax_withholding_category(
category_name,
rate,
from_date,
to_date,
account,
single_threshold=0,
cumulative_threshold=0,
round_off_tax_amount=0,
consider_party_ledger_amount=0,
tax_on_excess_amount=0,
):
if not frappe.db.exists("Tax Withholding Category", category_name):
frappe.get_doc(
{
"doctype": "Tax Withholding Category",
"name": "Test Multi Invoice Category",
"category_name": "Test Multi Invoice Category",
"name": category_name,
"category_name": category_name,
"round_off_tax_amount": round_off_tax_amount,
"consider_party_ledger_amount": consider_party_ledger_amount,
"tax_on_excess_amount": tax_on_excess_amount,
"rates": [
{
"from_date": fiscal_year[1],
"to_date": fiscal_year[2],
"tax_withholding_rate": 10,
"single_threshold": 5000,
"cumulative_threshold": 10000,
"from_date": from_date,
"to_date": to_date,
"tax_withholding_rate": rate,
"single_threshold": single_threshold,
"cumulative_threshold": cumulative_threshold,
}
],
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
"accounts": [{"company": "_Test Company", "account": account}],
}
).insert()

View File

@@ -211,7 +211,13 @@ def set_address_details(
else:
party_details.update(get_company_address(company))
if doctype and doctype in ["Delivery Note", "Sales Invoice", "Sales Order", "Quotation"]:
if doctype and doctype in [
"Delivery Note",
"Sales Invoice",
"Sales Order",
"Quotation",
"POS Invoice",
]:
if party_details.company_address:
party_details.update(
get_fetch_values(doctype, "company_address", party_details.company_address)
@@ -544,7 +550,7 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
elif term.due_date_based_on == "Day(s) after the end of the invoice month":
due_date = max(due_date, add_days(get_last_day(due_date), term.credit_days))
else:
due_date = max(due_date, add_months(get_last_day(due_date), term.credit_months))
due_date = max(due_date, get_last_day(add_months(due_date, term.credit_months)))
return due_date

View File

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

View File

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

View File

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

View File

@@ -526,7 +526,7 @@ def get_columns(filters):
"options": "GL Entry",
"hidden": 1,
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 90},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
{
"label": _("Account"),
"fieldname": "account",
@@ -538,13 +538,13 @@ def get_columns(filters):
"label": _("Debit ({0})").format(currency),
"fieldname": "debit",
"fieldtype": "Float",
"width": 100,
"width": 130,
},
{
"label": _("Credit ({0})").format(currency),
"fieldname": "credit",
"fieldtype": "Float",
"width": 100,
"width": 130,
},
{
"label": _("Balance ({0})").format(currency),

View File

@@ -50,6 +50,20 @@ frappe.query_reports["Gross Profit"] = {
"fieldtype": "Link",
"options": "Sales Person"
},
{
"fieldname": "warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse",
"get_query": function () {
var company = frappe.query_report.get_filter_value('company');
return {
filters: [
["Warehouse", "company", "=", company]
]
};
},
},
],
"tree": true,
"name_field": "parent",

View File

@@ -655,10 +655,35 @@ class GrossProfitGenerator(object):
return self.calculate_buying_amount_from_sle(
row, my_sle, parenttype, parent, item_row, item_code
)
elif row.sales_order and row.so_detail:
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
if incoming_amount:
return incoming_amount
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
return 0.0
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
from frappe.query_builder.functions import Sum
delivery_note = frappe.qb.DocType("Delivery Note")
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
query = (
frappe.qb.from_(delivery_note)
.inner_join(delivery_note_item)
.on(delivery_note.name == delivery_note_item.parent)
.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
.where(delivery_note.docstatus == 1)
.where(delivery_note_item.item_code == item_code)
.where(delivery_note_item.against_sales_order == sales_order)
.where(delivery_note_item.so_detail == so_detail)
.groupby(delivery_note_item.item_code)
)
incoming_amount = query.run()
return flt(incoming_amount[0][0]) if incoming_amount else 0
def get_average_buying_rate(self, row, item_code):
args = row
@@ -750,6 +775,13 @@ class GrossProfitGenerator(object):
if self.filters.get("item_code"):
conditions += " and `tabSales Invoice Item`.item_code = %(item_code)s"
if self.filters.get("warehouse"):
warehouse_details = frappe.db.get_value(
"Warehouse", self.filters.get("warehouse"), ["lft", "rgt"], as_dict=1
)
if warehouse_details:
conditions += f" and `tabSales Invoice Item`.warehouse in (select name from `tabWarehouse` wh where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
self.si_list = frappe.db.sql(
"""
select
@@ -760,7 +792,8 @@ class GrossProfitGenerator(object):
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.warehouse, `tabSales Invoice Item`.item_group,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.brand, `tabSales Invoice Item`.so_detail,
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.dn_detail,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.stock_qty as qty,
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.name as "item_row", `tabSales Invoice`.is_return,

View File

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

View File

@@ -4,6 +4,7 @@
import frappe
from frappe import _
from frappe.utils import flt
def execute(filters=None):
@@ -65,6 +66,12 @@ def get_result(
else:
total_amount_credited += entry.credit
## Check if ldc is applied and show rate as per ldc
actual_rate = (tds_deducted / total_amount_credited) * 100
if flt(actual_rate) < flt(rate):
rate = actual_rate
if tds_deducted:
row = {
"pan"

View File

@@ -439,8 +439,7 @@ def reconcile_against_document(args): # nosemgrep
# cancel advance entry
doc = frappe.get_doc(voucher_type, voucher_no)
frappe.flags.ignore_party_validation = True
gl_map = doc.build_gl_map()
create_payment_ledger_entry(gl_map, cancel=1, adv_adj=1)
_delete_pl_entries(voucher_type, voucher_no)
for entry in entries:
check_if_advance_entry_modified(entry)
@@ -452,11 +451,23 @@ def reconcile_against_document(args): # nosemgrep
else:
update_reference_in_payment_entry(entry, doc, do_not_save=True)
if doc.doctype == "Journal Entry":
try:
doc.validate_total_debit_and_credit()
except Exception as validation_exception:
raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception
doc.save(ignore_permissions=True)
# re-submit advance entry
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
gl_map = doc.build_gl_map()
create_payment_ledger_entry(gl_map, cancel=0, adv_adj=1)
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
# Only update outstanding for newly linked vouchers
for entry in entries:
update_voucher_outstanding(
entry.against_voucher_type, entry.against_voucher, entry.account, entry.party_type, entry.party
)
frappe.flags.ignore_party_validation = False
@@ -1501,9 +1512,12 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
ref_doc = frappe.get_doc(voucher_type, voucher_no)
# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"]
ref_doc.outstanding_amount = outstanding["outstanding_in_account_currency"] or 0.0
frappe.db.set_value(
voucher_type, voucher_no, "outstanding_amount", outstanding["outstanding_in_account_currency"]
voucher_type,
voucher_no,
"outstanding_amount",
outstanding["outstanding_in_account_currency"] or 0.0,
)
ref_doc.set_status(update=True)

View File

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

View File

@@ -68,6 +68,7 @@
"column_break_51",
"purchase_receipt_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from"
],
"fields": [
@@ -473,6 +474,16 @@
"fieldtype": "Int",
"label": "Asset Quantity",
"read_only_depends_on": "eval:!doc.is_existing_asset"
},
{
"fieldname": "depr_entry_posting_status",
"fieldtype": "Select",
"hidden": 1,
"label": "Depreciation Entry Posting Status",
"no_copy": 1,
"options": "\nSuccessful\nFailed",
"print_hide": 1,
"read_only": 1
}
],
"idx": 72,
@@ -487,7 +498,7 @@
{
"group": "Repair",
"link_doctype": "Asset Repair",
"link_fieldname": "asset_name"
"link_fieldname": "asset"
},
{
"group": "Value",
@@ -498,9 +509,15 @@
"group": "Depreciation",
"link_doctype": "Asset Depreciation Schedule",
"link_fieldname": "asset"
},
{
"group": "Journal Entry",
"link_doctype": "Journal Entry",
"link_fieldname": "reference_name",
"table_fieldname": "accounts"
}
],
"modified": "2022-11-25 12:47:19.689702",
"modified": "2023-02-02 00:03:11.706427",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -36,7 +36,6 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
get_depr_schedule,
make_draft_asset_depr_schedules,
make_draft_asset_depr_schedules_if_not_present,
set_draft_asset_depr_schedule_details,
update_draft_asset_depr_schedules,
)
from erpnext.controllers.accounts_controller import AccountsController
@@ -240,17 +239,6 @@ class Asset(AccountsController):
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
)
def _get_value_after_depreciation(self, finance_book):
# value_after_depreciation - current Asset value
if self.docstatus == 1 and finance_book.value_after_depreciation:
value_after_depreciation = flt(finance_book.value_after_depreciation)
else:
value_after_depreciation = flt(self.gross_purchase_amount) - flt(
self.opening_accumulated_depreciation
)
return value_after_depreciation
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
has_pro_rata = False
@@ -392,18 +380,23 @@ class Asset(AccountsController):
movement.cancel()
def delete_depreciation_entries(self):
for row in self.get("finance_books"):
depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book)
if self.calculate_depreciation:
for row in self.get("finance_books"):
depr_schedule = get_depr_schedule(self.name, "Active", row.finance_book)
for d in depr_schedule or []:
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
d.db_set("journal_entry", None)
for d in depr_schedule or []:
if d.journal_entry:
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
else:
depr_entries = self.get_manual_depreciation_entries()
self.db_set(
"value_after_depreciation",
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
)
for depr_entry in depr_entries or []:
frappe.get_doc("Journal Entry", depr_entry.name).cancel()
self.db_set(
"value_after_depreciation",
(flt(self.gross_purchase_amount) - flt(self.opening_accumulated_depreciation)),
)
def set_status(self, status=None):
"""Get and update status"""
@@ -434,6 +427,19 @@ class Asset(AccountsController):
status = "Cancelled"
return status
def get_value_after_depreciation(self, finance_book=None):
if not self.calculate_depreciation:
return flt(self.value_after_depreciation, self.precision("gross_purchase_amount"))
if not finance_book:
return flt(
self.get("finance_books")[0].value_after_depreciation, self.precision("gross_purchase_amount")
)
for row in self.get("finance_books"):
if finance_book == row.finance_book:
return flt(row.value_after_depreciation, self.precision("gross_purchase_amount"))
def get_default_finance_book_idx(self):
if not self.get("default_finance_book") and self.company:
self.default_finance_book = erpnext.get_default_finance_book(self.company)
@@ -443,6 +449,43 @@ class Asset(AccountsController):
if d.finance_book == self.default_finance_book:
return cint(d.idx) - 1
@frappe.whitelist()
def get_manual_depreciation_entries(self):
(_, _, depreciation_expense_account) = get_depreciation_accounts(self)
gle = frappe.qb.DocType("GL Entry")
records = (
frappe.qb.from_(gle)
.select(gle.voucher_no.as_("name"), gle.debit.as_("value"), gle.posting_date)
.where(gle.against_voucher == self.name)
.where(gle.account == depreciation_expense_account)
.where(gle.debit != 0)
.where(gle.is_cancelled == 0)
.orderby(gle.posting_date)
).run(as_dict=True)
return records
@erpnext.allow_regional
def get_depreciation_amount(self, depreciable_value, fb_row):
if fb_row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not self.flags.increase_in_asset_life:
depreciation_amount = (
flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
) / flt(fb_row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
) / (date_diff(self.to_date, self.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100))
return depreciation_amount
def validate_make_gl_entry(self):
purchase_document = self.get_purchase_document()
if not purchase_document:
@@ -603,7 +646,6 @@ def update_maintenance_status():
def make_post_gl_entry():
asset_categories = frappe.db.get_all("Asset Category", fields=["name", "enable_cwip_accounting"])
for asset_category in asset_categories:
@@ -756,7 +798,7 @@ def make_journal_entry(asset_name):
depreciation_expense_account,
) = get_depreciation_accounts(asset)
depreciation_cost_center, depreciation_series = frappe.db.get_value(
depreciation_cost_center, depreciation_series = frappe.get_cached_value(
"Company", asset.company, ["depreciation_cost_center", "series_for_depreciation_entry"]
)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
@@ -821,6 +863,13 @@ def is_cwip_accounting_enabled(asset_category):
return cint(frappe.db.get_value("Asset Category", asset_category, "enable_cwip_accounting"))
@frappe.whitelist()
def get_asset_value_after_depreciation(asset_name, finance_book=None):
asset = frappe.get_doc("Asset", asset_name)
return asset.get_value_after_depreciation(finance_book)
def get_total_days(date, frequency):
period_start_date = add_months(date, cint(frequency) * -1)
@@ -886,7 +935,7 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
)
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, asset, row)
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(asset, row)
accumulated_depreciation = 0
@@ -938,7 +987,7 @@ def create_new_asset_after_split(asset, split_qty):
)
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
set_draft_asset_depr_schedule_details(new_asset_depr_schedule_doc, new_asset, row)
new_asset_depr_schedule_doc.set_draft_asset_depr_schedule_details(new_asset, row)
accumulated_depreciation = 0

View File

@@ -4,7 +4,18 @@
import frappe
from frappe import _
from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, nowdate, today
from frappe.utils import (
add_months,
cint,
flt,
get_last_day,
get_link_to_form,
getdate,
is_last_day_of_the_month,
nowdate,
today,
)
from frappe.utils.user import get_users_with_role
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
@@ -18,7 +29,7 @@ from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_sched
)
def post_depreciation_entries(date=None, commit=True):
def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
if not cint(
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
@@ -27,13 +38,24 @@ def post_depreciation_entries(date=None, commit=True):
if not date:
date = today()
failed_asset_names = []
for asset_name in get_depreciable_assets(date):
asset_doc = frappe.get_doc("Asset", asset_name)
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
if commit:
try:
make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
frappe.db.commit()
except Exception as e:
frappe.db.rollback()
failed_asset_names.append(asset_name)
if failed_asset_names:
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
notify_depr_entry_posting_error(failed_asset_names)
frappe.db.commit()
def get_depreciable_assets(date):
@@ -146,6 +168,8 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None):
row.value_after_depreciation -= d.depreciation_amount
row.db_update()
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Successful")
asset.set_status()
return asset_depr_schedule_doc
@@ -209,6 +233,42 @@ def get_credit_and_debit_accounts(accumulated_depreciation_account, depreciation
return credit_account, debit_account
def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
for asset_name in failed_asset_names:
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
def notify_depr_entry_posting_error(failed_asset_names):
recipients = get_users_with_role("Accounts Manager")
if not recipients:
recipients = get_users_with_role("System Manager")
subject = _("Error while posting depreciation entries")
asset_links = get_comma_separated_asset_links(failed_asset_names)
message = (
_("Hi,")
+ "<br>"
+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+ "."
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
def get_comma_separated_asset_links(asset_names):
asset_links = []
for asset_name in asset_names:
asset_links.append(get_link_to_form("Asset", asset_name))
asset_links = ", ".join(asset_links)
return asset_links
@frappe.whitelist()
def scrap_asset(asset_name):
asset = frappe.get_doc("Asset", asset_name)
@@ -295,12 +355,12 @@ def reset_depreciation_schedule(asset_doc, date, notes):
asset_doc, notes, date_of_return=date
)
modify_depreciation_schedule_for_asset_repairs(asset_doc)
modify_depreciation_schedule_for_asset_repairs(asset_doc, notes)
asset_doc.save()
def modify_depreciation_schedule_for_asset_repairs(asset):
def modify_depreciation_schedule_for_asset_repairs(asset, notes):
asset_repairs = frappe.get_all(
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
)
@@ -309,10 +369,6 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
if repair.increase_in_asset_life:
asset_repair = frappe.get_doc("Asset Repair", repair.name)
asset_repair.modify_depreciation_schedule()
notes = _("This schedule was created when Asset {0} went through Asset Repair {1}.").format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(asset_repair.doctype, asset_repair.name),
)
make_new_active_asset_depr_schedules_and_cancel_current_ones(asset, notes)
@@ -354,6 +410,9 @@ def disposal_was_made_on_original_schedule_date(schedule_idx, row, posting_date_
row.depreciation_start_date, schedule_idx * cint(row.frequency_of_depreciation)
)
if is_last_day_of_the_month(row.depreciation_start_date):
orginal_schedule_date = get_last_day(orginal_schedule_date)
if orginal_schedule_date == posting_date_of_disposal:
return True
@@ -474,18 +533,8 @@ def get_asset_details(asset, finance_book=None):
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
depreciation_cost_center = asset.cost_center or depreciation_cost_center
idx = 1
if finance_book:
for d in asset.finance_books:
if d.finance_book == finance_book:
idx = d.idx
break
value_after_depreciation = asset.get_value_after_depreciation(finance_book)
value_after_depreciation = (
asset.finance_books[idx - 1].value_after_depreciation
if asset.calculate_depreciation
else asset.value_after_depreciation
)
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
return (

View File

@@ -16,6 +16,7 @@ from frappe.utils import (
nowdate,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.assets.doctype.asset.asset import (
make_sales_invoice,
@@ -28,7 +29,6 @@ from erpnext.assets.doctype.asset.depreciation import (
scrap_asset,
)
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
clear_depr_schedule,
get_asset_depr_schedule_doc,
get_depr_schedule,
)
@@ -924,11 +924,6 @@ class TestDepreciationBasics(AssetSetup):
def test_get_depreciation_amount(self):
"""Tests if get_depreciation_amount() returns the right value."""
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_depreciation_amount,
)
asset = create_asset(item_code="Macbook Pro", available_for_use_date="2019-12-31")
asset.calculate_depreciation = 1
@@ -943,7 +938,7 @@ class TestDepreciationBasics(AssetSetup):
},
)
depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
depreciation_amount = asset.get_depreciation_amount(100000, asset.finance_books[0])
self.assertEqual(depreciation_amount, 30000)
def test_make_depr_schedule(self):
@@ -1259,7 +1254,7 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
clear_depr_schedule(asset_depr_schedule_doc)
asset_depr_schedule_doc.clear_depr_schedule()
self.assertEqual(len(asset_depr_schedule_doc.get("depreciation_schedule")), 1)
@@ -1308,19 +1303,19 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc_1 = get_asset_depr_schedule_doc(
asset.name, "Active", "Test Finance Book 1"
)
clear_depr_schedule(asset_depr_schedule_doc_1)
asset_depr_schedule_doc_1.clear_depr_schedule()
self.assertEqual(len(asset_depr_schedule_doc_1.get("depreciation_schedule")), 3)
asset_depr_schedule_doc_2 = get_asset_depr_schedule_doc(
asset.name, "Active", "Test Finance Book 2"
)
clear_depr_schedule(asset_depr_schedule_doc_2)
asset_depr_schedule_doc_2.clear_depr_schedule()
self.assertEqual(len(asset_depr_schedule_doc_2.get("depreciation_schedule")), 3)
asset_depr_schedule_doc_3 = get_asset_depr_schedule_doc(
asset.name, "Active", "Test Finance Book 3"
)
clear_depr_schedule(asset_depr_schedule_doc_3)
asset_depr_schedule_doc_3.clear_depr_schedule()
self.assertEqual(len(asset_depr_schedule_doc_3.get("depreciation_schedule")), 0)
def test_depreciation_schedules_are_set_up_for_multiple_finance_books(self):
@@ -1503,6 +1498,36 @@ class TestDepreciationBasics(AssetSetup):
for i, schedule in enumerate(get_depr_schedule(asset.name, "Active")):
self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
def test_manual_depreciation_for_existing_asset(self):
asset = create_asset(
item_code="Macbook Pro",
is_existing_asset=1,
purchase_date="2020-01-30",
available_for_use_date="2020-01-30",
submit=1,
)
self.assertEqual(asset.status, "Submitted")
self.assertEqual(asset.get("value_after_depreciation"), 100000)
jv = make_journal_entry(
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
)
for d in jv.accounts:
d.reference_type = "Asset"
d.reference_name = asset.name
jv.voucher_type = "Depreciation Entry"
jv.insert()
jv.submit()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 99900)
jv.cancel()
asset.reload()
self.assertEqual(asset.get("value_after_depreciation"), 100000)
def create_asset_data():
if not frappe.db.exists("Asset Category", "Computers"):
@@ -1549,6 +1574,7 @@ def create_asset(**args):
"asset_owner": args.asset_owner or "Company",
"is_existing_asset": args.is_existing_asset or 1,
"asset_quantity": args.get("asset_quantity") or 1,
"depr_entry_posting_status": args.depr_entry_posting_status or "",
}
)

View File

@@ -8,9 +8,9 @@ import frappe
# import erpnext
from frappe import _
from frappe.utils import cint, flt, get_link_to_form
from six import string_types
import erpnext
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import (
depreciate_asset,
get_gl_entries_on_asset_disposal,
@@ -22,9 +22,6 @@ from erpnext.assets.doctype.asset_category.asset_category import get_asset_categ
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
make_new_active_asset_depr_schedules_and_cancel_current_ones,
)
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value,
)
from erpnext.controllers.stock_controller import StockController
from erpnext.setup.doctype.brand.brand import get_brand_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@@ -262,7 +259,9 @@ class AssetCapitalization(StockController):
for d in self.get("asset_items"):
if d.asset:
finance_book = d.get("finance_book") or self.get("finance_book")
d.current_asset_value = flt(get_current_asset_value(d.asset, finance_book=finance_book))
d.current_asset_value = flt(
get_asset_value_after_depreciation(d.asset, finance_book=finance_book)
)
d.asset_value = get_value_after_depreciation_on_disposal_date(
d.asset, self.posting_date, finance_book=finance_book
)
@@ -431,7 +430,7 @@ class AssetCapitalization(StockController):
if asset.calculate_depreciation:
notes = _(
"This schedule was created when Asset {0} was consumed when Asset Capitalization {1} was submitted."
"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.get("name"))
)
@@ -522,7 +521,7 @@ class AssetCapitalization(StockController):
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
notes = _(
"This schedule was created when target Asset {0} was updated when Asset Capitalization {1} was submitted."
"This schedule was created when target Asset {0} was updated through Asset Capitalization {1}."
).format(
get_link_to_form(asset_doc.doctype, asset_doc.name), get_link_to_form(self.doctype, self.name)
)
@@ -538,7 +537,7 @@ class AssetCapitalization(StockController):
if asset.calculate_depreciation:
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
notes = _(
"This schedule was created when Asset {0} was restored when Asset Capitalization {1} was cancelled."
"This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation."
).format(
get_link_to_form(asset.doctype, asset.name), get_link_to_form(self.doctype, self.name)
)
@@ -626,7 +625,7 @@ def get_target_asset_details(asset=None, company=None):
@frappe.whitelist()
def get_consumed_stock_item_details(args):
if isinstance(args, string_types):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@@ -678,7 +677,7 @@ def get_consumed_stock_item_details(args):
@frappe.whitelist()
def get_warehouse_details(args):
if isinstance(args, string_types):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@@ -694,7 +693,7 @@ def get_warehouse_details(args):
@frappe.whitelist()
def get_consumed_asset_details(args):
if isinstance(args, string_types):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@@ -714,7 +713,7 @@ def get_consumed_asset_details(args):
if args.asset:
out.current_asset_value = flt(
get_current_asset_value(args.asset, finance_book=args.finance_book)
get_asset_value_after_depreciation(args.asset, finance_book=args.finance_book)
)
out.asset_value = get_value_after_depreciation_on_disposal_date(
args.asset, args.posting_date, finance_book=args.finance_book
@@ -746,7 +745,7 @@ def get_consumed_asset_details(args):
@frappe.whitelist()
def get_service_item_details(args):
if isinstance(args, string_types):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)

View File

@@ -159,7 +159,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2023-01-02 15:38:30.766779",
"modified": "2023-01-16 21:08:21.421260",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Depreciation Schedule",

View File

@@ -4,17 +4,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import (
add_days,
add_months,
cint,
date_diff,
flt,
get_last_day,
is_last_day_of_the_month,
)
import erpnext
from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month
class AssetDepreciationSchedule(Document):
@@ -83,7 +73,267 @@ class AssetDepreciationSchedule(Document):
)
asset_finance_book_doc = frappe.get_doc("Asset Finance Book", asset_finance_book_name)
prepare_draft_asset_depr_schedule_data(self, asset_doc, asset_finance_book_doc)
self.prepare_draft_asset_depr_schedule_data(asset_doc, asset_finance_book_doc)
def prepare_draft_asset_depr_schedule_data(
self,
asset_doc,
row,
date_of_disposal=None,
date_of_return=None,
update_asset_finance_book_row=True,
):
self.set_draft_asset_depr_schedule_details(asset_doc, row)
self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
def set_draft_asset_depr_schedule_details(self, asset_doc, row):
self.asset = asset_doc.name
self.finance_book = row.finance_book
self.finance_book_id = row.idx
self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation
self.depreciation_method = row.depreciation_method
self.total_number_of_depreciations = row.total_number_of_depreciations
self.frequency_of_depreciation = row.frequency_of_depreciation
self.rate_of_depreciation = row.rate_of_depreciation
self.expected_value_after_useful_life = row.expected_value_after_useful_life
self.status = "Draft"
def make_depr_schedule(
self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
):
if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"):
self.depreciation_schedule = []
if not asset_doc.available_for_use_date:
return
start = self.clear_depr_schedule()
self._make_depr_schedule(asset_doc, row, start, date_of_disposal, update_asset_finance_book_row)
def clear_depr_schedule(self):
start = 0
num_of_depreciations_completed = 0
depr_schedule = []
for schedule in self.get("depreciation_schedule"):
if schedule.journal_entry:
num_of_depreciations_completed += 1
depr_schedule.append(schedule)
else:
start = num_of_depreciations_completed
break
self.depreciation_schedule = depr_schedule
return start
def _make_depr_schedule(
self, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
):
asset_doc.validate_asset_finance_books(row)
value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
row.value_after_depreciation = value_after_depreciation
if update_asset_finance_book_row:
row.db_update()
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
asset_doc.number_of_depreciations_booked
)
has_pro_rata = asset_doc.check_is_pro_rata(row)
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
for n in range(start, number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = asset_doc.get_depreciation_amount(value_after_depreciation, row)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(
row.depreciation_start_date, n * cint(row.frequency_of_depreciation)
)
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, -row.frequency_of_depreciation + 1)
# if asset is being sold or scrapped
if date_of_disposal:
from_date = asset_doc.available_for_use_date
if self.depreciation_schedule:
from_date = self.depreciation_schedule[-1].schedule_date
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, from_date, date_of_disposal
)
if depreciation_amount > 0:
self.add_depr_schedule_row(
date_of_disposal,
depreciation_amount,
row.depreciation_method,
)
break
# For first row
if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
from_date = add_days(
asset_doc.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, from_date, row.depreciation_start_date
)
# 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(row.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not asset_doc.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
asset_doc.to_date = add_months(
asset_doc.available_for_use_date,
(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, schedule_date, asset_doc.to_date
)
depreciation_amount = self.get_adjusted_depreciation_amount(
depreciation_amount_without_pro_rata, depreciation_amount
)
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
value_after_depreciation -= flt(
depreciation_amount, asset_doc.precision("gross_purchase_amount")
)
# Adjust depreciation amount in the last period based on the expected value after useful life
if row.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != row.expected_value_after_useful_life
)
or value_after_depreciation < row.expected_value_after_useful_life
):
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
self.add_depr_schedule_row(
schedule_date,
depreciation_amount,
row.depreciation_method,
)
# to ensure that final accumulated depreciation amount is accurate
def get_adjusted_depreciation_amount(
self, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
):
if not self.opening_accumulated_depreciation:
depreciation_amount_for_first_row = self.get_depreciation_amount_for_first_row()
if (
depreciation_amount_for_first_row + depreciation_amount_for_last_row
!= depreciation_amount_without_pro_rata
):
depreciation_amount_for_last_row = (
depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
)
return depreciation_amount_for_last_row
def get_depreciation_amount_for_first_row(self):
return self.get("depreciation_schedule")[0].depreciation_amount
def add_depr_schedule_row(
self,
schedule_date,
depreciation_amount,
depreciation_method,
):
self.append(
"depreciation_schedule",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": depreciation_method,
},
)
def set_accumulated_depreciation(
self,
row,
date_of_disposal=None,
date_of_return=None,
ignore_booked_entry=False,
):
straight_line_idx = [
d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line"
]
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(row.value_after_depreciation)
for i, d in enumerate(self.get("depreciation_schedule")):
if ignore_booked_entry and d.journal_entry:
continue
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
if (
straight_line_idx
and i == max(straight_line_idx) - 1
and not date_of_disposal
and not date_of_return
):
depreciation_amount += flt(
value_after_depreciation - flt(row.expected_value_after_useful_life),
d.precision("depreciation_amount"),
)
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
d.accumulated_depreciation_amount = flt(
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
)
def _get_value_after_depreciation_for_making_schedule(asset_doc, fb_row):
if asset_doc.docstatus == 1 and fb_row.value_after_depreciation:
value_after_depreciation = flt(fb_row.value_after_depreciation)
else:
value_after_depreciation = flt(asset_doc.gross_purchase_amount) - flt(
asset_doc.opening_accumulated_depreciation
)
return value_after_depreciation
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
@@ -108,7 +358,7 @@ def make_draft_asset_depr_schedules(asset_doc):
def make_draft_asset_depr_schedule(asset_doc, row):
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row)
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row)
asset_depr_schedule_doc.insert()
@@ -120,41 +370,11 @@ def update_draft_asset_depr_schedules(asset_doc):
if not asset_depr_schedule_doc:
continue
prepare_draft_asset_depr_schedule_data(asset_depr_schedule_doc, asset_doc, row)
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(asset_doc, row)
asset_depr_schedule_doc.save()
def prepare_draft_asset_depr_schedule_data(
asset_depr_schedule_doc,
asset_doc,
row,
date_of_disposal=None,
date_of_return=None,
update_asset_finance_book_row=True,
):
set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row)
make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row
)
set_accumulated_depreciation(asset_depr_schedule_doc, row, date_of_disposal, date_of_return)
def set_draft_asset_depr_schedule_details(asset_depr_schedule_doc, asset_doc, row):
asset_depr_schedule_doc.asset = asset_doc.name
asset_depr_schedule_doc.finance_book = row.finance_book
asset_depr_schedule_doc.finance_book_id = row.idx
asset_depr_schedule_doc.opening_accumulated_depreciation = (
asset_doc.opening_accumulated_depreciation
)
asset_depr_schedule_doc.depreciation_method = row.depreciation_method
asset_depr_schedule_doc.total_number_of_depreciations = row.total_number_of_depreciations
asset_depr_schedule_doc.frequency_of_depreciation = row.frequency_of_depreciation
asset_depr_schedule_doc.rate_of_depreciation = row.rate_of_depreciation
asset_depr_schedule_doc.expected_value_after_useful_life = row.expected_value_after_useful_life
asset_depr_schedule_doc.status = "Draft"
def convert_draft_asset_depr_schedules_into_active(asset_doc):
for row in asset_doc.get("finance_books"):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_doc.name, "Draft", row.finance_book)
@@ -192,8 +412,8 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
make_depr_schedule(new_asset_depr_schedule_doc, asset_doc, row, date_of_disposal)
set_accumulated_depreciation(new_asset_depr_schedule_doc, row, date_of_disposal, date_of_return)
new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal)
new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
new_asset_depr_schedule_doc.notes = notes
@@ -208,8 +428,7 @@ def get_temp_asset_depr_schedule_doc(
):
asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
prepare_draft_asset_depr_schedule_data(
asset_depr_schedule_doc,
asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
asset_doc,
row,
date_of_disposal,
@@ -220,21 +439,6 @@ def get_temp_asset_depr_schedule_doc(
return asset_depr_schedule_doc
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
finance_book_filter = ["finance_book", "is", "not set"]
if finance_book:
finance_book_filter = ["finance_book", "=", finance_book]
return frappe.db.get_value(
doctype="Asset Depreciation Schedule",
filters=[
["asset", "=", asset_name],
finance_book_filter,
["status", "=", status],
],
)
@frappe.whitelist()
def get_depr_schedule(asset_name, status, finance_book=None):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset_name, status, finance_book)
@@ -256,261 +460,16 @@ def get_asset_depr_schedule_doc(asset_name, status, finance_book=None):
return asset_depr_schedule_doc
def make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
):
if row.depreciation_method != "Manual" and not asset_depr_schedule_doc.get(
"depreciation_schedule"
):
asset_depr_schedule_doc.depreciation_schedule = []
def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
finance_book_filter = ["finance_book", "is", "not set"]
if finance_book:
finance_book_filter = ["finance_book", "=", finance_book]
if not asset_doc.available_for_use_date:
return
start = clear_depr_schedule(asset_depr_schedule_doc)
_make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
return frappe.db.get_value(
doctype="Asset Depreciation Schedule",
filters=[
["asset", "=", asset_name],
finance_book_filter,
["status", "=", status],
],
)
def clear_depr_schedule(asset_depr_schedule_doc):
start = 0
num_of_depreciations_completed = 0
depr_schedule = []
for schedule in asset_depr_schedule_doc.get("depreciation_schedule"):
if schedule.journal_entry:
num_of_depreciations_completed += 1
depr_schedule.append(schedule)
else:
start = num_of_depreciations_completed
break
asset_depr_schedule_doc.depreciation_schedule = depr_schedule
return start
def _make_depr_schedule(
asset_depr_schedule_doc, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
):
asset_doc.validate_asset_finance_books(row)
value_after_depreciation = asset_doc._get_value_after_depreciation(row)
row.value_after_depreciation = value_after_depreciation
if update_asset_finance_book_row:
row.db_update()
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
asset_doc.number_of_depreciations_booked
)
has_pro_rata = asset_doc.check_is_pro_rata(row)
if has_pro_rata:
number_of_pending_depreciations += 1
skip_row = False
should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
for n in range(start, number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
continue
depreciation_amount = get_depreciation_amount(asset_doc, value_after_depreciation, row)
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
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, -row.frequency_of_depreciation + 1)
# if asset is being sold or scrapped
if date_of_disposal:
from_date = asset_doc.available_for_use_date
if asset_depr_schedule_doc.depreciation_schedule:
from_date = asset_depr_schedule_doc.depreciation_schedule[-1].schedule_date
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, from_date, date_of_disposal
)
if depreciation_amount > 0:
add_depr_schedule_row(
asset_depr_schedule_doc,
date_of_disposal,
depreciation_amount,
row.depreciation_method,
)
break
# For first row
if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
from_date = add_days(
asset_doc.available_for_use_date, -1
) # needed to calc depr amount for available_for_use_date too
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, from_date, row.depreciation_start_date
)
# 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(row.depreciation_start_date, -months + 1)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
if not asset_doc.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
asset_doc.to_date = add_months(
asset_doc.available_for_use_date,
(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
)
depreciation_amount_without_pro_rata = depreciation_amount
depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
row, depreciation_amount, schedule_date, asset_doc.to_date
)
depreciation_amount = get_adjusted_depreciation_amount(
asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount
)
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
value_after_depreciation -= flt(
depreciation_amount, asset_doc.precision("gross_purchase_amount")
)
# Adjust depreciation amount in the last period based on the expected value after useful life
if row.expected_value_after_useful_life and (
(
n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != row.expected_value_after_useful_life
)
or value_after_depreciation < row.expected_value_after_useful_life
):
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
skip_row = True
if depreciation_amount > 0:
add_depr_schedule_row(
asset_depr_schedule_doc,
schedule_date,
depreciation_amount,
row.depreciation_method,
)
# to ensure that final accumulated depreciation amount is accurate
def get_adjusted_depreciation_amount(
asset_depr_schedule_doc, depreciation_amount_without_pro_rata, depreciation_amount_for_last_row
):
if not asset_depr_schedule_doc.opening_accumulated_depreciation:
depreciation_amount_for_first_row = get_depreciation_amount_for_first_row(
asset_depr_schedule_doc
)
if (
depreciation_amount_for_first_row + depreciation_amount_for_last_row
!= depreciation_amount_without_pro_rata
):
depreciation_amount_for_last_row = (
depreciation_amount_without_pro_rata - depreciation_amount_for_first_row
)
return depreciation_amount_for_last_row
def get_depreciation_amount_for_first_row(asset_depr_schedule_doc):
return asset_depr_schedule_doc.get("depreciation_schedule")[0].depreciation_amount
@erpnext.allow_regional
def get_depreciation_amount(asset_doc, depreciable_value, row):
if row.depreciation_method in ("Straight Line", "Manual"):
# if the Depreciation Schedule is being prepared for the first time
if not asset_doc.flags.increase_in_asset_life:
depreciation_amount = (
flt(asset_doc.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
) / (date_diff(asset_doc.to_date, asset_doc.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
return depreciation_amount
def add_depr_schedule_row(
asset_depr_schedule_doc,
schedule_date,
depreciation_amount,
depreciation_method,
):
asset_depr_schedule_doc.append(
"depreciation_schedule",
{
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": depreciation_method,
},
)
def set_accumulated_depreciation(
asset_depr_schedule_doc,
row,
date_of_disposal=None,
date_of_return=None,
ignore_booked_entry=False,
):
straight_line_idx = [
d.idx
for d in asset_depr_schedule_doc.get("depreciation_schedule")
if d.depreciation_method == "Straight Line"
]
accumulated_depreciation = flt(asset_depr_schedule_doc.opening_accumulated_depreciation)
value_after_depreciation = flt(row.value_after_depreciation)
for i, d in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
if ignore_booked_entry and d.journal_entry:
continue
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
if (
straight_line_idx
and i == max(straight_line_idx) - 1
and not date_of_disposal
and not date_of_return
):
depreciation_amount += flt(
value_after_depreciation - flt(row.expected_value_after_useful_life),
d.precision("depreciation_amount"),
)
d.depreciation_amount = depreciation_amount
accumulated_depreciation += d.depreciation_amount
d.accumulated_depreciation_amount = flt(
accumulated_depreciation, d.precision("accumulated_depreciation_amount")
)

View File

@@ -56,8 +56,11 @@ class AssetRepair(AccountsController):
):
self.modify_depreciation_schedule()
notes = _("This schedule was created when Asset Repair {0} was submitted.").format(
get_link_to_form(self.doctype, self.name)
notes = _(
"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
).format(
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
@@ -80,8 +83,9 @@ class AssetRepair(AccountsController):
):
self.revert_depreciation_schedule_on_cancellation()
notes = _("This schedule was created when Asset Repair {0} was cancelled.").format(
get_link_to_form(self.doctype, self.name)
notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
get_link_to_form(self.doctype, self.name),
)
self.asset_doc.flags.ignore_validate_update_after_submit = True
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)

View File

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

View File

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

View File

@@ -10,11 +10,10 @@ from frappe.utils import date_diff, flt, formatdate, get_link_to_form, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
get_depreciation_amount,
set_accumulated_depreciation,
)
@@ -46,7 +45,7 @@ class AssetValueAdjustment(Document):
def set_current_asset_value(self):
if not self.current_asset_value and self.asset:
self.current_asset_value = get_current_asset_value(self.asset, self.finance_book)
self.current_asset_value = get_asset_value_after_depreciation(self.asset, self.finance_book)
def make_depreciation_entry(self):
asset = frappe.get_doc("Asset", self.asset)
@@ -127,12 +126,20 @@ class AssetValueAdjustment(Document):
current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
current_asset_depr_schedule_doc.cancel()
notes = _(
"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.get("doctype"), self.get("name")),
)
if self.docstatus == 1:
notes = _(
"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.get("doctype"), self.get("name")),
)
elif self.docstatus == 2:
notes = _(
"This schedule was created when Asset {0}'s Asset Value Adjustment {1} was cancelled."
).format(
get_link_to_form(asset.doctype, asset.name),
get_link_to_form(self.get("doctype"), self.get("name")),
)
new_asset_depr_schedule_doc.notes = notes
new_asset_depr_schedule_doc.insert()
@@ -155,7 +162,7 @@ class AssetValueAdjustment(Document):
depreciation_amount = days * rate_per_day
from_date = data.schedule_date
else:
depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, d)
if depreciation_amount:
value_after_depreciation -= flt(depreciation_amount)
@@ -163,18 +170,9 @@ class AssetValueAdjustment(Document):
d.db_update()
set_accumulated_depreciation(new_asset_depr_schedule_doc, d, ignore_booked_entry=True)
new_asset_depr_schedule_doc.set_accumulated_depreciation(d, ignore_booked_entry=True)
for asset_data in depr_schedule:
if not asset_data.journal_entry:
asset_data.db_update()
new_asset_depr_schedule_doc.submit()
@frappe.whitelist()
def get_current_asset_value(asset, finance_book=None):
cond = {"parent": asset, "parenttype": "Asset"}
if finance_book:
cond.update({"finance_book": finance_book})
return frappe.db.get_value("Asset Finance Book", cond, "value_after_depreciation")

View File

@@ -6,13 +6,11 @@ import unittest
import frappe
from frappe.utils import add_days, get_last_day, nowdate
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
from erpnext.assets.doctype.asset.test_asset import create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
)
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
get_current_asset_value,
)
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -46,7 +44,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
)
asset_doc.submit()
current_value = get_current_asset_value(asset_doc.name)
current_value = get_asset_value_after_depreciation(asset_doc.name)
self.assertEqual(current_value, 100000.0)
def test_asset_depreciation_value_adjustment(self):
@@ -79,7 +77,7 @@ class TestAssetValueAdjustment(unittest.TestCase):
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
self.assertEquals(first_asset_depr_schedule.status, "Active")
current_value = get_current_asset_value(asset_doc.name)
current_value = get_asset_value_after_depreciation(asset_doc.name)
adj_doc = make_asset_value_adjustment(
asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
)

View File

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

View File

@@ -15,17 +15,6 @@ class TestBulkTransactionLog(unittest.TestCase):
create_customer()
create_item()
def test_for_single_record(self):
so_name = create_so()
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
data = frappe.db.get_list(
"Sales Invoice",
filters={"posting_date": date.today(), "customer": "Bulk Customer"},
fields=["*"],
)
if not data:
self.fail("No Sales Invoice Created !")
def test_entry_in_log(self):
so_name = create_so()
transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")

View File

@@ -9,8 +9,8 @@
"supplier_and_price_defaults_section",
"supp_master_name",
"supplier_group",
"column_break_4",
"buying_price_list",
"column_break_4",
"maintain_same_rate_action",
"role_to_override_stop_action",
"transaction_settings_section",
@@ -20,6 +20,7 @@
"maintain_same_rate",
"allow_multiple_items",
"bill_for_rejected_quantity_in_purchase_invoice",
"disable_last_purchase_rate",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
@@ -71,7 +72,7 @@
},
{
"fieldname": "subcontract",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Subcontracting Settings"
},
{
@@ -118,8 +119,8 @@
},
{
"fieldname": "supplier_and_price_defaults_section",
"fieldtype": "Section Break",
"label": "Supplier and Price Defaults"
"fieldtype": "Tab Break",
"label": "Naming Series and Price Defaults"
},
{
"fieldname": "column_break_4",
@@ -127,12 +128,18 @@
},
{
"fieldname": "transaction_settings_section",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Transaction Settings"
},
{
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "disable_last_purchase_rate",
"fieldtype": "Check",
"label": "Disable Last Purchase Rate"
}
],
"icon": "fa fa-cog",
@@ -140,7 +147,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-09-27 10:50:27.050252",
"modified": "2023-01-09 17:08:28.828173",
"modified_by": "Administrator",
"module": "Buying",
"name": "Buying Settings",

View File

@@ -1221,6 +1221,7 @@
},
{
"default": "0",
"depends_on": "apply_tds",
"fieldname": "tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
@@ -1230,12 +1231,13 @@
"read_only": 1
},
{
"depends_on": "apply_tds",
"fieldname": "base_tax_withholding_net_total",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Tax Withholding Net Total",
"no_copy": 1,
"options": "currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1269,7 +1271,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-12-25 18:08:59.074182",
"modified": "2023-01-28 18:59:16.322824",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",

View File

@@ -219,20 +219,16 @@ class PurchaseOrder(BuyingController):
else:
if not frappe.get_value("Item", item.fg_item, "is_sub_contracted_item"):
frappe.throw(
_(
"Row #{0}: Finished Good Item {1} must be a sub-contracted item for service item {2}"
).format(item.idx, item.fg_item, item.item_code)
_("Row #{0}: Finished Good Item {1} must be a sub-contracted item").format(
item.idx, item.fg_item
)
)
elif not frappe.get_value("Item", item.fg_item, "default_bom"):
frappe.throw(
_("Row #{0}: Default BOM not found for FG Item {1}").format(item.idx, item.fg_item)
)
if not item.fg_item_qty:
frappe.throw(
_("Row #{0}: Finished Good Item Qty is not specified for service item {0}").format(
item.idx, item.item_code
)
)
frappe.throw(_("Row #{0}: Finished Good Item Qty can not be zero").format(item.idx))
else:
for item in self.items:
item.set("fg_item", None)

View File

@@ -10,6 +10,7 @@ from frappe.utils import add_days, flt, getdate, nowdate
from frappe.utils.data import today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.party import get_due_date_from_template
from erpnext.buying.doctype.purchase_order.purchase_order import make_inter_company_sales_order
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as make_pi_from_po,
@@ -685,6 +686,12 @@ class TestPurchaseOrder(FrappeTestCase):
else:
raise Exception
def test_default_payment_terms(self):
due_date = get_due_date_from_template(
"_Test Payment Term Template 1", "2023-02-03", None
).strftime("%Y-%m-%d")
self.assertEqual(due_date, "2023-03-31")
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
po = create_purchase_order(do_not_save=1)
po.payment_terms_template = "_Test Payment Term Template"
@@ -743,9 +750,9 @@ class TestPurchaseOrder(FrappeTestCase):
pe = get_payment_entry("Purchase Order", po_doc.name)
pe.mode_of_payment = "Cash"
pe.paid_from = "Cash - _TC"
pe.source_exchange_rate = 80
pe.target_exchange_rate = 1
pe.paid_amount = po_doc.grand_total
pe.source_exchange_rate = 1
pe.target_exchange_rate = 80
pe.paid_amount = po_doc.base_grand_total
pe.save(ignore_permissions=True)
pe.submit()
@@ -889,6 +896,11 @@ class TestPurchaseOrder(FrappeTestCase):
self.assertEqual(po.status, "Completed")
self.assertEqual(mr.status, "Received")
def test_variant_item_po(self):
po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1)
self.assertRaises(frappe.ValidationError, po.save)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
@@ -994,8 +1006,8 @@ def create_purchase_order(**args):
},
)
po.set_missing_values()
if not args.do_not_save:
po.set_missing_values()
po.insert()
if not args.do_not_submit:
if po.is_subcontracted:

View File

@@ -124,12 +124,11 @@ frappe.ui.form.on("Request for Quotation",{
frappe.urllib.get_full_url(
"/api/method/erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_pdf?" +
new URLSearchParams({
doctype: frm.doc.doctype,
name: frm.doc.name,
supplier: data.supplier,
print_format: data.print_format || "Standard",
language: data.language || frappe.boot.lang,
letter_head: data.letter_head || frm.doc.letter_head || "",
letterhead: data.letter_head || frm.doc.letter_head || "",
}).toString()
)
);

View File

@@ -29,6 +29,7 @@
"message_for_supplier",
"terms_section_break",
"incoterm",
"named_place",
"tc_name",
"terms",
"printing_settings",
@@ -278,13 +279,19 @@
"fieldtype": "Link",
"label": "Incoterm",
"options": "Incoterm"
},
{
"depends_on": "incoterm",
"fieldname": "named_place",
"fieldtype": "Data",
"label": "Named Place"
}
],
"icon": "fa fa-shopping-cart",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-11-17 17:26:33.770993",
"modified": "2023-01-31 23:22:06.684694",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",

View File

@@ -3,6 +3,7 @@
import json
from typing import Optional
import frappe
from frappe import _
@@ -216,6 +217,7 @@ class RequestforQuotation(BuyingController):
recipients=data.email_id,
sender=sender,
attachments=attachments,
print_format=self.meta.default_print_format or "Standard",
send_email=True,
doctype=self.doctype,
name=self.name,
@@ -224,9 +226,7 @@ class RequestforQuotation(BuyingController):
frappe.msgprint(_("Email Sent to Supplier {0}").format(data.supplier))
def get_attachments(self):
attachments = [d.name for d in get_attachments(self.doctype, self.name)]
attachments.append(frappe.attach_print(self.doctype, self.name, doc=self))
return attachments
return [d.name for d in get_attachments(self.doctype, self.name)]
def update_rfq_supplier_status(self, sup_name=None):
for supplier in self.suppliers:
@@ -389,24 +389,26 @@ def create_rfq_items(sq_doc, supplier, data):
@frappe.whitelist()
def get_pdf(doctype, name, supplier, print_format=None, language=None, letter_head=None):
# permissions get checked in `download_pdf`
if doc := get_rfq_doc(doctype, name, supplier):
download_pdf(
doctype,
name,
print_format,
doc=doc,
language=language,
letter_head=letter_head or None,
)
def get_rfq_doc(doctype, name, supplier):
def get_pdf(
name: str,
supplier: str,
print_format: Optional[str] = None,
language: Optional[str] = None,
letterhead: Optional[str] = None,
):
doc = frappe.get_doc("Request for Quotation", name)
if supplier:
doc = frappe.get_doc(doctype, name)
doc.update_supplier_part_no(supplier)
return doc
# permissions get checked in `download_pdf`
download_pdf(
doc.doctype,
doc.name,
print_format,
doc=doc,
language=language,
letterhead=letterhead or None,
)
@frappe.whitelist()

View File

@@ -8,6 +8,7 @@ from frappe.utils import nowdate
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
create_supplier_quotation,
get_pdf,
make_supplier_quotation_from_rfq,
)
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
@@ -124,6 +125,11 @@ class TestRequestforQuotation(FrappeTestCase):
rfq.status = "Draft"
rfq.submit()
def test_get_pdf(self):
rfq = make_request_for_quotation()
get_pdf(rfq.name, rfq.get("suppliers")[0].supplier)
self.assertEqual(frappe.local.response.type, "pdf")
def make_request_for_quotation(**args):
"""

View File

@@ -15,60 +15,4 @@ from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestProcurementTracker(FrappeTestCase):
def test_result_for_procurement_tracker(self):
filters = {"company": "_Test Procurement Company", "cost_center": "Main - _TPC"}
expected_data = self.generate_expected_data()
report = execute(filters)
length = len(report[1])
self.assertEqual(expected_data, report[1][length - 1])
def generate_expected_data(self):
if not frappe.db.exists("Company", "_Test Procurement Company"):
frappe.get_doc(
dict(
doctype="Company",
company_name="_Test Procurement Company",
abbr="_TPC",
default_currency="INR",
country="Pakistan",
)
).insert()
warehouse = create_warehouse("_Test Procurement Warehouse", company="_Test Procurement Company")
mr = make_material_request(
company="_Test Procurement Company", warehouse=warehouse, cost_center="Main - _TPC"
)
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
po.get("items")[0].cost_center = "Main - _TPC"
po.submit()
pr = make_purchase_receipt(po.name)
pr.get("items")[0].cost_center = "Main - _TPC"
pr.submit()
date_obj = datetime.date(datetime.now())
po.load_from_db()
expected_data = {
"material_request_date": date_obj,
"cost_center": "Main - _TPC",
"project": None,
"requesting_site": "_Test Procurement Warehouse - _TPC",
"requestor": "Administrator",
"material_request_no": mr.name,
"item_code": "_Test Item",
"quantity": 10.0,
"unit_of_measurement": "_Test UOM",
"status": "To Bill",
"purchase_order_date": date_obj,
"purchase_order": po.name,
"supplier": "_Test Supplier",
"estimated_cost": 0.0,
"actual_cost": 0.0,
"purchase_order_amt": po.net_total,
"purchase_order_amt_in_company_currency": po.base_net_total,
"expected_delivery_date": date_obj,
"actual_delivery_date": date_obj,
}
return expected_data
pass

View File

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

View File

@@ -274,6 +274,9 @@ class BuyingController(SubcontractingController):
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
return
if not self.is_internal_transfer():
return
ref_doctype_map = {
"Purchase Order": "Sales Order Item",
"Purchase Receipt": "Delivery Note Item",
@@ -544,7 +547,9 @@ class BuyingController(SubcontractingController):
self.process_fixed_asset()
self.update_fixed_asset(field)
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value(
"Buying Settings", "disable_last_purchase_rate"
):
update_last_purchase_rate(self, is_submit=1)
def on_cancel(self):
@@ -553,7 +558,9 @@ class BuyingController(SubcontractingController):
if self.get("is_return"):
return
if self.doctype in ["Purchase Order", "Purchase Receipt"]:
if self.doctype in ["Purchase Order", "Purchase Receipt"] and not frappe.db.get_single_value(
"Buying Settings", "disable_last_purchase_rate"
):
update_last_purchase_rate(self, is_submit=0)
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
@@ -705,6 +712,8 @@ class BuyingController(SubcontractingController):
asset.purchase_date = self.posting_date
asset.supplier = self.supplier
elif self.docstatus == 2:
if asset.docstatus == 2:
continue
if asset.docstatus == 0:
asset.set(field, None)
asset.supplier = None

View File

@@ -37,7 +37,7 @@ def validate_return_against(doc):
if (
ref_doc.company == doc.company
and ref_doc.get(party_type) == doc.get(party_type)
and ref_doc.docstatus == 1
and ref_doc.docstatus.is_submitted()
):
# validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
@@ -252,6 +252,7 @@ def get_already_returned_items(doc):
child.parent = par.name and par.docstatus = 1
and par.is_return = 1 and par.return_against = %s
group by item_code
for update
""".format(
column, doc.doctype, doc.doctype
),
@@ -305,7 +306,7 @@ def get_returned_qty_map_for_row(return_against, party, row_name, doctype):
fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
# Used retrun against and supplier and is_retrun because there is an index added for it
data = frappe.db.get_list(
data = frappe.get_all(
doctype,
fields=fields,
filters=[

View File

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

View File

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

View File

@@ -74,24 +74,25 @@ class SubcontractingController(StockController):
)
if not is_stock_item:
msg = f"Item {item.item_name} must be a stock item."
frappe.throw(_(msg))
frappe.throw(_("Row {0}: Item {1} must be a stock item.").format(item.idx, item.item_name))
if not is_sub_contracted_item:
msg = f"Item {item.item_name} must be a subcontracted item."
frappe.throw(_(msg))
frappe.throw(
_("Row {0}: Item {1} must be a subcontracted item.").format(item.idx, item.item_name)
)
if item.bom:
bom = frappe.get_doc("BOM", item.bom)
if not bom.is_active:
msg = f"Please select an active BOM for Item {item.item_name}."
frappe.throw(_(msg))
frappe.throw(
_("Row {0}: Please select an active BOM for Item {1}.").format(item.idx, item.item_name)
)
if bom.item != item.item_code:
msg = f"Please select an valid BOM for Item {item.item_name}."
frappe.throw(_(msg))
frappe.throw(
_("Row {0}: Please select an valid BOM for Item {1}.").format(item.idx, item.item_name)
)
else:
msg = f"Please select a BOM for Item {item.item_name}."
frappe.throw(_(msg))
frappe.throw(_("Row {0}: Please select a BOM for Item {1}.").format(item.idx, item.item_name))
def __get_data_before_save(self):
item_dict = {}

View File

@@ -6,6 +6,7 @@ import json
import frappe
from frappe import _, scrub
from frappe.model.document import Document
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
import erpnext
@@ -20,7 +21,7 @@ from erpnext.stock.get_item_details import _get_item_tax_template
class calculate_taxes_and_totals(object):
def __init__(self, doc):
def __init__(self, doc: Document):
self.doc = doc
frappe.flags.round_off_applicable_accounts = []
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
@@ -677,7 +678,7 @@ class calculate_taxes_and_totals(object):
)
def calculate_total_advance(self):
if self.doc.docstatus < 2:
if not self.doc.docstatus.is_cancelled():
total_allocated_amount = sum(
flt(adv.allocated_amount, adv.precision("allocated_amount"))
for adv in self.doc.get("advances")
@@ -708,7 +709,7 @@ class calculate_taxes_and_totals(object):
)
)
if self.doc.docstatus == 0:
if self.doc.docstatus.is_draft():
if self.doc.get("write_off_outstanding_amount_automatically"):
self.doc.write_off_amount = 0

View File

@@ -312,7 +312,8 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Title",
"print_hide": 1
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "language",
@@ -514,11 +515,10 @@
"idx": 5,
"image_field": "image",
"links": [],
"modified": "2022-10-13 12:42:04.277879",
"modified": "2023-01-24 18:20:05.044791",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead",
"name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [

View File

@@ -282,6 +282,7 @@ def _make_customer(source_name, target_doc=None, ignore_permissions=False):
"contact_no": "phone_1",
"fax": "fax_1",
},
"field_no_map": ["disabled"],
}
},
target_doc,
@@ -390,7 +391,7 @@ def get_lead_details(lead, posting_date=None, company=None):
{
"territory": lead.territory,
"customer_name": lead.company_name or lead.lead_name,
"contact_display": " ".join(filter(None, [lead.salutation, lead.lead_name])),
"contact_display": " ".join(filter(None, [lead.lead_name])),
"contact_email": lead.email_id,
"contact_mobile": lead.mobile_no,
"contact_phone": lead.phone,

View File

@@ -26,10 +26,11 @@
}
],
"links": [],
"modified": "2021-02-08 12:51:48.971517",
"modified": "2023-02-10 00:51:44.973957",
"modified_by": "Administrator",
"module": "CRM",
"name": "Lead Source",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -58,5 +59,7 @@
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": [],
"translated_doctype": 1
}

View File

@@ -18,10 +18,11 @@
}
],
"links": [],
"modified": "2020-05-20 12:22:01.866472",
"modified": "2023-02-10 01:40:23.713390",
"modified_by": "Administrator",
"module": "CRM",
"name": "Sales Stage",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -40,5 +41,7 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
"states": [],
"track_changes": 1,
"translated_doctype": 1
}

View File

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

View File

@@ -11,6 +11,40 @@ frappe.query_reports["Loan Interest Report"] = {
"options": "Company",
"default": frappe.defaults.get_user_default("Company"),
"reqd": 1
}
},
{
"fieldname":"applicant_type",
"label": __("Applicant Type"),
"fieldtype": "Select",
"options": ["Customer", "Employee"],
"reqd": 1,
"default": "Customer",
on_change: function() {
frappe.query_report.set_filter_value('applicant', "");
}
},
{
"fieldname": "applicant",
"label": __("Applicant"),
"fieldtype": "Dynamic Link",
"get_options": function() {
var applicant_type = frappe.query_report.get_filter_value('applicant_type');
var applicant = frappe.query_report.get_filter_value('applicant');
if(applicant && !applicant_type) {
frappe.throw(__("Please select Applicant Type first"));
}
return applicant_type;
}
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
},
{
"fieldname":"to_date",
"label": __("From Date"),
"fieldtype": "Date",
},
]
};

View File

@@ -13,12 +13,12 @@ from erpnext.loan_management.report.applicant_wise_loan_security_exposure.applic
def execute(filters=None):
columns = get_columns(filters)
columns = get_columns()
data = get_active_loan_details(filters)
return columns, data
def get_columns(filters):
def get_columns():
columns = [
{"label": _("Loan"), "fieldname": "loan", "fieldtype": "Link", "options": "Loan", "width": 160},
{"label": _("Status"), "fieldname": "status", "fieldtype": "Data", "width": 160},
@@ -70,6 +70,13 @@ def get_columns(filters):
"options": "currency",
"width": 120,
},
{
"label": _("Accrued Principal"),
"fieldname": "accrued_principal",
"fieldtype": "Currency",
"options": "currency",
"width": 120,
},
{
"label": _("Total Repayment"),
"fieldname": "total_repayment",
@@ -137,11 +144,16 @@ def get_columns(filters):
def get_active_loan_details(filters):
filter_obj = {"status": ("!=", "Closed")}
filter_obj = {
"status": ("!=", "Closed"),
"docstatus": 1,
}
if filters.get("company"):
filter_obj.update({"company": filters.get("company")})
if filters.get("applicant"):
filter_obj.update({"applicant": filters.get("applicant")})
loan_details = frappe.get_all(
"Loan",
fields=[
@@ -167,8 +179,8 @@ def get_active_loan_details(filters):
sanctioned_amount_map = get_sanctioned_amount_map()
penal_interest_rate_map = get_penal_interest_rate_map()
payments = get_payments(loan_list)
accrual_map = get_interest_accruals(loan_list)
payments = get_payments(loan_list, filters)
accrual_map = get_interest_accruals(loan_list, filters)
currency = erpnext.get_company_currency(filters.get("company"))
for loan in loan_details:
@@ -183,6 +195,7 @@ def get_active_loan_details(filters):
- flt(loan.written_off_amount),
"total_repayment": flt(payments.get(loan.loan)),
"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
"accrued_principal": flt(accrual_map.get(loan.loan, {}).get("accrued_principal")),
"interest_outstanding": flt(accrual_map.get(loan.loan, {}).get("interest_outstanding")),
"penalty": flt(accrual_map.get(loan.loan, {}).get("penalty")),
"penalty_interest": penal_interest_rate_map.get(loan.loan_type),
@@ -212,20 +225,35 @@ def get_sanctioned_amount_map():
)
def get_payments(loans):
def get_payments(loans, filters):
query_filters = {"against_loan": ("in", loans)}
if filters.get("from_date"):
query_filters.update({"posting_date": (">=", filters.get("from_date"))})
if filters.get("to_date"):
query_filters.update({"posting_date": ("<=", filters.get("to_date"))})
return frappe._dict(
frappe.get_all(
"Loan Repayment",
fields=["against_loan", "sum(amount_paid)"],
filters={"against_loan": ("in", loans)},
filters=query_filters,
group_by="against_loan",
as_list=1,
)
)
def get_interest_accruals(loans):
def get_interest_accruals(loans, filters):
accrual_map = {}
query_filters = {"loan": ("in", loans)}
if filters.get("from_date"):
query_filters.update({"posting_date": (">=", filters.get("from_date"))})
if filters.get("to_date"):
query_filters.update({"posting_date": ("<=", filters.get("to_date"))})
interest_accruals = frappe.get_all(
"Loan Interest Accrual",
@@ -236,8 +264,9 @@ def get_interest_accruals(loans):
"penalty_amount",
"paid_interest_amount",
"accrual_type",
"payable_principal_amount",
],
filters={"loan": ("in", loans)},
filters=query_filters,
order_by="posting_date desc",
)
@@ -246,6 +275,7 @@ def get_interest_accruals(loans):
entry.loan,
{
"accrued_interest": 0.0,
"accrued_principal": 0.0,
"undue_interest": 0.0,
"interest_outstanding": 0.0,
"last_accrual_date": "",
@@ -270,6 +300,7 @@ def get_interest_accruals(loans):
accrual_map[entry.loan]["undue_interest"] += entry.interest_amount - entry.paid_interest_amount
accrual_map[entry.loan]["accrued_interest"] += entry.interest_amount
accrual_map[entry.loan]["accrued_principal"] += entry.payable_principal_amount
if last_accrual_date and getdate(entry.posting_date) == last_accrual_date:
accrual_map[entry.loan]["penalty"] = entry.penalty_amount

View File

@@ -0,0 +1,315 @@
{
"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",
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "loan",
"idx": 0,
"is_hidden": 0,
"label": "Loans",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Loan",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Type",
"link_count": 0,
"link_to": "Loan Type",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Application",
"link_count": 0,
"link_to": "Loan Application",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan",
"link_count": 0,
"link_to": "Loan",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Loan Processes",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Process Loan Security Shortfall",
"link_count": 0,
"link_to": "Process Loan Security Shortfall",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Process Loan Interest Accrual",
"link_count": 0,
"link_to": "Process Loan Interest Accrual",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Disbursement and Repayment",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Disbursement",
"link_count": 0,
"link_to": "Loan Disbursement",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Repayment",
"link_count": 0,
"link_to": "Loan Repayment",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Write Off",
"link_count": 0,
"link_to": "Loan Write Off",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Interest Accrual",
"link_count": 0,
"link_to": "Loan Interest Accrual",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security Type",
"link_count": 0,
"link_to": "Loan Security Type",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security Price",
"link_count": 0,
"link_to": "Loan Security Price",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security",
"link_count": 0,
"link_to": "Loan Security",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security Pledge",
"link_count": 0,
"link_to": "Loan Security Pledge",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security Unpledge",
"link_count": 0,
"link_to": "Loan Security Unpledge",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Loan Security Shortfall",
"link_count": 0,
"link_to": "Loan Security Shortfall",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 6,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Loan Repayment and Closure",
"link_count": 0,
"link_to": "Loan Repayment and Closure",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 1,
"label": "Loan Security Status",
"link_count": 0,
"link_to": "Loan Security Status",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Loan Interest Report",
"link_count": 0,
"link_to": "Loan Interest Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Loan Security Exposure",
"link_count": 0,
"link_to": "Loan Security Exposure",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Applicant-Wise Loan Security Exposure",
"link_count": 0,
"link_to": "Applicant-Wise Loan Security Exposure",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 1,
"label": "Loan Security Status",
"link_count": 0,
"link_to": "Loan Security Status",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-01-31 19:47:13.114415",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loans",
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 16.0,
"shortcuts": [
{
"color": "Green",
"format": "{} Open",
"label": "Loan Application",
"link_to": "Loan Application",
"stats_filter": "{ \"status\": \"Open\" }",
"type": "DocType"
},
{
"label": "Loan",
"link_to": "Loan",
"type": "DocType"
},
{
"doc_view": "",
"label": "Dashboard",
"link_to": "Loan Dashboard",
"type": "Dashboard"
}
],
"title": "Loans"
}

View File

@@ -65,7 +65,21 @@ frappe.ui.form.on("BOM", {
});
},
onload_post_render(frm) {
validate: function(frm) {
if (frm.doc.fg_based_operating_cost && frm.doc.with_operations) {
frappe.throw({message: __("Please check either with operations or FG Based Operating Cost."), title: __("Mandatory")});
}
},
with_operations: function(frm) {
frm.set_df_property("fg_based_operating_cost", "hidden", frm.doc.with_operations ? 1 : 0);
},
fg_based_operating_cost: function(frm) {
frm.set_df_property("with_operations", "hidden", frm.doc.fg_based_operating_cost ? 1 : 0);
},
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
@@ -532,18 +546,25 @@ erpnext.bom.update_cost = function(doc) {
};
erpnext.bom.calculate_op_cost = function(doc) {
var op = doc.operations || [];
doc.operating_cost = 0.0;
doc.base_operating_cost = 0.0;
for(var i=0;i<op.length;i++) {
var operating_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
var base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
frappe.model.set_value('BOM Operation',op[i].name, "operating_cost", operating_cost);
frappe.model.set_value('BOM Operation',op[i].name, "base_operating_cost", base_operating_cost);
if(doc.with_operations) {
doc.operations.forEach((item) => {
let operating_cost = flt(flt(item.hour_rate) * flt(item.time_in_mins) / 60, 2);
let base_operating_cost = flt(operating_cost * doc.conversion_rate, 2);
frappe.model.set_value('BOM Operation',item.name, {
"operating_cost": operating_cost,
"base_operating_cost": base_operating_cost
});
doc.operating_cost += operating_cost;
doc.base_operating_cost += base_operating_cost;
doc.operating_cost += operating_cost;
doc.base_operating_cost += base_operating_cost;
});
} else if(doc.fg_based_operating_cost) {
let total_operating_cost = doc.quantity * flt(doc.operating_cost_per_bom_quantity);
doc.operating_cost = total_operating_cost;
doc.base_operating_cost = flt(total_operating_cost * doc.conversion_rate, 2);
}
refresh_field(['operating_cost', 'base_operating_cost']);
};

View File

@@ -33,6 +33,9 @@
"column_break_23",
"transfer_material_against",
"routing",
"fg_based_operating_cost",
"fg_based_section_section",
"operating_cost_per_bom_quantity",
"operations_section",
"operations",
"materials_section",
@@ -286,7 +289,7 @@
{
"fieldname": "scrap_items",
"fieldtype": "Table",
"label": "Items",
"label": "Scrap Items",
"options": "BOM Scrap Item"
},
{
@@ -575,7 +578,26 @@
{
"fieldname": "scrap_items_section",
"fieldtype": "Section Break",
"hide_border": 1,
"label": "Scrap Items"
},
{
"default": "0",
"fieldname": "fg_based_operating_cost",
"fieldtype": "Check",
"label": "FG based Operating Cost"
},
{
"depends_on": "fg_based_operating_cost",
"fieldname": "fg_based_section_section",
"fieldtype": "Section Break",
"label": "FG Based Operating Cost Section"
},
{
"depends_on": "fg_based_operating_cost",
"fieldname": "operating_cost_per_bom_quantity",
"fieldtype": "Currency",
"label": "Operating Cost Per BOM Quantity"
}
],
"icon": "fa fa-sitemap",
@@ -583,7 +605,7 @@
"image_field": "image",
"is_submittable": 1,
"links": [],
"modified": "2023-01-03 18:42:27.732107",
"modified": "2023-02-13 17:31:37.504565",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",

View File

@@ -614,18 +614,26 @@ class BOM(WebsiteGenerator):
"""Update workstation rate and calculates totals"""
self.operating_cost = 0
self.base_operating_cost = 0
for d in self.get("operations"):
if d.workstation:
self.update_rate_and_time(d, update_hour_rate)
if self.get("with_operations"):
for d in self.get("operations"):
if d.workstation:
self.update_rate_and_time(d, update_hour_rate)
operating_cost = d.operating_cost
base_operating_cost = d.base_operating_cost
if d.set_cost_based_on_bom_qty:
operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
operating_cost = d.operating_cost
base_operating_cost = d.base_operating_cost
if d.set_cost_based_on_bom_qty:
operating_cost = flt(d.cost_per_unit) * flt(self.quantity)
base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity)
self.operating_cost += flt(operating_cost)
self.base_operating_cost += flt(base_operating_cost)
self.operating_cost += flt(operating_cost)
self.base_operating_cost += flt(base_operating_cost)
elif self.get("fg_based_operating_cost"):
total_operating_cost = flt(self.get("quantity")) * flt(
self.get("operating_cost_per_bom_quantity")
)
self.operating_cost = total_operating_cost
self.base_operating_cost = flt(total_operating_cost * self.conversion_rate, 2)
def update_rate_and_time(self, row, update_hour_rate=False):
if not row.hour_rate or update_hour_rate:

View File

@@ -202,6 +202,33 @@ class TestBOM(FrappeTestCase):
self.assertEqual(bom.items[0].rate, 20)
def test_bom_cost_with_fg_based_operating_cost(self):
bom = frappe.copy_doc(test_records[4])
bom.insert()
raw_material_cost = 0.0
op_cost = 0.0
op_cost = bom.quantity * bom.operating_cost_per_bom_quantity
for row in bom.items:
raw_material_cost += row.amount
base_raw_material_cost = raw_material_cost * flt(
bom.conversion_rate, bom.precision("conversion_rate")
)
base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
# test amounts in selected currency, almostEqual checks for 7 digits by default
self.assertAlmostEqual(bom.operating_cost, op_cost)
self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
# test amounts in selected currency
self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
def test_subcontractor_sourced_item(self):
item_code = "_Test Subcontracted FG Item 1"
set_backflush_based_on("Material Transferred for Subcontract")

View File

@@ -162,5 +162,31 @@
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 1
},
{
"items": [
{
"amount": 5000.0,
"doctype": "BOM Item",
"item_code": "_Test Item",
"parentfield": "items",
"qty": 2.0,
"rate": 3000.0,
"uom": "_Test UOM",
"stock_uom": "_Test UOM",
"source_warehouse": "_Test Warehouse - _TC",
"include_item_in_manufacturing": 1
}
],
"docstatus": 1,
"doctype": "BOM",
"is_active": 1,
"is_default": 1,
"currency": "USD",
"item": "_Test Variant Item",
"quantity": 1.0,
"with_operations": 0,
"fg_based_operating_cost": 1,
"operating_cost_per_bom_quantity": 140
}
]

View File

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

View File

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

View File

@@ -194,7 +194,6 @@ erpnext.patches.v13_0.update_project_template_tasks
erpnext.patches.v13_0.convert_qi_parameter_to_link_field
erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
erpnext.patches.v13_0.update_payment_terms_outstanding
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
@@ -269,6 +268,7 @@ erpnext.patches.v13_0.reset_corrupt_defaults
erpnext.patches.v13_0.create_accounting_dimensions_for_asset_repair
erpnext.patches.v15_0.delete_taxjar_doctypes
erpnext.patches.v15_0.create_asset_depreciation_schedules_from_assets
erpnext.patches.v14_0.update_reference_due_date_in_journal_entry
[post_model_sync]
execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
@@ -291,6 +291,7 @@ erpnext.patches.v13_0.update_exchange_rate_settings
erpnext.patches.v14_0.delete_amazon_mws_doctype
erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
erpnext.patches.v13_0.update_accounts_in_loan_docs
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
erpnext.patches.v14_0.update_batch_valuation_flag
erpnext.patches.v14_0.delete_non_profit_doctypes
erpnext.patches.v13_0.add_cost_center_in_loans
@@ -316,9 +317,13 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
erpnext.patches.v13_0.update_schedule_type_in_loans
erpnext.patches.v13_0.drop_unused_sle_index_parts
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
erpnext.patches.v14_0.update_partial_tds_fields
erpnext.patches.v14_0.create_incoterms_and_migrate_shipment
erpnext.patches.v14_0.setup_clear_repost_logs
erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request
erpnext.patches.v14_0.update_entry_type_for_journal_entry
erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
erpnext.patches.v14_0.set_pick_list_status
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries

View File

@@ -1,16 +1,17 @@
import frappe
from frappe import _
def execute():
from erpnext.setup.setup_wizard.operations.install_fixtures import default_sales_partner_type
from erpnext.setup.setup_wizard.operations.install_fixtures import read_lines
frappe.reload_doc("selling", "doctype", "sales_partner_type")
frappe.local.lang = frappe.db.get_default("lang") or "en"
default_sales_partner_type = read_lines("sales_partner_type.txt")
for s in default_sales_partner_type:
insert_sales_partner_type(_(s))
insert_sales_partner_type(s)
# get partner type in existing forms (customized)
# and create a document if not created

View File

@@ -0,0 +1,14 @@
import frappe
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import on_doctype_update
def execute():
try:
frappe.db.sql_ddl("ALTER TABLE `tabStock Ledger Entry` DROP INDEX `posting_sort_index`")
except Exception:
frappe.log_error("Failed to drop index")
return
# Recreate indexes
on_doctype_update()

View File

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

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
if (
frappe.db.sql(
"""select data_type FROM information_schema.columns
where column_name = 'name' and table_name = 'tabTax Withheld Vouchers'"""
)[0][0]
== "bigint"
):
frappe.db.change_column_type("Tax Withheld Vouchers", "name", "varchar(140)")

View File

@@ -18,9 +18,11 @@ def create_new_cost_center_allocation_records(cc_allocations):
cca = frappe.new_doc("Cost Center Allocation")
cca.main_cost_center = main_cc
cca.valid_from = today()
cca._skip_from_date_validation = True
for child_cc, percentage in allocations.items():
cca.append("allocation_percentages", ({"cost_center": child_cc, "percentage": percentage}))
cca.save()
cca.submit()

View File

@@ -2,7 +2,8 @@ import frappe
from frappe import qb
from frappe.query_builder import Case, CustomFunction
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull
from frappe.query_builder.functions import Count, IfNull
from frappe.utils import flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimensions,
@@ -17,9 +18,9 @@ def create_accounting_dimension_fields():
make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
def generate_name_for_payment_ledger_entries(gl_entries):
for index, entry in enumerate(gl_entries, 1):
entry.name = index
def generate_name_for_payment_ledger_entries(gl_entries, start):
for index, entry in enumerate(gl_entries, 0):
entry.name = start + index
def get_columns():
@@ -81,6 +82,14 @@ def insert_chunk_into_payment_ledger(insert_query, gl_entries):
def execute():
"""
Description:
Migrate records from `tabGL Entry` to `tabPayment Ledger Entry`.
Patch is non-resumable. if patch failed or is terminatted abnormally, clear 'tabPayment Ledger Entry' table manually before re-running. Re-running is safe only during V13->V14 update.
Note: Post successful migration to V14, re-running is NOT-SAFE and SHOULD NOT be attempted.
"""
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
# create accounting dimension fields in Payment Ledger
create_accounting_dimension_fields()
@@ -89,52 +98,90 @@ def execute():
account = qb.DocType("Account")
ifelse = CustomFunction("IF", ["condition", "then", "else"])
gl_entries = (
qb.from_(gl)
.inner_join(account)
.on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
.select(
gl.star,
ConstantColumn(1).as_("docstatus"),
account.account_type.as_("account_type"),
IfNull(
ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
).as_("against_voucher_type"),
IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
"against_voucher_no"
),
# convert debit/credit to amount
Case()
.when(account.account_type == "Receivable", gl.debit - gl.credit)
.else_(gl.credit - gl.debit)
.as_("amount"),
# convert debit/credit in account currency to amount in account currency
Case()
.when(
account.account_type == "Receivable",
gl.debit_in_account_currency - gl.credit_in_account_currency,
)
.else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
.as_("amount_in_account_currency"),
)
.where(gl.is_cancelled == 0)
.orderby(gl.creation)
.run(as_dict=True)
# Get Records Count
accounts = (
qb.from_(account)
.select(account.name)
.where((account.account_type == "Receivable") | (account.account_type == "Payable"))
.orderby(account.name)
)
un_processed = (
qb.from_(gl)
.select(Count(gl.name))
.where((gl.is_cancelled == 0) & (gl.account.isin(accounts)))
.run()
)[0][0]
# primary key(name) for payment ledger records
generate_name_for_payment_ledger_entries(gl_entries)
if un_processed:
print(f"Migrating {un_processed} GL Entries to Payment Ledger")
# split data into chunks
chunk_size = 1000
try:
for i in range(0, len(gl_entries), chunk_size):
insert_query = build_insert_query()
insert_chunk_into_payment_ledger(insert_query, gl_entries[i : i + chunk_size])
frappe.db.commit()
except Exception as err:
frappe.db.rollback()
ple = qb.DocType("Payment Ledger Entry")
qb.from_(ple).delete().where(ple.docstatus >= 0).run()
frappe.db.commit()
raise err
processed = 0
last_update_percent = 0
batch_size = 5000
last_name = None
while True:
if last_name:
where_clause = gl.name.gt(last_name) & (gl.is_cancelled == 0)
else:
where_clause = gl.is_cancelled == 0
gl_entries = (
qb.from_(gl)
.inner_join(account)
.on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
.select(
gl.star,
ConstantColumn(1).as_("docstatus"),
account.account_type.as_("account_type"),
IfNull(
ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
).as_("against_voucher_type"),
IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
"against_voucher_no"
),
# convert debit/credit to amount
Case()
.when(account.account_type == "Receivable", gl.debit - gl.credit)
.else_(gl.credit - gl.debit)
.as_("amount"),
# convert debit/credit in account currency to amount in account currency
Case()
.when(
account.account_type == "Receivable",
gl.debit_in_account_currency - gl.credit_in_account_currency,
)
.else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
.as_("amount_in_account_currency"),
)
.where(where_clause)
.orderby(gl.name)
.limit(batch_size)
.run(as_dict=True)
)
if gl_entries:
last_name = gl_entries[-1].name
# primary key(name) for payment ledger records
generate_name_for_payment_ledger_entries(gl_entries, processed)
try:
insert_query = build_insert_query()
insert_chunk_into_payment_ledger(insert_query, gl_entries)
frappe.db.commit()
processed += len(gl_entries)
# Progress message
percent = flt((processed / un_processed) * 100, 2)
if percent - last_update_percent > 1:
print(f"{percent}% ({processed}) records processed")
last_update_percent = percent
except Exception as err:
print("Migration Failed. Clear `tabPayment Ledger Entry` table before re-running")
raise err
else:
break
print(f"{processed} records have been sucessfully migrated")

View File

@@ -1,81 +1,98 @@
import frappe
from frappe import qb
from frappe.utils import create_batch
def remove_duplicate_entries(pl_entries):
unique_vouchers = set()
for x in pl_entries:
unique_vouchers.add(
(x.company, x.account, x.party_type, x.party, x.voucher_type, x.voucher_no, x.gle_remarks)
)
entries = []
for x in unique_vouchers:
entries.append(
frappe._dict(
company=x[0],
account=x[1],
party_type=x[2],
party=x[3],
voucher_type=x[4],
voucher_no=x[5],
gle_remarks=x[6],
)
)
return entries
from frappe.query_builder import CustomFunction
from frappe.query_builder.functions import Count, IfNull
from frappe.utils import flt
def execute():
"""
Migrate 'remarks' field from 'tabGL Entry' to 'tabPayment Ledger Entry'
"""
if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
gle = qb.DocType("GL Entry")
ple = qb.DocType("Payment Ledger Entry")
# get ple and their remarks from GL Entry
pl_entries = (
qb.from_(ple)
.left_join(gle)
.on(
(ple.account == gle.account)
& (ple.party_type == gle.party_type)
& (ple.party == gle.party)
& (ple.voucher_type == gle.voucher_type)
& (ple.voucher_no == gle.voucher_no)
& (ple.company == gle.company)
)
.select(
ple.company,
ple.account,
ple.party_type,
ple.party,
ple.voucher_type,
ple.voucher_no,
gle.remarks.as_("gle_remarks"),
)
.where((ple.delinked == 0) & (gle.is_cancelled == 0))
.run(as_dict=True)
)
# Get empty PLE records
un_processed = (
qb.from_(ple).select(Count(ple.name)).where((ple.remarks.isnull()) & (ple.delinked == 0)).run()
)[0][0]
pl_entries = remove_duplicate_entries(pl_entries)
if un_processed:
print(f"Remarks for {un_processed} Payment Ledger records will be updated from GL Entry")
if pl_entries:
# split into multiple batches, update and commit for each batch
ifelse = CustomFunction("IF", ["condition", "then", "else"])
processed = 0
last_percent_update = 0
batch_size = 1000
for batch in create_batch(pl_entries, batch_size):
for entry in batch:
query = (
qb.update(ple)
.set(ple.remarks, entry.gle_remarks)
.where(
(ple.company == entry.company)
& (ple.account == entry.account)
& (ple.party_type == entry.party_type)
& (ple.party == entry.party)
& (ple.voucher_type == entry.voucher_type)
& (ple.voucher_no == entry.voucher_no)
)
)
query.run()
last_name = None
frappe.db.commit()
while True:
if last_name:
where_clause = (ple.name.gt(last_name)) & (ple.remarks.isnull()) & (ple.delinked == 0)
else:
where_clause = (ple.remarks.isnull()) & (ple.delinked == 0)
# results are deterministic
names = (
qb.from_(ple).select(ple.name).where(where_clause).orderby(ple.name).limit(batch_size).run()
)
if names:
last_name = names[-1][0]
pl_entries = (
qb.from_(ple)
.left_join(gle)
.on(
(ple.account == gle.account)
& (ple.party_type == gle.party_type)
& (ple.party == gle.party)
& (ple.voucher_type == gle.voucher_type)
& (ple.voucher_no == gle.voucher_no)
& (
ple.against_voucher_type
== IfNull(
ifelse(gle.against_voucher_type == "", None, gle.against_voucher_type), gle.voucher_type
)
)
& (
ple.against_voucher_no
== IfNull(ifelse(gle.against_voucher == "", None, gle.against_voucher), gle.voucher_no)
)
& (ple.company == gle.company)
& (
((ple.account_type == "Receivable") & (ple.amount == (gle.debit - gle.credit)))
| (ple.account_type == "Payable") & (ple.amount == (gle.credit - gle.debit))
)
& (gle.remarks.notnull())
& (gle.is_cancelled == 0)
)
.select(ple.name)
.distinct()
.select(
gle.remarks.as_("gle_remarks"),
)
.where(ple.name.isin(names))
.run(as_dict=True)
)
if pl_entries:
for entry in pl_entries:
query = qb.update(ple).set(ple.remarks, entry.gle_remarks).where((ple.name == entry.name))
query.run()
frappe.db.commit()
processed += len(pl_entries)
percentage = flt((processed / un_processed) * 100, 2)
if percentage - last_percent_update > 1:
print(f"{percentage}% ({processed}) PLE records updated")
last_percent_update = percentage
else:
break
print("Remarks succesfully migrated")

View File

@@ -0,0 +1,40 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import frappe
from pypika.terms import ExistsCriterion
def execute():
pl = frappe.qb.DocType("Pick List")
se = frappe.qb.DocType("Stock Entry")
dn = frappe.qb.DocType("Delivery Note")
(
frappe.qb.update(pl).set(
pl.status,
(
frappe.qb.terms.Case()
.when(pl.docstatus == 0, "Draft")
.when(pl.docstatus == 2, "Cancelled")
.else_("Completed")
),
)
).run()
(
frappe.qb.update(pl)
.set(pl.status, "Open")
.where(
(
ExistsCriterion(
frappe.qb.from_(se).select(se.name).where((se.docstatus == 1) & (se.pick_list == pl.name))
)
| ExistsCriterion(
frappe.qb.from_(dn).select(dn.name).where((dn.docstatus == 1) & (dn.pick_list == pl.name))
)
).negate()
& (pl.docstatus == 1)
)
).run()

View File

@@ -0,0 +1,12 @@
import frappe
def execute():
if frappe.db.get_value("Journal Entry Account", {"reference_due_date": ""}):
frappe.db.sql(
"""
UPDATE `tabJournal Entry Account`
SET reference_due_date = NULL
WHERE reference_due_date = ''
"""
)

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