Compare commits

...

204 Commits

Author SHA1 Message Date
frappe-pr-bot
f4c8762eb0 chore: update POT file 2024-05-26 09:35:20 +00:00
rohitwaghchaure
683c1f04c8 fix: Unable to 'Get Suppliers' using tags in Request for Quotation (#41626) 2024-05-24 13:03:57 +05:30
Akhil Narang
b6350c86d1 Merge pull request #41601 from akhilnarang/add-indices
fix: add in some indices to speed up Purchase Order deletion
2024-05-24 10:57:04 +05:30
ruthra kumar
fac0fcb9d5 Merge pull request #41443 from cogk/fix-use-get-all-for-advance-status-update
fix(AccountsController): Use get_all to get payment requests when updating advance payment status
2024-05-24 10:40:15 +05:30
Akhil Narang
3f06f1905f refactor: use json directly instead of using frappe.json (#41609) 2024-05-23 16:00:42 +02:00
Frappe PR Bot
954ef85b95 fix: sync translations from crowdin (#41549) 2024-05-23 15:56:33 +02:00
ruthra kumar
fffcb5dc9f Merge pull request #41594 from ruthra-kumar/refactor_payment_period_report
refactor: 'Payment Period Based On Invoice Date' report
2024-05-23 17:45:28 +05:30
ruthra kumar
5d0e82f40b Merge pull request #41603 from ruthra-kumar/remove_format_based_naming_on_internal_doctypes
refactor: remove 'format:' based naming in internal doctypes
2024-05-23 17:44:14 +05:30
ruthra kumar
e2ec3e453a refactor: remove 'format:' based naming in internal doctypes 2024-05-23 17:01:24 +05:30
Akhil Narang
0303d7dbdc fix: add in some indices to speed up Purchase Order deletion
`tabSales Order`.`inter_company_order_reference`
`tabSales Invoice Item`.`purchase_order`

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-05-23 16:36:41 +05:30
rohitwaghchaure
418439b58f fix: SCR batch qty issue (#41595) 2024-05-23 14:51:54 +05:30
Akhil Narang
e39d2e1e69 Merge pull request #41576 from akhilnarang/sync-ruff-config
chore: sync ruff config with framework
2024-05-23 12:50:33 +05:30
ruthra kumar
c4e2abb973 refactor: replace against_voucher with against_voucher_no 2024-05-23 12:08:51 +05:30
ruthra kumar
014b542cf3 refactor: remove debit and credit 2024-05-23 11:59:57 +05:30
ruthra kumar
cb3c20dcd3 refactor: query payment ledger for payments 2024-05-23 11:52:53 +05:30
rohitwaghchaure
ad817cddad perf: sales order UI render (#41591) 2024-05-22 19:57:52 +05:30
rohitwaghchaure
04a49cef24 fix: opening stock not showing in the stock ledger report for the bat… (#41584)
fix: opening stock not showing in the stock ledger report for the batch filter
2024-05-22 18:22:40 +05:30
Ankush Menat
85b1a8001b ci: ruff only fix imports 2024-05-21 16:05:16 +05:30
Ankush Menat
3b4913ec81 ci: dont auto apply fixes
I've personally encountered ~5 instances of bad auto fixes, let
developers apply the fixes.
2024-05-21 16:03:31 +05:30
jabir-elat
d7df2cbdc5 fix: correct report name in inner button (#41568)
Previously, the report name was incorrectly listed as "Cash Flow Statement" instead of "Cash Flow". This mismatch caused the "Cash Flow" report to not open correctly in the chart of accounts tree view, as the report ID is "Cash Flow".
2024-05-21 14:38:13 +05:30
rohitwaghchaure
ac4bcd5ca7 fix: job card time logs overlap issue (#41567) 2024-05-21 14:37:04 +05:30
rohitwaghchaure
4261c3474b fix: not able to delete line items in the subcontracting receipt (#41569) 2024-05-21 14:36:35 +05:30
rohitwaghchaure
4e4c1e4bef fix: Deadlock on submitting Sales Invoice (#41417) 2024-05-21 10:33:29 +05:30
rohitwaghchaure
ae3f5a38e1 fix: BOM creator validation for parent row no (#41413)
fix: bom creator valiation for parent row no
2024-05-20 19:19:45 +05:30
Nabin Hait
dfdb95bbbe patch: create custom field for accounting dimensions only if the field not exists already (#41484)
fix: create custom field for accounting dimensions only if the field not exists already
2024-05-20 12:54:20 +00:00
Nabin Hait
a1d489195d fix: convert invoice_portion value from str to float (#41485) 2024-05-20 17:57:59 +05:30
Nabin Hait
f24d61daea fix: Auto reconcile only after selecting bank account (#41489) 2024-05-20 17:57:33 +05:30
Nabin Hait
0191727149 Merge pull request #41502 from khushi8112/gross-purchase-amount-too-low-to-make-depr-schedule
fix: added validation message for low gross purchase amount
2024-05-20 17:55:09 +05:30
Nabin Hait
e22b0fd6e1 Merge pull request #41495 from nabinhait/target-variance-for-parent-item-groups
fix: Show acheived amount and variance for parent item groups
2024-05-20 16:25:01 +05:30
ruthra kumar
be1fce2da6 Merge pull request #41492 from ruthra-kumar/reconciliation_on_advance_payment_date
refactor: For advances booked in Separate account, reconciliation date can be configured
2024-05-20 14:58:58 +05:30
ruthra kumar
30aa4e031d test: reconciliation date for advance payments 2024-05-20 14:42:41 +05:30
rohitwaghchaure
214b38f7c8 fix: valuation rate for legacy serial nos (#41543)
fix: valuation rate for serial nos
2024-05-20 13:41:16 +05:30
Frappe PR Bot
28e10dd703 fix: sync translations from crowdin (#41411) 2024-05-19 17:14:23 +02:00
Raffael Meyer
f7eb9d44a8 Merge pull request #41540 from frappe/pot_develop_2024-05-19 2024-05-19 15:22:28 +02:00
frappe-pr-bot
b071e952dd chore: update POT file 2024-05-19 09:35:33 +00:00
Raffael Meyer
7b9fd394b0 feat: enter serial range in Serial/Batch Selector (#41530)
* feat: util for generating serial nos from range string

* feat: enter serial range in Serial/Batch Selector
2024-05-18 10:25:50 +05:30
ruthra kumar
7ce8fc9ba2 Merge pull request #41529 from ruthra-kumar/ignore_internal_doctypes_on_cancel_for_dunning
refactor: ignore internal doctypes on Dunning cancellation
2024-05-17 21:00:22 +05:30
ruthra kumar
63c2cf13fd refactor: ignore internal doctypes on Dunning cancellation 2024-05-17 20:43:19 +05:30
Nabin Hait
37d97fad17 Merge pull request #41523 from ruthra-kumar/fix_pr_and_pi_debit_credit_difference
fix: minor Dr and Cr between Purchase Receipt and Purchase Invoice
2024-05-17 17:46:10 +05:30
rohitwaghchaure
5cf5b18aea fix: priority not working for multiple pricing rules (#41516) 2024-05-17 17:09:01 +05:30
ruthra kumar
1c0a24424a fix: minor Dr and Cr between Purchase Receipt and Purchase Invoice
This applies for Provisional Accounting for Non-stock items
2024-05-17 17:06:39 +05:30
rohitwaghchaure
61a4188440 fix: timeout error while submitting purchase invoice (#41520) 2024-05-17 16:36:38 +05:30
ruthra kumar
3f6787b267 Merge pull request #41517 from ruthra-kumar/fix_typeerror_in_purchase_invoice
fix: typerror on hide_fields
2024-05-17 14:47:07 +05:30
ruthra kumar
deb9766f2a fix: typerror on hide_fields 2024-05-17 14:41:17 +05:30
ruthra kumar
d61f56207c Merge pull request #41511 from ruthra-kumar/possible_sql_error_on_general_ledger
fix: possible sql error on General Ledger
2024-05-17 10:55:19 +05:30
ruthra kumar
76131f8e10 fix: possible sql error on General Ledger 2024-05-17 10:37:07 +05:30
ruthra kumar
cafa2f52e5 refactor: enable no-copy on advance payment flags 2024-05-17 10:21:37 +05:30
Deepesh Garg
e883d6a7d3 Merge pull request #41451 from deepeshgarg007/demo_deletion_fixes
fix: Demo data clearing
2024-05-17 09:16:11 +05:30
Deepesh Garg
44e6563af6 Merge pull request #41504 from deepeshgarg007/pos_error_message
fix: Unable to add items in POS Invoice
2024-05-16 19:31:53 +05:30
Deepesh Garg
7a3f8a5afb fix: Unable to add items in POS Invoice 2024-05-16 19:19:52 +05:30
Khushi Rawat
8a30701893 fix: added validation message for low gross purchase amount 2024-05-16 16:37:49 +05:30
rohitwaghchaure
500c546691 fix: stock levels for batch (#41494) 2024-05-16 15:58:58 +05:30
ruthra kumar
af8275c321 Merge pull request #40072 from Nihantra-Patel/fix_print_bold
fix: print format bold for field "total"
2024-05-16 15:19:37 +05:30
Samuel Danieli
bd381cc0c6 chore(BOM Explorer): display items in the same order as in the BOM (#41496) 2024-05-16 15:13:22 +05:30
Nabin Hait
8ac11ae88d fix: Show achieved amount and variance for parent item groups 2024-05-16 14:28:22 +05:30
Nihantra C. Patel
2761705c62 Merge branch 'develop' into fix_print_bold 2024-05-16 14:11:31 +05:30
ruthra kumar
000c1b49dc chore: better description 2024-05-16 12:40:09 +05:30
ruthra kumar
070e2d4d26 refactor: advance payments, allow configurable reconciliation date 2024-05-16 12:07:19 +05:30
rohitwaghchaure
81a9521f04 fix: not able to submit landed cost voucher (#41481) 2024-05-15 17:49:02 +05:30
rohitwaghchaure
0363afcfd0 fix: validate reorder group warehouse (#41478) 2024-05-15 14:03:51 +05:30
Ankush Menat
7a47b2157b ci: remove mergify from release notes (#41475)
Mergify isn't author, just backporter.

All our stable branch PRs don't have authors so eh.
2024-05-15 05:37:30 +00:00
Nabin Hait
fc797836af Merge pull request #41235 from khushi8112/depreciation_based_daily_prorata
Depreciation based on daily prorata
2024-05-15 08:40:16 +05:30
Nabin Hait
7d86881579 style: new line before function 2024-05-15 08:18:05 +05:30
Khushi Rawat
d22df324ec fix: removed same named function 2024-05-15 01:10:54 +05:30
Nabin Hait
4a4c674553 Merge pull request #41467 from khushi8112/unlink-asset-reference-issue
Asset cancelation issue
2024-05-14 22:41:45 +05:30
Khushi Rawat
7b264e5e11 refactor: code optimization 2024-05-14 20:12:01 +05:30
Khushi Rawat
c0c8c1bb02 fix: pro rata based depreciation with opening accumulated depreciation 2024-05-14 20:12:01 +05:30
Khushi Rawat
d40b55468c test: made minor change in existing test 2024-05-14 20:12:01 +05:30
Khushi Rawat
fccd37d32d fix: incorrect total days calculation 2024-05-14 20:12:01 +05:30
Khushi Rawat
b8a98a273b fix: daily prorata based depreciation bug in wdv method 2024-05-14 20:11:52 +05:30
Nabin Hait
99d417bbc2 Merge pull request #41457 from khushi8112/comparison-between-none-type-and-date-time-instances
fix: filtered out data having none values in date field
2024-05-14 20:10:52 +05:30
Khushi Rawat
f337392f3e fix(wip): daily depreciation bug 2024-05-14 20:10:42 +05:30
Khushi Rawat
e843683ad1 style: code optimization 2024-05-14 20:03:58 +05:30
Khushi Rawat
a36b7fb95a style: code optimization 2024-05-14 19:52:57 +05:30
Nabin Hait
16751ec935 Merge pull request #41420 from khushi8112/get-prorata-depreciation-based-on-total-days-per-year
Get prorata depreciation based on total days per year
2024-05-14 19:24:30 +05:30
Nabin Hait
0736154074 Merge pull request #41089 from khushi8112/rename-purchase-receipt-amount-field-to-purchase-amount
Refactor: Rename purchase receipt amount field to purchase amount
2024-05-14 18:16:39 +05:30
Khushi Rawat
fa2b6c4490 fix: Asset cancelation issue 2024-05-14 17:56:06 +05:30
Khushi Rawat
3bd455ac85 fix: filtered out data having none values in date field 2024-05-14 14:24:18 +05:30
Deepesh Garg
d6fe220fab fix: Demo data clearing 2024-05-14 10:27:56 +05:30
Deepesh Garg
e3f539325e Merge pull request #41412 from deepeshgarg007/party_name_ar_ap_report
fix: Duplicate party name column in AR/AP report
2024-05-14 08:22:22 +05:30
rohitwaghchaure
e3a80ebdf3 fix: zero valuation rate for batched item (#41446) 2024-05-14 05:19:32 +05:30
rohitwaghchaure
3309359822 fix: 'Bill for Rejected Quantity in Purchase Invoice' feature not working (#41437) 2024-05-13 21:38:24 +05:30
rohitwaghchaure
6bd13d7452 fix: Unknown column 'tabBatch.batch_no' in 'where clause' (#41418)
fix: unknown column 'tabBatch.batch_no' in 'where clause'
2024-05-13 21:37:35 +05:30
Corentin Flr
13a3a6a82e fix(AccountsController): Use get_all to get payment requests when updating advance payment status 2024-05-13 17:11:08 +02:00
Raffael Meyer
64333373ca Merge pull request #41384 from barredterra/sales-order-actions 2024-05-13 15:40:58 +02:00
Deepesh Garg
c6fd42fe60 Merge pull request #41429 from deepeshgarg007/immutable_ledger
feat: Config to enable immutable ledger
2024-05-13 17:42:59 +05:30
Deepesh Garg
77bfea975f Merge branch 'develop' of https://github.com/frappe/erpnext into immutable_ledger 2024-05-13 17:22:27 +05:30
rohitwaghchaure
663fcb374d fix: data getting override in delivery trip (#41431)
fix: data getting override
2024-05-13 16:24:46 +05:30
rohitwaghchaure
065163146c fix: valuation issue for batch (#41425) 2024-05-13 16:03:44 +05:30
Deepesh Garg
d56f52b0ba feat: Config to enable immutable ledger 2024-05-13 15:39:22 +05:30
Deepesh Garg
32a463652e Merge pull request #40468 from Nihantra-Patel/upd_accounts_description
fix: update description of Supplier Invoice Number
2024-05-13 15:12:23 +05:30
Nihantra C. Patel
7f2062e2c5 Merge branch 'develop' into upd_accounts_description 2024-05-13 14:56:23 +05:30
Khushi Rawat
98e7dfe97f fix(minor): removed extra parameter 2024-05-13 00:10:43 +05:30
Khushi Rawat
6b24143f72 refactor: removed code duplicacies 2024-05-12 23:01:44 +05:30
Khushi Rawat
d3200fb67f fix(wip): depreciation calculation after asset value adjustment 2024-05-12 23:01:44 +05:30
Deepesh Garg
7501fe8ebd fix: Duplicate party name column in AR/AP report 2024-05-11 19:35:52 +05:30
Frappe PR Bot
3fa8706f57 chore: update POT file (#41326) 2024-05-10 16:29:49 +02:00
Frappe PR Bot
f92aabd0f3 fix: sync translations from crowdin (#41262) 2024-05-10 16:28:17 +02:00
rehanrehman389
628d7e6458 chore: typo in Stock Settings (#41396) 2024-05-10 15:40:02 +05:30
Khushi Rawat
360c3b36ed fix: removed unrelated code modification 2024-05-10 14:39:59 +05:30
“Khushi
31841b4ab2 refactor: renamed purchase receipt amount field to purchase amount 2024-05-10 14:17:03 +05:30
Nihantra C. Patel
ba60b5911a fix: bold total in exponential smoothing forecasting (#41393)
* fix: bold total in exponential smoothing forecasting

* fix: bold total in exponential smoothing forecasting
2024-05-10 13:12:23 +05:30
ruthra kumar
08899d05fb Merge pull request #41040 from Nihantra-Patel/fix_quot_prospect
fix: address filter and quotation to for prospect
2024-05-09 12:14:41 +05:30
ruthra kumar
754c7f6d1c refactor: make use of doc.quotation_to 2024-05-09 11:58:33 +05:30
Nabin Hait
92673d706c Merge pull request #41024 from khushi8112/expense-account-based-on-asset-category
fix: expense account should be fetched from related asset category
2024-05-09 01:30:45 +05:30
Raffael Meyer
9c3a105f85 fix: consistent use of "Address & Contact" (#41386) 2024-05-08 16:20:35 +00:00
barredterra
418bdc1dcc fix(Delivery Note): only show permitted actions 2024-05-08 17:55:16 +02:00
barredterra
c29d955371 fix(Sales Order): only show permitted actions 2024-05-08 16:23:46 +02:00
Khushi Rawat
272ce24016 style: code changes for better readability
Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2024-05-08 19:17:09 +05:30
“Khushi
651e4696fd fix: expense account should be fetched from related asset category 2024-05-08 19:14:51 +05:30
rohitwaghchaure
5ed1b6b8fb fix: incorrect qty picked in the pick list (#41378) 2024-05-08 17:56:10 +05:30
Nabin Hait
cd5d244d2c Merge pull request #41236 from nabinhait/merge_gle_in_transaction_currency
fix: Merge debit and credit in transaction currency while merging gle with similar head
2024-05-08 17:50:56 +05:30
rohitwaghchaure
0887161f2a perf: index on item code for the Pick List Item doctype (#41357) 2024-05-07 16:29:20 +05:30
rohitwaghchaure
544fc60093 fix: filter validation for batch-wise balance history report (#41356)
fix: filter validation for batchwise balance history report
2024-05-07 16:29:08 +05:30
ruthra kumar
c50663c640 Merge pull request #41288 from ruthra-kumar/bug_in_promotional_scheme
fix: pricing rule rounding
2024-05-07 10:01:00 +05:30
ruthra kumar
c41a037174 refactor(test): test floor based rounding 2024-05-07 09:44:10 +05:30
Raffael Meyer
c710ad822c fix(Item): allow UOM conversion for non-stock items (#41267) 2024-05-06 18:50:30 +02:00
rohitwaghchaure
ebfbe94aaf fix: pick list with multiple batch issue (#41335)
fix: pick list with batchb issue
2024-05-06 13:17:07 +05:30
ruthra kumar
e51ccecd8a Merge pull request #41334 from ruthra-kumar/fix_gp_purchase_invoice_item
fix: incorrect query for Purchase Invoice rate in GP
2024-05-06 12:53:46 +05:30
ruthra kumar
bd8382c592 fix: incorrect query for Purchase Invoice rate in GP 2024-05-06 12:07:33 +05:30
Ankush Menat
6142d07f1a fix: update project URLs (#41331) 2024-05-06 06:02:57 +00:00
Deepesh Garg
ed5344d58b Merge pull request #41318 from deepeshgarg007/psoa_cost_center
fix: Cost center not getting saved in PSOA
2024-05-04 13:55:40 +05:30
Deepesh Garg
44228b754f Merge pull request #41314 from deepeshgarg007/tcs_opening
fix: Do not deduct TCS for opening invoices
2024-05-04 13:54:46 +05:30
Deepesh Garg
d19ae4ce4c Merge pull request #41279 from deepeshgarg007/order_advance
fix: GL Entries against orders as an advance
2024-05-03 19:41:24 +05:30
Deepesh Garg
58f7039630 fix: Cost center not getting saved in PSOA 2024-05-03 18:34:27 +05:30
Deepesh Garg
eac7be2d0f test: Add bank account 2024-05-03 18:30:47 +05:30
Deepesh Garg
42ef95759d test: Update failing tests 2024-05-03 17:04:37 +05:30
Deepesh Garg
eb31017058 fix: Add PO reference 2024-05-03 15:56:29 +05:30
Deepesh Garg
69d68592ad Merge pull request #41311 from deepeshgarg007/future_subscription_updates
fix: future subscription updates
2024-05-03 12:04:39 +05:30
Deepesh Garg
eb9f579b8f fix: Do not deduct TCS for opening invoices 2024-05-03 11:47:43 +05:30
Raffael Meyer
e4db0562ac fix: search for item price in stock UOM (#41075) 2024-05-03 10:06:52 +05:30
Raffael Meyer
a61148c464 feat(Item Price): make UOM mandatory (#40588) 2024-05-03 10:04:24 +05:30
Deepesh Garg
7fa22069d8 test: Add posting dates 2024-05-03 09:25:26 +05:30
Deepesh Garg
54a12c5186 Merge pull request #41260 from deepeshgarg007/pi_gl_entries_pi
fix: Purchase Invoice gain loss gl entry for periodic inventory
2024-05-03 08:54:57 +05:30
HENRY Florian
c8e92cb1b2 fix: missing Item Name on Save for Quotation created from Item (#41233)
* fix: missing Item Name on Save for Quotation created from Item

* fix: missing Item Name on Save for Quotation created from Item
2024-05-02 22:22:12 +05:30
Akhil Narang
93c6324115 Merge pull request #41299 from frappe/revert-41146-fix-add-import
Revert "fix(supplier): add back import that got removed by ruff"
2024-05-02 17:59:10 +05:30
Akhil Narang
f26be2a303 Revert "fix(supplier): add back import that got removed by ruff" 2024-05-02 17:42:33 +05:30
David Arnold
61f5ebd2dd feat: create delivery trip on draft notes (#38559)
feat: create draft delivery trips from draft delivery notes
2024-05-02 16:00:21 +05:30
Akhil Narang
22c345a24a Merge pull request #41146 from akhilnarang/fix-add-import
fix(supplier): add back import that got removed by ruff
2024-05-02 12:23:58 +05:30
ruthra kumar
9bf37426c1 fix: pricing rule rounding
Consider a pricing rule of 20:1 with recursion enabled, free items
should follow the below progression

|   Qty | Free item qty |
|-------+---------------|
|  0-19 |             0 |
| 20-39 |             1 |
| 40-59 |             2 |
2024-05-02 09:40:07 +05:30
rohitwaghchaure
1cbc200770 fix: added brand column in Warehouse wise Item Balance Age and Value … (#41280)
fix: added brand coulmn in Warehouse wise Item Balance Age and Value report
2024-05-01 15:46:43 +05:30
Deepesh Garg
8289f3c724 fix: GL Entries against orders as an advance 2024-05-01 12:17:08 +05:30
mergify[bot]
da3010a41f fix: multiple pricing rules with discount amount and discount percentage not working (backport #41211) (#41241)
fix: multiple pricing rules with discount amount and discount percentage not working (#41211)

(cherry picked from commit 54313b5db9)

Co-authored-by: rohitwaghchaure <rohitw1991@gmail.com>
2024-04-30 21:48:46 +05:30
ruthra kumar
ed9af7d12e Merge pull request #41268 from ruthra-kumar/validation_to_prevent_overallocation_on_reconciliation
fix: validation to prevent overallocation
2024-04-30 17:37:46 +05:30
ruthra kumar
bf755fab55 fix: validation to prevent overallocation 2024-04-30 17:19:41 +05:30
Deepesh Garg
2402447568 fix: Purchase Invoice gain loss gl entry for periodic inventory 2024-04-30 13:28:43 +05:30
Deepesh Garg
f77d5db38e Merge pull request #41258 from deepeshgarg007/psoa_ageing
fix: PSOA ageing
2024-04-30 13:10:12 +05:30
Deepesh Garg
fed2d11905 fix: PSOA ageing 2024-04-30 12:50:21 +05:30
ruthra kumar
8861a76bd0 Merge pull request #41252 from ruthra-kumar/permission_issue_on_tax_withholding_category
fix: permission issue when user permission restricts on company
2024-04-30 11:20:18 +05:30
Deepesh Garg
56f0b2628e Merge pull request #41171 from deepeshgarg007/deferred_revenue_expense_report
fix: Invoice with no GLEs in deferred report
2024-04-30 11:14:54 +05:30
ruthra kumar
7a6cc84d41 fix: permission issue when user permission restricts on company 2024-04-30 10:26:55 +05:30
Deepesh Garg
a34c99385c Merge pull request #41173 from deepeshgarg007/br_paid_amount
fix: paid amount in bank reconciliation tool
2024-04-30 10:25:03 +05:30
Deepesh Garg
94339c569e Merge pull request #41219 from deepeshgarg007/br_company_user_perm
fix: Ignore user perm in Bank Reco Tool for company
2024-04-30 10:22:27 +05:30
ruthra kumar
1df06efd97 Merge pull request #41240 from ruthra-kumar/fix_based_on_payment_term_for_single_term
fix: display term name for single term invoices in AR/AP
2024-04-30 07:09:00 +05:30
ruthra kumar
52e52e83a9 Merge pull request #41194 from ruthra-kumar/fix_incorrectly_applying_tds_when_advance_in_prev_fiscal_year
fix: TDS incorrectly applied when Advance is in previous FY
2024-04-30 06:56:49 +05:30
Frappe PR Bot
9f092dbe9f chore: update POT file (#41220) 2024-04-30 00:51:38 +02:00
Frappe PR Bot
b324a02fbb fix: sync translations from crowdin (#40965) 2024-04-30 00:50:41 +02:00
ruthra kumar
5fa4cfee04 fix: display term name for single term invoices 2024-04-29 19:31:21 +05:30
Deepesh Garg
5e48c1ac51 Merge pull request #41237 from nikkothari22/fix-party-type-label-on-accounting-preview
fix: party and party type label on accounting preview
2024-04-29 18:53:39 +05:30
Nabin Hait
e0d12ba4d0 fix: Patch to fix the incorrect debit and credit in transaction currency 2024-04-29 17:44:41 +05:30
Nikhil Kothari
f7f3b22786 fix: party and party type label on accounting preview 2024-04-29 17:31:39 +05:30
Nabin Hait
e43697d359 fix: Merge debit and credit in transaction currency while merging gle with similar head 2024-04-29 17:01:48 +05:30
ruthra kumar
2f9a144023 test: TDS deduction across fiscal year 2024-04-29 12:29:05 +05:30
Deepesh Garg
9e2ecd67f9 Merge pull request #40865 from rtdany10/def-expns
fix: missing def expense if no exp in first month
2024-04-29 10:24:35 +05:30
ruthra kumar
b195f519e2 fix: incorrectly applying TDS when Advance is in previous FY 2024-04-29 08:24:20 +05:30
Deepesh Garg
9f346e7ba0 fix: Ignore user perm in Bank Reco Tool for company 2024-04-28 14:41:34 +05:30
ruthra kumar
41695e601d Merge pull request #41208 from ruthra-kumar/incorrect_receivable_account_handling_on_reconciliation_tool
fix: set receivable account based on response type
2024-04-26 14:12:02 +05:30
ruthra kumar
066859cca0 fix: handle and receivable accounts based on response type 2024-04-26 13:59:22 +05:30
rohitwaghchaure
44f9059338 Merge pull request #41165 from rohitwaghchaure/feat-allow-to-do-reposting-for-all-transactions
feat: allow to do reposting for all stock transactions (audit)
2024-04-26 13:43:34 +05:30
rohitwaghchaure
d95d00b7c8 Merge pull request #41167 from rohitwaghchaure/feat-warehouse-type-filter-for-reports
fix: warehouse type filter for stock reports
2024-04-26 13:10:25 +05:30
rohitwaghchaure
880d061164 Merge pull request #41182 from Nihantra-Patel/email_-rendering
fix: rendering the email template when user HTML
2024-04-26 13:09:53 +05:30
rohitwaghchaure
bb5f1dae75 Merge pull request #41185 from Nihantra-Patel/delivery_trip
fix: args when get the delivery note in delivery trip
2024-04-26 13:09:11 +05:30
Rushabh Mehta
1bb1b69dea Merge pull request #40716 from khushi8112/incorrect-cash-flow-report
Incorrect cash flow report
2024-04-26 12:52:57 +05:30
rohitwaghchaure
3562c2d1d1 Merge pull request #41195 from rohitwaghchaure/fixed-incorrect-basic-rate-13963
fix: basic rate for SABB
2024-04-26 12:49:46 +05:30
Khushi Rawat
46736a737f fix(minor): don't translate voucher type 2024-04-26 12:36:34 +05:30
Rushabh Mehta
5b1e860489 Merge pull request #40915 from khushi8112/company-field-should-be-fetch-from-parent-acc
fix: company field should be fetch from parent acc
2024-04-26 12:28:06 +05:30
rohitwaghchaure
8e095335be Merge pull request #41192 from rohitwaghchaure/fixed-13960
fix: duplicate column in the stock ledger report
2024-04-26 12:21:47 +05:30
Rohit Waghchaure
7fa94843aa fix: basic rate for SABB 2024-04-26 12:20:08 +05:30
Rohit Waghchaure
be7fd6bfb4 fix: duplicate column in the stock ledger report 2024-04-26 10:43:30 +05:30
ruthra kumar
90d1873ad0 Merge pull request #41183 from ruthra-kumar/disable_advance_for_non_customer_or_supplier
fix: restrict Advance in separate party to customer and supplier
2024-04-25 19:59:16 +05:30
Ankush Menat
341fb6d8f3 fix: handle stock balance unbuffered_cursor error (#41186) 2024-04-25 11:43:58 +00:00
Nihantra Patel
ca577f7aaa fix: args when get the delivery note in delivery trip 2024-04-25 16:46:29 +05:30
Nihantra Patel
2f359e201d fix: args when get the delivery note in delivery trip 2024-04-25 16:42:38 +05:30
ruthra kumar
3c1af2acf0 fix: enable advance in separate acc only for customer and Supplier 2024-04-25 13:26:15 +05:30
Nihantra C. Patel
7cb66f7fd3 fix: rendering the email template when user HTML 2024-04-25 12:01:28 +05:30
Deepesh Garg
a48966f08c fix: paid amount in bank reconciliation tool 2024-04-24 21:50:03 +05:30
Deepesh Garg
ecf07bd128 chore: remove debug flag 2024-04-24 19:44:47 +05:30
Deepesh Garg
1c613ada6f fix: Invoice with no GLEs in deferred report 2024-04-24 19:40:05 +05:30
Rohit Waghchaure
4250ac821f fix: warehouse type filter for stock reports 2024-04-24 14:41:10 +05:30
Rohit Waghchaure
aefbe21b46 feat: allow to do reposting for all transactions (audit) 2024-04-24 14:31:29 +05:30
Akhil Narang
808dbfcade fix(supplier): add back import that got removed by ruff
It isn't unused, just isn't directly called

Fixes 3effaf21ef

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
2024-04-23 13:26:05 +05:30
Nihantra Patel
2896e3666c fix: address filter and quotation to for prospect 2024-04-16 18:13:49 +05:30
Nihantra Patel
24a68a79df fix: address filter and quotation to for prospect 2024-04-16 18:12:20 +05:30
Nihantra Patel
fe5b88522e fix: address filter and quotation to for prospect 2024-04-16 18:06:42 +05:30
Khushi Rawat
5f7e06ba6f Merge branch 'develop' into company-field-should-be-fetch-from-parent-acc 2024-04-09 14:36:04 +05:30
“Khushi
d791fa7460 fix: by default company field value is fetched from parent account 2024-04-09 11:18:33 +05:30
Dany Robert
01888c98bc fix: expense causing p&l test case to fail 2024-04-06 06:13:42 +00:00
Dany Robert
581af4eced chore: semgrep 2024-04-05 18:16:26 +00:00
Dany Robert
7ef4dbcaf6 fix: test case for zero deferred expense 2024-04-05 17:59:48 +00:00
Dany Robert
5c9ce575f6 fix: missing def expense if no exp in first month 2024-04-04 16:18:20 +00:00
“Khushi
db1f7fcf63 fix: fixed incorrect cash flow data in finacial statement report 2024-03-27 16:48:49 +05:30
Deepesh Garg
eccd5b4c5d chore: better description 2024-03-16 19:11:59 +05:30
Nihantra Patel
d721de13aa fix: update description of Supplier Invoice Number 2024-03-15 12:14:34 +05:30
Nihantra C. Patel
3c9640df27 fix: print format bold for field "total" 2024-02-23 16:37:11 +05:30
153 changed files with 25201 additions and 19239 deletions

View File

@@ -29,7 +29,11 @@ jobs:
steps:
- name: Update notes
run: |
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG \
| jq -r '.body' \
| sed -E '/^\* (chore|ci|test|docs|style)/d' \
| sed -E 's/by @mergify //'
)
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"

View File

@@ -59,12 +59,14 @@ repos:
rev: v0.2.0
hooks:
- id: ruff
name: "Run ruff linter and apply fixes"
args: ["--fix"]
name: "Run ruff import sorter"
args: ["--select=I", "--fix"]
- id: ruff
name: "Run ruff linter"
- id: ruff-format
name: "Format Python code"
name: "Run ruff formatter"
ci:
autoupdate_schedule: weekly

View File

@@ -360,45 +360,45 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
)
if not amount:
return
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry:
book_revenue_via_journal_entry(
doc,
credit_account,
debit_account,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
submit_journal_entry,
)
else:
make_gl_entries(
doc,
credit_account,
debit_account,
against,
amount,
base_amount,
gl_posting_date,
project,
account_currency,
item.cost_center,
item,
deferred_process,
)
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:

View File

@@ -65,6 +65,8 @@
"label": "Is Group"
},
{
"fetch_from": "parent_account.company",
"fetch_if_empty": 1,
"fieldname": "company",
"fieldtype": "Link",
"in_standard_filter": 1,

View File

@@ -222,7 +222,7 @@ frappe.treeview_settings["Account"] = {
"General Ledger",
"Balance Sheet",
"Profit and Loss Statement",
"Cash Flow Statement",
"Cash Flow",
"Accounts Payable",
"Accounts Receivable",
]) {

View File

@@ -3,4 +3,23 @@
frappe.ui.form.on("Accounts Settings", {
refresh: function (frm) {},
enable_immutable_ledger: function (frm) {
if (!frm.doc.enable_immutable_ledger) {
return;
}
let msg = __("Enabling this will change the way how cancelled transactions are handled.");
msg += " ";
msg += __("Please enable only if the understand the effects of enabling this.");
msg += "<br>";
msg += "Do you still want to enable immutable ledger?";
frappe.confirm(
msg,
() => {},
() => {
frm.set_value("enable_immutable_ledger", 0);
}
);
},
});

View File

@@ -12,6 +12,7 @@
"unlink_advance_payment_on_cancelation_of_order",
"column_break_13",
"delete_linked_ledger_entries",
"enable_immutable_ledger",
"invoicing_features_section",
"check_supplier_invoice_uniqueness",
"automatically_fetch_payment_terms",
@@ -105,7 +106,7 @@
},
{
"default": "0",
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
"description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year",
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness"
@@ -454,6 +455,13 @@
"fieldname": "remarks_section",
"fieldtype": "Section Break",
"label": "Remarks Column Length"
},
{
"default": "0",
"description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well",
"fieldname": "enable_immutable_ledger",
"fieldtype": "Check",
"label": "Enable Immutable Ledger"
}
],
"icon": "icon-cog",
@@ -461,7 +469,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-27 13:05:57.568638",
"modified": "2024-05-11 23:19:44.673975",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
@@ -490,4 +498,4 @@
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -39,6 +39,7 @@ class AccountsSettings(Document):
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
enable_common_party_accounting: DF.Check
enable_fuzzy_matching: DF.Check
enable_immutable_ledger: DF.Check
enable_party_matching: DF.Check
frozen_accounts_modifier: DF.Link | None
general_ledger_remarks_length: DF.Int

View File

@@ -59,6 +59,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
);
frm.add_custom_button(__("Auto Reconcile"), function () {
if (!frm.doc.bank_account) {
frappe.msgprint(__("Please select Bank Account"));
return;
}
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
args: {

View File

@@ -26,6 +26,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Company",
"options": "Company"
},
@@ -118,7 +119,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-03-27 13:06:37.922473",
"modified": "2024-04-28 14:40:50.910884",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Reconciliation Tool",

View File

@@ -495,12 +495,12 @@ def check_matching(
bank_account,
company,
transaction,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
document_types=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
):
exact_match = True if "exact_match" in document_types else False
@@ -540,14 +540,14 @@ def get_queries(
bank_account,
company,
transaction,
document_types,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
exact_match,
common_filters,
document_types=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
exact_match=None,
common_filters=None,
):
# get queries to get matching vouchers
account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
@@ -580,15 +580,15 @@ def get_matching_queries(
bank_account,
company,
transaction,
document_types,
exact_match,
account_from_to,
from_date,
to_date,
filter_by_reference_date,
from_reference_date,
to_reference_date,
common_filters,
document_types=None,
exact_match=None,
account_from_to=None,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
common_filters=None,
):
queries = []
currency = get_account_currency(bank_account)
@@ -719,7 +719,7 @@ def get_pe_matching_query(
(ref_rank + amount_rank + party_rank + 1).as_("rank"),
ConstantColumn("Payment Entry").as_("doctype"),
pe.name,
pe.paid_amount,
pe.paid_amount_after_tax.as_("paid_amount"),
pe.reference_no,
pe.reference_date,
pe.party,

View File

@@ -148,7 +148,19 @@ class Dunning(AccountsController):
def on_cancel(self):
super().on_cancel()
self.ignore_linked_doctypes = ["GL Entry"]
self.ignore_linked_doctypes = [
"GL Entry",
"Stock Ledger Entry",
"Repost Item Valuation",
"Repost Payment Ledger",
"Repost Payment Ledger Items",
"Repost Accounting Ledger",
"Repost Accounting Ledger Items",
"Unreconcile Payment",
"Unreconcile Payment Entries",
"Payment Ledger Entry",
"Serial and Batch Bundle",
]
def resolve_dunning(doc, state):

View File

@@ -457,7 +457,7 @@ class JournalEntry(AccountsController):
self.voucher_type == "Depreciation Entry"
and d.reference_type == "Asset"
and d.reference_name
and d.account_type == "Depreciation"
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
and d.debit
):
asset = frappe.get_doc("Asset", d.reference_name)

View File

@@ -20,6 +20,7 @@
"party",
"party_name",
"book_advance_payments_in_separate_party_account",
"reconcile_on_advance_payment_date",
"column_break_11",
"bank_account",
"party_bank_account",
@@ -751,6 +752,7 @@
"fieldtype": "Check",
"hidden": 1,
"label": "Book Advance Payments in Separate Party Account",
"no_copy": 1,
"read_only": 1
},
{
@@ -766,6 +768,16 @@
"label": "In Words",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fetch_from": "company.reconcile_on_advance_payment_date",
"fieldname": "reconcile_on_advance_payment_date",
"fieldtype": "Check",
"hidden": 1,
"label": "Reconcile on Advance Payment Date",
"no_copy": 1,
"read_only": 1
}
],
"index_web_pages_for_search": 1,
@@ -779,7 +791,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-04-11 11:25:07.366347",
"modified": "2024-05-17 10:21:11.199445",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -197,6 +197,7 @@ class PaymentEntry(AccountsController):
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
return
self.book_advance_payments_in_separate_party_account = False
if self.party_type not in ("Customer", "Supplier"):
return
@@ -1225,88 +1226,71 @@ class PaymentEntry(AccountsController):
)
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
if self.book_advance_payments_in_separate_party_account:
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(d.reference_doctype, d.reference_name, "cost_center")
gle = party_gl_dict.copy()
if self.payment_type == "Receive":
amount = self.base_paid_amount
else:
amount = self.base_received_amount
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
exchange_rate = self.get_exchange_rate()
amount_in_account_currency = amount * exchange_rate
gle.update(
{
dr_or_cr: amount,
dr_or_cr + "_in_account_currency": amount_in_account_currency,
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
"cost_center": self.cost_center,
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)
gl_entries.append(gle)
else:
for d in self.get("references"):
# re-defining dr_or_cr for every reference in order to avoid the last value affecting calculation of reverse
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
cost_center = self.cost_center
if d.reference_doctype == "Sales Invoice" and not cost_center:
cost_center = frappe.db.get_value(
d.reference_doctype, d.reference_name, "cost_center"
)
gle = party_gl_dict.copy()
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(
d
)
reverse_dr_or_cr = 0
if d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]:
is_return = frappe.db.get_value(d.reference_doctype, d.reference_name, "is_return")
payable_party_types = get_party_types_from_account_type("Payable")
receivable_party_types = get_party_types_from_account_type("Receivable")
if (
is_return
and self.party_type in receivable_party_types
and (self.payment_type == "Pay")
):
reverse_dr_or_cr = 1
elif (
is_return
and self.party_type in payable_party_types
and (self.payment_type == "Receive")
):
reverse_dr_or_cr = 1
if is_return and not reverse_dr_or_cr:
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
if self.book_advance_payments_in_separate_party_account:
gle.update(
{
dr_or_cr: abs(allocated_amount_in_company_currency),
dr_or_cr + "_in_account_currency": abs(d.allocated_amount),
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
}
)
gl_entries.append(gle)
if self.unallocated_amount:
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
exchange_rate = self.get_exchange_rate()
base_unallocated_amount = self.unallocated_amount * exchange_rate
gle = party_gl_dict.copy()
gle.update(
{
dr_or_cr + "_in_account_currency": self.unallocated_amount,
dr_or_cr: base_unallocated_amount,
}
)
gl_entries.append(gle)
gl_entries.append(gle)
def make_advance_gl_entries(
self, entry: object | dict = None, cancel: bool = 0, update_outstanding: str = "Yes"
@@ -1321,7 +1305,7 @@ class PaymentEntry(AccountsController):
def add_advance_gl_entries(self, gl_entries: list, entry: object | dict | None):
"""
If 'entry' is passed, GL enties only for that reference is added.
If 'entry' is passed, GL entries only for that reference is added.
"""
if self.book_advance_payments_in_separate_party_account:
references = [x for x in self.get("references")]
@@ -1333,8 +1317,6 @@ class PaymentEntry(AccountsController):
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Sales Order",
"Purchase Order",
"Payment Entry",
):
self.add_advance_gl_for_reference(gl_entries, ref)
@@ -1362,13 +1344,16 @@ class PaymentEntry(AccountsController):
"voucher_detail_no": invoice.name,
}
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
if getdate(posting_date) < getdate(self.posting_date):
if self.reconcile_on_advance_payment_date:
posting_date = self.posting_date
else:
date_field = "posting_date"
if invoice.reference_doctype in ["Sales Order", "Purchase Order"]:
date_field = "transaction_date"
posting_date = frappe.db.get_value(invoice.reference_doctype, invoice.reference_name, date_field)
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
dr_or_cr, account = self.get_dr_and_account_for_advances(invoice)
args_dict["account"] = account

View File

@@ -1477,6 +1477,68 @@ class TestPaymentEntry(FrappeTestCase):
self.check_gl_entries()
self.check_pl_entries()
def test_advance_as_liability_against_order(self):
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as _make_purchase_invoice,
)
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
company = "_Test Company"
advance_account = create_account(
parent_account="Current Liabilities - _TC",
account_name="Advances Paid",
company=company,
account_type="Liability",
)
frappe.db.set_value(
"Company",
company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": advance_account,
},
)
po = create_purchase_order(supplier="_Test Supplier")
pe = get_payment_entry("Purchase Order", po.name, bank_account="Cash - _TC")
pe.save().submit()
pre_reconciliation_gle = [
{"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0},
{"account": advance_account, "debit": 5000.0, "credit": 0.0},
]
self.voucher_no = pe.name
self.expected_gle = pre_reconciliation_gle
self.check_gl_entries()
# Make Purchase Invoice against the order
pi = _make_purchase_invoice(po.name)
pi.append(
"advances",
{
"reference_type": pe.doctype,
"reference_name": pe.name,
"reference_row": pe.references[0].name,
"advance_amount": 5000,
"allocated_amount": 5000,
},
)
pi.save().submit()
# # assert General and Payment Ledger entries post partial reconciliation
self.expected_gle = [
{"account": pi.credit_to, "debit": 5000.0, "credit": 0.0},
{"account": "Cash - _TC", "debit": 0.0, "credit": 5000.0},
{"account": advance_account, "debit": 5000.0, "credit": 0.0},
{"account": advance_account, "debit": 0.0, "credit": 5000.0},
]
self.voucher_no = pe.name
self.check_gl_entries()
def check_pl_entries(self):
ple = frappe.qb.DocType("Payment Ledger Entry")
pl_entries = (

View File

@@ -176,8 +176,12 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
},
callback: (r) => {
if (!r.exc && r.message) {
this.frm.set_value("receivable_payable_account", r.message[0]);
this.frm.set_value("default_advance_account", r.message[1]);
if (typeof r.message === "string") {
this.frm.set_value("receivable_payable_account", r.message);
} else if (Array.isArray(r.message)) {
this.frm.set_value("receivable_payable_account", r.message[0]);
this.frm.set_value("default_advance_account", r.message[1]);
}
}
this.frm.refresh();
},

View File

@@ -1525,6 +1525,55 @@ class TestPaymentReconciliation(FrappeTestCase):
]
self.assertEqual(pl_entries, expected_ple)
def test_advance_payment_reconciliation_date(self):
frappe.db.set_value(
"Company",
self.company,
{
"book_advance_payments_in_separate_party_account": 1,
"default_advance_paid_account": self.advance_payable_account,
"reconcile_on_advance_payment_date": 1,
},
)
self.supplier = "_Test Supplier"
amount = 1500
pe = self.create_payment_entry(amount=amount)
pe.posting_date = add_days(nowdate(), -1)
pe.party_type = "Supplier"
pe.party = self.supplier
pe.payment_type = "Pay"
pe.paid_from = self.cash
pe.paid_to = self.advance_payable_account
pe.save().submit()
pi = self.create_purchase_invoice(qty=10, rate=100)
self.assertNotEqual(pe.posting_date, pi.posting_date)
pr = self.create_payment_reconciliation(party_is_customer=False)
pr.default_advance_account = self.advance_payable_account
pr.from_payment_date = pe.posting_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [invoice.as_dict() for invoice in pr.invoices]
payments = [payment.as_dict() for payment in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# Assert Ledger Entries
gl_entries = frappe.db.get_all(
"GL Entry",
filters={"voucher_no": pe.name, "is_cancelled": 0, "posting_date": pe.posting_date},
)
self.assertEqual(len(gl_entries), 4)
pl_entries = frappe.db.get_all(
"Payment Ledger Entry",
filters={"voucher_no": pe.name, "delinked": 0, "posting_date": pe.posting_date},
)
self.assertEqual(len(pl_entries), 3)
def make_customer(customer_name, currency=None):
if not frappe.db.exists("Customer", customer_name):

View File

@@ -481,7 +481,7 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
if closing_entry:
closing_entry.set_status(update=True, status="Failed")
if isinstance(error_message, list):
error_message = frappe.json.dumps(error_message)
error_message = json.dumps(error_message)
closing_entry.db_set("error_message", error_message)
raise

View File

@@ -74,15 +74,21 @@
"discount_amount",
"discount_percentage",
"for_price_list",
"section_break_13",
"threshold_percentage",
"priority",
"dynamic_condition_tab",
"condition",
"column_break_66",
"section_break_13",
"apply_multiple_pricing_rules",
"apply_discount_on_rate",
"column_break_66",
"threshold_percentage",
"validate_pricing_rule_section",
"validate_applied_rule",
"column_break_texp",
"rule_description",
"priority_section",
"has_priority",
"column_break_sayg",
"priority",
"help_section",
"pricing_rule_help",
"reference_section",
@@ -477,7 +483,7 @@
{
"collapsible": 1,
"fieldname": "section_break_13",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Advanced Settings"
},
{
@@ -487,6 +493,7 @@
"label": "Threshold for Suggestion (In Percentage)"
},
{
"depends_on": "has_priority",
"description": "Higher the number, higher the priority",
"fieldname": "priority",
"fieldtype": "Select",
@@ -513,6 +520,7 @@
{
"default": "0",
"depends_on": "eval:doc.price_or_product_discount == 'Price'",
"description": "If enabled, then system will only validate the pricing rule and not apply automatically. User has to manually set the discount percentage / margin / free items to validate the pricing rule",
"fieldname": "validate_applied_rule",
"fieldtype": "Check",
"label": "Validate Applied Rule"
@@ -525,7 +533,8 @@
},
{
"fieldname": "help_section",
"fieldtype": "Section Break",
"fieldtype": "Tab Break",
"label": "Help Article",
"options": "Simple"
},
{
@@ -603,12 +612,42 @@
"fieldname": "apply_recursion_over",
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)"
},
{
"fieldname": "priority_section",
"fieldtype": "Section Break",
"label": "Priority"
},
{
"fieldname": "dynamic_condition_tab",
"fieldtype": "Tab Break",
"label": "Dynamic Condition"
},
{
"fieldname": "validate_pricing_rule_section",
"fieldtype": "Section Break",
"label": "Validate Pricing Rule"
},
{
"fieldname": "column_break_texp",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_sayg",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Enable this checkbox even if you want to set the zero priority",
"fieldname": "has_priority",
"fieldtype": "Check",
"label": "Has Priority"
}
],
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2024-03-27 13:10:17.521896",
"modified": "2024-05-17 13:16:34.496704",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -27,9 +27,7 @@ class PricingRule(Document):
from frappe.types import DF
from erpnext.accounts.doctype.pricing_rule_brand.pricing_rule_brand import PricingRuleBrand
from erpnext.accounts.doctype.pricing_rule_item_code.pricing_rule_item_code import (
PricingRuleItemCode,
)
from erpnext.accounts.doctype.pricing_rule_item_code.pricing_rule_item_code import PricingRuleItemCode
from erpnext.accounts.doctype.pricing_rule_item_group.pricing_rule_item_group import (
PricingRuleItemGroup,
)
@@ -67,6 +65,7 @@ class PricingRule(Document):
free_item_rate: DF.Currency
free_item_uom: DF.Link | None
free_qty: DF.Float
has_priority: DF.Check
is_cumulative: DF.Check
is_recursive: DF.Check
item_groups: DF.Table[PricingRuleItemGroup]
@@ -156,6 +155,12 @@ class PricingRule(Document):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
def validate_mandatory(self):
if self.has_priority and not self.priority:
throw(_("Priority is mandatory"), frappe.MandatoryError, _("Please Set Priority"))
if self.priority and not self.has_priority:
self.has_priority = 1
for apply_on, field in apply_on_dict.items():
if self.apply_on == apply_on and len(self.get(field) or []) < 1:
throw(_("{0} is not added in the table").format(apply_on), frappe.MandatoryError)
@@ -573,6 +578,22 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"):
# Apply discount on discounted rate
item_details[field] += (100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)
elif args.price_list_rate:
value = pricing_rule.get(field, 0)
calculate_discount_percentage = False
if field == "discount_percentage":
field = "discount_amount"
value = args.price_list_rate * (value / 100)
calculate_discount_percentage = True
if field not in item_details:
item_details.setdefault(field, 0)
item_details[field] += value if pricing_rule else args.get(field, 0)
if calculate_discount_percentage and args.price_list_rate and item_details.discount_amount:
item_details.discount_percentage = flt(
(flt(item_details.discount_amount) / flt(args.price_list_rate)) * 100
)
else:
if field not in item_details:
item_details.setdefault(field, 0)

View File

@@ -1102,7 +1102,116 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4)
self.assertEqual(so.items[1].qty, 3)
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 1",
"name": "_Test Pricing Rule 1",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"apply_multiple_pricing_rules": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 2",
"name": "_Test Pricing Rule 2",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Amount",
"discount_amount": 100,
"apply_multiple_pricing_rules": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
self.assertEqual(so.items[0].discount_amount, 200)
self.assertEqual(so.items[0].rate, 800)
frappe.delete_doc_if_exists("Sales Order", so.name)
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
def test_priority_of_multiple_pricing_rules(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 1",
"name": "_Test Pricing Rule 1",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"has_priority": 1,
"priority": 1,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule 2",
"name": "_Test Pricing Rule 2",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"price_or_product_discount": "Price",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 20,
"has_priority": 1,
"priority": 3,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
so = make_sales_order(item_code="_Test Item", qty=1, price_list_rate=1000, do_not_submit=True)
self.assertEqual(so.items[0].discount_percentage, 20)
self.assertEqual(so.items[0].rate, 800)
frappe.delete_doc_if_exists("Sales Order", so.name)
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")
test_dependencies = ["Campaign"]
@@ -1132,6 +1241,7 @@ def make_pricing_rule(**args):
"priority": args.priority or 1,
"discount_amount": args.discount_amount or 0.0,
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0,
"has_priority": args.has_priority or 0,
}
)

View File

@@ -6,6 +6,7 @@
import copy
import json
import math
import frappe
from frappe import _, bold
@@ -32,6 +33,9 @@ def get_pricing_rules(args, doc=None):
for apply_on in ["Item Code", "Item Group", "Brand"]:
pricing_rules.extend(_get_pricing_rules(apply_on, args, values))
if pricing_rules and pricing_rules[0].has_priority:
continue
if pricing_rules and not apply_multiple_pricing_rules(pricing_rules):
break
@@ -653,7 +657,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = round(qty)
qty = math.floor(qty)
free_item_data_args = {
"item_code": free_item,

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _, qb
from frappe.model.document import Document
@@ -502,7 +504,7 @@ def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None
if for_filter:
if isinstance(for_filter, str):
for_filter = frappe.json.loads(for_filter)
for_filter = json.loads(for_filter)
running_doc = frappe.db.get_value(
"Process Payment Reconciliation",

View File

@@ -158,7 +158,7 @@ def set_ageing(doc, entry):
ageing_filters = frappe._dict(
{
"company": doc.company,
"report_date": doc.to_date,
"report_date": doc.posting_date,
"ageing_based_on": doc.ageing_based_on,
"range1": 30,
"range2": 60,

View File

@@ -340,10 +340,11 @@
<table class="table table-bordered">
<thead>
<tr>
<th style="width: 25%">30 Days</th>
<th style="width: 25%">60 Days</th>
<th style="width: 25%">90 Days</th>
<th style="width: 25%">120 Days</th>
<th style="width: 25%">0 - 30 Days</th>
<th style="width: 25%">30 - 60 Days</th>
<th style="width: 25%">60 - 90 Days</th>
<th style="width: 25%">90 - 120 Days</th>
<th style="width: 20%">Above 120 Days</th>
</tr>
</thead>
<tbody>
@@ -352,6 +353,7 @@
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>

View File

@@ -11,13 +11,15 @@
{
"fieldname": "cost_center_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Cost Center",
"options": "Cost Center"
"options": "Cost Center",
"reqd": 1
}
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:23.244686",
"modified": "2024-05-03 17:16:51.666461",
"modified_by": "Administrator",
"module": "Accounts",
"name": "PSOA Cost Center",

View File

@@ -15,7 +15,7 @@ class PSOACostCenter(Document):
if TYPE_CHECKING:
from frappe.types import DF
cost_center_name: DF.Link | None
cost_center_name: DF.Link
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data

View File

@@ -489,10 +489,12 @@ function hide_fields(doc) {
var item_fields_stock = ["warehouse_section", "received_qty", "rejected_qty"];
cur_frm.fields_dict["items"].grid.set_column_disp(
item_fields_stock,
cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false
);
if (cur_frm.fields_dict["items"]) {
cur_frm.fields_dict["items"].grid.set_column_disp(
item_fields_stock,
cint(doc.update_stock) == 1 || cint(doc.is_return) == 1 ? true : false
);
}
cur_frm.refresh_fields();
}

View File

@@ -1063,7 +1063,7 @@ class PurchaseInvoice(BuyingController):
)
# check if the exchange rate has changed
if item.get("purchase_receipt"):
if item.get("purchase_receipt") and self.auto_accounting_for_stock:
if (
exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
@@ -1147,7 +1147,7 @@ class PurchaseInvoice(BuyingController):
pr_items = frappe.get_all(
"Purchase Receipt Item",
filters={"parent": ("in", linked_purchase_receipts)},
fields=["name", "provisional_expense_account", "qty", "base_rate"],
fields=["name", "provisional_expense_account", "qty", "base_rate", "rate"],
)
default_provisional_account = self.get_company_default("default_provisional_account")
provisional_accounts = set(
@@ -1175,6 +1175,7 @@ class PurchaseInvoice(BuyingController):
"provisional_account": item.provisional_expense_account or default_provisional_account,
"qty": item.qty,
"base_rate": item.base_rate,
"rate": item.rate,
"has_provisional_entry": item.name in rows_with_provisional_entries,
}
@@ -1191,7 +1192,10 @@ class PurchaseInvoice(BuyingController):
self.posting_date,
pr_item.get("provisional_account"),
reverse=1,
item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
item_amount=(
(min(item.qty, pr_item.get("qty")) * pr_item.get("rate"))
* purchase_receipt_doc.get("conversion_rate")
),
)
def update_gross_purchase_amount_for_linked_assets(self, item):
@@ -1207,7 +1211,7 @@ class PurchaseInvoice(BuyingController):
asset.name,
{
"gross_purchase_amount": purchase_amount,
"purchase_receipt_amount": purchase_amount,
"purchase_amount": purchase_amount,
},
)

View File

@@ -1,7 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:ACC-REPOST-{#####}",
"creation": "2023-07-04 13:07:32.923675",
"default_view": "List",
"doctype": "DocType",
@@ -55,11 +53,10 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:32.013542",
"modified": "2024-05-23 17:00:42.984798",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Accounting Ledger",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{

View File

@@ -1,6 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2022-10-19 21:59:33.553852",
"doctype": "DocType",
"editable_grid": 1,
@@ -99,7 +98,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:10:32.740806",
"modified": "2024-05-23 17:00:31.540640",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Repost Payment Ledger",

View File

@@ -2042,7 +2042,7 @@
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
"label": "Address & Contact"
},
{
"fieldname": "payments_tab",
@@ -2203,7 +2203,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2024-04-11 11:30:26.272441",
"modified": "2024-05-08 18:02:28.549041",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -393,6 +393,9 @@ class SalesInvoice(SellingController):
validate_account_head(item.idx, item.income_account, self.company, "Income")
def set_tax_withholding(self):
if self.get("is_opening") == "Yes":
return
tax_withholding_details = get_party_tax_withholding_details(self)
if not tax_withholding_details:

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import copy
import json
import frappe
from frappe.model.dynamic_links import get_dynamic_link_map
@@ -1783,6 +1784,49 @@ class TestSalesInvoice(FrappeTestCase):
self.assertTrue(gle)
def test_gle_in_transaction_currency(self):
# create multi currency sales invoice with 2 items with same income account
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
)
# add 2nd item with same income account
si.append(
"items",
{
"item_code": "_Test Item",
"qty": 1,
"rate": 80,
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
si.submit()
gl_entries = frappe.db.sql(
"""select transaction_currency, transaction_exchange_rate,
debit_in_transaction_currency, credit_in_transaction_currency
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC'
order by account asc""",
si.name,
as_dict=1,
)
expected_gle = {
"transaction_currency": "USD",
"transaction_exchange_rate": 50,
"debit_in_transaction_currency": 0,
"credit_in_transaction_currency": 180,
}
for gle in gl_entries:
for field in expected_gle:
self.assertEqual(expected_gle[field], gle[field])
def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",
@@ -3690,9 +3734,9 @@ class TestSalesInvoice(FrappeTestCase):
map_docs(
method="erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
source_names=frappe.json.dumps([dn1.name, dn2.name]),
source_names=json.dumps([dn1.name, dn2.name]),
target_doc=si,
args=frappe.json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
args=json.dumps({"customer": dn1.customer, "merge_taxes": 1, "filtered_children": []}),
)
si.save().submit()

View File

@@ -870,7 +870,8 @@
"label": "Purchase Order",
"options": "Purchase Order",
"print_hide": 1,
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "column_break_92",
@@ -926,7 +927,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:36.139679",
"modified": "2024-05-23 16:36:18.970862",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -445,11 +445,11 @@ class TestSubscription(FrappeTestCase):
# Process subscription and create first invoice
# Subscription status will be unpaid since due date has already passed
subscription.process()
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")
subscription.process()
subscription.process(posting_date="2018-04-01")
self.assertEqual(len(subscription.invoices), 1)
def test_multi_currency_subscription(self):
@@ -462,7 +462,7 @@ class TestSubscription(FrappeTestCase):
party=party,
)
subscription.process()
subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid")

View File

@@ -12,6 +12,7 @@
{
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
@@ -20,6 +21,7 @@
{
"fieldname": "account",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Account",
"options": "Account",
@@ -28,7 +30,7 @@
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:52.419915",
"modified": "2024-04-30 10:26:48.218294",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Tax Withholding Account",

View File

@@ -9,6 +9,8 @@ from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt, getdate
from erpnext.controllers.accounts_controller import validate_account_head
class TaxWithholdingCategory(Document):
# begin: auto-generated types
@@ -53,6 +55,7 @@ class TaxWithholdingCategory(Document):
if d.get("account") in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
validate_account_head(d.idx, d.get("account"), d.get("company"))
existing_accounts.append(d.get("account"))
def validate_thresholds(self):
@@ -282,6 +285,14 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if taxable_vouchers:
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
# If advance is outside the current tax withholding period (usually a fiscal year), `get_deducted_tax` won't fetch it.
# updating `tax_deducted` with correct advance tax value (from current and previous previous withholding periods), will allow the
# rest of the below logic to function properly
# ---FY 2023-------------||---------------------FY 2024-----------------------||--
# ---Advance-------------||---------Inv_1--------Inv_2------------------------||--
if tax_deducted_on_advances:
tax_deducted += get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details)
tax_amount = 0
if party_type == "Supplier":
@@ -418,7 +429,7 @@ def get_taxes_deducted_on_advances_allocated(inv, tax_details):
frappe.qb.from_(at)
.inner_join(pe)
.on(pe.name == at.parent)
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
.select(pe.posting_date, at.parent, at.name, at.tax_amount, at.allocated_amount)
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
.where(at.parent.isin(advances))
.where(at.account_head == tax_details.account_head)
@@ -443,6 +454,16 @@ def get_deducted_tax(taxable_vouchers, tax_details):
return sum(entries)
def get_advance_tax_across_fiscal_year(tax_deducted_on_advances, tax_details):
"""
Only applies for Taxes deducted on Advance Payments
"""
advance_tax_from_across_fiscal_year = sum(
[adv.tax_amount for adv in tax_deducted_on_advances if adv.posting_date < tax_details.from_date]
)
return advance_tax_from_across_fiscal_year
def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
tds_amount = 0
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}

View File

@@ -1,18 +1,22 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import datetime
import unittest
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.utils import today
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.utils import get_fiscal_year
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
test_dependencies = ["Supplier Group", "Customer Group"]
class TestTaxWithholdingCategory(unittest.TestCase):
class TestTaxWithholdingCategory(FrappeTestCase):
@classmethod
def setUpClass(self):
# create relevant supplier, etc
@@ -21,7 +25,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
make_pan_no_field()
def tearDown(self):
cancel_invoices()
frappe.db.rollback()
def test_cumulative_threshold_tds(self):
frappe.db.set_value(
@@ -317,8 +321,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
d.cancel()
def test_tds_deduction_for_po_via_payment_entry(self):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
frappe.db.set_value(
"Supplier", "Test TDS Supplier8", "tax_withholding_category", "Cumulative Threshold TDS"
)
@@ -485,6 +487,133 @@ class TestTaxWithholdingCategory(unittest.TestCase):
pi2.cancel()
pi3.cancel()
def set_previous_fy_and_tax_category(self):
test_company = "_Test Company"
category = "Cumulative Threshold TDS"
def add_company_to_fy(fy, company):
if not [x.company for x in fy.companies if x.company == company]:
fy.append("companies", {"company": company})
fy.save()
# setup previous fiscal year
fiscal_year = get_fiscal_year(today(), company=test_company)
if prev_fiscal_year := get_fiscal_year(add_days(fiscal_year[1], -10)):
self.prev_fy = frappe.get_doc("Fiscal Year", prev_fiscal_year[0])
add_company_to_fy(self.prev_fy, test_company)
else:
# make previous fiscal year
start = datetime.date(fiscal_year[1].year - 1, fiscal_year[1].month, fiscal_year[1].day)
end = datetime.date(fiscal_year[2].year - 1, fiscal_year[2].month, fiscal_year[2].day)
self.prev_fy = frappe.get_doc(
{
"doctype": "Fiscal Year",
"year_start_date": start,
"year_end_date": end,
"companies": [{"company": test_company}],
}
)
self.prev_fy.save()
# setup tax withholding category for previous fiscal year
cat = frappe.get_doc("Tax Withholding Category", category)
cat.append(
"rates",
{
"from_date": self.prev_fy.year_start_date,
"to_date": self.prev_fy.year_end_date,
"tax_withholding_rate": 10,
"single_threshold": 0,
"cumulative_threshold": 30000,
},
)
cat.save()
def test_tds_across_fiscal_year(self):
"""
Advance TDS on previous fiscal year should be properly allocated on Invoices in upcoming fiscal year
--||-----FY 2023-----||-----FY 2024-----||--
--||-----Advance-----||---Inv1---Inv2---||--
"""
self.set_previous_fy_and_tax_category()
supplier = "Test TDS Supplier"
# Cumulative threshold 30000 and tax rate 10%
category = "Cumulative Threshold TDS"
frappe.db.set_value(
"Supplier",
supplier,
{
"tax_withholding_category": category,
"pan": "ABCTY1234D",
},
)
po_and_advance_posting_date = add_days(self.prev_fy.year_end_date, -10)
po = create_purchase_order(supplier=supplier, qty=10, rate=10000)
po.transaction_date = po_and_advance_posting_date
po.taxes = []
po.apply_tds = False
po.tax_withholding_category = None
po.save().submit()
# Partial advance
payment = get_payment_entry(po.doctype, po.name)
payment.posting_date = po_and_advance_posting_date
payment.paid_amount = 60000
payment.apply_tax_withholding_amount = 1
payment.tax_withholding_category = category
payment.references = []
payment.taxes = []
payment.save().submit()
self.assertEqual(len(payment.taxes), 1)
self.assertEqual(payment.taxes[0].tax_amount, 6000)
# Multiple partial invoices
payment.reload()
pi1 = make_purchase_invoice(source_name=po.name)
pi1.apply_tds = True
pi1.tax_withholding_category = category
pi1.items[0].qty = 3
pi1.items[0].rate = 10000
advances = pi1.get_advance_entries()
pi1.append(
"advances",
{
"reference_type": advances[0].reference_type,
"reference_name": advances[0].reference_name,
"advance_amount": advances[0].amount,
"allocated_amount": 30000,
},
)
pi1.save().submit()
pi1.reload()
payment.reload()
self.assertEqual(pi1.taxes, [])
self.assertEqual(payment.taxes[0].tax_amount, 6000)
self.assertEqual(payment.taxes[0].allocated_amount, 3000)
pi2 = make_purchase_invoice(source_name=po.name)
pi2.apply_tds = True
pi2.tax_withholding_category = category
pi2.items[0].qty = 3
pi2.items[0].rate = 10000
advances = pi2.get_advance_entries()
pi2.append(
"advances",
{
"reference_type": advances[0].reference_type,
"reference_name": advances[0].reference_name,
"advance_amount": advances[0].amount,
"allocated_amount": 30000,
},
)
pi2.save().submit()
pi2.reload()
payment.reload()
self.assertEqual(pi2.taxes, [])
self.assertEqual(payment.taxes[0].tax_amount, 6000)
self.assertEqual(payment.taxes[0].allocated_amount, 6000)
def cancel_invoices():
purchase_invoices = frappe.get_all(

View File

@@ -1,6 +1,8 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _, qb
from frappe.model.document import Document
@@ -163,7 +165,7 @@ def get_linked_payments_for_doc(
@frappe.whitelist()
def create_unreconcile_doc_for_selection(selections=None):
if selections:
selections = frappe.json.loads(selections)
selections = json.loads(selections)
# assuming each row is a unique voucher
for row in selections:
unrecon = frappe.new_doc("Unreconcile Payment")

View File

@@ -8,6 +8,7 @@ import frappe
from frappe import _
from frappe.model.meta import get_field_precision
from frappe.utils import cint, flt, formatdate, getdate, now
from frappe.utils.dashboard import cache_source
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -240,10 +241,16 @@ def merge_similar_entries(gl_map, precision=None):
same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
entry.debit_in_account_currency
)
same_head.debit_in_transaction_currency = flt(same_head.debit_in_transaction_currency) + flt(
entry.debit_in_transaction_currency
)
same_head.credit = flt(same_head.credit) + flt(entry.credit)
same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
entry.credit_in_account_currency
)
same_head.credit_in_transaction_currency = flt(same_head.credit_in_transaction_currency) + flt(
entry.credit_in_transaction_currency
)
else:
merged_gl_map.append(entry)
@@ -574,6 +581,8 @@ def make_reverse_gl_entries(
and make reverse gl entries by swapping debit and credit
"""
immutable_ledger_enabled = is_immutable_ledger_enabled()
if not gl_entries:
gl_entry = frappe.qb.DocType("GL Entry")
gl_entries = (
@@ -605,7 +614,6 @@ def make_reverse_gl_entries(
for x in gl_entries:
query = (
frappe.qb.update(gle)
.set(gle.is_cancelled, True)
.set(gle.modified, now())
.set(gle.modified_by, frappe.session.user)
.where(
@@ -620,9 +628,14 @@ def make_reverse_gl_entries(
& (gle.voucher_detail_no == x.voucher_detail_no)
)
)
if not immutable_ledger_enabled:
query = query.set(gle.is_cancelled, True)
query.run()
else:
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
if not immutable_ledger_enabled:
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
for entry in gl_entries:
new_gle = copy.deepcopy(entry)
@@ -641,6 +654,10 @@ def make_reverse_gl_entries(
new_gle["remarks"] = "On cancellation of " + new_gle["voucher_no"]
new_gle["is_cancelled"] = 1
if immutable_ledger_enabled:
new_gle["is_cancelled"] = 0
new_gle["posting_date"] = frappe.form_dict.get("posting_date") or getdate()
if new_gle["debit"] or new_gle["credit"]:
make_entry(new_gle, adv_adj, "Yes")
@@ -733,3 +750,7 @@ def validate_allowed_dimensions(gl_entry, dimension_filter_map):
),
InvalidAccountDimensionError,
)
def is_immutable_ledger_enabled():
return frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger")

View File

@@ -188,7 +188,9 @@ def set_address_details(
*,
ignore_permissions=False,
):
billing_address_field = "customer_address" if party_type == "Lead" else party_type.lower() + "_address"
billing_address_field = (
"customer_address" if party_type in ["Lead", "Prospect"] else party_type.lower() + "_address"
)
party_details[billing_address_field] = party_address or get_default_address(party_type, party.name)
if doctype:
party_details.update(

View File

@@ -501,8 +501,9 @@ class ReceivablePayableReport:
# Deduct that from paid amount pre allocation
row.paid -= flt(payment_terms_details[0].total_advance)
# If no or single payment terms, no need to split the row
if len(payment_terms_details) <= 1:
# If single payment terms, no need to split the row
if len(payment_terms_details) == 1 and payment_terms_details[0].payment_term:
self.append_payment_term(row, payment_terms_details[0], original_row)
return
for d in payment_terms_details:
@@ -1027,20 +1028,6 @@ class ReceivablePayableReport:
fieldtype="Link",
options="Contact",
)
if self.filters.party_type == "Customer":
self.add_column(
_("Customer Name"),
fieldname="customer_name",
fieldtype="Link",
options="Customer",
)
elif self.filters.party_type == "Supplier":
self.add_column(
_("Supplier Name"),
fieldname="supplier_name",
fieldtype="Link",
options="Supplier",
)
self.add_column(label=_("Cost Center"), fieldname="cost_center", fieldtype="Data")
self.add_column(label=_("Voucher Type"), fieldname="voucher_type", fieldtype="Data")

View File

@@ -266,6 +266,7 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters):
filters.end_date = fiscal_year.year_end_date
for company in companies:
filters.company = company
amount = get_account_type_based_gl_data(company, filters)
if amount and account_type == "Depreciation":

View File

@@ -58,9 +58,9 @@ class Deferred_Item:
For a given GL/Journal posting, get balance based on item type
"""
if self.type == "Deferred Sale Item":
return entry.debit - entry.credit
return flt(entry.debit) - flt(entry.credit)
elif self.type == "Deferred Purchase Item":
return -(entry.credit - entry.debit)
return -(flt(entry.credit) - flt(entry.debit))
return 0
def get_item_total(self):
@@ -147,7 +147,7 @@ class Deferred_Item:
actual = 0
for posting in self.gle_entries:
# if period.from_date <= posting.posting_date <= period.to_date:
if period.from_date <= posting.gle_posting_date <= period.to_date:
if period.from_date <= getdate(posting.gle_posting_date) <= period.to_date:
period_sum += self.get_amount(posting)
if posting.posted == "posted":
actual += self.get_amount(posting)
@@ -285,7 +285,7 @@ class Deferred_Revenue_and_Expense_Report:
qb.from_(inv_item)
.join(inv)
.on(inv.name == inv_item.parent)
.join(gle)
.left_join(gle)
.on((inv_item.name == gle.voucher_detail_no) & (deferred_account_field == gle.account))
.select(
inv.name.as_("doc"),

View File

@@ -279,3 +279,79 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
{"key": "aug_2021", "total": 0, "actual": 0},
]
self.assertEqual(report.period_total, expected)
@change_settings(
"Accounts Settings",
{"book_deferred_entries_based_on": "Months", "book_deferred_entries_via_journal_entry": 0},
)
def test_zero_amount(self):
self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
item = frappe.get_doc("Item", self.item)
item.enable_deferred_expense = 1
item.item_defaults[0].deferred_expense_account = self.deferred_expense_account
item.no_of_months_exp = 12
item.save()
pi = make_purchase_invoice(
item=self.item,
company=self.company,
supplier=self.supplier,
is_return=False,
update_stock=False,
posting_date=frappe.utils.datetime.date(2021, 12, 30),
parent_cost_center=self.cost_center,
cost_center=self.cost_center,
do_not_save=True,
rate=3910,
price_list_rate=3910,
warehouse=self.warehouse,
qty=1,
)
pi.set_posting_time = True
pi.items[0].enable_deferred_expense = 1
pi.items[0].service_start_date = "2021-12-30"
pi.items[0].service_end_date = "2022-12-30"
pi.items[0].deferred_expense_account = self.deferred_expense_account
pi.items[0].expense_account = self.expense_account
pi.save()
pi.submit()
pda = frappe.get_doc(
doctype="Process Deferred Accounting",
posting_date=nowdate(),
start_date="2022-01-01",
end_date="2022-01-31",
type="Expense",
company=self.company,
)
pda.insert()
pda.submit()
# execute report
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2022-01-31"))
self.filters = frappe._dict(
{
"company": self.company,
"filter_based_on": "Date Range",
"period_start_date": "2022-01-01",
"period_end_date": "2022-01-31",
"from_fiscal_year": fiscal_year.year,
"to_fiscal_year": fiscal_year.year,
"periodicity": "Monthly",
"type": "Expense",
"with_upcoming_postings": False,
}
)
report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
report.run()
# fetch the invoice from deferred invoices list
inv = [d for d in report.deferred_invoices if d.name == pi.name]
# make sure the list isn't empty
self.assertTrue(inv)
# calculate the total deferred expense for the period
inv = inv[0].calculate_invoice_revenue_expense_for_period()
deferred_exp = sum([inv[idx].actual for idx in range(len(report.period_list))])
# make sure the total deferred expense is greater than 0
self.assertLess(deferred_exp, 0)

View File

@@ -219,7 +219,8 @@ def get_conditions(filters):
if filters.get("account"):
filters.account = get_accounts_with_children(filters.account)
conditions.append("account in %(account)s")
if filters.account:
conditions.append("account in %(account)s")
if filters.get("cost_center"):
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
@@ -329,7 +330,7 @@ def get_accounts_with_children(accounts):
else:
frappe.throw(_("Account: {0} does not exist").format(d))
return list(set(all_accounts))
return list(set(all_accounts)) if all_accounts else None
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
@@ -460,7 +461,6 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
for gle in gl_entries:
group_by_value = gle.get(group_by)
gle.voucher_type = gle.voucher_type
gle.voucher_subtype = _(gle.voucher_subtype)
gle.against_voucher_type = _(gle.against_voucher_type)
gle.remarks = _(gle.remarks)

View File

@@ -720,20 +720,22 @@ class GrossProfitGenerator:
frappe.qb.from_(purchase_invoice_item)
.inner_join(purchase_invoice)
.on(purchase_invoice.name == purchase_invoice_item.parent)
.select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor)
.select(
purchase_invoice.name,
purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor,
)
.where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.posting_date <= self.filters.to_date)
.where(purchase_invoice_item.item_code == item_code)
)
if row.project:
query.where(purchase_invoice_item.project == row.project)
query = query.where(purchase_invoice_item.project == row.project)
if row.cost_center:
query.where(purchase_invoice_item.cost_center == row.cost_center)
query = query.where(purchase_invoice_item.cost_center == row.cost_center)
query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc)
query.limit(1)
query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1)
last_purchase_rate = query.run()
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0

View File

@@ -3,7 +3,9 @@
import frappe
from frappe import _
from frappe import _, qb
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs
from frappe.utils import flt, getdate
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@@ -21,16 +23,12 @@ def execute(filters=None):
data = []
for d in entries:
invoice = invoice_details.get(d.against_voucher) or frappe._dict()
if d.reference_type == "Purchase Invoice":
payment_amount = flt(d.debit) or -1 * flt(d.credit)
else:
payment_amount = flt(d.credit) or -1 * flt(d.debit)
invoice = invoice_details.get(d.against_voucher_no) or frappe._dict()
payment_amount = d.amount
d.update({"range1": 0, "range2": 0, "range3": 0, "range4": 0, "outstanding": payment_amount})
if d.against_voucher:
if d.against_voucher_no:
ReceivablePayableReport(filters).get_ageing_data(invoice.posting_date, d)
row = [
@@ -39,11 +37,10 @@ def execute(filters=None):
d.party_type,
d.party,
d.posting_date,
d.against_voucher,
d.against_voucher_no,
invoice.posting_date,
invoice.due_date,
d.debit,
d.credit,
d.amount,
d.remarks,
d.age,
d.range1,
@@ -111,8 +108,7 @@ def get_columns(filters):
"width": 100,
},
{"fieldname": "due_date", "label": _("Payment Due Date"), "fieldtype": "Date", "width": 100},
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 140},
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 140},
{"fieldname": "amount", "label": _("Amount"), "fieldtype": "Currency", "width": 140},
{"fieldname": "remarks", "label": _("Remarks"), "fieldtype": "Data", "width": 200},
{"fieldname": "age", "label": _("Age"), "fieldtype": "Int", "width": 50},
{"fieldname": "range1", "label": _("0-30"), "fieldtype": "Currency", "width": 140},
@@ -129,51 +125,68 @@ def get_columns(filters):
def get_conditions(filters):
ple = qb.DocType("Payment Ledger Entry")
conditions = []
if not filters.party_type:
if filters.payment_type == _("Outgoing"):
filters.party_type = "Supplier"
else:
filters.party_type = "Customer"
if filters.party_type:
conditions.append("party_type=%(party_type)s")
conditions.append(ple.delinked.eq(0))
if filters.payment_type == _("Outgoing"):
conditions.append(ple.party_type.eq("Supplier"))
conditions.append(ple.against_voucher_type.eq("Purchase Invoice"))
else:
conditions.append(ple.party_type.eq("Customer"))
conditions.append(ple.against_voucher_type.eq("Sales Invoice"))
if filters.party:
conditions.append("party=%(party)s")
if filters.party_type:
conditions.append("against_voucher_type=%(reference_type)s")
filters["reference_type"] = (
"Sales Invoice" if filters.party_type == "Customer" else "Purchase Invoice"
)
conditions.append(ple.party.eq(filters.party))
if filters.get("from_date"):
conditions.append("posting_date >= %(from_date)s")
conditions.append(ple.posting_date.gte(filters.get("from_date")))
if filters.get("to_date"):
conditions.append("posting_date <= %(to_date)s")
conditions.append(ple.posting_date.lte(filters.get("to_date")))
return "and " + " and ".join(conditions) if conditions else ""
if filters.get("company"):
conditions.append(ple.company.eq(filters.get("company")))
return conditions
def get_entries(filters):
return frappe.db.sql(
"""select
voucher_type, voucher_no, party_type, party, posting_date, debit, credit, remarks, against_voucher
from `tabGL Entry`
where company=%(company)s and voucher_type in ('Journal Entry', 'Payment Entry') and is_cancelled = 0 {}
""".format(get_conditions(filters)),
filters,
as_dict=1,
ple = qb.DocType("Payment Ledger Entry")
conditions = get_conditions(filters)
query = (
qb.from_(ple)
.select(
ple.voucher_type,
ple.voucher_no,
ple.party_type,
ple.party,
ple.posting_date,
Abs(ple.amount).as_("amount"),
ple.remarks,
ple.against_voucher_no,
)
.where(Criterion.all(conditions))
)
res = query.run(as_dict=True)
return res
def get_invoice_posting_date_map(filters):
invoice_details = {}
dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice"
for t in frappe.db.sql(f"select name, posting_date, due_date from `tab{dt}`", as_dict=1):
dt = (
qb.DocType("Sales Invoice")
if filters.get("payment_type") == _("Incoming")
else qb.DocType("Purchase Invoice")
)
res = (
qb.from_(dt)
.select(dt.name, dt.posting_date, dt.due_date)
.where((dt.docstatus.eq(1)) & (dt.company.eq(filters.get("company"))))
.run(as_dict=1)
)
for t in res:
invoice_details[t.name] = t
return invoice_details

View File

@@ -56,7 +56,7 @@ def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
):
if isinstance(boolean, str):
boolean = frappe.json.loads(boolean)
boolean = loads(boolean)
fiscal_years = get_fiscal_years(
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
@@ -516,6 +516,10 @@ def reconcile_against_document(
doc.make_advance_gl_entries()
else:
gl_map = doc.build_gl_map()
# Make sure there is no overallocation
from erpnext.accounts.general_ledger import process_debit_credit_difference
process_debit_credit_difference(gl_map)
create_payment_ledger_entry(gl_map, update_outstanding="No", cancel=0, adv_adj=1)
# Only update outstanding for newly linked vouchers
@@ -1105,7 +1109,7 @@ def get_companies():
@frappe.whitelist()
def get_children(doctype, parent, company, is_root=False, include_disabled=False):
if isinstance(include_disabled, str):
include_disabled = frappe.json.loads(include_disabled)
include_disabled = loads(include_disabled)
from erpnext.accounts.report.financial_statements import sort_accounts
parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")

View File

@@ -652,7 +652,7 @@ frappe.ui.form.on("Asset", {
);
frm.set_value("gross_purchase_amount", purchase_amount);
frm.set_value("purchase_receipt_amount", purchase_amount);
frm.set_value("purchase_amount", purchase_amount);
frm.set_value("asset_quantity", asset_quantity);
frm.set_value("cost_center", item.cost_center || purchase_doc.cost_center);
if (item.asset_location) {

View File

@@ -72,7 +72,7 @@
"status",
"booked_fixed_asset",
"column_break_51",
"purchase_receipt_amount",
"purchase_amount",
"default_finance_book",
"depr_entry_posting_status",
"amended_from",
@@ -408,15 +408,6 @@
"options": "Purchase Receipt",
"print_hide": 1
},
{
"fieldname": "purchase_receipt_amount",
"fieldtype": "Currency",
"hidden": 1,
"label": "Purchase Receipt Amount",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
},
{
"depends_on": "eval:!doc.is_composite_asset && !doc.is_existing_asset",
"fieldname": "purchase_invoice",
@@ -546,6 +537,15 @@
"label": "Additional Asset Cost",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"fieldname": "purchase_amount",
"fieldtype": "Currency",
"hidden": 1,
"label": "Purchase Amount",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"idx": 72,
@@ -589,7 +589,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-03-27 13:06:32.494326",
"modified": "2024-04-18 16:45:47.306032",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",
@@ -628,7 +628,7 @@
}
],
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "asset_name",

View File

@@ -92,10 +92,10 @@ class Asset(AccountsController):
number_of_depreciations_booked: DF.Int
opening_accumulated_depreciation: DF.Currency
policy_number: DF.Data | None
purchase_amount: DF.Currency
purchase_date: DF.Date | None
purchase_invoice: DF.Link | None
purchase_receipt: DF.Link | None
purchase_receipt_amount: DF.Currency
split_from: DF.Link | None
status: DF.Literal[
"Draft",
@@ -356,7 +356,7 @@ class Asset(AccountsController):
if self.is_existing_asset:
return
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_amount:
error_message = _(
"Gross Purchase Amount should be <b>equal</b> to purchase amount of one single Asset."
)
@@ -695,11 +695,7 @@ class Asset(AccountsController):
purchase_document = self.get_purchase_document()
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
if (
purchase_document
and self.purchase_receipt_amount
and getdate(self.available_for_use_date) <= getdate()
):
if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate():
gl_entries.append(
self.get_gl_dict(
{
@@ -707,8 +703,8 @@ class Asset(AccountsController):
"against": fixed_asset_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"credit": self.purchase_receipt_amount,
"credit_in_account_currency": self.purchase_receipt_amount,
"credit": self.purchase_amount,
"credit_in_account_currency": self.purchase_amount,
"cost_center": self.cost_center,
},
item=self,
@@ -722,8 +718,8 @@ class Asset(AccountsController):
"against": cwip_account,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"posting_date": self.available_for_use_date,
"debit": self.purchase_receipt_amount,
"debit_in_account_currency": self.purchase_receipt_amount,
"debit": self.purchase_amount,
"debit_in_account_currency": self.purchase_amount,
"cost_center": self.cost_center,
},
item=self,
@@ -1119,8 +1115,8 @@ def create_new_asset_after_split(asset, split_qty):
)
new_asset.gross_purchase_amount = new_gross_purchase_amount
if asset.purchase_receipt_amount:
new_asset.purchase_receipt_amount = new_gross_purchase_amount
if asset.purchase_amount:
new_asset.purchase_amount = new_gross_purchase_amount
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
new_asset.asset_quantity = split_qty
new_asset.split_from = asset.name

View File

@@ -1000,7 +1000,7 @@ class TestDepreciationBasics(AssetSetup):
asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active")
depreciation_amount = get_depreciation_amount(
depreciation_amount, prev_per_day_depr = get_depreciation_amount(
asset_depr_schedule_doc, asset, 100000, 100000, asset.finance_books[0]
)
self.assertEqual(depreciation_amount, 30000)
@@ -1698,7 +1698,7 @@ def create_asset(**args):
"opening_accumulated_depreciation": args.opening_accumulated_depreciation or 0,
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
"gross_purchase_amount": args.gross_purchase_amount or 100000,
"purchase_receipt_amount": args.purchase_receipt_amount or 100000,
"purchase_amount": args.purchase_amount or 100000,
"maintenance_required": args.maintenance_required or 0,
"warehouse": args.warehouse or "_Test Warehouse - _TC",
"available_for_use_date": args.available_for_use_date or "2020-06-06",
@@ -1723,6 +1723,7 @@ def create_asset(**args):
"depreciation_start_date": args.depreciation_start_date,
"daily_prorata_based": args.daily_prorata_based or 0,
"shift_based": args.shift_based or 0,
"rate_of_depreciation": args.rate_of_depreciation or 0,
},
)
@@ -1738,12 +1739,12 @@ def create_asset(**args):
return asset
def create_asset_category():
def create_asset_category(enable_cwip=1):
asset_category = frappe.new_doc("Asset Category")
asset_category.asset_category_name = "Computers"
asset_category.total_number_of_depreciations = 3
asset_category.frequency_of_depreciation = 3
asset_category.enable_cwip_accounting = 1
asset_category.enable_cwip_accounting = enable_cwip
asset_category.append(
"accounts",
{

View File

@@ -616,8 +616,7 @@ class AssetCapitalization(StockController):
asset_doc.available_for_use_date = self.posting_date
asset_doc.purchase_date = self.posting_date
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.purchase_amount = total_target_asset_value
asset_doc.capitalized_in = self.name
asset_doc.flags.ignore_validate = True
asset_doc.flags.asset_created_via_asset_capitalization = True
@@ -653,7 +652,7 @@ class AssetCapitalization(StockController):
asset_doc = frappe.get_doc("Asset", self.target_asset)
asset_doc.gross_purchase_amount = total_target_asset_value
asset_doc.purchase_receipt_amount = total_target_asset_value
asset_doc.purchase_amount = total_target_asset_value
asset_doc.capitalized_in = self.name
asset_doc.flags.ignore_validate = True
asset_doc.save()

View File

@@ -89,7 +89,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Test Target Asset values
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
self.assertEqual(target_asset.purchase_amount, total_amount)
# Test Consumed Asset values
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
@@ -179,7 +179,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Test Target Asset values
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
self.assertEqual(target_asset.purchase_amount, total_amount)
# Test Consumed Asset values
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
@@ -256,7 +256,7 @@ class TestAssetCapitalization(unittest.TestCase):
# Test Target Asset values
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
self.assertEqual(target_asset.purchase_amount, total_amount)
# Test General Ledger Entries
expected_gle = {
@@ -526,7 +526,7 @@ def create_depreciation_asset(**args):
asset.available_for_use_date = args.available_for_use_date or asset.purchase_date
asset.gross_purchase_amount = args.asset_value or 100000
asset.purchase_receipt_amount = asset.gross_purchase_amount
asset.purchase_amount = asset.gross_purchase_amount
finance_book = asset.append("finance_books")
finance_book.depreciation_start_date = args.depreciation_start_date or "2020-12-31"

View File

@@ -285,6 +285,7 @@ class AssetDepreciationSchedule(Document):
number_of_pending_depreciations = final_number_of_depreciations - start
yearly_opening_wdv = value_after_depreciation
current_fiscal_year_end_date = None
prev_per_day_depr = True
for n in range(start, final_number_of_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row:
@@ -301,8 +302,7 @@ class AssetDepreciationSchedule(Document):
prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
else:
prev_depreciation_amount = 0
depreciation_amount = get_depreciation_amount(
depreciation_amount, prev_per_day_depr = get_depreciation_amount(
self,
asset_doc,
value_after_depreciation,
@@ -312,6 +312,7 @@ class AssetDepreciationSchedule(Document):
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
number_of_pending_depreciations,
prev_per_day_depr,
)
if not has_pro_rata or (
n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
@@ -362,6 +363,16 @@ class AssetDepreciationSchedule(Document):
row.depreciation_start_date,
has_wdv_or_dd_non_yearly_pro_rata,
)
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) <= 0:
frappe.throw(
_(
"Gross Purchase Amount Too Low: {0} cannot be depreciated over {1} cycles with a frequency of {2} depreciations."
).format(
frappe.bold(asset_doc.gross_purchase_amount),
frappe.bold(row.total_number_of_depreciations),
frappe.bold(row.frequency_of_depreciation),
)
)
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
if not is_first_day_of_the_month(getdate(asset_doc.available_for_use_date)):
from_date = get_last_day(
@@ -599,11 +610,12 @@ def get_depreciation_amount(
prev_depreciation_amount=0,
has_wdv_or_dd_non_yearly_pro_rata=False,
number_of_pending_depreciations=0,
prev_per_day_depr=0,
):
if fb_row.depreciation_method in ("Straight Line", "Manual"):
return get_straight_line_or_manual_depr_amount(
asset_depr_schedule, asset, fb_row, schedule_idx, number_of_pending_depreciations
)
), None
else:
return get_wdv_or_dd_depr_amount(
asset,
@@ -614,6 +626,7 @@ def get_depreciation_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
)
@@ -637,49 +650,14 @@ def get_straight_line_or_manual_depr_amount(
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
if row.daily_prorata_based:
amount = flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
total_days = (
date_diff(
get_last_day(
add_months(
row.depreciation_start_date,
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
* row.frequency_of_depreciation,
)
),
add_days(
get_last_day(
add_months(
row.depreciation_start_date,
flt(
row.total_number_of_depreciations
- asset.number_of_depreciations_booked
- number_of_pending_depreciations
- 1
)
* row.frequency_of_depreciation,
)
),
1,
),
)
+ 1
)
daily_depr_amount = amount / total_days
to_date = get_last_day(
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
return get_daily_prorata_based_straight_line_depr(
asset,
row,
schedule_idx,
number_of_pending_depreciations,
amount,
)
from_date = add_days(
get_last_day(
add_months(
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
)
),
1,
)
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
else:
return (
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
@@ -692,40 +670,9 @@ def get_straight_line_or_manual_depr_amount(
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
)
total_days = (
date_diff(
get_last_day(
add_months(
row.depreciation_start_date,
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
* row.frequency_of_depreciation,
)
),
add_days(
get_last_day(
add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)
),
1,
),
)
+ 1
return get_daily_prorata_based_straight_line_depr(
asset, row, schedule_idx, number_of_pending_depreciations, amount
)
daily_depr_amount = amount / total_days
to_date = get_last_day(
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
)
from_date = add_days(
get_last_day(
add_months(
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
)
),
1,
)
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
else:
return (
flt(asset.gross_purchase_amount)
@@ -734,6 +681,23 @@ def get_straight_line_or_manual_depr_amount(
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
def get_daily_prorata_based_straight_line_depr(
asset, row, schedule_idx, number_of_pending_depreciations, amount
):
total_years = flt(number_of_pending_depreciations * row.frequency_of_depreciation) / 12
every_year_depr = amount / total_years
year_start_date = add_years(
row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12
)
year_end_date = add_days(add_years(year_start_date, 1), -1)
daily_depr_amount = every_year_depr / (date_diff(year_end_date, year_start_date) + 1)
from_date, total_depreciable_days = _get_total_days(
row.depreciation_start_date, schedule_idx, row.frequency_of_depreciation
)
return daily_depr_amount * total_depreciable_days
def get_shift_depr_amount(asset_depr_schedule, asset, row, schedule_idx):
if asset_depr_schedule.get("__islocal") and not asset.flags.shift_allocation:
return (
@@ -779,6 +743,7 @@ def get_wdv_or_dd_depr_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
):
return get_default_wdv_or_dd_depr_amount(
asset,
@@ -788,6 +753,7 @@ def get_wdv_or_dd_depr_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
)
@@ -799,6 +765,39 @@ def get_default_wdv_or_dd_depr_amount(
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
):
if not fb_row.daily_prorata_based or cint(fb_row.frequency_of_depreciation) == 12:
return _get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
), None
else:
return _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
)
def _get_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
):
if cint(fb_row.frequency_of_depreciation) == 12:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
@@ -825,6 +824,75 @@ def get_default_wdv_or_dd_depr_amount(
return prev_depreciation_amount
def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
asset,
fb_row,
depreciable_value,
schedule_idx,
prev_depreciation_amount,
has_wdv_or_dd_non_yearly_pro_rata,
asset_depr_schedule,
prev_per_day_depr,
):
if has_wdv_or_dd_non_yearly_pro_rata: # If applicable days for ther first month is less than full month
if schedule_idx == 0:
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100), None
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1: # Year changes
return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
else:
return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
else:
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0: # year changes
return get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value)
else:
return get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr)
def get_monthly_depr_amount(fb_row, schedule_idx, depreciable_value):
""" "
Returns monthly depreciation amount when year changes
1. Calculate per day depr based on new year
2. Calculate monthly amount based on new per day amount
"""
from_date, days_in_month = _get_total_days(
fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
)
per_day_depr = get_per_day_depr(fb_row, depreciable_value, from_date)
return (per_day_depr * days_in_month), per_day_depr
def get_monthly_depr_amount_based_on_prev_per_day_depr(fb_row, schedule_idx, prev_per_day_depr):
""" "
Returns monthly depreciation amount based on prev per day depr
Calculate per day depr only for the first month
"""
from_date, days_in_month = _get_total_days(
fb_row.depreciation_start_date, schedule_idx, cint(fb_row.frequency_of_depreciation)
)
return (prev_per_day_depr * days_in_month), prev_per_day_depr
def get_per_day_depr(
fb_row,
depreciable_value,
from_date,
):
to_date = add_days(add_years(from_date, 1), -1)
total_days = date_diff(to_date, from_date) + 1
per_day_depr = (flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)) / total_days
return per_day_depr
def _get_total_days(depreciation_start_date, schedule_idx, frequency_of_depreciation):
from_date = add_months(depreciation_start_date, (schedule_idx - 1) * frequency_of_depreciation)
to_date = add_months(from_date, frequency_of_depreciation)
if is_last_day_of_the_month(depreciation_start_date):
to_date = get_last_day(to_date)
from_date = add_days(get_last_day(from_date), 1)
return from_date, date_diff(to_date, from_date) + 1
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
asset_depr_schedules_names = []

View File

@@ -3,10 +3,12 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import cstr
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
get_asset_depr_schedule_doc,
get_depr_schedule,
)
@@ -25,3 +27,136 @@ class TestAssetDepreciationSchedule(FrappeTestCase):
)
self.assertRaises(frappe.ValidationError, second_asset_depr_schedule.insert)
def test_daily_prorata_based_depr_on_sl_methond(self):
asset = create_asset(
calculate_depreciation=1,
depreciation_method="Straight Line",
daily_prorata_based=1,
available_for_use_date="2020-01-01",
depreciation_start_date="2020-01-31",
frequency_of_depreciation=1,
total_number_of_depreciations=24,
)
expected_schedules = [
["2020-01-31", 4234.97, 4234.97],
["2020-02-29", 3961.75, 8196.72],
["2020-03-31", 4234.97, 12431.69],
["2020-04-30", 4098.36, 16530.05],
["2020-05-31", 4234.97, 20765.02],
["2020-06-30", 4098.36, 24863.38],
["2020-07-31", 4234.97, 29098.35],
["2020-08-31", 4234.97, 33333.32],
["2020-09-30", 4098.36, 37431.68],
["2020-10-31", 4234.97, 41666.65],
["2020-11-30", 4098.36, 45765.01],
["2020-12-31", 4234.97, 49999.98],
["2021-01-31", 4246.58, 54246.56],
["2021-02-28", 3835.62, 58082.18],
["2021-03-31", 4246.58, 62328.76],
["2021-04-30", 4109.59, 66438.35],
["2021-05-31", 4246.58, 70684.93],
["2021-06-30", 4109.59, 74794.52],
["2021-07-31", 4246.58, 79041.1],
["2021-08-31", 4246.58, 83287.68],
["2021-09-30", 4109.59, 87397.27],
["2021-10-31", 4246.58, 91643.85],
["2021-11-30", 4109.59, 95753.44],
["2021-12-31", 4246.56, 100000.0],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Test for Written Down Value Method
# Frequency of deprciation = 3
def test_for_daily_prorata_based_depreciation_wdv_method_frequency_3_months(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
depreciation_method="Written Down Value",
daily_prorata_based=1,
available_for_use_date="2021-02-20",
depreciation_start_date="2021-03-31",
frequency_of_depreciation=3,
total_number_of_depreciations=6,
rate_of_depreciation=40,
)
expected_schedules = [
["2021-03-31", 4383.56, 4383.56],
["2021-06-30", 9535.45, 13919.01],
["2021-09-30", 9640.23, 23559.24],
["2021-12-31", 9640.23, 33199.47],
["2022-03-31", 9430.66, 42630.13],
["2022-06-30", 5721.27, 48351.4],
["2022-08-20", 51648.6, 100000.0],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Frequency of deprciation = 6
def test_for_daily_prorata_based_depreciation_wdv_method_frequency_6_months(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
depreciation_method="Written Down Value",
daily_prorata_based=1,
available_for_use_date="2020-02-20",
depreciation_start_date="2020-02-29",
frequency_of_depreciation=6,
total_number_of_depreciations=6,
rate_of_depreciation=40,
)
expected_schedules = [
["2020-02-29", 1092.90, 1092.90],
["2020-08-31", 19944.01, 21036.91],
["2021-02-28", 19618.83, 40655.74],
["2021-08-31", 11966.4, 52622.14],
["2022-02-28", 11771.3, 64393.44],
["2022-08-31", 7179.84, 71573.28],
["2023-02-20", 28426.72, 100000.0],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Frequency of deprciation = 12
def test_for_daily_prorata_based_depreciation_wdv_method_frequency_12_months(self):
asset = create_asset(
item_code="Macbook Pro",
calculate_depreciation=1,
depreciation_method="Written Down Value",
daily_prorata_based=1,
available_for_use_date="2020-02-20",
depreciation_start_date="2020-03-31",
frequency_of_depreciation=12,
total_number_of_depreciations=4,
rate_of_depreciation=40,
)
expected_schedules = [
["2020-03-31", 4480.87, 4480.87],
["2021-03-31", 38207.65, 42688.52],
["2022-03-31", 22924.59, 65613.11],
["2023-03-31", 13754.76, 79367.87],
["2024-02-20", 20632.13, 100000],
]
schedules = [
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)

View File

@@ -159,8 +159,9 @@ def prepare_chart_data(data, filters):
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
filters_filter_based_on = "Date Range"
date_field = "purchase_date"
filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
filtered_data = [d for d in data if not d.get(date_field)]
filters_from_date = min(filtered_data, key=lambda a: a.get(date_field)).get(date_field)
filters_to_date = max(filtered_data, key=lambda a: a.get(date_field)).get(date_field)
else:
filters_filter_based_on = filters.filter_based_on
date_field = frappe.scrub(filters.date_based_on)

View File

@@ -772,12 +772,7 @@ class TestPurchaseOrder(FrappeTestCase):
}
).insert()
else:
account = frappe.db.get_value(
"Account",
filters={"account_name": account_name, "company": company},
fieldname="name",
pluck=True,
)
account = frappe.get_doc("Account", {"account_name": account_name, "company": company})
return account
@@ -808,22 +803,6 @@ class TestPurchaseOrder(FrappeTestCase):
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_invoice
pi = make_purchase_invoice(po_doc.name)
pi.append(
"advances",
{
"reference_type": pe.doctype,
"reference_name": pe.name,
"reference_row": pe.references[0].name,
"advance_amount": 5000,
"allocated_amount": 5000,
},
)
pi.save().submit()
pe.reload()
po_doc.reload()
self.assertEqual(po_doc.advance_paid, 0)
company_doc.book_advance_payments_in_separate_party_account = False
company_doc.save()

View File

@@ -513,7 +513,7 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll
method: "frappe.desk.doctype.tag.tag.get_tagged_docs",
args: {
doctype: "Supplier",
tag: args.tag,
tag: "%" + args.tag + "%",
},
callback: load_suppliers,
});

View File

@@ -406,7 +406,7 @@
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
"label": "Address & Contact"
},
{
"fieldname": "accounting_tab",
@@ -485,7 +485,7 @@
"link_fieldname": "party"
}
],
"modified": "2024-03-27 13:10:48.412732",
"modified": "2024-05-08 18:02:57.342931",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",

View File

@@ -778,6 +778,9 @@ class AccountsController(TransactionBase):
# reset pricing rule fields if pricing_rule_removed
item.set(fieldname, value)
elif fieldname == "expense_account" and not item.get("expense_account"):
item.expense_account = value
if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field(
"is_fixed_asset"
):
@@ -1939,7 +1942,7 @@ class AccountsController(TransactionBase):
def set_advance_payment_status(self):
new_status = None
stati = frappe.get_list(
stati = frappe.get_all(
"Payment Request",
{
"reference_doctype": self.doctype,
@@ -2208,10 +2211,10 @@ class AccountsController(TransactionBase):
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(
grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
grand_total * flt(d.invoice_portion) / 100, d.precision("payment_amount")
)
d.base_payment_amount = flt(
base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
base_grand_total * flt(d.invoice_portion) / 100, d.precision("base_payment_amount")
)
d.outstanding = d.payment_amount
elif not d.invoice_portion:

View File

@@ -712,6 +712,7 @@ class BuyingController(SubcontractingController):
def auto_make_assets(self, asset_items):
items_data = get_asset_item_details(asset_items)
messages = []
alert = False
for d in self.items:
if d.is_fixed_asset:
@@ -761,9 +762,10 @@ class BuyingController(SubcontractingController):
frappe.bold(d.item_code)
)
)
alert = True
for message in messages:
frappe.msgprint(message, title="Success", indicator="green")
frappe.msgprint(message, title="Success", indicator="green", alert=alert)
def make_asset(self, row, is_grouped_asset=False):
if not row.asset_location:
@@ -787,7 +789,7 @@ class BuyingController(SubcontractingController):
"supplier": self.supplier,
"purchase_date": self.posting_date,
"calculate_depreciation": 0,
"purchase_receipt_amount": purchase_amount,
"purchase_amount": purchase_amount,
"gross_purchase_amount": purchase_amount,
"asset_quantity": asset_quantity,
"purchase_receipt": self.name if self.doctype == "Purchase Receipt" else None,

View File

@@ -205,6 +205,7 @@ class StockController(AccountsController):
"company": self.company,
"is_rejected": 1 if row.get("rejected_warehouse") else 0,
"use_serial_batch_fields": row.use_serial_batch_fields,
"via_landed_cost_voucher": via_landed_cost_voucher,
"do_not_submit": True if not via_landed_cost_voucher else False,
}
@@ -1227,8 +1228,8 @@ def get_accounting_ledger_preview(doc, filters):
"debit",
"credit",
"against",
"party",
"party_type",
"party",
"cost_center",
"against_voucher_type",
"against_voucher",
@@ -1404,7 +1405,12 @@ def is_reposting_pending():
)
def future_sle_exists(args, sl_entries=None):
def future_sle_exists(args, sl_entries=None, allow_force_reposting=True):
if allow_force_reposting and frappe.db.get_single_value(
"Stock Reposting Settings", "do_reposting_for_each_stock_transaction"
):
return True
key = (args.voucher_type, args.voucher_no)
if not hasattr(frappe.local, "future_sle"):
frappe.local.future_sle = {}

View File

@@ -327,13 +327,13 @@ class SubcontractingController(StockController):
consumed_bundles.batch_nos[batch_no] += abs(qty)
# Will be deprecated in v16
if row.serial_no:
if row.serial_no and not consumed_bundles.serial_nos:
self.available_materials[key]["serial_no"] = list(
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
)
# Will be deprecated in v16
if row.batch_no:
if row.batch_no and not consumed_bundles.batch_nos:
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
def get_available_materials(self):

View File

@@ -121,7 +121,7 @@ def send_mail(entry, email_campaign):
doctype="Email Campaign",
name=email_campaign.name,
subject=frappe.render_template(email_template.get("subject"), context),
content=frappe.render_template(email_template.get("response"), context),
content=frappe.render_template(email_template.response_, context),
sender=sender,
recipients=recipient_list,
communication_medium="Email",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -80,6 +80,18 @@ class BOMCreator(Document):
if row.is_expandable and row.item_code == self.item_code:
frappe.throw(_("Item {0} cannot be added as a sub-assembly of itself").format(row.item_code))
if not row.parent_row_no and row.fg_item and row.fg_item != self.item_code:
frappe.throw(
_("At row {0}: set Parent Row No for item {1}").format(row.idx, row.item_code),
title=_("Set Parent Row No in Items Table"),
)
elif row.parent_row_no and row.fg_item == self.item_code:
frappe.throw(
_("At row {0}: Parent Row No cannot be set for item {1}").format(row.idx, row.item_code),
title=_("Remove Parent Row No in Items Table"),
)
def set_status(self, save=False):
self.status = {
0: "Draft",
@@ -410,6 +422,10 @@ def add_sub_assembly(**kwargs):
parent_row_no = item_row.idx
name = ""
else:
parent_row_no = [row.idx for row in doc.items if row.name == kwargs.fg_reference_id]
if parent_row_no:
parent_row_no = parent_row_no[0]
for row in bom_item.get("items"):
row = frappe._dict(row)

View File

@@ -214,7 +214,11 @@ class JobCard(Document):
if d.to_time and get_datetime(d.from_time) > get_datetime(d.to_time):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
data = self.get_overlap_for(d)
open_job_cards = []
if d.get("employee"):
open_job_cards = self.get_open_job_cards(d.get("employee"))
data = self.get_overlap_for(d, open_job_cards=open_job_cards)
if data:
frappe.throw(
_("Row {0}: From Time and To Time of {1} is overlapping with {2}").format(
@@ -235,12 +239,12 @@ class JobCard(Document):
for row in self.sub_operations:
self.total_completed_qty += row.completed_qty
def get_overlap_for(self, args):
def get_overlap_for(self, args, open_job_cards=None):
time_logs = []
time_logs.extend(self.get_time_logs(args, "Job Card Time Log"))
time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time"))
time_logs.extend(self.get_time_logs(args, "Job Card Scheduled Time", open_job_cards=open_job_cards))
if not time_logs:
return {}
@@ -304,7 +308,7 @@ class JobCard(Document):
return True
return overlap
def get_time_logs(self, args, doctype):
def get_time_logs(self, args, doctype, open_job_cards=None):
jc = frappe.qb.DocType("Job Card")
jctl = frappe.qb.DocType(doctype)
@@ -341,8 +345,14 @@ class JobCard(Document):
if self.workstation:
query = query.where(jc.workstation == self.workstation)
if args.get("employee") and doctype == "Job Card Time Log":
query = query.where(jctl.employee == args.get("employee"))
if args.get("employee"):
if not open_job_cards and doctype == "Job Card Scheduled Time":
return []
if doctype == "Job Card Time Log":
query = query.where(jctl.employee == args.get("employee"))
else:
query = query.where(jc.name.isin(open_job_cards))
if doctype != "Job Card Time Log":
query = query.where(jc.total_time_in_mins == 0)
@@ -351,6 +361,27 @@ class JobCard(Document):
return time_logs
def get_open_job_cards(self, employee):
jc = frappe.qb.DocType("Job Card")
jctl = frappe.qb.DocType("Job Card Time Log")
query = (
frappe.qb.from_(jc)
.left_join(jctl)
.on(jc.name == jctl.parent)
.select(jc.name)
.where(
(jctl.parent == jc.name)
& (jc.workstation == self.workstation)
& (jctl.employee == employee)
& (jc.docstatus < 1)
& (jc.name != self.name)
)
)
jobs = query.run(as_dict=True)
return [job.get("name") for job in jobs] if jobs else []
def get_workstation_based_on_available_slot(self, existing_time_logs) -> dict:
workstations = get_workstations(self.workstation_type)
if workstations:

View File

@@ -42,8 +42,7 @@
"fieldname": "completed_qty",
"fieldtype": "Float",
"in_list_view": 1,
"label": "Completed Qty",
"reqd": 1
"label": "Completed Qty"
},
{
"fieldname": "employee",
@@ -64,7 +63,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:09:57.448800",
"modified": "2024-05-21 12:40:55.765860",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Time Log",

View File

@@ -21,7 +21,8 @@ def get_exploded_items(bom, data, indent=0, qty=1):
exploded_items = frappe.get_all(
"BOM Item",
filters={"parent": bom},
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom"],
fields=["qty", "bom_no", "qty", "item_code", "item_name", "description", "uom", "idx"],
order_by="idx ASC",
)
for item in exploded_items:

View File

@@ -93,4 +93,11 @@ frappe.query_reports["Exponential Smoothing Forecasting"] = {
},
},
],
formatter: function (value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname === "item_code" && value.includes("Total Quantity")) {
value = "<strong>" + value + "</strong>";
}
return value;
},
};

View File

@@ -144,7 +144,7 @@ class ForecastingReport(ExponentialSmoothingForecast):
if not self.data:
return
total_row = {"item_code": _(frappe.bold("Total Quantity"))}
total_row = {"item_code": _("Total Quantity")}
for value in self.data:
for period in self.period_list:

View File

@@ -363,4 +363,7 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
erpnext.patches.v14_0.set_maintain_stock_for_bom_item
erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset
erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency
erpnext.patches.v15_0.rename_purchase_receipt_amount_to_purchase_amount
erpnext.patches.v14_0.enable_set_priority_for_pricing_rules #1

View File

@@ -13,8 +13,9 @@ def execute():
for d in accounting_dimensions:
doctype = "Asset Repair"
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
docfield = frappe.db.get_value("DocField", {"parent": doctype, "fieldname": d.fieldname})
if field:
if field or docfield:
continue
df = {

View File

@@ -0,0 +1,10 @@
import frappe
def execute():
pr_table = frappe.qb.DocType("Pricing Rule")
(
frappe.qb.update(pr_table)
.set(pr_table.has_priority, 1)
.where((pr_table.priority.isnotnull()) & (pr_table.priority != ""))
).run()

View File

@@ -0,0 +1,21 @@
import frappe
def execute():
# update debit and credit in transaction currency:
# if transaction currency is same as account currency,
# then debit and credit in transaction currency is same as debit and credit in account currency
# else debit and credit divided by exchange rate
# nosemgrep
frappe.db.sql(
"""
UPDATE `tabGL Entry`
SET
debit_in_transaction_currency = IF(transaction_currency = account_currency, debit_in_account_currency, debit / transaction_exchange_rate),
credit_in_transaction_currency = IF(transaction_currency = account_currency, credit_in_account_currency, credit / transaction_exchange_rate)
WHERE
transaction_exchange_rate > 0
and transaction_currency is not null
"""
)

View File

@@ -0,0 +1,8 @@
import frappe
from frappe.model.utils.rename_field import rename_field
def execute():
frappe.reload_doc("assets", "doctype", "asset")
if frappe.db.has_column("Asset", "purchase_receipt_amount"):
rename_field("Asset", "purchase_receipt_amount", "purchase_amount")

View File

@@ -83,7 +83,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total"));
}
this.frm.refresh_fields();
this.frm.refresh_field("taxes");
}
calculate_discount_amount() {
@@ -853,7 +853,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
});
}
this.frm.refresh_fields();
this.frm.refresh_field("taxes");
}
set_default_payment(total_amount_to_pay, update_paid_amount) {

View File

@@ -325,7 +325,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
}
const me = this;
if (!this.frm.is_new() && this.frm.doc.docstatus === 0) {
if (!this.frm.is_new() && this.frm.doc.docstatus === 0 && frappe.model.can_create("Quality Inspection")) {
this.frm.add_custom_button(__("Quality Inspection(s)"), () => {
me.make_quality_inspection();
}, __("Create"));

View File

@@ -1184,4 +1184,39 @@ $.extend(erpnext.stock.utils, {
const barcode_scanner = new erpnext.utils.BarcodeScanner({ frm: frm });
barcode_scanner.scan_api_call(child_row.barcode, callback);
},
get_serial_range(range_string, separator) {
/* Return an array of serial numbers generated from a range string.
Examples (using separator "::"):
- "1::5" => ["1", "2", "3", "4", "5"]
- "SN0009::12" => ["SN0009", "SN0010", "SN0011", "SN0012"]
- "ABC//05::8" => ["ABC//05", "ABC//06", "ABC//07", "ABC//08"]
*/
if (!range_string) {
return;
}
const [start_str, end_str] = range_string.trim().split(separator);
if (!start_str || !end_str) {
return;
}
const end_int = parseInt(end_str);
const length_difference = start_str.length - end_str.length;
const start_int = parseInt(start_str.substring(length_difference));
if (isNaN(start_int) || isNaN(end_int)) {
return;
}
const serial_numbers = Array(end_int - start_int + 1)
.fill(1)
.map((x, y) => x + y)
.map((x) => x + start_int - 1);
return serial_numbers.map((val) => {
return start_str.substring(0, length_difference) + val.toString().padStart(end_str.length, "0");
});
},
});

View File

@@ -22,6 +22,7 @@ erpnext.accounts.dimensions = {
});
me.default_dimensions = r.message[1];
me.setup_filters(frm, doctype);
me.update_dimension(frm, doctype);
},
});
},

View File

@@ -373,6 +373,7 @@ erpnext.sales_common = {
frappe.model.set_value(item.doctype, item.name, {
serial_and_batch_bundle: r.name,
use_serial_batch_fields: 0,
incoming_rate: r.avg_rate,
qty:
qty /
flt(

View File

@@ -206,6 +206,16 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
label: __("{0} {1} Manually", [primary_label, label]),
depends_on: "eval:doc.import_using_csv_file === 0",
},
{
fieldtype: "Data",
label: __("Enter Serial No Range"),
fieldname: "serial_no_range",
depends_on: "eval:doc.import_using_csv_file === 0",
description: __('Enter "ABC-001::100" for serial nos "ABC-001" to "ABC-100".'),
onchange: () => {
this.set_serial_nos_from_range();
},
},
{
fieldtype: "Small Text",
label: __("Enter Serial Nos"),
@@ -255,6 +265,20 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate {
return fields;
}
set_serial_nos_from_range() {
const serial_no_range = this.dialog.get_value("serial_no_range");
if (!serial_no_range) {
return;
}
const serial_nos = erpnext.stock.utils.get_serial_range(serial_no_range, "::");
if (serial_nos) {
this.dialog.set_value("upload_serial_nos", serial_nos.join("\n"));
}
}
create_serial_nos() {
let { upload_serial_nos } = this.dialog.get_values();

View File

@@ -482,7 +482,7 @@
{
"fieldname": "contact_and_address_tab",
"fieldtype": "Tab Break",
"label": "Contact & Address"
"label": "Address & Contact"
},
{
"fieldname": "defaults_tab",
@@ -583,7 +583,7 @@
"link_fieldname": "party"
}
],
"modified": "2024-03-27 13:06:48.056107",
"modified": "2024-05-08 18:03:20.716169",
"modified_by": "Administrator",
"module": "Selling",
"name": "Customer",

View File

@@ -2,6 +2,8 @@
# License: GNU General Public License v3. See license.txt
import json
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
@@ -321,7 +323,7 @@ class TestCustomer(FrappeTestCase):
frappe.ValidationError,
update_child_qty_rate,
so.doctype,
frappe.json.dumps([modified_item]),
json.dumps([modified_item]),
so.name,
)

View File

@@ -71,6 +71,8 @@ frappe.ui.form.on("Quotation", {
frm.trigger("set_label");
frm.trigger("toggle_reqd_lead_customer");
frm.trigger("set_dynamic_field_label");
frm.set_value("party_name", "");
frm.set_value("customer_name", "");
},
set_label: function (frm) {
@@ -97,7 +99,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
frappe.dynamic_link = {
doc: this.frm.doc,
fieldname: "party_name",
doctype: doc.quotation_to == "Customer" ? "Customer" : "Lead",
doctype: doc.quotation_to,
};
var me = this;
@@ -197,6 +199,7 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
};
} else if (this.frm.doc.quotation_to == "Prospect") {
this.frm.set_df_property("party_name", "label", "Prospect");
this.frm.fields_dict.party_name.get_query = null;
}
}

View File

@@ -58,7 +58,8 @@ frappe.ui.form.on("Sales Order", {
if (
frm.doc.status !== "Closed" &&
flt(frm.doc.per_delivered, 2) < 100 &&
flt(frm.doc.per_billed, 2) < 100
flt(frm.doc.per_billed, 2) < 100 &&
frm.has_perm("write")
) {
frm.add_custom_button(__("Update Items"), () => {
erpnext.utils.update_child_items({
@@ -74,7 +75,8 @@ frappe.ui.form.on("Sales Order", {
if (
frm.doc.__onload &&
frm.doc.__onload.has_unreserved_stock &&
flt(frm.doc.per_picked) === 0
flt(frm.doc.per_picked) === 0 &&
frappe.model.can_create("Stock Reservation Entry")
) {
frm.add_custom_button(
__("Reserve"),
@@ -85,7 +87,11 @@ frappe.ui.form.on("Sales Order", {
}
// Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock.
if (frm.doc.__onload && frm.doc.__onload.has_reserved_stock) {
if (
frm.doc.__onload &&
frm.doc.__onload.has_reserved_stock &&
frappe.model.can_cancel("Stock Reservation Entry")
) {
frm.add_custom_button(
__("Unreserve"),
() => frm.events.cancel_stock_reservation_entries(frm),
@@ -94,7 +100,7 @@ frappe.ui.form.on("Sales Order", {
}
frm.doc.items.forEach((item) => {
if (flt(item.stock_reserved_qty) > 0) {
if (flt(item.stock_reserved_qty) > 0 && frappe.model.can_read("Stock Reservation Entry")) {
frm.add_custom_button(
__("Reserved Stock"),
() => frm.events.show_reserved_stock(frm),
@@ -142,6 +148,10 @@ frappe.ui.form.on("Sales Order", {
},
get_items_from_internal_purchase_order(frm) {
if (!frappe.model.can_read("Purchase Order")) {
return;
}
frm.add_custom_button(
__("Purchase Order"),
() => {
@@ -630,15 +640,17 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
}
}
if (!doc.__onload || !doc.__onload.has_reserved_stock) {
// Don't show the `Reserve` button if the Sales Order has Picked Items.
if (flt(doc.per_picked, 2) < 100 && flt(doc.per_delivered, 2) < 100) {
this.frm.add_custom_button(
__("Pick List"),
() => this.create_pick_list(),
__("Create")
);
}
if (
(!doc.__onload || !doc.__onload.has_reserved_stock) &&
flt(doc.per_picked, 2) < 100 &&
flt(doc.per_delivered, 2) < 100 &&
frappe.model.can_create("Pick List")
) {
this.frm.add_custom_button(
__("Pick List"),
() => this.create_pick_list(),
__("Create")
);
}
const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1;
@@ -653,20 +665,25 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
(order_is_a_sale || order_is_a_custom_sale) &&
allow_delivery
) {
this.frm.add_custom_button(
__("Delivery Note"),
() => this.make_delivery_note_based_on_delivery_date(true),
__("Create")
);
this.frm.add_custom_button(
__("Work Order"),
() => this.make_work_order(),
__("Create")
);
if (frappe.model.can_create("Delivery Note")) {
this.frm.add_custom_button(
__("Delivery Note"),
() => this.make_delivery_note_based_on_delivery_date(true),
__("Create")
);
}
if (frappe.model.can_create("Work Order")) {
this.frm.add_custom_button(
__("Work Order"),
() => this.make_work_order(),
__("Create")
);
}
}
// sales invoice
if (flt(doc.per_billed, 2) < 100) {
if (flt(doc.per_billed, 2) < 100 && frappe.model.can_create("Sales Invoice")) {
this.frm.add_custom_button(
__("Sales Invoice"),
() => me.make_sales_invoice(),
@@ -676,8 +693,10 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
// material request
if (
!doc.order_type ||
((order_is_a_sale || order_is_a_custom_sale) && flt(doc.per_delivered, 2) < 100)
(!doc.order_type ||
((order_is_a_sale || order_is_a_custom_sale) &&
flt(doc.per_delivered, 2) < 100)) &&
frappe.model.can_create("Material Request")
) {
this.frm.add_custom_button(
__("Material Request"),
@@ -692,7 +711,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
}
// Make Purchase Order
if (!this.frm.doc.is_internal_customer) {
if (!this.frm.doc.is_internal_customer && frappe.model.can_create("Purchase Order")) {
this.frm.add_custom_button(
__("Purchase Order"),
() => this.make_purchase_order(),
@@ -702,24 +721,32 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
// maintenance
if (flt(doc.per_delivered, 2) < 100 && (order_is_maintenance || order_is_a_custom_sale)) {
this.frm.add_custom_button(
__("Maintenance Visit"),
() => this.make_maintenance_visit(),
__("Create")
);
this.frm.add_custom_button(
__("Maintenance Schedule"),
() => this.make_maintenance_schedule(),
__("Create")
);
if (frappe.model.can_create("Maintenance Visit")) {
this.frm.add_custom_button(
__("Maintenance Visit"),
() => this.make_maintenance_visit(),
__("Create")
);
}
if (frappe.model.can_create("Maintenance Schedule")) {
this.frm.add_custom_button(
__("Maintenance Schedule"),
() => this.make_maintenance_schedule(),
__("Create")
);
}
}
// project
if (flt(doc.per_delivered, 2) < 100) {
if (flt(doc.per_delivered, 2) < 100 && frappe.model.can_create("Project")) {
this.frm.add_custom_button(__("Project"), () => this.make_project(), __("Create"));
}
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
if (
doc.docstatus === 1 &&
!doc.inter_company_order_reference &&
frappe.model.can_create("Purchase Order")
) {
let me = this;
let internal = me.frm.doc.is_internal_customer;
if (internal) {
@@ -743,18 +770,27 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
flt(doc.per_billed, precision("per_billed", doc)) <
100 + frappe.boot.sysdefaults.over_billing_allowance
) {
this.frm.add_custom_button(
__("Payment Request"),
() => this.make_payment_request(),
__("Create")
);
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
if (frappe.model.can_create("Payment Request")) {
this.frm.add_custom_button(
__("Payment Request"),
() => this.make_payment_request(),
__("Create")
);
}
if (frappe.model.can_create("Payment Entry")) {
this.frm.add_custom_button(
__("Payment"),
() => this.make_payment_entry(),
__("Create")
);
}
}
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
}
if (this.frm.doc.docstatus === 0) {
if (this.frm.doc.docstatus === 0 && frappe.model.can_read("Quotation")) {
this.frm.add_custom_button(
__("Quotation"),
function () {

View File

@@ -1140,7 +1140,8 @@
"hide_seconds": 1,
"label": "Inter Company Order Reference",
"options": "Purchase Order",
"read_only": 1
"read_only": 1,
"search_index": 1
},
{
"fieldname": "project",
@@ -1658,7 +1659,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2024-03-29 16:27:41.539613",
"modified": "2024-05-23 16:35:54.905804",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class {
const is_stock_item = resp[1];
frappe.dom.unfreeze();
const bold_uom = item_row.stock_uom.bold();
const bold_uom = item_row.uom.bold();
const bold_item_code = item_row.item_code.bold();
const bold_warehouse = warehouse.bold();
const bold_available_qty = available_qty.toString().bold();

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