Compare commits

..

130 Commits

Author SHA1 Message Date
Michelle Alva
ce21653266 fix: link for customize ERPNext 2024-08-08 12:33:04 +05:30
Raffael Meyer
755c162093 Merge pull request #42670 from barredterra/extract-incoterms 2024-08-07 20:45:57 +02:00
barredterra
e177ece008 fix: extract incoterm titles as translatable strings 2024-08-07 20:28:39 +02:00
Shariq Ansari
47c2127a31 Merge pull request #42596 from Didiman1998/bugfix/fix-opportunity-contact-address-bug
fix: changes in opportunity.py to show contacts and addresses created from opportunity
2024-08-07 19:58:36 +05:30
ruthra kumar
efadf94cf3 Merge pull request #42578 from ljain112/fix-dimensions
fix: dimensions in common party journal entry
2024-08-07 16:02:46 +05:30
Khushi Rawat
b1c23ba85c Merge pull request #42593 from khushi8112/disable-submit-button-for-composite-asset
fix: disable primary action button only when there are no active capitalization
2024-08-06 19:11:58 +05:30
Raffael Meyer
82b02d6c9b Merge pull request #42400 from barredterra/root-supplier-group 2024-08-06 14:46:02 +02:00
ruthra kumar
8040544216 test: dimension inheritance on Exc Gain/Loss JV on Common party 2024-08-06 17:46:19 +05:30
ruthra kumar
34ad9d33ff Merge pull request #41708 from Mutantpenguin/patch-1
refactor: use doctype File directly instead of save_file from file_manager.py
2024-08-06 14:34:07 +05:30
ruthra kumar
113362e2d9 Merge pull request #42592 from ljain112/tds-filters
fix: company filter for filtring tax withheld vouchers
2024-08-06 13:16:13 +05:30
ruthra kumar
165ec2ed53 Merge pull request #42545 from ljain112/fix-sales-funnel
fix: min height for rows in sales funnel
2024-08-06 13:01:41 +05:30
rohitwaghchaure
faff84c6e5 feat: expiry date column in Available Batch Report (#42628) 2024-08-06 12:20:41 +05:30
ruthra kumar
4a4e1d36f2 Merge pull request #42636 from ruthra-kumar/ignore_on_gl_should_pull_all_records
refactor: posting date should not be considered for ignore filters in GL
2024-08-06 10:54:54 +05:30
ruthra kumar
c930f8ba9d refactor: posting date is not considered for ignore filters in GL 2024-08-06 10:36:26 +05:30
rohitwaghchaure
c8af544ef3 fix: do not update item price and last purchase rate for inter transf… (#42616)
fix: do not update item price and last purchase rate for inter transfer transaction
2024-08-05 20:20:36 +05:30
ruthra kumar
0f0b19688a Merge pull request #42555 from ruthra-kumar/sales_pipeline_analytics_filters
refactor: date filters should be mandatory in Sales Pipeline Analytics report
2024-08-05 15:07:59 +05:30
ruthra kumar
4253caf910 refactor(test): use test fixture and supply from and to dates 2024-08-05 14:47:09 +05:30
ruthra kumar
213b2ba942 refactor: consider empty-string as Not Assigned 2024-08-05 14:08:08 +05:30
ruthra kumar
e78e948b7c Merge pull request #42621 from ruthra-kumar/clear_data_on_gl_tests
refactor(test): clear data before GL report tests
2024-08-05 13:43:59 +05:30
ruthra kumar
56620785a0 refactor(test): clear old records on GL report tests 2024-08-05 13:26:03 +05:30
ruthra kumar
751a25c4b7 refactor: report columns should be based on from and to dates 2024-08-05 11:31:13 +05:30
ruthra kumar
05b92c5321 Merge pull request #42597 from ruthra-kumar/ignore_reconciliation_jv_on_general_ledger
refactor: filter to ignore system generated cr / dr reconciliation journals on general ledger
2024-08-05 11:12:56 +05:30
ruthra kumar
9ade269b7a refactor(test): filter and reconcile concerned vouchers 2024-08-05 10:46:35 +05:30
ruthra kumar
991069bfbc test: clear old data 2024-08-05 10:23:52 +05:30
ruthra kumar
3617b41b95 refactor: make 'from_date' and 'to_date' mandatory 2024-08-05 10:18:42 +05:30
ruthra kumar
03f3ab522f refactor: make use of date filters on ignore filterss 2024-08-05 10:01:09 +05:30
ruthra kumar
3ffac73598 test: ignore filter for system generated cr / dr note journals 2024-08-05 10:01:07 +05:30
Raffael Meyer
6d2a36c4f9 Merge pull request #42612 from frappe/pot_develop_2024-08-04 2024-08-04 13:48:21 +02:00
frappe-pr-bot
5040e3ca7d chore: update POT file 2024-08-04 09:34:51 +00:00
rohitwaghchaure
da22a8df42 Merge pull request #42584 from rohitwaghchaure/fixed-support-19715
fix: all warehouse filter for the stock report
2024-08-02 18:55:42 +05:30
Dietmar Fischer
511a0b9f37 fix: pre-commit for better code formatting 2024-08-02 12:51:48 +02:00
Khushi Rawat
5d58eb67a6 fix: use get_last_day to get the correct date (#42564) 2024-08-02 16:21:21 +05:30
ruthra kumar
bb8c9b5a58 refactor: ignore system generated cr / dr notes on general ledger 2024-08-02 15:47:47 +05:30
ruthra kumar
59d5beee20 refactor: ignore filter in general ledger for cr / dr notes 2024-08-02 15:43:04 +05:30
Khushi Rawat
bb877f4a6b fix: disable primary action button only when there are no active capitalization 2024-08-02 13:37:59 +05:30
ljain112
cfe2ae604b fix: company filter for filtring tax withheld vouchers 2024-08-02 13:12:04 +05:30
Rohit Waghchaure
a4311e345d fix: all warehouse filter for the stock report 2024-08-01 23:10:46 +05:30
Dietmar Fischer
61576ca030 feat: changes in opportunity.py to show contacts and addresses from referenced and opportunities 2024-08-01 17:01:43 +02:00
Nabin Hait
281198456d fix: Discount and taxes in return document should follow the reference document (#41911)
* fix: Discount and taxes in return document should follow the reference document

* fix: Ignore Pricing rule on debit/credit note if created against PI/SI with test cases

* fix: linter issue
2024-08-01 14:51:01 +05:30
ljain112
ac629ede79 fix: dimensions in common party journal entry 2024-07-31 18:49:19 +05:30
ruthra kumar
fb2aa7d205 Merge pull request #42563 from ruthra-kumar/set_query_filter_on_pe_tax_templates
fix: set query filters for sales / purchase tax template on PE
2024-07-31 17:08:11 +05:30
ruthra kumar
9fe47ac101 fix: set query filters for sales / purchase tax template on PE 2024-07-31 16:42:33 +05:30
ruthra kumar
d811fdf675 Merge pull request #42558 from ruthra-kumar/fix_undefined_in_consolidate_report
fix: 'undefined' in PL and BS report summary on Consolidated report
2024-07-31 15:32:36 +05:30
ruthra kumar
dd5a5e4919 fix: 'undefined' in PL and BS report summary on Consolidated report 2024-07-31 14:40:55 +05:30
rohitwaghchaure
f620ef20ae fix: inter transfer delivery note issue with batch (#42552) 2024-07-31 14:17:22 +05:30
ruthra kumar
40c166a0a0 refactor: date filters should be explicit 2024-07-31 14:01:27 +05:30
Smit Vora
33a0a529a5 Merge pull request #42392 from ljain112/fix-asset
fix: allow sale of asset for internal transfer
2024-07-31 13:22:20 +05:30
Lakshit Jain
8624aeca54 fix: promotional scheme doctype fields in consitency with pricing rule (#42432)
* fix: add "round_free_qty" check box in promotional scheme

* fix: add `add_for_price_list` field

* fix: set_query in setup for promotional scheme

---------
2024-07-31 13:19:54 +05:30
Lakshit Jain
a694390a12 fix: reverse debit credit for party gl entry in payment entry based on negative amount (#42367)
* fix: do not absolute the amount for party gl entries

* fix: reverse debit credit for party gl entry based on negative amount

* refactor: reduce nesting of if condition

---------
2024-07-31 12:46:10 +05:30
Nabin Hait
4986f28a89 fix: set currency on change of company considering customer default currency (#42405) 2024-07-31 11:44:05 +05:30
Khushi Rawat
0e5e503b42 Merge pull request #42525 from khushi8112/depreciation-adjustment-for-existing-asset
fix: Adjust initial month's depreciation to end of depreciation period
2024-07-30 19:04:26 +05:30
ljain112
fd71d8af52 fix: min height for rows in sales funnel 2024-07-30 17:49:56 +05:30
rohitwaghchaure
1c7f7c8d1a perf: huge number of serial no creation (#42522) 2024-07-30 16:33:53 +05:30
ruthra kumar
93ee922c5f Merge pull request #42393 from blaggacao/payments/pr-5
refactor: explicate intent on make_payment_request interface
2024-07-30 16:31:53 +05:30
rohitwaghchaure
23fed831a0 fix: price_list_currency not found error (#42534) 2024-07-30 14:26:29 +05:30
rohitwaghchaure
0ecfa709d8 fix: warehouse filter in Product Bundle Balance (#42532) 2024-07-30 14:26:06 +05:30
rohitwaghchaure
c5d68333c9 fix: purchase return from rejected warehouse (#42531) 2024-07-30 14:24:35 +05:30
ruthra kumar
ff3a5058c1 Merge pull request #42476 from ruthra-kumar/configuration_to_control_payment_request_creation
refactor: checkbox to control Payment Request creation
2024-07-30 13:35:59 +05:30
ruthra kumar
5ec9df8d1c Merge pull request #42528 from ruthra-kumar/rename_button_on_payment_order
chore: button name should reflect what it creates
2024-07-30 11:59:46 +05:30
ruthra kumar
0b6e7f83cd chore: button name should reflect what it creates 2024-07-30 11:54:41 +05:30
Khushi Rawat
f0768010d9 fix(tests): added tests for usecase 2024-07-30 01:28:52 +05:30
Khushi Rawat
cbb749a3a5 fix: Adjust initial month's depreciation to end of depreciation period 2024-07-29 23:50:57 +05:30
rohitwaghchaure
8eff168d76 fix: Warranty Expiry Date not set in the serial number (#42513)
* fix: Warranty Expiry Date not set in the serial number

* chore: fix linters issue
2024-07-29 14:47:42 +05:30
rohitwaghchaure
25dac1f18e fix: builtins.KeyError: ('ABC', 'Store - CP') (#42505) 2024-07-29 09:49:53 +05:30
rohitwaghchaure
cb522f8f22 fix: performance issue for the report Purchase Order Analysis report (#42503) 2024-07-28 23:41:00 +05:30
Raffael Meyer
21d8b09f47 Merge pull request #42499 from frappe/l10n_develop
fix: sync translations from crowdin
2024-07-28 14:49:19 +02:00
Frappe PR Bot
e9c1a7e3e3 fix: Esperanto translations 2024-07-27 22:53:17 +05:30
Raffael Meyer
24b26627e7 Merge pull request #42498 from frappe/pot_develop_2024-07-27 2024-07-27 19:18:38 +02:00
frappe-pr-bot
9aaf6e47d5 chore: update POT file 2024-07-27 17:02:08 +00:00
Raffael Meyer
4931b74b3f Merge pull request #42466 from frappe/l10n_develop 2024-07-27 19:00:36 +02:00
Raffael Meyer
532bc163f6 Merge pull request #42457 from barredterra/review-translations 2024-07-27 18:59:26 +02:00
Raffael Meyer
4c3ec767ce Merge pull request #42418 from barredterra/si-queries 2024-07-27 18:58:27 +02:00
ruthra kumar
0d77e0b0cd Merge pull request #42477 from ruthra-kumar/incorrect_cost_center_on_AR_AP_report
fix: incorrect cost_center on AR/AP report
2024-07-26 19:42:56 +05:30
Ninad Parikh
723ac0ffc4 fix: Update Rate as per Valuation Rate for Internal Transfers only if Setting is Enabled (#42050)
* fix: update rate for internal transfers only if settings enabled

* fix: better naming

* fix: create field for storing incoming rate in purchase doctypes

* fix: use qty instead of qty_in_stock_uom

* fix: add description, refactor for readablility

* test: test case to validate internal transfers at arm's length price

* fix: minor fix

* fix: deletion of code not required

---------

Co-authored-by: Smit Vora <smitvora203@gmail.com>
2024-07-26 18:22:40 +05:30
ruthra kumar
9a0894fd65 test: invoice cost center reported in AR/AP report 2024-07-26 18:16:40 +05:30
Smit Vora
096ec2db6a Merge pull request #42470 from Ninad1306/field_changes_for_subcontracting
fix: Fields Alteration Related to Subcontracting
2024-07-26 16:24:47 +05:30
Smit Vora
3b481ae656 Merge pull request #42305 from ljain112/fix-tds-adv
fix: consider payment entries for checking if tds is deducted
2024-07-26 14:40:15 +05:30
Smit Vora
ad31b63537 Merge pull request #42444 from ljain112/parenttype
fix: parenttype in purchase and sales item query
2024-07-26 14:23:21 +05:30
ruthra kumar
3e19041fa3 fix: incorrect cost_center on AR/AP report 2024-07-26 08:00:13 +05:30
ruthra kumar
ce81fd9ba6 refactor: checkbox to control Payment Request creation 2024-07-26 06:52:55 +05:30
ruthra kumar
07281d30bb Merge pull request #42472 from ruthra-kumar/index_on_release_date
refactor: index on Purchase Invoice 'release_date'
2024-07-25 21:26:22 +05:30
ruthra kumar
764dd12b10 refactor: index on Purchase Invoice 'release_date' 2024-07-25 19:51:54 +05:30
Ninad1306
77590e6077 fix: fields alteration related to subcontracting 2024-07-25 18:05:31 +05:30
ljain112
40b59de4cd fix: consider payment entries for checking if tds is deducted 2024-07-25 17:26:52 +05:30
Ninad Parikh
302339998f fix: Fields Modification for Subcontracting DocTypes (#42383)
* fix: fields renaming and reordering for enhanced user experience

* fix: dashboard data for stock entry
2024-07-25 10:18:21 +05:30
rohitwaghchaure
0e817f42ef fix: dynamic condition in the pricing rule not working (#42467) 2024-07-25 10:15:05 +05:30
Frappe PR Bot
8c6d666163 fix: Swedish translations 2024-07-24 20:18:54 +05:30
Frappe PR Bot
4ab6134306 fix: Esperanto translations 2024-07-24 20:18:50 +05:30
ruthra kumar
e062ec38b0 Merge pull request #42462 from ruthra-kumar/monthly_err_revaluation
refactor: provision for re-evaluating Exchange Rates in monthly frequency
2024-07-24 18:44:04 +05:30
ruthra kumar
fc4e5f165c refactor: hooks for monthly re-evaluation jobs 2024-07-24 18:18:21 +05:30
ruthra kumar
ce2b9e0f1a refactor: provision for monthly re-evaluation 2024-07-24 18:18:13 +05:30
rohitwaghchaure
06e2d7265c fix: keyerror posting_time (#42452)
fix: keyerror: posting_time
2024-07-24 16:39:11 +05:30
rohitwaghchaure
9cd3374101 fix: incorrect current qty for the batch in stock reco (#42434) 2024-07-24 16:38:20 +05:30
barredterra
89c458e229 chore: set PR reviewer for translations 2024-07-24 09:59:46 +02:00
Raffael Meyer
ffc5bbfa4c Merge pull request #42319 from frappe/pot_develop_2024-07-14 2024-07-24 09:51:13 +02:00
Raffael Meyer
92a6fc8c2e Merge pull request #42206 from frappe/l10n_develop 2024-07-24 09:50:43 +02:00
ruthra kumar
8ccf8c0ea2 Merge pull request #42390 from ruthra-kumar/remove_stale_code_from_reposting
refactor: cleaning up stale code related to reposting
2024-07-24 12:57:51 +05:30
ljain112
972329cc16 fix: allow sale of asset for internal transfer 2024-07-23 17:44:37 +05:30
ljain112
35981b8730 fix: parenttype in item wise purchase and sales register 2024-07-23 17:36:33 +05:30
Khushi Rawat
6e1fc33d01 Merge pull request #42372 from khushi8112/correct-validation-for-depreciation-posting-date
fix: correct validation for depreciation posting date
2024-07-23 15:23:41 +05:30
Smit Vora
c8e85f4bb7 Merge pull request #42377 from vorasmit/ignore-duplicates
fix: ignore duplicates while creating default templates
2024-07-23 10:25:29 +05:30
Raffael Meyer
bf2c9a7f41 Merge pull request #42419 from barredterra/si-initial-value 2024-07-22 20:40:49 +02:00
rohitwaghchaure
be2648245b fix: qty in the 'Serial No Ledger' report (#42429) 2024-07-22 17:45:48 +05:30
barredterra
65f80abf2f fix: provide initial value for .reduce() call
Fixes the error "TypeError: Reduce of empty array with no initial value" (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Reduce_of_empty_array_with_no_initial_value#invalid_cases)
2024-07-20 00:44:31 +02:00
barredterra
9b463753b7 refactor(Sales Invoice): queries 2024-07-20 00:27:16 +02:00
Frappe PR Bot
9e93bc5819 fix: Swedish translations 2024-07-19 17:47:54 +05:30
barredterra
5d4578a172 refactor(Supplier Group): use frm.is_new() instead of frm.doc.__islocal 2024-07-18 21:00:42 +02:00
barredterra
45391c951b fix(Supplier Group): remove useless headline
Not sure what it was supposed to mean. It was only visible on root group, along with the other message.
2024-07-18 20:59:26 +02:00
barredterra
741fc54eca fix(Supplier Group): properly call frm.set_read_only() 2024-07-18 20:56:56 +02:00
David
6d83c11d8e refactor: explicate intent on make_payment_request interface 2024-07-18 15:18:27 +02:00
ruthra kumar
e71cb4eab7 refactor(test): remove assert on 'repost_required' 2024-07-18 15:39:30 +05:30
ruthra kumar
07fc952a43 refactor: remove attribute check on 'repost_required' 2024-07-18 15:38:15 +05:30
ruthra kumar
09f429ffba refactor: repost without checking on flag 2024-07-18 15:34:59 +05:30
ruthra kumar
fe46e1d089 chore: remove stale UI code related to repost 2024-07-18 15:34:02 +05:30
ruthra kumar
e81373bb6a chore: remove 'repost_required' from Journal Entry 2024-07-18 15:32:51 +05:30
ruthra kumar
a467888a67 chore: remove 'repost_required' from purchase invoice 2024-07-18 15:31:59 +05:30
ruthra kumar
06c5334f2a chore: remove stale 'repost_required' flag from sales invoice 2024-07-18 15:31:01 +05:30
ruthra kumar
f3fda9ce98 chore: remove stale code from sales invoice 2024-07-18 15:29:30 +05:30
Smit Vora
cf55c2ab3d fix: ignore duplicates while creating default templates 2024-07-18 10:07:02 +05:30
Khushi Rawat
da4ed90a3e fix: correct validation for depreciation posting date 2024-07-17 19:19:31 +05:30
Frappe PR Bot
656e363aef fix: Swedish translations 2024-07-16 02:32:01 +05:30
frappe-pr-bot
372b5a4ca7 chore: update POT file 2024-07-14 09:35:44 +00:00
Frappe PR Bot
5e3359c5c5 fix: Swedish translations 2024-07-14 02:09:38 +05:30
Frappe PR Bot
62fc3bd586 fix: Swedish translations 2024-07-11 01:37:52 +05:30
Frappe PR Bot
e7ee508de4 fix: Esperanto translations 2024-07-09 00:53:26 +05:30
Frappe PR Bot
4b415987f9 fix: Swedish translations 2024-07-08 00:51:51 +05:30
Frappe PR Bot
cfda332faf fix: Swedish translations 2024-07-07 00:29:52 +05:30
Markus Lobedann
0ba4bff943 Merge branch 'frappe:develop' into patch-1 2024-05-31 14:39:41 +02:00
Markus Lobedann
9d9331408f Merge branch 'frappe:develop' into patch-1 2024-04-18 14:51:05 +02:00
Markus Lobedann
a2f878e1d1 fix: 🐛 use doctype File directly instead of save_file from file_manager.py
`save_file` will change the filename and not clean up after itself after a rollback
2024-04-04 10:31:34 +02:00
93 changed files with 25148 additions and 19963 deletions

View File

@@ -37,4 +37,4 @@ gh auth setup-git
git push -u upstream "${branch_name}"
echo "Creating a PR..."
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/erpnext
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" --reviewer ${PR_REVIEWER} -R frappe/erpnext

View File

@@ -36,3 +36,4 @@ jobs:
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
BASE_BRANCH: ${{ matrix.branch }}
PR_REVIEWER: barredterra # change to your GitHub username if you copied this file

View File

@@ -30,7 +30,7 @@ ERPNext as a monolith includes the following areas for managing businesses:
1. [Quality Management](https://erpnext.com/docs/user/manual/en/quality-management)
1. [Manufacturing](https://erpnext.com/open-source-manufacturing-erp-software)
1. [Website Management](https://erpnext.com/open-source-website-builder-software)
1. [Customize ERPNext](https://erpnext.com/docs/user/manual/en/customize-erpnext)
1. [Customize ERPNext](https://docs.erpnext.com/docs/v13/user/manual/en/customize-erpnext)
1. [And More](https://erpnext.com/docs/user/manual/en/)
ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a full-stack web app framework built with Python & JavaScript.

View File

@@ -1 +1,2 @@
**/setup/setup_wizard/data/uom_data.json,erpnext.gettext.extractors.uom_data.extract
**/setup/doctype/incoterm/incoterms.csv,erpnext.gettext.extractors.incoterms.extract
1 **/setup/setup_wizard/data/uom_data.json erpnext.gettext.extractors.uom_data.extract
2 **/setup/doctype/incoterm/incoterms.csv erpnext.gettext.extractors.incoterms.extract

View File

@@ -4,5 +4,7 @@ files:
pull_request_title: "fix: sync translations from crowdin"
pull_request_labels:
- translation
pull_request_reviewers:
- barredterra # change to your GitHub username if you copied this file
commit_message: "fix: %language% translations"
append_commit_message: false

View File

@@ -73,7 +73,9 @@
"remarks_section",
"general_ledger_remarks_length",
"column_break_lvjk",
"receivable_payable_remarks_length"
"receivable_payable_remarks_length",
"payment_request_settings",
"create_pr_in_draft_status"
],
"fields": [
{
@@ -475,6 +477,18 @@
"fieldname": "calculate_depr_using_total_days",
"fieldtype": "Check",
"label": "Calculate daily depreciation using total days in depreciation period"
},
{
"description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.",
"fieldname": "payment_request_settings",
"fieldtype": "Tab Break",
"label": "Payment Request"
},
{
"default": "1",
"fieldname": "create_pr_in_draft_status",
"fieldtype": "Check",
"label": "Create in Draft Status"
}
],
"icon": "icon-cog",
@@ -482,7 +496,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-07-12 00:24:20.957726",
"modified": "2024-07-26 06:48:52.714630",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -35,6 +35,7 @@ class AccountsSettings(Document):
book_tax_discount_loss: DF.Check
calculate_depr_using_total_days: DF.Check
check_supplier_invoice_uniqueness: DF.Check
create_pr_in_draft_status: DF.Check
credit_controller: DF.Link | None
delete_linked_ledger_entries: DF.Check
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]

View File

@@ -25,30 +25,6 @@ frappe.ui.form.on("Journal Entry", {
refresh: function (frm) {
erpnext.toggle_naming_series();
if (frm.doc.repost_required && frm.doc.docstatus === 1) {
frm.set_intro(
__(
"Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."
)
);
frm.add_custom_button(__("Repost Accounting Entries"), () => {
frm.call({
doc: frm.doc,
method: "repost_accounting_entries",
freeze: true,
freeze_message: __("Reposting..."),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__("Accounting Entries are reposted."));
frm.refresh();
}
},
});
})
.removeClass("btn-default")
.addClass("btn-warning");
}
if (frm.doc.docstatus > 0) {
frm.add_custom_button(
__("Ledger"),

View File

@@ -64,8 +64,7 @@
"stock_entry",
"subscription_section",
"auto_repeat",
"amended_from",
"repost_required"
"amended_from"
],
"fields": [
{
@@ -544,15 +543,6 @@
"label": "Is System Generated",
"no_copy": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-file-text",
@@ -567,7 +557,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2024-03-27 13:09:58.366953",
"modified": "2024-07-18 15:32:29.413598",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -47,9 +47,7 @@ class JournalEntry(AccountsController):
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import (
JournalEntryAccount,
)
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import JournalEntryAccount
accounts: DF.Table[JournalEntryAccount]
amended_from: DF.Link | None
@@ -197,14 +195,10 @@ class JournalEntry(AccountsController):
self.update_booked_depreciation()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
self.needs_repost = self.check_if_fields_updated(
fields_to_check=[], child_tables={"accounts": []}
)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
self.repost_accounting_entries()
self.needs_repost = self.check_if_fields_updated(fields_to_check=[], child_tables={"accounts": []})
if self.needs_repost:
self.validate_for_repost()
self.repost_accounting_entries()
def on_cancel(self):
# References for this Journal are removed on the `on_cancel` event in accounts_controller

View File

@@ -165,8 +165,25 @@ frappe.ui.form.on("Payment Entry", {
filters: filters,
};
});
},
frm.set_query("sales_taxes_and_charges_template", function () {
return {
filters: {
company: frm.doc.company,
disabled: false,
},
};
});
frm.set_query("purchase_taxes_and_charges_template", function () {
return {
filters: {
company: frm.doc.company,
disabled: false,
},
};
});
},
refresh: function (frm) {
erpnext.hide_company(frm);
frm.events.hide_unhide_fields(frm);

View File

@@ -37,7 +37,6 @@ from erpnext.accounts.utils import (
get_account_currency,
get_balance_on,
get_outstanding_invoices,
get_party_types_from_account_type,
)
from erpnext.controllers.accounts_controller import (
AccountsController,
@@ -1214,90 +1213,82 @@ class PaymentEntry(AccountsController):
self.make_advance_gl_entries(cancel=cancel)
def add_party_gl_entries(self, gl_entries):
if self.party_account:
if self.payment_type == "Receive":
against_account = self.paid_to
else:
against_account = self.paid_from
if not self.party_account:
return
party_gl_dict = self.get_gl_dict(
if self.payment_type == "Receive":
against_account = self.paid_to
else:
against_account = self.paid_from
party_account_type = frappe.db.get_value("Party Type", self.party_type, "account_type")
party_gl_dict = self.get_gl_dict(
{
"account": self.party_account,
"party_type": self.party_type,
"party": self.party,
"against": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
},
item=self,
)
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()
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
if (
d.reference_doctype in ["Sales Invoice", "Purchase Invoice"]
and d.allocated_amount < 0
and (
(party_account_type == "Receivable" and self.payment_type == "Pay")
or (party_account_type == "Payable" and self.payment_type == "Receive")
)
):
# reversing dr_cr because because it will get reversed in gl processing due to negative amount
dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
gle.update(
{
"account": self.party_account,
"party_type": self.party_type,
"party": self.party,
"against": against_account,
"account_currency": self.party_account_currency,
"cost_center": self.cost_center,
},
item=self,
dr_or_cr: allocated_amount_in_company_currency,
dr_or_cr + "_in_account_currency": d.allocated_amount,
"against_voucher_type": d.reference_doctype,
"against_voucher": d.reference_name,
"cost_center": cost_center,
}
)
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,
}
)
dr_or_cr = "credit" if self.payment_type == "Receive" else "debit"
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()
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"
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,
}
)
if self.book_advance_payments_in_separate_party_account:
gle.update(
{
"against_voucher_type": "Payment Entry",
"against_voucher": self.name,
}
)
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"

View File

@@ -36,7 +36,7 @@ frappe.ui.form.on("Payment Order", {
// payment Entry
if (frm.doc.docstatus === 1 && frm.doc.payment_order_type === "Payment Request") {
frm.add_custom_button(__("Create Payment Entries"), function () {
frm.add_custom_button(__("Create Journal Entries"), function () {
frm.trigger("make_payment_records");
});
}

View File

@@ -491,10 +491,15 @@ def make_payment_request(**args):
"party_type": args.get("party_type") or "Customer",
"party": args.get("party") or ref_doc.get("customer"),
"bank_account": bank_account,
"make_sales_invoice": args.order_type == "Shopping Cart",
"mute_email": args.mute_email
or args.order_type == "Shopping Cart"
or gateway_account.get("payment_channel", "Email") != "Email",
"make_sales_invoice": (
args.make_sales_invoice # new standard
or args.order_type == "Shopping Cart" # compat for webshop app
),
"mute_email": (
args.mute_email # new standard
or args.order_type == "Shopping Cart" # compat for webshop app
or gateway_account.get("payment_channel", "Email") != "Email"
),
}
)
@@ -509,7 +514,8 @@ def make_payment_request(**args):
for dimension in get_accounting_dimensions():
pr.update({dimension: ref_doc.get(dimension)})
pr.insert(ignore_permissions=True)
if frappe.db.get_single_value("Accounts Settings", "create_pr_in_draft_status", cache=True):
pr.insert(ignore_permissions=True)
if args.submit_doc:
pr.submit()

View File

@@ -194,7 +194,8 @@ class TestPaymentRequest(unittest.TestCase):
dt="Sales Order",
dn=so.name,
payment_gateway_account="_Test Gateway - USD", # email channel
order_type="Shopping Cart",
make_sales_invoice=True,
mute_email=True,
submit_doc=True,
return_doc=True,
)

View File

@@ -6,7 +6,9 @@ import unittest
import frappe
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.get_item_details import get_item_details
@@ -1311,6 +1313,69 @@ class TestPricingRule(unittest.TestCase):
pricing_rule.is_recursive = True
self.assertRaises(frappe.ValidationError, pricing_rule.save)
def test_ignore_pricing_rule_for_credit_note(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
pricing_rule = make_pricing_rule(
discount_percentage=20,
selling=1,
buying=1,
priority=1,
title="_Test Pricing Rule",
)
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1)
item = si.items[0]
si.submit()
self.assertEqual(item.discount_percentage, 20)
self.assertEqual(item.rate, 80)
# change discount on pricing rule
pricing_rule.discount_percentage = 30
pricing_rule.save()
credit_note = make_return_doc(si.doctype, si.name)
credit_note.save()
self.assertEqual(credit_note.ignore_pricing_rule, 1)
self.assertEqual(credit_note.pricing_rules, [])
self.assertEqual(credit_note.items[0].discount_percentage, 20)
self.assertEqual(credit_note.items[0].rate, 80)
self.assertEqual(credit_note.items[0].pricing_rules, None)
credit_note.delete()
si.cancel()
def test_ignore_pricing_rule_for_debit_note(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
pricing_rule = make_pricing_rule(
discount_percentage=20,
buying=1,
priority=1,
title="_Test Pricing Rule",
)
pi = make_purchase_invoice(do_not_submit=True, supplier="_Test Supplier 1", qty=1)
item = pi.items[0]
pi.submit()
self.assertEqual(item.discount_percentage, 20)
self.assertEqual(item.rate, 40)
# change discount on pricing rule
pricing_rule.discount_percentage = 30
pricing_rule.save()
# create debit note from purchase invoice
debit_note = make_return_doc(pi.doctype, pi.name)
debit_note.save()
self.assertEqual(debit_note.ignore_pricing_rule, 1)
self.assertEqual(debit_note.pricing_rules, [])
self.assertEqual(debit_note.items[0].discount_percentage, 20)
self.assertEqual(debit_note.items[0].rate, 40)
self.assertEqual(debit_note.items[0].pricing_rules, None)
debit_note.delete()
pi.cancel()
test_dependencies = ["Campaign"]

View File

@@ -2,6 +2,18 @@
// For license information, please see license.txt
frappe.ui.form.on("Promotional Scheme", {
setup: function (frm) {
frm.set_query("for_price_list", "price_discount_slabs", (doc) => {
return {
filters: {
selling: doc.selling,
buying: doc.buying,
currency: doc.currency,
},
};
});
},
refresh: function (frm) {
frm.trigger("set_options_for_applicable_for");
frm.trigger("toggle_reqd_apply_on");

View File

@@ -51,6 +51,7 @@ price_discount_fields = [
"discount_percentage",
"validate_applied_rule",
"apply_multiple_pricing_rules",
"for_price_list",
]
product_discount_fields = [
@@ -63,6 +64,7 @@ product_discount_fields = [
"recurse_for",
"apply_recursion_over",
"apply_multiple_pricing_rules",
"round_free_qty",
]

View File

@@ -21,6 +21,7 @@
"rate",
"discount_amount",
"discount_percentage",
"for_price_list",
"section_break_11",
"warehouse",
"threshold_percentage",
@@ -120,6 +121,13 @@
"fieldtype": "Float",
"label": "Discount Percentage"
},
{
"depends_on": "eval:doc.rate_or_discount!=\"Rate\"",
"fieldname": "for_price_list",
"fieldtype": "Link",
"label": "For Price List",
"options": "Price List"
},
{
"fieldname": "section_break_11",
"fieldtype": "Section Break"
@@ -169,7 +177,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:22.448265",
"modified": "2024-07-23 12:33:46.574950",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Price Discount",

View File

@@ -19,6 +19,7 @@ class PromotionalSchemePriceDiscount(Document):
disable: DF.Check
discount_amount: DF.Currency
discount_percentage: DF.Float
for_price_list: DF.Link | None
max_amount: DF.Currency
max_qty: DF.Float
min_amount: DF.Currency

View File

@@ -22,6 +22,7 @@
"column_break_9",
"free_item_uom",
"free_item_rate",
"round_free_qty",
"section_break_12",
"warehouse",
"threshold_percentage",
@@ -181,12 +182,18 @@
"fieldtype": "Float",
"label": "Apply Recursion Over (As Per Transaction UOM)",
"mandatory_depends_on": "is_recursive"
},
{
"default": "0",
"fieldname": "round_free_qty",
"fieldtype": "Check",
"label": "Round Free Qty"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-03-27 13:10:22.605892",
"modified": "2024-07-22 17:25:07.880984",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Promotional Scheme Product Discount",
@@ -195,4 +202,4 @@
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -53,6 +53,7 @@ class PromotionalSchemeProductDiscount(Document):
"20",
]
recurse_for: DF.Float
round_free_qty: DF.Check
rule_description: DF.SmallText
same_item: DF.Check
threshold_percentage: DF.Percent

View File

@@ -77,31 +77,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
}
if (this.frm.doc.repost_required && this.frm.doc.docstatus === 1) {
this.frm.set_intro(
__(
"Accounting entries for this invoice need to be reposted. Please click on 'Repost' button to update."
)
);
this.frm
.add_custom_button(__("Repost Accounting Entries"), () => {
this.frm.call({
doc: this.frm.doc,
method: "repost_accounting_entries",
freeze: true,
freeze_message: __("Reposting..."),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__("Accounting Entries are reposted."));
me.frm.refresh();
}
},
});
})
.removeClass("btn-default")
.addClass("btn-warning");
}
if (!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0) {
if (doc.on_hold) {
this.frm.add_custom_button(

View File

@@ -170,7 +170,6 @@
"against_expense_account",
"column_break_63",
"unrealized_profit_loss_account",
"repost_required",
"subscription_section",
"subscription",
"auto_repeat",
@@ -364,7 +363,8 @@
"description": "Once set, this invoice will be on hold till the set date",
"fieldname": "release_date",
"fieldtype": "Date",
"label": "Release Date"
"label": "Release Date",
"search_index": 1
},
{
"fieldname": "cb_17",
@@ -1604,15 +1604,6 @@
"fieldtype": "Check",
"label": "Use Company Default Round Off Cost Center"
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"options": "Account",
"read_only": 1
},
{
"default": "0",
"fieldname": "use_transaction_date_exchange_rate",
@@ -1640,7 +1631,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2024-04-11 11:28:42.802211",
"modified": "2024-07-25 19:42:36.931278",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -159,7 +159,6 @@ class PurchaseInvoice(BuyingController):
rejected_warehouse: DF.Link | None
release_date: DF.Date | None
remarks: DF.SmallText | None
repost_required: DF.Check
represents_company: DF.Link | None
return_against: DF.Link | None
rounded_total: DF.Currency
@@ -797,19 +796,17 @@ class PurchaseInvoice(BuyingController):
self.process_common_party_accounting()
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
fields_to_check = [
"cash_bank_account",
"write_off_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
self.repost_accounting_entries()
fields_to_check = [
"cash_bank_account",
"write_off_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
if self.needs_repost:
self.validate_for_repost()
self.repost_accounting_entries()
def make_gl_entries(self, gl_entries=None, from_repost=False):
update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
@@ -1710,6 +1707,9 @@ class PurchaseInvoice(BuyingController):
self.db_set("release_date", None)
def set_tax_withholding(self):
self.set("advance_tax", [])
self.set("tax_withheld_vouchers", [])
if not self.apply_tds:
return
@@ -1751,8 +1751,6 @@ class PurchaseInvoice(BuyingController):
self.remove(d)
## Add pending vouchers on which tax was withheld
self.set("tax_withheld_vouchers", [])
for voucher_no, voucher_details in voucher_wise_amount.items():
self.append(
"tax_withheld_vouchers",
@@ -1767,7 +1765,6 @@ class PurchaseInvoice(BuyingController):
self.calculate_taxes_and_totals()
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
self.set("advance_tax", [])
for tax in advance_taxes:
allocated_amount = 0
pending_amount = flt(tax.tax_amount - tax.allocated_amount)

View File

@@ -2024,8 +2024,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
["Service - _TC", 1000, 0.0, nowdate()],
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
pi.load_from_db()
self.assertFalse(pi.repost_required)
@change_settings("Buying Settings", {"supplier_group": None})
def test_purchase_invoice_without_supplier_group(self):

View File

@@ -57,6 +57,7 @@
"base_net_rate",
"base_net_amount",
"valuation_rate",
"sales_incoming_rate",
"item_tax_amount",
"landed_cost_voucher_amount",
"rm_supp_cost",
@@ -958,12 +959,22 @@
"print_hide": 1,
"read_only": 1,
"search_index": 1
},
{
"description": "Valuation rate for the item as per Sales Invoice (Only for Internal Transfers)",
"fieldname": "sales_incoming_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Sales Incoming Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-06-14 11:57:07.171700",
"modified": "2024-07-19 12:12:42.449298",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -79,6 +79,7 @@ class PurchaseInvoiceItem(Document):
rejected_serial_no: DF.Text | None
rejected_warehouse: DF.Link | None
rm_supp_cost: DF.Currency
sales_incoming_rate: DF.Currency
sales_invoice_item: DF.Data | None
serial_and_batch_bundle: DF.Link | None
serial_no: DF.Text | None

View File

@@ -68,31 +68,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
if (this.frm.doc.repost_required && this.frm.doc.docstatus === 1) {
this.frm.set_intro(
__(
"Accounting entries for this invoice needs to be reposted. Please click on 'Repost' button to update."
)
);
this.frm
.add_custom_button(__("Repost Accounting Entries"), () => {
this.frm.call({
doc: this.frm.doc,
method: "repost_accounting_entries",
freeze: true,
freeze_message: __("Reposting..."),
callback: (r) => {
if (!r.exc) {
frappe.msgprint(__("Accounting Entries are reposted"));
me.frm.refresh();
}
},
});
})
.removeClass("btn-default")
.addClass("btn-warning");
}
if (this.frm.doc.is_return) {
this.frm.return_print_format = "Sales Invoice Return";
}
@@ -161,7 +136,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
const payment_is_overdue = doc.payment_schedule
.map((row) => Date.parse(row.due_date) < Date.now())
.reduce((prev, current) => prev || current);
.reduce((prev, current) => prev || current, false);
if (payment_is_overdue) {
this.frm.add_custom_button(
@@ -596,49 +571,6 @@ cur_frm.cscript["Make Delivery Note"] = function () {
});
};
cur_frm.fields_dict.cash_bank_account.get_query = function (doc) {
return {
filters: [
["Account", "account_type", "in", ["Cash", "Bank"]],
["Account", "root_type", "=", "Asset"],
["Account", "is_group", "=", 0],
["Account", "company", "=", doc.company],
],
};
};
cur_frm.fields_dict.write_off_account.get_query = function (doc) {
return {
filters: {
report_type: "Profit and Loss",
is_group: 0,
company: doc.company,
},
};
};
// Write off cost center
//-----------------------
cur_frm.fields_dict.write_off_cost_center.get_query = function (doc) {
return {
filters: {
is_group: 0,
company: doc.company,
},
};
};
// Cost Center in Details Table
// -----------------------------
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function (doc) {
return {
filters: {
company: doc.company,
is_group: 0,
},
};
};
cur_frm.cscript.income_account = function (doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "income_account");
};
@@ -651,28 +583,6 @@ cur_frm.cscript.cost_center = function (doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_rows(doc, cdt, cdn, "items", "cost_center");
};
cur_frm.set_query("debit_to", function (doc) {
return {
filters: {
account_type: "Receivable",
is_group: 0,
company: doc.company,
},
};
});
cur_frm.set_query("asset", "items", function (doc, cdt, cdn) {
var d = locals[cdt][cdn];
return {
filters: [
["Asset", "item_code", "=", d.item_code],
["Asset", "docstatus", "=", 1],
["Asset", "status", "in", ["Submitted", "Partially Depreciated", "Fully Depreciated"]],
["Asset", "company", "=", doc.company],
],
};
});
frappe.ui.form.on("Sales Invoice", {
setup: function (frm) {
frm.add_fetch("customer", "tax_id", "tax_id");
@@ -682,71 +592,132 @@ frappe.ui.form.on("Sales Invoice", {
frm.set_df_property("packed_items", "cannot_add_rows", true);
frm.set_df_property("packed_items", "cannot_delete_rows", true);
frm.set_query("account_for_change_amount", function () {
frm.set_query("cash_bank_account", function (doc) {
return {
filters: [
["Account", "account_type", "in", ["Cash", "Bank"]],
["Account", "root_type", "=", "Asset"],
["Account", "is_group", "=", 0],
["Account", "company", "=", doc.company],
],
};
});
frm.set_query("write_off_account", function (doc) {
return {
filters: {
account_type: ["in", ["Cash", "Bank"]],
company: frm.doc.company,
report_type: "Profit and Loss",
is_group: 0,
company: doc.company,
},
};
});
frm.set_query("write_off_cost_center", function (doc) {
return {
filters: {
is_group: 0,
company: doc.company,
},
};
});
frm.set_query("cost_center", "items", function (doc) {
return {
filters: {
company: doc.company,
is_group: 0,
},
};
});
frm.set_query("unrealized_profit_loss_account", function () {
frm.set_query("debit_to", function (doc) {
return {
filters: {
company: frm.doc.company,
account_type: "Receivable",
is_group: 0,
company: doc.company,
},
};
});
frm.set_query("asset", "items", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: [
["Asset", "item_code", "=", row.item_code],
["Asset", "docstatus", "=", 1],
["Asset", "status", "in", ["Submitted", "Partially Depreciated", "Fully Depreciated"]],
["Asset", "company", "=", doc.company],
],
};
});
frm.set_query("account_for_change_amount", function (doc) {
return {
filters: {
account_type: ["in", ["Cash", "Bank"]],
company: doc.company,
is_group: 0,
},
};
});
frm.set_query("unrealized_profit_loss_account", function (doc) {
return {
filters: {
company: doc.company,
is_group: 0,
root_type: "Liability",
},
};
});
frm.set_query("adjustment_against", function () {
frm.set_query("adjustment_against", function (doc) {
return {
filters: {
company: frm.doc.company,
customer: frm.doc.customer,
company: doc.company,
customer: doc.customer,
docstatus: 1,
},
};
});
frm.set_query("additional_discount_account", function () {
frm.set_query("additional_discount_account", function (doc) {
return {
filters: {
company: frm.doc.company,
company: doc.company,
is_group: 0,
report_type: "Profit and Loss",
},
};
});
frm.set_query("income_account", "items", function () {
frm.set_query("income_account", "items", function (doc) {
return {
query: "erpnext.controllers.queries.get_income_account",
filters: {
company: frm.doc.company,
company: doc.company,
disabled: 0,
},
};
});
(frm.custom_make_buttons = {
frm.custom_make_buttons = {
"Delivery Note": "Delivery",
"Sales Invoice": "Return / Credit Note",
"Payment Request": "Payment Request",
"Payment Entry": "Payment",
}),
(frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function (doc, cdt, cdn) {
return {
query: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet",
filters: { project: doc.project },
};
});
};
// discount account
frm.fields_dict["items"].grid.get_field("discount_account").get_query = function (doc) {
frm.set_query("time_sheet", "timesheets", function (doc, cdt, cdn) {
return {
query: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet",
filters: { project: doc.project },
};
});
frm.set_query("discount_account", "items", function (doc) {
return {
filters: {
report_type: "Profit and Loss",
@@ -754,9 +725,9 @@ frappe.ui.form.on("Sales Invoice", {
is_group: 0,
},
};
};
});
frm.fields_dict["items"].grid.get_field("deferred_revenue_account").get_query = function (doc) {
frm.set_query("deferred_revenue_account", "items", function (doc) {
return {
filters: {
root_type: "Liability",
@@ -764,7 +735,7 @@ frappe.ui.form.on("Sales Invoice", {
is_group: 0,
},
};
};
});
frm.set_query("company_address", function (doc) {
if (!doc.company) {
@@ -793,25 +764,23 @@ frappe.ui.form.on("Sales Invoice", {
};
});
// set get_query for loyalty redemption account
frm.fields_dict["loyalty_redemption_account"].get_query = function () {
frm.set_query("loyalty_redemption_account", function () {
return {
filters: {
company: frm.doc.company,
is_group: 0,
},
};
};
});
// set get_query for loyalty redemption cost center
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function () {
frm.set_query("loyalty_redemption_cost_center", function () {
return {
filters: {
company: frm.doc.company,
is_group: 0,
},
};
};
});
},
// When multiple companies are set up. in case company name is changed set default company address
company: function (frm) {

View File

@@ -215,7 +215,6 @@
"is_internal_customer",
"is_discounted",
"remarks",
"repost_required",
"connections_tab"
],
"fields": [
@@ -2128,15 +2127,6 @@
"label": "Write Off",
"width": "50%"
},
{
"default": "0",
"fieldname": "repost_required",
"fieldtype": "Check",
"hidden": 1,
"label": "Repost Required",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "incoterm",
"fieldtype": "Link",
@@ -2205,7 +2195,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2024-05-23 14:02:28.549041",
"modified": "2024-07-18 15:30:39.428519",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -166,7 +166,6 @@ class SalesInvoice(SellingController):
project: DF.Link | None
redeem_loyalty_points: DF.Check
remarks: DF.SmallText | None
repost_required: DF.Check
represents_company: DF.Link | None
return_against: DF.Link | None
rounded_total: DF.Currency
@@ -569,7 +568,6 @@ class SalesInvoice(SellingController):
self.repost_future_sle_and_gle()
self.db_set("status", "Cancelled")
self.db_set("repost_required", 0)
if self.coupon_code:
update_coupon_code_count(self.coupon_code, "cancelled")
@@ -722,25 +720,23 @@ class SalesInvoice(SellingController):
data.sales_invoice = sales_invoice
def on_update_after_submit(self):
if hasattr(self, "repost_required"):
fields_to_check = [
"additional_discount_account",
"cash_bank_account",
"account_for_change_amount",
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {
"items": ("income_account", "expense_account", "discount_account"),
"taxes": ("account_head",),
}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
if self.needs_repost:
self.validate_for_repost()
self.db_set("repost_required", self.needs_repost)
self.repost_accounting_entries()
fields_to_check = [
"additional_discount_account",
"cash_bank_account",
"account_for_change_amount",
"write_off_account",
"loyalty_redemption_account",
"unrealized_profit_loss_account",
"is_opening",
]
child_tables = {
"items": ("income_account", "expense_account", "discount_account"),
"taxes": ("account_head",),
}
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
if self.needs_repost:
self.validate_for_repost()
self.repost_accounting_entries()
def set_paid_amount(self):
paid_amount = 0.0
@@ -1326,6 +1322,10 @@ class SalesInvoice(SellingController):
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
# Do not book income for transfer within same company
if self.is_internal_transfer():
continue
if item.is_fixed_asset:
asset = self.get_asset(item)
@@ -1384,37 +1384,33 @@ class SalesInvoice(SellingController):
self.set_asset_status(asset)
else:
# Do not book income for transfer within same company
if not self.is_internal_transfer():
income_account = (
item.income_account
if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account
)
income_account = (
item.income_account
if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account
)
amount, base_amount = self.get_amount_and_base_amount(
item, enable_discount_accounting
)
amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict(
{
"account": income_account,
"against": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount"))
if account_currency == self.company_currency
else flt(amount, item.precision("net_amount"))
),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict(
{
"account": income_account,
"against": self.customer,
"credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (
flt(base_amount, item.precision("base_net_amount"))
if account_currency == self.company_currency
else flt(amount, item.precision("net_amount"))
),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
account_currency,
item=item,
)
)
# expense account gl entries
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):

View File

@@ -2965,9 +2965,6 @@ class TestSalesInvoice(FrappeTestCase):
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
si.load_from_db()
self.assertFalse(si.repost_required)
def test_asset_depreciation_on_sale_with_pro_rata(self):
"""
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on its date of sale.
@@ -3099,6 +3096,84 @@ class TestSalesInvoice(FrappeTestCase):
party_link.delete()
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
def test_sales_invoice_against_supplier_usd_with_dimensions(self):
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
from erpnext.accounts.doctype.party_link.party_link import create_party_link
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
# create a customer
customer = make_customer(customer="_Test Common Supplier USD")
cust_doc = frappe.get_doc("Customer", customer)
cust_doc.default_currency = "USD"
cust_doc.save()
# create a supplier
supplier = create_supplier(supplier_name="_Test Common Supplier USD").name
supp_doc = frappe.get_doc("Supplier", supplier)
supp_doc.default_currency = "USD"
supp_doc.save()
# create a party link between customer & supplier
party_link = create_party_link("Supplier", supplier, customer)
# enable common party accounting
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1)
# create a dimension and make it mandatory
if not frappe.get_all("Accounting Dimension", filters={"document_type": "Department"}):
dim = frappe.get_doc(
{
"doctype": "Accounting Dimension",
"document_type": "Department",
"dimension_defaults": [{"company": "_Test Company", "mandatory_for_bs": True}],
}
)
dim.save()
else:
dim = frappe.get_doc(
"Accounting Dimension",
frappe.get_all("Accounting Dimension", filters={"document_type": "Department"})[0],
)
dim.disabled = False
dim.dimension_defaults = []
dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True})
dim.save()
# create a sales invoice
si = create_sales_invoice(
customer=customer, parent_cost_center="_Test Cost Center - _TC", do_not_submit=True
)
si.department = "All Departments"
si.save().submit()
# check outstanding of sales invoice
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(flt(si.outstanding_amount), 0.0)
# check creation of journal entry
jv = frappe.get_all(
"Journal Entry Account",
{
"account": si.debit_to,
"party_type": "Customer",
"party": si.customer,
"reference_type": si.doctype,
"reference_name": si.name,
"department": "All Departments",
},
pluck="credit_in_account_currency",
)
self.assertTrue(jv)
self.assertEqual(jv[0], si.grand_total)
dim.disabled = True
dim.save()
party_link.delete()
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
def test_payment_statuses(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry

View File

@@ -268,6 +268,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
vouchers, voucher_wise_amount = get_invoice_vouchers(
parties, tax_details, inv.company, party_type=party_type
)
payment_entry_vouchers = get_payment_entry_vouchers(
parties, tax_details, inv.company, party_type=party_type
)
advance_vouchers = get_advance_vouchers(
parties,
company=inv.company,
@@ -275,7 +280,8 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
to_date=tax_details.to_date,
party_type=party_type,
)
taxable_vouchers = vouchers + advance_vouchers
taxable_vouchers = vouchers + advance_vouchers + payment_entry_vouchers
tax_deducted_on_advances = 0
if inv.doctype == "Purchase Invoice":
@@ -369,12 +375,14 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
AND ja.party in %s
AND j.apply_tds = 1
AND j.tax_withholding_category = %s
AND j.company = %s
""",
(
tax_details.from_date,
tax_details.to_date,
tuple(parties),
tax_details.get("tax_withholding_category"),
company,
),
as_dict=1,
)
@@ -387,6 +395,20 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
return vouchers, voucher_wise_amount
def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"):
payment_entry_filters = {
"party_type": party_type,
"party": ("in", parties),
"docstatus": 1,
"apply_tax_withholding_amount": 1,
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
"tax_withholding_category": tax_details.get("tax_withholding_category"),
"company": company,
}
return frappe.db.get_all("Payment Entry", filters=payment_entry_filters, pluck="name")
def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, party_type="Supplier"):
"""
Use Payment Ledger to fetch unallocated Advance Payments
@@ -477,6 +499,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
"unallocated_amount": (">", 0),
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
"tax_withholding_category": tax_details.get("tax_withholding_category"),
"company": inv.company,
}
field = "sum(tax_withholding_net_total)"

View File

@@ -139,6 +139,7 @@ class ReceivablePayableReport:
paid_in_account_currency=0.0,
credit_note_in_account_currency=0.0,
outstanding_in_account_currency=0.0,
cost_center=ple.cost_center,
)
self.get_invoices(ple)
@@ -253,7 +254,7 @@ class ReceivablePayableReport:
row.paid -= amount
row.paid_in_account_currency -= amount_in_account_currency
if ple.cost_center:
if not row.cost_center and ple.cost_center:
row.cost_center = str(ple.cost_center)
def update_sub_total_row(self, row, party):

View File

@@ -53,11 +53,13 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
si = si.submit()
return si
def create_payment_entry(self, docname):
def create_payment_entry(self, docname, do_not_submit=False):
pe = get_payment_entry("Sales Invoice", docname, bank_account=self.cash, party_amount=40)
pe.paid_from = self.debit_to
pe.insert()
pe.submit()
if not do_not_submit:
pe.submit()
return pe
def create_credit_note(self, docname, do_not_submit=False):
credit_note = create_sales_invoice(
@@ -984,3 +986,40 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
expected_data_after_payment,
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
)
def test_cost_center_on_report_output(self):
filters = {
"company": self.company,
"report_date": today(),
"range1": 30,
"range2": 60,
"range3": 90,
"range4": 120,
}
# check invoice grand total and invoiced column's value for 3 payment terms
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
si.cost_center = self.cost_center
si.save().submit()
new_cc = frappe.get_doc(
{
"doctype": "Cost Center",
"cost_center_name": "East Wing",
"parent_cost_center": self.company + " - " + self.company_abbr,
"company": self.company,
}
)
new_cc.save()
# check invoice grand total, invoiced, paid and outstanding column's value after payment
pe = self.create_payment_entry(si.name, do_not_submit=True)
pe.cost_center = new_cc.name
pe.save().submit()
report = execute(filters)
expected_data_after_payment = [si.name, si.cost_center, 60]
self.assertEqual(len(report[1]), 1)
row = report[1][0]
self.assertEqual(expected_data_after_payment, [row.voucher_no, row.cost_center, row.outstanding])

View File

@@ -104,7 +104,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
if total_credit:
data.append(total_credit)
report_summary = get_bs_summary(
report_summary, primitive_summary = get_bs_summary(
companies,
asset,
liability,
@@ -175,7 +175,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss)
report_summary = get_pl_summary(
report_summary, primitive_summary = get_pl_summary(
companies, "", income, expense, net_profit_loss, company_currency, filters, True
)

View File

@@ -209,6 +209,11 @@ frappe.query_reports["General Ledger"] = {
label: __("Ignore Exchange Rate Revaluation Journals"),
fieldtype: "Check",
},
{
fieldname: "ignore_cr_dr_notes",
label: __("Ignore System Generated Credit / Debit Notes"),
fieldtype: "Check",
},
],
};

View File

@@ -236,6 +236,20 @@ def get_conditions(filters):
if err_journals:
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
if filters.get("ignore_cr_dr_notes"):
system_generated_cr_dr_journals = frappe.db.get_all(
"Journal Entry",
filters={
"company": filters.get("company"),
"docstatus": 1,
"voucher_type": ("in", ["Credit Note", "Debit Note"]),
"is_system_generated": 1,
},
as_list=True,
)
if system_generated_cr_dr_journals:
filters.update({"voucher_no_not_in": [x[0] for x in system_generated_cr_dr_journals]})
if filters.get("voucher_no_not_in"):
conditions.append("voucher_no not in %(voucher_no_not_in)s")

View File

@@ -2,13 +2,32 @@
# MIT License. See license.txt
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, today
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.general_ledger.general_ledger import execute
from erpnext.controllers.sales_and_purchase_return import make_return_doc
class TestGeneralLedger(FrappeTestCase):
def setUp(self):
self.company = "_Test Company"
self.clear_old_entries()
def clear_old_entries(self):
doctype_list = [
"GL Entry",
"Payment Ledger Entry",
"Sales Invoice",
"Purchase Invoice",
"Payment Entry",
"Journal Entry",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def test_foreign_account_balance_after_exchange_rate_revaluation(self):
"""
Checks the correctness of balance after exchange rate revaluation
@@ -248,3 +267,68 @@ class TestGeneralLedger(FrappeTestCase):
)
)
self.assertIn(revaluation_jv.name, set([x.voucher_no for x in data]))
def test_ignore_cr_dr_notes_filter(self):
si = create_sales_invoice()
cr_note = make_return_doc(si.doctype, si.name)
cr_note.submit()
pr = frappe.get_doc("Payment Reconciliation")
pr.company = si.company
pr.party_type = "Customer"
pr.party = si.customer
pr.receivable_payable_account = si.debit_to
pr.get_unreconciled_entries()
invoices = [invoice.as_dict() for invoice in pr.invoices if invoice.invoice_number == si.name]
payments = [payment.as_dict() for payment in pr.payments if payment.reference_name == cr_note.name]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
system_generated_journal = frappe.db.get_all(
"Journal Entry",
filters={
"docstatus": 1,
"reference_type": si.doctype,
"reference_name": si.name,
"voucher_type": "Credit Note",
"is_system_generated": True,
},
fields=["name"],
)
self.assertEqual(len(system_generated_journal), 1)
expected = set([si.name, cr_note.name, system_generated_journal[0].name])
# Without ignore_cr_dr_notes
columns, data = execute(
frappe._dict(
{
"company": si.company,
"from_date": si.posting_date,
"to_date": si.posting_date,
"account": [si.debit_to],
"group_by": "Group by Voucher (Consolidated)",
"ignore_cr_dr_notes": False,
}
)
)
actual = set([x.voucher_no for x in data if x.voucher_no])
self.assertEqual(expected, actual)
# Without ignore_cr_dr_notes
expected = set([si.name, cr_note.name])
columns, data = execute(
frappe._dict(
{
"company": si.company,
"from_date": si.posting_date,
"to_date": si.posting_date,
"account": [si.debit_to],
"group_by": "Group by Voucher (Consolidated)",
"ignore_cr_dr_notes": True,
}
)
)
actual = set([x.voucher_no for x in data if x.voucher_no])
self.assertEqual(expected, actual)

View File

@@ -315,8 +315,9 @@ def apply_conditions(query, pi, pii, filters):
def get_items(filters, additional_table_columns):
pi = frappe.qb.DocType("Purchase Invoice")
pii = frappe.qb.DocType("Purchase Invoice Item")
doctype = "Purchase Invoice"
pi = frappe.qb.DocType(doctype)
pii = frappe.qb.DocType(f"{doctype} Item")
Item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(pi)
@@ -353,6 +354,7 @@ def get_items(filters, additional_table_columns):
pi.mode_of_payment,
)
.where(pi.docstatus == 1)
.where(pii.parenttype == doctype)
)
if filters.get("supplier"):

View File

@@ -410,8 +410,9 @@ def apply_group_by_conditions(query, si, ii, filters):
def get_items(filters, additional_query_columns, additional_conditions=None):
si = frappe.qb.DocType("Sales Invoice")
sii = frappe.qb.DocType("Sales Invoice Item")
doctype = "Sales Invoice"
si = frappe.qb.DocType(doctype)
sii = frappe.qb.DocType(f"{doctype} Item")
item = frappe.qb.DocType("Item")
query = (
@@ -459,6 +460,7 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
sii.qty,
)
.where(si.docstatus == 1)
.where(sii.parenttype == doctype)
)
if additional_query_columns:

View File

@@ -1614,6 +1614,18 @@ def auto_create_exchange_rate_revaluation_weekly() -> None:
create_err_and_its_journals(companies)
def auto_create_exchange_rate_revaluation_monthly() -> None:
"""
Executed by background job
"""
companies = frappe.db.get_all(
"Company",
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Montly"},
fields=["name", "submit_err_jv"],
)
create_err_and_its_journals(companies)
def get_payment_ledger_entries(gl_entries, cancel=0):
ple_map = []
if gl_entries:

View File

@@ -188,11 +188,21 @@ frappe.ui.form.on("Asset", {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
if (frm.doc.is_composite_asset) {
$(".primary-action").prop("hidden", true);
$(".form-message").text("Capitalize this asset to confirm");
frappe.call({
method: "erpnext.assets.doctype.asset.asset.has_active_capitalization",
args: {
asset: frm.doc.name,
},
callback: function (r) {
if (!r.message) {
$(".primary-action").prop("hidden", true);
$(".form-message").text("Capitalize this asset to confirm");
frm.add_custom_button(__("Capitalize Asset"), function () {
frm.trigger("create_asset_capitalization");
frm.add_custom_button(__("Capitalize Asset"), function () {
frm.trigger("create_asset_capitalization");
});
}
},
});
}
}
@@ -775,11 +785,8 @@ frappe.ui.form.on("Asset Finance Book", {
depreciation_start_date: function (frm, cdt, cdn) {
const book = locals[cdt][cdn];
if (
frm.doc.available_for_use_date &&
book.depreciation_start_date == frm.doc.available_for_use_date
) {
frappe.msgprint(__("Depreciation Posting Date should not be equal to Available for Use Date."));
if (frm.doc.available_for_use_date && book.depreciation_start_date < frm.doc.available_for_use_date) {
frappe.msgprint(__("Depreciation Posting Date cannot be before Available-for-use Date"));
book.depreciation_start_date = "";
frm.refresh_field("finance_books");
}

View File

@@ -221,7 +221,6 @@
"read_only": 1
},
{
"depends_on": "eval:!doc.is_composite_asset",
"fieldname": "gross_purchase_amount",
"fieldtype": "Currency",
"label": "Gross Purchase Amount",
@@ -580,7 +579,7 @@
"link_fieldname": "target_asset"
}
],
"modified": "2024-07-07 22:27:14.733839",
"modified": "2024-08-01 16:39:09.340973",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@@ -268,10 +268,10 @@ class Asset(AccountsController):
frappe.throw(_("Available for use date is required"))
for d in self.finance_books:
if d.depreciation_start_date == self.available_for_use_date:
if getdate(d.depreciation_start_date) < getdate(self.available_for_use_date):
frappe.throw(
_(
"Row #{}: Depreciation Posting Date should not be equal to Available for Use Date."
"Depreciation Row {0}: Depreciation Posting Date cannot be before Available-for-use Date"
).format(d.idx),
title=_("Incorrect Date"),
)
@@ -1036,6 +1036,14 @@ def get_asset_value_after_depreciation(asset_name, finance_book=None):
return asset.get_value_after_depreciation(finance_book)
@frappe.whitelist()
def has_active_capitalization(asset):
active_capitalizations = frappe.db.count(
"Asset Capitalization", filters={"target_asset": asset, "docstatus": 1}
)
return active_capitalizations > 0
@frappe.whitelist()
def split_asset(asset_name, split_qty):
asset = frappe.get_doc("Asset", asset_name)

View File

@@ -740,7 +740,7 @@ class TestDepreciationMethods(AssetSetup):
available_for_use_date="2030-06-06",
is_existing_asset=1,
opening_number_of_booked_depreciations=2,
opening_accumulated_depreciation=47095.89,
opening_accumulated_depreciation=47178.08,
expected_value_after_useful_life=10000,
depreciation_start_date="2032-12-31",
total_number_of_depreciations=3,
@@ -748,7 +748,7 @@ class TestDepreciationMethods(AssetSetup):
)
self.assertEqual(asset.status, "Draft")
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
expected_schedules = [["2032-12-31", 30000.0, 77178.08], ["2033-06-06", 12821.92, 90000.0]]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")

View File

@@ -552,9 +552,18 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
# if not existing asset, from_date = available_for_use_date
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
# from_date = 01/01/2022
from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False)
days = date_diff(row.depreciation_start_date, from_date) + 1
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if row.depreciation_method in ("Straight Line", "Manual"):
prev_depreciation_start_date = add_months(
row.depreciation_start_date,
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
)
from_date = asset_doc.available_for_use_date
days = date_diff(prev_depreciation_start_date, from_date) + 1
total_days = get_total_days(prev_depreciation_start_date, row.frequency_of_depreciation)
else:
from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False)
days = date_diff(row.depreciation_start_date, from_date) + 1
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days <= 0:
frappe.throw(
_(
@@ -682,20 +691,15 @@ def get_straight_line_or_manual_depr_amount(
# if the Depreciation Schedule is being prepared for the first time
else:
if row.daily_prorata_based:
amount = (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
)
amount = flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
return get_daily_prorata_based_straight_line_depr(
asset, row, schedule_idx, number_of_pending_depreciations, amount
)
else:
return (
flt(asset.gross_purchase_amount)
- flt(asset.opening_accumulated_depreciation)
- flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations - asset.opening_number_of_booked_depreciations)
depreciation_amount = (
flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
) / flt(row.total_number_of_depreciations)
return depreciation_amount
def get_daily_prorata_based_straight_line_depr(
@@ -725,7 +729,16 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount):
)
),
add_days(
get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)),
get_last_day(
add_months(
row.depreciation_start_date,
(
row.frequency_of_depreciation
* (asset.opening_number_of_booked_depreciations + 1)
)
* -1,
),
),
1,
),
)
@@ -904,7 +917,7 @@ def _get_daily_prorata_based_default_wdv_or_dd_depr_amount(
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

View File

@@ -75,6 +75,116 @@ class TestAssetDepreciationSchedule(FrappeTestCase):
]
self.assertEqual(schedules, expected_schedules)
def test_schedule_for_slm_for_existing_asset_daily_pro_rata_enabled(self):
frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 1)
asset = create_asset(
calculate_depreciation=1,
depreciation_method="Straight Line",
available_for_use_date="2023-10-10",
is_existing_asset=1,
opening_number_of_booked_depreciations=9,
opening_accumulated_depreciation=265,
depreciation_start_date="2024-07-31",
total_number_of_depreciations=24,
frequency_of_depreciation=1,
gross_purchase_amount=731,
daily_prorata_based=1,
)
expected_schedules = [
["2024-07-31", 31.0, 296.0],
["2024-08-31", 31.0, 327.0],
["2024-09-30", 30.0, 357.0],
["2024-10-31", 31.0, 388.0],
["2024-11-30", 30.0, 418.0],
["2024-12-31", 31.0, 449.0],
["2025-01-31", 31.0, 480.0],
["2025-02-28", 28.0, 508.0],
["2025-03-31", 31.0, 539.0],
["2025-04-30", 30.0, 569.0],
["2025-05-31", 31.0, 600.0],
["2025-06-30", 30.0, 630.0],
["2025-07-31", 31.0, 661.0],
["2025-08-31", 31.0, 692.0],
["2025-09-30", 30.0, 722.0],
["2025-10-10", 9.0, 731.0],
]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 0)
def test_schedule_for_slm_for_existing_asset(self):
asset = create_asset(
calculate_depreciation=1,
depreciation_method="Straight Line",
available_for_use_date="2023-10-10",
is_existing_asset=1,
opening_number_of_booked_depreciations=9,
opening_accumulated_depreciation=265.30,
depreciation_start_date="2024-07-31",
total_number_of_depreciations=24,
frequency_of_depreciation=1,
gross_purchase_amount=731,
)
expected_schedules = [
["2024-07-31", 30.46, 295.76],
["2024-08-31", 30.46, 326.22],
["2024-09-30", 30.46, 356.68],
["2024-10-31", 30.46, 387.14],
["2024-11-30", 30.46, 417.6],
["2024-12-31", 30.46, 448.06],
["2025-01-31", 30.46, 478.52],
["2025-02-28", 30.46, 508.98],
["2025-03-31", 30.46, 539.44],
["2025-04-30", 30.46, 569.9],
["2025-05-31", 30.46, 600.36],
["2025-06-30", 30.46, 630.82],
["2025-07-31", 30.46, 661.28],
["2025-08-31", 30.46, 691.74],
["2025-09-30", 30.46, 722.2],
["2025-10-10", 8.8, 731.0],
]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
def test_schedule_sl_method_for_existing_asset_with_frequency_of_3_months(self):
asset = create_asset(
calculate_depreciation=1,
depreciation_method="Straight Line",
available_for_use_date="2023-11-01",
is_existing_asset=1,
opening_number_of_booked_depreciations=4,
opening_accumulated_depreciation=223.15,
depreciation_start_date="2024-12-31",
total_number_of_depreciations=12,
frequency_of_depreciation=3,
gross_purchase_amount=731,
)
expected_schedules = [
["2024-12-31", 60.92, 284.07],
["2025-03-31", 60.92, 344.99],
["2025-06-30", 60.92, 405.91],
["2025-09-30", 60.92, 466.83],
["2025-12-31", 60.92, 527.75],
["2026-03-31", 60.92, 588.67],
["2026-06-30", 60.92, 649.59],
["2026-09-30", 60.92, 710.51],
["2026-11-01", 20.49, 731.0],
]
schedules = [
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in get_depr_schedule(asset.name, "Draft")
]
self.assertEqual(schedules, expected_schedules)
# Enable Checkbox to Calculate depreciation using total days in depreciation period
def test_daily_prorata_based_depr_after_enabling_configuration(self):
frappe.db.set_single_value("Accounts Settings", "calculate_depr_using_total_days", 1)

View File

@@ -39,16 +39,14 @@ def validate_filters(filters):
def get_data(filters):
po = frappe.qb.DocType("Purchase Order")
po_item = frappe.qb.DocType("Purchase Order Item")
pi = frappe.qb.DocType("Purchase Invoice")
pi_item = frappe.qb.DocType("Purchase Invoice Item")
query = (
frappe.qb.from_(po)
.from_(po_item)
.inner_join(po_item)
.on(po_item.parent == po.name)
.left_join(pi_item)
.on(pi_item.po_detail == po_item.name & pi_item.docstatus == 1)
.left_join(pi)
.on(pi.name == pi_item.parent & pi.docstatus == 1)
.on((pi_item.po_detail == po_item.name) & (pi_item.docstatus == 1))
.select(
po.transaction_date.as_("date"),
po_item.schedule_date.as_("required_date"),

View File

@@ -14,6 +14,9 @@ from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_
def update_last_purchase_rate(doc, is_submit) -> None:
"""updates last_purchase_rate in item table for each item"""
if doc.get("is_internal_supplier"):
return
this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))
for d in doc.get("items"):

View File

@@ -85,7 +85,6 @@ force_item_fields = (
"brand",
"stock_uom",
"is_fixed_asset",
"item_tax_rate",
"pricing_rules",
"weight_per_unit",
"weight_uom",
@@ -743,7 +742,6 @@ class AccountsController(TransactionBase):
args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None:
if item.get(fieldname) is None or fieldname in force_item_fields:
@@ -753,7 +751,10 @@ class AccountsController(TransactionBase):
fieldname
):
item.set(fieldname, value)
elif fieldname == "item_tax_rate" and not (
self.get("is_return") and self.get("return_against")
):
item.set(fieldname, value)
elif fieldname == "serial_no":
# Ensure that serial numbers are matched against Stock UOM
item_conversion_factor = item.get("conversion_factor") or 1.0
@@ -2460,6 +2461,15 @@ class AccountsController(TransactionBase):
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
advance_entry.is_advance = "Yes"
# update dimesions
dimensions_dict = frappe._dict()
active_dimensions = get_dimensions()[0]
for dim in active_dimensions:
dimensions_dict[dim.fieldname] = self.get(dim.fieldname)
reconcilation_entry.update(dimensions_dict)
advance_entry.update(dimensions_dict)
if self.doctype == "Sales Invoice":
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
advance_entry.debit_in_account_currency = self.outstanding_amount
@@ -2517,16 +2527,12 @@ class AccountsController(TransactionBase):
@frappe.whitelist()
def repost_accounting_entries(self):
if self.repost_required:
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
repost_ledger.company = self.company
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
repost_ledger.flags.ignore_permissions = True
repost_ledger.insert()
repost_ledger.submit()
self.db_set("repost_required", 0)
else:
frappe.throw(_("No updates pending for reposting"))
repost_ledger = frappe.new_doc("Repost Accounting Ledger")
repost_ledger.company = self.company
repost_ledger.append("vouchers", {"voucher_type": self.doctype, "voucher_no": self.name})
repost_ledger.flags.ignore_permissions = True
repost_ledger.insert()
repost_ledger.submit()
@frappe.whitelist()

View File

@@ -314,18 +314,22 @@ class BuyingController(SubcontractingController):
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
)
net_rate = item.base_net_amount
if item.sales_incoming_rate: # for internal transfer
net_rate = item.qty * item.sales_incoming_rate
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
if self.get("is_old_subcontracting_flow"):
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
item.valuation_rate = (
item.base_net_amount
net_rate
+ item.item_tax_amount
+ item.rm_supp_cost
+ flt(item.landed_cost_voucher_amount)
) / qty_in_stock_uom
else:
item.valuation_rate = (
item.base_net_amount
net_rate
+ item.item_tax_amount
+ flt(item.landed_cost_voucher_amount)
+ flt(item.get("rate_difference_with_purchase_invoice"))
@@ -336,72 +340,88 @@ class BuyingController(SubcontractingController):
update_regional_item_valuation_rate(self)
def set_incoming_rate(self):
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
"""
Override item rate with incoming rate for internal stock transfer
"""
if self.doctype not in ("Purchase Receipt", "Purchase Invoice"):
return
if not (self.doctype == "Purchase Receipt" or self.get("update_stock")):
return
if cint(self.get("is_return")):
# Get outgoing rate based on original item cost based on valuation method
return
if not self.is_internal_transfer():
return
allow_at_arms_length_price = frappe.get_cached_value(
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
)
if allow_at_arms_length_price:
return
self.set_sales_incoming_rate_for_internal_transfer()
for d in self.get("items"):
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
if d.rate == d.sales_incoming_rate:
continue
d.rate = d.sales_incoming_rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
def set_sales_incoming_rate_for_internal_transfer(self):
"""
Set incoming rate from the sales transaction against which the
purchase is made (internal transfer)
"""
ref_doctype_map = {
"Purchase Order": "Sales Order Item",
"Purchase Receipt": "Delivery Note Item",
"Purchase Invoice": "Sales Invoice Item",
}
ref_doctype = ref_doctype_map.get(self.doctype)
items = self.get("items")
for d in items:
if not cint(self.get("is_return")):
# Get outgoing rate based on original item cost based on valuation method
for d in self.get("items"):
if not d.get(frappe.scrub(ref_doctype)):
posting_time = self.get("posting_time")
if not posting_time:
posting_time = nowtime()
if not d.get(frappe.scrub(ref_doctype)):
posting_time = self.get("posting_time")
if not posting_time and self.doctype == "Purchase Order":
posting_time = nowtime()
outgoing_rate = get_incoming_rate(
{
"item_code": d.item_code,
"warehouse": d.get("from_warehouse"),
"posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": posting_time,
"qty": -1 * flt(d.get("stock_qty")),
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
"voucher_detail_no": d.name,
},
raise_error_if_no_rate=False,
)
outgoing_rate = get_incoming_rate(
{
"item_code": d.item_code,
"warehouse": d.get("from_warehouse"),
"posting_date": self.get("posting_date") or self.get("transaction_date"),
"posting_time": posting_time,
"qty": -1 * flt(d.get("stock_qty")),
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
"company": self.company,
"voucher_type": self.doctype,
"voucher_no": self.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
"voucher_detail_no": d.name,
},
raise_error_if_no_rate=False,
)
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
field = (
"incoming_rate"
if self.get("is_internal_supplier") and not self.doctype == "Purchase Order"
else "rate"
)
rate = flt(
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
* (d.conversion_factor or 1),
d.precision("rate"),
)
if self.is_internal_transfer():
if self.doctype == "Purchase Receipt" or self.get("update_stock"):
if rate != d.rate:
d.rate = rate
frappe.msgprint(
_(
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
).format(d.idx),
alert=1,
)
d.discount_percentage = 0.0
d.discount_amount = 0.0
d.margin_rate_or_amount = 0.0
d.sales_incoming_rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
else:
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
d.sales_incoming_rate = flt(
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
* (d.conversion_factor or 1),
d.precision("rate"),
)
def validate_for_subcontracting(self):
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
@@ -566,11 +586,9 @@ class BuyingController(SubcontractingController):
if d.from_warehouse:
sle.dependant_sle_voucher_detail_no = d.name
else:
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
sle.update(
{
"incoming_rate": incoming_rate,
"incoming_rate": d.valuation_rate,
"recalculate_rate": 1
if (self.is_subcontracted and (d.bom or d.get("fg_item"))) or d.from_warehouse
else 0,

View File

@@ -319,6 +319,8 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
def set_missing_values(source, target):
doc = frappe.get_doc(target)
doc.is_return = 1
doc.ignore_pricing_rule = 1
doc.pricing_rules = []
doc.return_against = source.name
doc.set_warehouse = ""
if doctype == "Sales Invoice" or doctype == "POS Invoice":
@@ -478,6 +480,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = -1 * source_doc.qty
target_doc.pricing_rules = None
if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
returned_qty_map = get_returned_qty_map_for_row(
source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -640,6 +643,12 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
def update_terms(source_doc, target_doc, source_parent):
target_doc.payment_amount = -source_doc.payment_amount
def item_condition(doc):
if return_against_rejected_qty:
return doc.rejected_qty
return doc.qty
doclist = get_mapped_doc(
doctype,
source_name,
@@ -654,6 +663,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None, return_agai
"doctype": doctype + " Item",
"field_map": {"serial_no": "serial_no", "batch_no": "batch_no", "bom": "bom"},
"postprocess": update_item,
"condition": item_condition,
},
"Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms},
},

View File

@@ -432,6 +432,9 @@ class SellingController(StockController):
if self.doctype not in ("Delivery Note", "Sales Invoice"):
return
allow_at_arms_length_price = frappe.get_cached_value(
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
)
items = self.get("items") + (self.get("packed_items") or [])
for d in items:
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
@@ -478,6 +481,9 @@ class SellingController(StockController):
if d.incoming_rate != incoming_rate:
d.incoming_rate = incoming_rate
else:
if allow_at_arms_length_price:
continue
rate = flt(
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
d.precision("rate"),
@@ -536,7 +542,9 @@ class SellingController(StockController):
def get_sle_for_source_warehouse(self, item_row):
serial_and_batch_bundle = (
item_row.serial_and_batch_bundle if not self.is_internal_transfer() else None
item_row.serial_and_batch_bundle
if not self.is_internal_transfer() or self.docstatus == 1
else None
)
if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return:
if self.docstatus == 1:

View File

@@ -93,6 +93,9 @@ class calculate_taxes_and_totals:
self.doc.base_tax_withholding_net_total = sum_base_net_amount
def validate_item_tax_template(self):
if self.doc.get("is_return") and self.doc.get("return_against"):
return
for item in self._items:
if item.item_code and item.get("item_tax_template"):
item_doc = frappe.get_cached_doc("Item", item.item_code)
@@ -242,7 +245,6 @@ class calculate_taxes_and_totals:
"tax_fraction_for_current_item",
"grand_total_fraction_for_current_item",
]
if tax.charge_type != "Actual" and not (
self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total"
):

View File

@@ -5,7 +5,7 @@
import frappe
from frappe import qb
from frappe.query_builder.functions import Sum
from frappe.tests.utils import FrappeTestCase
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, getdate, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -13,6 +13,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.buying.doctype.purchase_order.test_purchase_order import prepare_data_for_internal_transfer
from erpnext.stock.doctype.item.test_item import create_item
@@ -804,6 +805,41 @@ class TestAccountsController(FrappeTestCase):
self.assertEqual(exc_je_for_si, [])
self.assertEqual(exc_je_for_pe, [])
@change_settings("Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1})
def test_16_internal_transfer_at_arms_length_price(self):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
prepare_data_for_internal_transfer()
company = "_Test Company with perpetual inventory"
target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company)
warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company)
arms_length_price = 40
si = create_sales_invoice(
company=company,
customer="_Test Internal Customer 2",
debit_to="Debtors - TCP1",
target_warehouse=target_warehouse,
warehouse=warehouse,
income_account="Sales - TCP1",
expense_account="Cost of Goods Sold - TCP1",
cost_center="Main - TCP1",
update_stock=True,
do_not_save=True,
do_not_submit=True,
)
si.items[0].rate = arms_length_price
si.save()
# rate should not reset to incoming rate
self.assertEqual(si.items[0].rate, arms_length_price)
frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0)
si.items[0].rate = arms_length_price
si.save()
# rate should reset to incoming rate
self.assertEqual(si.items[0].rate, 100)
def test_20_journal_against_sales_invoice(self):
# Invoice in Foreign Currency
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)

View File

@@ -93,7 +93,26 @@ class Opportunity(TransactionBase, CRMNote):
def onload(self):
ref_doc = frappe.get_doc(self.opportunity_from, self.party_name)
load_address_and_contact(ref_doc)
load_address_and_contact(self)
ref_doc_contact_list = ref_doc.get("__onload").get("contact_list")
opportunity_doc_contact_list = [
contact
for contact in self.get("__onload").get("contact_list")
if contact not in ref_doc_contact_list
]
ref_doc_contact_list.extend(opportunity_doc_contact_list)
ref_doc.set_onload("contact_list", ref_doc_contact_list)
ref_doc_addr_list = ref_doc.get("__onload").get("addr_list")
opportunity_doc_addr_list = [
addr for addr in self.get("__onload").get("addr_list") if addr not in ref_doc_addr_list
]
ref_doc_addr_list.extend(opportunity_doc_addr_list)
ref_doc.set_onload("addr_list", ref_doc_addr_list)
self.set("__onload", ref_doc.get("__onload"))
def after_insert(self):

View File

@@ -8,7 +8,7 @@ from itertools import groupby
import frappe
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe.utils import cint, flt
from frappe.utils import cint, flt, getdate
from erpnext.setup.utils import get_exchange_rate
@@ -21,7 +21,15 @@ class SalesPipelineAnalytics:
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
def validate_filters(self):
if not self.filters.from_date:
frappe.throw(_("From Date is mandatory"))
if not self.filters.to_date:
frappe.throw(_("To Date is mandatory"))
def run(self):
self.validate_filters()
self.get_columns()
self.get_data()
self.get_chart_data()
@@ -185,7 +193,7 @@ class SalesPipelineAnalytics:
count_or_amount = info.get(based_on)
if self.filters.get("pipeline_by") == "Owner":
if value == "Not Assigned" or value == "[]" or value is None:
if value == "Not Assigned" or value == "[]" or value is None or not value:
assigned_to = ["Not Assigned"]
else:
assigned_to = json.loads(value)
@@ -227,10 +235,9 @@ class SalesPipelineAnalytics:
def get_month_list(self):
month_list = []
current_date = date.today()
month_number = date.today().month
current_date = getdate(self.filters.get("from_date"))
for _month in range(month_number, 13):
while current_date < getdate(self.filters.get("to_date")):
month_list.append(current_date.strftime("%B"))
current_date = current_date + relativedelta(months=1)

View File

@@ -1,19 +1,21 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
class TestSalesPipelineAnalytics(unittest.TestCase):
@classmethod
def setUpClass(self):
class TestSalesPipelineAnalytics(FrappeTestCase):
def setUp(self):
frappe.db.delete("Opportunity")
create_company()
create_customer()
create_opportunity()
def test_sales_pipeline_analytics(self):
self.from_date = "2021-01-01"
self.to_date = "2021-12-31"
self.check_for_monthly_and_number()
self.check_for_monthly_and_amount()
self.check_for_quarterly_and_number()
@@ -28,6 +30,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -43,6 +47,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -59,6 +65,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -74,6 +82,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -90,6 +100,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -105,6 +117,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -121,6 +135,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -136,6 +152,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"status": "Open",
"opportunity_type": "Sales",
"company": "Best Test",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)
@@ -153,8 +171,8 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
"opportunity_type": "Sales",
"company": "Best Test",
"opportunity_source": "Cold Calling",
"from_date": "2021-08-01",
"to_date": "2021-08-31",
"from_date": self.from_date,
"to_date": self.to_date,
}
report = execute(filters)

View File

@@ -0,0 +1,10 @@
from csv import DictReader
from io import StringIO
def extract(fileobj, *args, **kwargs):
"""Extract incoterm titles from a CSV file."""
file = StringIO(fileobj.read().decode()) # CSV reader expects a text file
reader = DictReader(file)
for i, row in enumerate(reader):
yield i + 2, "_", row["title"], ["Title of an incoterm"]

View File

@@ -449,6 +449,7 @@ scheduler_events = {
],
"monthly_long": [
"erpnext.accounts.deferred_revenue.process_deferred_accounting",
"erpnext.accounts.utils.auto_create_exchange_rate_revaluation_monthly",
],
}

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

@@ -534,6 +534,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
quotation_to: me.frm.doc.quotation_to,
supplier: me.frm.doc.supplier,
currency: me.frm.doc.currency,
is_internal_supplier: me.frm.doc.is_internal_supplier,
is_internal_customer: me.frm.doc.is_internal_customer,
update_stock: update_stock,
conversion_rate: me.frm.doc.conversion_rate,
price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list,
@@ -826,47 +828,76 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
var me = this;
var set_pricing = function() {
if(me.frm.doc.company && me.frm.fields_dict.currency) {
var company_currency = me.get_company_currency();
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
if (!me.frm.doc.currency) {
me.frm.set_value("currency", company_currency);
}
if (me.frm.doc.currency == company_currency) {
me.frm.set_value("conversion_rate", 1.0);
}
if (me.frm.doc.price_list_currency == company_currency) {
me.frm.set_value('plc_conversion_rate', 1.0);
}
if (company_doc){
if (company_doc.default_letter_head) {
if(me.frm.fields_dict.letter_head) {
me.frm.set_value("letter_head", company_doc.default_letter_head);
}
}
let selling_doctypes_for_tc = ["Sales Invoice", "Quotation", "Sales Order", "Delivery Note"];
if (company_doc.default_selling_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
selling_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_selling_terms);
}
let buying_doctypes_for_tc = ["Request for Quotation", "Supplier Quotation", "Purchase Order",
"Material Request", "Purchase Receipt"];
// Purchase Invoice is excluded as per issue #3345
if (company_doc.default_buying_terms && frappe.meta.has_field(me.frm.doc.doctype, "tc_name") &&
buying_doctypes_for_tc.includes(me.frm.doc.doctype) && !me.frm.doc.tc_name) {
me.frm.set_value("tc_name", company_doc.default_buying_terms);
}
}
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
() => get_party_currency(),
() => me.update_item_tax_map(),
() => me.apply_default_taxes(),
() => me.apply_pricing_rule()
() => me.apply_pricing_rule(),
() => set_terms(),
() => set_letter_head(),
]);
}
}
var get_party_currency = function() {
var party_type = frappe.meta.has_field(me.frm.doc.doctype, "customer") ? "Customer" : "Supplier";
var party_name = me.frm.doc[party_type.toLowerCase()];
if (party_name) {
frappe.call({
method: "frappe.client.get_value",
args: {
doctype: party_type,
filters: { name: party_name },
fieldname: "default_currency",
},
callback: function (r) {
if (r.message) {
set_currency(r.message.default_currency);
}
}
})
} else {
set_currency();
}
}
var set_currency = function(party_default_currency) {
var company_currency = me.get_company_currency();
var currency = party_default_currency || company_currency;
if (me.frm.doc.currency != currency) {
me.frm.set_value("currency", currency);
}
if (me.frm.doc.currency == company_currency) {
me.frm.set_value("conversion_rate", 1.0);
}
if (me.frm.doc.price_list_currency == company_currency) {
me.frm.set_value('plc_conversion_rate', 1.0);
}
me.frm.script_manager.trigger("currency");
}
var set_terms = function() {
if (frappe.meta.has_field(me.frm.doc.doctype, "tc_name") && !me.frm.doc.tc_name) {
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
var selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"];
var company_terms_fieldname = selling_doctypes.includes(me.frm.doc.doctype) ? "default_selling_terms" : "default_buying_terms";
if (company_doc && company_doc[company_terms_fieldname]) {
me.frm.set_value("tc_name", company_doc[company_terms_fieldname]);
}
}
}
var set_letter_head = function() {
if(me.frm.fields_dict.letter_head) {
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
if (company_doc && company_doc.default_letter_head) {
me.frm.set_value("letter_head", company_doc.default_letter_head);
}
}
}
var set_party_account = function(set_pricing) {
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
if(me.frm.doc.doctype=="Sales Invoice") {
@@ -1631,7 +1662,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
"conversion_factor": me.frm.doc.conversion_factor,
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
"coupon_code": me.frm.doc.coupon_code
"coupon_code": me.frm.doc.coupon_code,
"is_internal_supplier": me.frm.doc.is_internal_supplier,
"is_internal_customer": me.frm.doc.is_internal_customer,
};
}
@@ -1791,6 +1824,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
apply_price_list(item, reset_plc_conversion) {
// We need to reset plc_conversion_rate sometimes because the call to
// `erpnext.stock.get_item_details.apply_price_list` is sensitive to its value
if (this.frm.doc.doctype === "Material Request") {
return;
}
if (!reset_plc_conversion) {
this.frm.set_value("plc_conversion_rate", "");
}
@@ -1806,7 +1845,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
me.in_apply_price_list = true;
return this.frm.call({
method: "erpnext.stock.get_item_details.apply_price_list",
args: { args: args },
args: { args: args, doc: me.frm.doc },
callback: function(r) {
if (!r.exc) {
frappe.run_serially([
@@ -1953,6 +1992,8 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
let item_rates = {};
let item_tax_templates = {};
if (me.frm.doc.is_return && me.frm.doc.return_against) return;
$.each(this.frm.doc.items || [], function(i, item) {
if (item.item_code) {
// Use combination of name and item code in case same item is added multiple times

View File

@@ -12,7 +12,6 @@ from frappe import _
from frappe.model.document import Document
from frappe.utils import flt, get_datetime_str, today
from frappe.utils.data import format_datetime
from frappe.utils.file_manager import save_file
import erpnext
@@ -101,16 +100,15 @@ class ImportSupplierInvoice(Document):
self.file_count += 1
if pi_name:
self.purchase_invoices_count += 1
save_file(
file_name,
encoded_content,
"Purchase Invoice",
pi_name,
folder=None,
decode=False,
is_private=0,
df=None,
)
file_doc = frappe.new_doc("File")
file_doc.file_name = file_name
file_doc.attached_to_doctype = "Purchase Invoice"
file_doc.attached_to_name = pi_name
file_doc.content = encoded_content
file_doc.decode = False
file_doc.is_private = False
file_doc.insert(ignore_permissions=True)
def prepare_items_for_invoice(self, file_content, invoices_args):
qty = 1

View File

@@ -193,6 +193,9 @@ erpnext.SalesFunnel = class SalesFunnel {
this.options.width = ($(this.elements.funnel_wrapper).width() * 2.0) / 3.0;
this.options.height = (Math.sqrt(3) * this.options.width) / 2.0;
const min_height = (this.options.height * 0.1) / this.options.data.length;
const height = this.options.height * 0.9;
// calculate total weightage
// as height decreases, area decreases by the square of the reduction
// hence, compensating by squaring the index value
@@ -202,7 +205,7 @@ erpnext.SalesFunnel = class SalesFunnel {
// calculate height for each data
$.each(this.options.data, function (i, d) {
d.height = (me.options.height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage;
d.height = (height * d.value * Math.pow(i + 1, 2)) / me.options.total_weightage + min_height;
});
this.elements.canvas = $("<canvas></canvas>")

View File

@@ -738,7 +738,7 @@
"fieldname": "auto_err_frequency",
"fieldtype": "Select",
"label": "Frequency",
"options": "Daily\nWeekly"
"options": "Daily\nWeekly\nMonthly"
},
{
"default": "0",
@@ -792,7 +792,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2024-06-21 17:46:25.567565",
"modified": "2024-07-24 18:17:56.413971",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@@ -31,7 +31,7 @@ class Company(NestedSet):
accumulated_depreciation_account: DF.Link | None
allow_account_creation_against_child_company: DF.Check
asset_received_but_not_billed: DF.Link | None
auto_err_frequency: DF.Literal["Daily", "Weekly"]
auto_err_frequency: DF.Literal["Daily", "Weekly", "Monthly"]
auto_exchange_rate_revaluation: DF.Check
book_advance_payments_in_separate_party_account: DF.Check
capital_work_in_progress_account: DF.Link | None

View File

@@ -35,12 +35,11 @@ frappe.ui.form.on("Supplier Group", {
});
},
refresh: function (frm) {
frm.set_intro(frm.doc.__islocal ? "" : __("There is nothing to edit."));
frm.trigger("set_root_readonly");
},
set_root_readonly: function (frm) {
if (!frm.doc.parent_supplier_group && !frm.doc.__islocal) {
frm.trigger("set_read_only");
if (!frm.doc.parent_supplier_group && !frm.is_new()) {
frm.set_read_only();
frm.set_intro(__("This is a root supplier group and cannot be edited."));
} else {
frm.set_intro(null);

View File

@@ -163,7 +163,7 @@ def make_taxes_and_charges_template(company_name, doctype, template):
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.flags.ignore_mandatory = True
doc.insert(ignore_permissions=True)
doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
return doc
@@ -196,7 +196,7 @@ def make_item_tax_template(company_name, template):
# Ingone validations to make doctypes faster
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.insert(ignore_permissions=True)
doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
return doc
@@ -233,7 +233,7 @@ def get_or_create_account(company_name, account):
doc = frappe.get_doc(account)
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.insert(ignore_permissions=True, ignore_mandatory=True)
doc.insert(ignore_permissions=True, ignore_mandatory=True, ignore_if_duplicate=True)
return doc

View File

@@ -6,7 +6,7 @@ import json
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, today
from frappe.utils import add_days, cstr, flt, getdate, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.utils import get_balance_on
@@ -2032,6 +2032,40 @@ class TestDeliveryNote(FrappeTestCase):
self.assertRaises(frappe.ValidationError, dn5.submit)
def test_warranty_expiry_date_for_serial_item(self):
item_code = make_item(
"Test Warranty Expiry Date Item",
properties={
"has_serial_no": 1,
"serial_no_series": "TWE.#####",
"is_stock_item": 1,
"warranty_period": 100,
},
).name
se = make_stock_entry(
item_code=item_code,
target="_Test Warehouse - _TC",
qty=2,
basic_rate=50,
posting_date=nowdate(),
)
serial_nos = get_serial_nos_from_bundle(se.items[0].serial_and_batch_bundle)
create_delivery_note(
item_code=item_code,
qty=2,
rate=300,
use_serial_batch_fields=0,
serial_no=serial_nos,
)
for row in serial_nos:
sn = frappe.get_doc("Serial No", row)
self.assertEqual(getdate(sn.warranty_expiry_date), getdate(add_days(nowdate(), 100)))
self.assertEqual(sn.status, "Delivered")
self.assertEqual(sn.warranty_period, 100)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -1925,9 +1925,19 @@ class TestPurchaseReceipt(FrappeTestCase):
rate=100,
rejected_qty=2,
rejected_warehouse=rejected_warehouse,
do_not_save=1,
)
pr.append(
"items",
{"item_code": item_code, "qty": 2, "rate": 100, "warehouse": warehouse, "rejected_qty": 0},
)
pr.save()
pr.submit()
self.assertEqual(len(pr.items), 2)
pr_return = make_purchase_return_against_rejected_warehouse(pr.name)
self.assertEqual(len(pr_return.items), 1)
self.assertEqual(pr_return.items[0].warehouse, rejected_warehouse)
self.assertEqual(pr_return.items[0].qty, 2.0 * -1)
self.assertEqual(pr_return.items[0].rejected_qty, 0.0)
@@ -3506,6 +3516,122 @@ class TestPurchaseReceipt(FrappeTestCase):
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
def test_internal_transfer_for_batch_items_with_cancel_use_serial_batch_fields(self):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0)
prepare_data_for_internal_transfer()
customer = "_Test Internal Customer 2"
company = "_Test Company with perpetual inventory"
batch_item_doc = make_item(
"_Test Batch Item For Stock Transfer Cancel Case 11",
{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF11-BT-CANBIFST-.####"},
)
serial_item_doc = make_item(
"_Test Serial No Item For Stock Transfer Cancel Case 11",
{"has_serial_no": 1, "serial_no_series": "USBF11-BT-CANBIFST-.####"},
)
inward_entry = make_purchase_receipt(
item_code=batch_item_doc.name,
qty=10,
rate=150,
warehouse="Stores - TCP1",
company="_Test Company with perpetual inventory",
use_serial_batch_fields=1,
do_not_submit=1,
)
inward_entry.append(
"items",
{
"item_code": serial_item_doc.name,
"qty": 15,
"rate": 250,
"item_name": serial_item_doc.item_name,
"conversion_factor": 1.0,
"uom": serial_item_doc.stock_uom,
"stock_uom": serial_item_doc.stock_uom,
"warehouse": "Stores - TCP1",
"use_serial_batch_fields": 1,
},
)
inward_entry.submit()
inward_entry.reload()
for row in inward_entry.items:
self.assertTrue(row.serial_and_batch_bundle)
inter_transfer_dn = create_delivery_note(
item_code=inward_entry.items[0].item_code,
company=company,
customer=customer,
cost_center="Main - TCP1",
expense_account="Cost of Goods Sold - TCP1",
qty=10,
rate=500,
warehouse="Stores - TCP1",
target_warehouse="Work In Progress - TCP1",
batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
use_serial_batch_fields=1,
do_not_submit=1,
)
inter_transfer_dn.append(
"items",
{
"item_code": serial_item_doc.name,
"qty": 15,
"rate": 350,
"item_name": serial_item_doc.item_name,
"conversion_factor": 1.0,
"uom": serial_item_doc.stock_uom,
"stock_uom": serial_item_doc.stock_uom,
"warehouse": "Stores - TCP1",
"target_warehouse": "Work In Progress - TCP1",
"serial_no": "\n".join(
get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
),
"use_serial_batch_fields": 1,
},
)
inter_transfer_dn.submit()
inter_transfer_dn.reload()
for row in inter_transfer_dn.items:
if row.item_code == batch_item_doc.name:
self.assertEqual(row.rate, 150.0)
else:
self.assertEqual(row.rate, 250.0)
self.assertTrue(row.serial_and_batch_bundle)
inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
for row in inter_transfer_pr.items:
row.from_warehouse = "Work In Progress - TCP1"
row.warehouse = "Stores - TCP1"
inter_transfer_pr.submit()
for row in inter_transfer_pr.items:
if row.item_code == batch_item_doc.name:
self.assertEqual(row.rate, 150.0)
else:
self.assertEqual(row.rate, 250.0)
self.assertTrue(row.serial_and_batch_bundle)
inter_transfer_pr.cancel()
inter_transfer_dn.cancel()
frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
def prepare_data_for_internal_transfer():
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier

View File

@@ -67,6 +67,7 @@
"base_net_rate",
"base_net_amount",
"valuation_rate",
"sales_incoming_rate",
"item_tax_amount",
"rm_supp_cost",
"landed_cost_voucher_amount",
@@ -1124,12 +1125,22 @@
"fieldtype": "Check",
"label": "Return Qty from Rejected Warehouse",
"read_only": 1
},
{
"description": "Valuation rate for the item as per Sales Invoice (Only for Internal Transfers)",
"fieldname": "sales_incoming_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Sales Incoming Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-05-28 09:48:24.448815",
"modified": "2024-07-19 12:14:21.521466",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt Item",

View File

@@ -88,6 +88,7 @@ class PurchaseReceiptItem(Document):
return_qty_from_rejected_warehouse: DF.Check
returned_qty: DF.Float
rm_supp_cost: DF.Currency
sales_incoming_rate: DF.Currency
sales_order: DF.Link | None
sales_order_item: DF.Data | None
sample_quantity: DF.Int

View File

@@ -92,8 +92,10 @@ class SerialandBatchBundle(Document):
if self.type_of_transaction == "Maintenance":
return
self.validate_serial_nos_duplicate()
self.check_future_entries_exists()
if not self.flags.ignore_validate_serial_batch or frappe.flags.in_test:
self.validate_serial_nos_duplicate()
self.check_future_entries_exists()
self.set_is_outward()
self.calculate_total_qty()
self.set_warehouse()
@@ -340,6 +342,9 @@ class SerialandBatchBundle(Document):
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
for d in self.entries:
if (d.incoming_rate == rate) and d.qty and d.stock_value_difference:
continue
d.incoming_rate = flt(rate, precision)
if d.qty:
d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
@@ -393,32 +398,6 @@ class SerialandBatchBundle(Document):
self.calculate_qty_and_amount(save=True)
self.validate_quantity(row, qty_field=qty_field)
self.set_warranty_expiry_date()
def set_warranty_expiry_date(self):
if self.type_of_transaction != "Outward":
return
if not (self.docstatus == 1 and self.voucher_type == "Delivery Note" and self.has_serial_no):
return
warranty_period = frappe.get_cached_value("Item", self.item_code, "warranty_period")
if not warranty_period:
return
warranty_expiry_date = add_days(self.posting_date, cint(warranty_period))
serial_nos = self.get_serial_nos()
if not serial_nos:
return
sn_table = frappe.qb.DocType("Serial No")
(
frappe.qb.update(sn_table)
.set(sn_table.warranty_expiry_date, warranty_expiry_date)
.where(sn_table.name.isin(serial_nos))
).run()
def validate_voucher_no(self):
if not (self.voucher_type and self.voucher_no):
@@ -867,6 +846,9 @@ class SerialandBatchBundle(Document):
self.validate_serial_nos_inventory()
def set_purchase_document_no(self):
if self.flags.ignore_validate_serial_batch:
return
if not self.has_serial_no:
return
@@ -2188,6 +2170,8 @@ def get_stock_ledgers_for_serial_nos(kwargs):
def get_stock_ledgers_batches(kwargs):
from erpnext.stock.utils import get_combine_datetime
stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
batch_table = frappe.qb.DocType("Batch")
@@ -2214,6 +2198,19 @@ def get_stock_ledgers_batches(kwargs):
else:
query = query.where(stock_ledger_entry[field] == kwargs.get(field))
if kwargs.get("posting_date"):
if kwargs.get("posting_time") is None:
kwargs.posting_time = nowtime()
timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime(
kwargs.posting_date, kwargs.posting_time
)
query = query.where(timestamp_condition)
if kwargs.get("ignore_voucher_nos"):
query = query.where(stock_ledger_entry.voucher_no.notin(kwargs.get("ignore_voucher_nos")))
if kwargs.based_on == "LIFO":
query = query.orderby(batch_table.creation, order=frappe.qb.desc)
elif kwargs.based_on == "Expiry":

View File

@@ -83,7 +83,8 @@
"total_amount",
"amended_from",
"credit_note",
"is_return"
"is_return",
"tab_connections"
],
"fields": [
{
@@ -683,6 +684,12 @@
"label": "Asset Repair",
"options": "Asset Repair",
"read_only": 1
},
{
"fieldname": "tab_connections",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"icon": "fa fa-file-text",

View File

@@ -0,0 +1,26 @@
from frappe import _
# Todo: non_standard_fieldnames is to be decided
def get_data():
return {
"fieldname": "stock_entry",
"non_standard_fieldnames": {
# "DocType Name": "Reference field name",
},
"internal_links": {
"Purchase Order": ["items", "purchase_order"],
"Subcontracting Order": ["items", "subcontracting_order"],
"Subcontracting Receipt": ["items", "subcontracting_receipt"],
},
"transactions": [
{
"label": _("Reference"),
"items": [
"Purchase Order",
"Subcontracting Order",
"Subcontracting Receipt",
],
},
],
}

View File

@@ -16,7 +16,7 @@ from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle impor
get_available_serial_nos,
)
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.utils import get_stock_balance
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
class OpeningEntryAccountError(frappe.ValidationError):
@@ -952,14 +952,21 @@ class StockReconciliation(StockController):
precesion = row.precision("current_qty")
if flt(current_qty, precesion) != flt(row.current_qty, precesion):
if not row.serial_no:
val_rate = get_valuation_rate(
row.item_code,
row.warehouse,
self.doctype,
self.name,
company=self.company,
batch_no=row.batch_no,
serial_and_batch_bundle=row.current_serial_and_batch_bundle,
val_rate = get_incoming_rate(
frappe._dict(
{
"item_code": row.item_code,
"warehouse": row.warehouse,
"qty": current_qty * -1,
"serial_and_batch_bundle": row.current_serial_and_batch_bundle,
"batch_no": row.batch_no,
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
}
)
)
row.current_valuation_rate = val_rate

View File

@@ -4,6 +4,7 @@
# ERPNext - web based ERP (http://erpnext.com)
# For license information, please see license.txt
import json
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
@@ -1182,6 +1183,98 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
self.assertAlmostEqual(row.incoming_rate, 1000.00)
self.assertEqual(row.serial_no, serial_nos[row.idx - 1])
def test_stock_reco_with_legacy_batch(self):
from erpnext.stock.doctype.batch.batch import get_batch_qty
batch_item_code = self.make_item(
"Test Batch Item Legacy Batch 1",
{
"is_stock_item": 1,
"has_batch_no": 1,
"create_new_batch": 1,
"batch_number_series": "BH1-NRALL-S-.###",
},
).name
warehouse = "_Test Warehouse - _TC"
frappe.flags.ignore_serial_batch_bundle_validation = True
frappe.flags.use_serial_and_batch_fields = True
batch_id = "BH1-NRALL-S-0001"
if not frappe.db.exists("Batch", batch_id):
batch_doc = frappe.get_doc(
{
"doctype": "Batch",
"batch_id": batch_id,
"item": batch_item_code,
"use_batchwise_valuation": 0,
}
).insert(ignore_permissions=True)
self.assertTrue(batch_doc.use_batchwise_valuation)
stock_queue = []
qty_after_transaction = 0
balance_value = 0
i = 0
for qty, valuation in {10: 100, 20: 200}.items():
i += 1
stock_queue.append([qty, valuation])
qty_after_transaction += qty
balance_value += qty_after_transaction * valuation
doc = frappe.get_doc(
{
"doctype": "Stock Ledger Entry",
"posting_date": add_days(nowdate(), -2 * i),
"posting_time": nowtime(),
"batch_no": batch_id,
"incoming_rate": valuation,
"qty_after_transaction": qty_after_transaction,
"stock_value_difference": valuation * qty,
"balance_value": balance_value,
"valuation_rate": balance_value / qty_after_transaction,
"actual_qty": qty,
"item_code": batch_item_code,
"warehouse": "_Test Warehouse - _TC",
"stock_queue": json.dumps(stock_queue),
}
)
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
doc.flags.ignore_links = True
doc.flags.ignore_validate = True
doc.submit()
doc.reload()
frappe.flags.ignore_serial_batch_bundle_validation = False
frappe.flags.use_serial_and_batch_fields = False
batch_doc = frappe.get_doc("Batch", batch_id)
qty = get_batch_qty(batch_id, warehouse, batch_item_code)
self.assertEqual(qty, 30)
sr = create_stock_reconciliation(
item_code=batch_item_code,
posting_date=add_days(nowdate(), -3),
posting_time=nowtime(),
warehouse=warehouse,
qty=100,
rate=1000,
reconcile_all_serial_batch=0,
batch_no=batch_id,
use_serial_batch_fields=1,
)
self.assertEqual(sr.items[0].current_qty, 20)
self.assertEqual(sr.items[0].qty, 100)
qty = get_batch_qty(batch_id, warehouse, batch_item_code)
self.assertEqual(qty, 110)
def create_batch_item_with_batch(item_name, batch_id):
batch_item_doc = create_item(item_name, is_stock_item=1)

View File

@@ -32,6 +32,7 @@
"allow_negative_stock",
"show_barcode_field",
"clean_description_html",
"allow_internal_transfer_at_arms_length_price",
"quality_inspection_settings_section",
"action_if_quality_inspection_is_not_submitted",
"column_break_23",
@@ -434,12 +435,18 @@
},
{
"default": "1",
"depends_on": "use_serial_batch_fields",
"description": "If enabled, do not update serial / batch values in the stock transactions on creation of auto Serial \n / Batch Bundle. ",
"fieldname": "do_not_update_serial_batch_on_creation_of_auto_bundle",
"fieldtype": "Check",
"label": "Do Not Update Serial / Batch on Creation of Auto Bundle"
},
{
"default": "0",
"description": "If enabled, the item rate won't adjust to the valuation rate during internal transfers, but accounting will still use the valuation rate.",
"fieldname": "allow_internal_transfer_at_arms_length_price",
"fieldtype": "Check",
"label": "Allow Internal Transfers at Arm's Length Price"
},
{
"default": "0",
"depends_on": "eval:doc.valuation_method === \"Moving Average\"",
@@ -460,7 +467,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-07-15 17:18:23.872161",
"modified": "2024-07-29 14:55:19.093508",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",

View File

@@ -27,6 +27,7 @@ class StockSettings(Document):
action_if_quality_inspection_is_rejected: DF.Literal["Stop", "Warn"]
allow_from_dn: DF.Check
allow_from_pr: DF.Check
allow_internal_transfer_at_arms_length_price: DF.Check
allow_negative_stock: DF.Check
allow_partial_reservation: DF.Check
allow_to_edit_stock_uom_qty_for_purchase: DF.Check

View File

@@ -820,6 +820,9 @@ def get_price_list_rate(args, item_doc, out=None):
if price_list_rate is None or frappe.db.get_single_value(
"Stock Settings", "update_existing_price_list_rate"
):
if args.get("is_internal_supplier") or args.get("is_internal_customer"):
return out
if args.price_list and args.rate:
insert_item_price(args)
@@ -831,7 +834,11 @@ def get_price_list_rate(args, item_doc, out=None):
if frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
return out
if not out.price_list_rate and args.transaction_type == "buying":
if (
not args.get("is_internal_supplier")
and not out.price_list_rate
and args.transaction_type == "buying"
):
from erpnext.stock.doctype.item.item import get_last_purchase_details
out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))
@@ -1203,7 +1210,7 @@ def get_batch_qty(batch_no, warehouse, item_code):
@frappe.whitelist()
def apply_price_list(args, as_doc=False):
def apply_price_list(args, as_doc=False, doc=None):
"""Apply pricelist on a document-like dict object and return as
{'parent': dict, 'children': list}
@@ -1242,7 +1249,7 @@ def apply_price_list(args, as_doc=False):
for item in item_list:
args_copy = frappe._dict(args.copy())
args_copy.update(item)
item_details = apply_price_list_on_item(args_copy)
item_details = apply_price_list_on_item(args_copy, doc=doc)
children.append(item_details)
if as_doc:
@@ -1260,10 +1267,10 @@ def apply_price_list(args, as_doc=False):
return {"parent": parent, "children": children}
def apply_price_list_on_item(args):
def apply_price_list_on_item(args, doc=None):
item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args))
item_details.update(get_pricing_rule_for_item(args, doc=doc))
return item_details

View File

@@ -54,6 +54,12 @@ def get_columns(filters):
"width": 150,
"options": "Batch",
},
{
"label": _("Expiry Date"),
"fieldname": "expiry_date",
"fieldtype": "Date",
"width": 120,
},
{"label": _("Balance Qty"), "fieldname": "balance_qty", "fieldtype": "Float", "width": 150},
]
)
@@ -97,6 +103,7 @@ def get_batchwise_data_from_stock_ledger(filters):
table.item_code,
table.batch_no,
table.warehouse,
batch.expiry_date,
Sum(table.actual_qty).as_("balance_qty"),
)
.where(table.is_cancelled == 0)
@@ -127,6 +134,7 @@ def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters):
table.item_code,
ch_table.batch_no,
table.warehouse,
batch.expiry_date,
Sum(ch_table.qty).as_("balance_qty"),
)
.where((table.is_cancelled == 0) & (table.docstatus == 1))
@@ -152,10 +160,14 @@ def get_query_based_on_filters(query, batch, table, filters):
if filters.batch_no:
query = query.where(batch.name == filters.batch_no)
if not filters.include_expired_batches:
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
if filters.to_date == today():
query = query.where(batch.batch_qty > 0)
if filters.to_date == today():
if not filters.include_expired_batches:
query = query.where((batch.expiry_date >= today()) | (batch.expiry_date.isnull()))
query = query.where(batch.batch_qty > 0)
else:
query = query.where(table.posting_date <= filters.to_date)
if filters.warehouse:
lft, rgt = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"])

View File

@@ -3,6 +3,14 @@
frappe.query_reports["Product Bundle Balance"] = {
filters: [
{
fieldname: "company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1,
},
{
fieldname: "date",
label: __("Date"),

View File

@@ -224,6 +224,9 @@ def get_stock_ledger_entries(filters, items):
.where((sle2.name.isnull()) & (sle.docstatus < 2) & (sle.item_code.isin(items)))
)
if filters.get("company"):
query = query.where(sle.company == filters.get("company"))
if date := filters.get("date"):
query = query.where(sle.posting_date <= date)
else:
@@ -237,7 +240,7 @@ def get_stock_ledger_entries(filters, items):
if warehouse_details:
wh = frappe.qb.DocType("Warehouse")
query = query.where(
ExistsCriterion(
sle.warehouse.isin(
frappe.qb.from_(wh)
.select(wh.name)
.where((wh.lft >= warehouse_details.lft) & (wh.rgt <= warehouse_details.rgt))

View File

@@ -157,6 +157,7 @@ def get_data(filters):
{
"serial_no": bundle_data.get("serial_no"),
"valuation_rate": bundle_data.get("valuation_rate"),
"qty": args.qty,
}
)

View File

@@ -465,10 +465,13 @@ class FIFOSlots:
)
)
for field in ["item_code", "warehouse"]:
for field in ["item_code"]:
if self.filters.get(field):
query = query.where(bundle[field] == self.filters.get(field))
if self.filters.get("warehouse"):
query = self.__get_warehouse_conditions(bundle, query)
bundle_wise_serial_nos = frappe._dict({})
for bundle_name, serial_no in query.run():
bundle_wise_serial_nos.setdefault(bundle_name, []).append(serial_no)

View File

@@ -114,18 +114,23 @@ def validate_filters(filters):
def get_warehouse_list(filters):
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
if not filters.get("warehouse"):
return frappe.get_all(
"Warehouse",
filters={"company": filters.get("company"), "is_group": 0},
fields=["name"],
order_by="name",
)
wh = frappe.qb.DocType("Warehouse")
query = frappe.qb.from_(wh).select(wh.name).where(wh.is_group == 0)
warehouse = frappe.qb.DocType("Warehouse")
lft, rgt = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"])
user_permitted_warehouse = get_permitted_documents("Warehouse")
if user_permitted_warehouse:
query = query.where(wh.name.isin(set(user_permitted_warehouse)))
elif filters.get("warehouse"):
query = query.where(wh.name == filters.get("warehouse"))
return query.run(as_dict=True)
return (
frappe.qb.from_(warehouse)
.select("name")
.where((warehouse.lft >= lft) & (warehouse.rgt <= rgt))
.run(as_dict=True)
)
def add_warehouse_column(columns, warehouse_list):

View File

@@ -4,7 +4,7 @@ import frappe
from frappe import _, bold
from frappe.model.naming import make_autoname
from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, now, nowtime, today
from pypika import Order
from erpnext.stock.deprecated_serial_batch import (
@@ -110,6 +110,7 @@ class SerialBatchBundle:
"type_of_transaction": "Inward" if self.sle.actual_qty > 0 else "Outward",
"company": self.company,
"is_rejected": self.is_rejected_entry(),
"make_bundle_from_sle": 1,
}
).make_serial_and_batch_bundle()
@@ -160,12 +161,13 @@ class SerialBatchBundle:
if msg:
error_msg = (
f"Serial and Batch Bundle not set for item {self.item_code} in warehouse {self.warehouse}."
f"Serial and Batch Bundle not set for item {self.item_code} in warehouse {self.warehouse}"
+ msg
)
frappe.throw(_(error_msg))
def set_serial_and_batch_bundle(self, sn_doc):
self.sle.auto_created_serial_and_batch_bundle = 1
self.sle.db_set({"serial_and_batch_bundle": sn_doc.name, "auto_created_serial_and_batch_bundle": 1})
if sn_doc.is_rejected:
@@ -324,6 +326,9 @@ class SerialBatchBundle:
def set_warehouse_and_status_in_serial_nos(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_parsed_serial_nos
if self.sle.auto_created_serial_and_batch_bundle and self.sle.actual_qty > 0:
return
serial_nos = get_serial_nos(self.sle.serial_and_batch_bundle)
if not self.sle.serial_and_batch_bundle and self.sle.serial_no:
serial_nos = get_parsed_serial_nos(self.sle.serial_no)
@@ -338,7 +343,8 @@ class SerialBatchBundle:
status = "Delivered"
sn_table = frappe.qb.DocType("Serial No")
(
query = (
frappe.qb.update(sn_table)
.set(sn_table.warehouse, warehouse)
.set(
@@ -351,7 +357,19 @@ class SerialBatchBundle:
)
.set(sn_table.company, self.sle.company)
.where(sn_table.name.isin(serial_nos))
).run()
)
if status == "Delivered":
warranty_period = frappe.get_cached_value("Item", self.sle.item_code, "warranty_period")
if warranty_period:
warranty_expiry_date = add_days(self.sle.posting_date, cint(warranty_period))
query = query.set(sn_table.warranty_expiry_date, warranty_expiry_date)
query = query.set(sn_table.warranty_period, warranty_period)
else:
query = query.set(sn_table.warranty_expiry_date, None)
query = query.set(sn_table.warranty_period, 0)
query.run()
def set_batch_no_in_serial_nos(self):
entries = frappe.get_all(
@@ -915,6 +933,10 @@ class SerialBatchCreation:
if doc.voucher_no and frappe.get_cached_value(doc.voucher_type, doc.voucher_no, "docstatus") == 2:
doc.voucher_no = ""
doc.flags.ignore_validate_serial_batch = False
if self.get("make_bundle_from_sle") and self.type_of_transaction == "Inward":
doc.flags.ignore_validate_serial_batch = True
doc.save()
self.validate_qty(doc)
@@ -1107,6 +1129,10 @@ class SerialBatchCreation:
msg = f"Please set Serial No Series in the item {self.item_code} or create Serial and Batch Bundle manually."
frappe.throw(_(msg))
voucher_no = ""
if self.get("voucher_no"):
voucher_no = self.get("voucher_no")
for _i in range(abs(cint(self.actual_qty))):
serial_no = make_autoname(self.serial_no_series, "Serial No")
sr_nos.append(serial_no)
@@ -1124,6 +1150,7 @@ class SerialBatchCreation:
self.item_name,
self.description,
"Active",
voucher_no,
self.batch_no,
)
)
@@ -1142,6 +1169,7 @@ class SerialBatchCreation:
"item_name",
"description",
"status",
"purchase_document_no",
"batch_no",
]

View File

@@ -275,7 +275,9 @@ def repost_future_sle(
)
affected_transactions.update(obj.affected_transactions)
distinct_item_warehouses[(args[i].get("item_code"), args[i].get("warehouse"))].reposting_status = True
key = (args[i].get("item_code"), args[i].get("warehouse"))
if distinct_item_warehouses.get(key):
distinct_item_warehouses[key].reposting_status = True
if obj.new_items_found:
for _item_wh, data in distinct_item_warehouses.items():
@@ -1588,9 +1590,11 @@ def get_stock_ledger_entries(
if not previous_sle.get("posting_date"):
previous_sle["posting_datetime"] = "1900-01-01 00:00:00"
else:
previous_sle["posting_datetime"] = get_combine_datetime(
previous_sle["posting_date"], previous_sle["posting_time"]
)
posting_time = previous_sle.get("posting_time")
if not posting_time:
posting_time = "00:00:00"
previous_sle["posting_datetime"] = get_combine_datetime(previous_sle["posting_date"], posting_time)
if operator in (">", "<=") and previous_sle.get("name"):
conditions += " and name!=%(name)s"

View File

@@ -23,18 +23,6 @@
"cost_center",
"dimension_col_break",
"project",
"address_and_contact_section",
"supplier_address",
"address_display",
"contact_person",
"contact_display",
"contact_mobile",
"contact_email",
"column_break_19",
"shipping_address",
"shipping_address_display",
"billing_address",
"billing_address_display",
"section_break_24",
"column_break_25",
"set_warehouse",
@@ -48,10 +36,23 @@
"raw_materials_supplied_section",
"set_reserve_warehouse",
"supplied_items",
"additional_costs_section",
"tab_address_and_contact",
"supplier_address",
"address_display",
"contact_person",
"contact_display",
"contact_mobile",
"contact_email",
"column_break_19",
"shipping_address",
"shipping_address_display",
"billing_address",
"billing_address_display",
"tab_additional_costs",
"distribute_additional_costs_based_on",
"additional_costs",
"total_additional_costs",
"tab_other_info",
"order_status_section",
"status",
"column_break_39",
@@ -59,7 +60,8 @@
"printing_settings_section",
"select_print_heading",
"column_break_43",
"letter_head"
"letter_head",
"tab_connections"
],
"fields": [
{
@@ -95,7 +97,7 @@
"fieldtype": "Link",
"in_global_search": 1,
"in_standard_filter": 1,
"label": "Supplier",
"label": "Job Worker",
"options": "Supplier",
"print_hide": 1,
"reqd": 1,
@@ -107,7 +109,7 @@
"fieldname": "supplier_name",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
"label": "Job Worker Name",
"read_only": 1,
"reqd": 1
},
@@ -115,7 +117,7 @@
"depends_on": "supplier",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
"label": "Job Worker Warehouse",
"options": "Warehouse",
"reqd": 1
},
@@ -166,9 +168,8 @@
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "address_and_contact_section",
"fieldtype": "Section Break",
"fieldname": "tab_address_and_contact",
"fieldtype": "Tab Break",
"label": "Address and Contact"
},
{
@@ -176,14 +177,14 @@
"fetch_if_empty": 1,
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Supplier Address",
"label": "Job Worker Address",
"options": "Address",
"print_hide": 1
},
{
"fieldname": "address_display",
"fieldtype": "Text Editor",
"label": "Supplier Address Details",
"label": "Job Worker Address Details",
"read_only": 1
},
{
@@ -191,7 +192,7 @@
"fetch_if_empty": 1,
"fieldname": "contact_person",
"fieldtype": "Link",
"label": "Supplier Contact",
"label": "Job Worker Contact",
"options": "Contact",
"print_hide": 1
},
@@ -337,11 +338,9 @@
"read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "total_additional_costs",
"depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)",
"fieldname": "additional_costs_section",
"fieldtype": "Section Break",
"fieldname": "tab_additional_costs",
"fieldtype": "Tab Break",
"label": "Additional Costs"
},
{
@@ -449,6 +448,17 @@
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "tab_other_info",
"fieldtype": "Tab Break",
"label": "Other Info"
},
{
"fieldname": "tab_connections",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"icon": "fa fa-file-text",

View File

@@ -23,18 +23,6 @@
"cost_center",
"dimension_col_break",
"project",
"section_addresses",
"supplier_address",
"contact_person",
"address_display",
"contact_display",
"contact_mobile",
"contact_email",
"col_break_address",
"shipping_address",
"shipping_address_display",
"billing_address",
"billing_address_display",
"sec_warehouse",
"set_warehouse",
"rejected_warehouse",
@@ -53,23 +41,36 @@
"get_current_stock",
"raw_material_details",
"supplied_items",
"additional_costs_section",
"distribute_additional_costs_based_on",
"additional_costs",
"total_additional_costs",
"section_break_46",
"in_words",
"bill_no",
"bill_date",
"tab_addresses",
"supplier_address",
"contact_person",
"address_display",
"contact_display",
"contact_mobile",
"contact_email",
"col_break_address",
"shipping_address",
"shipping_address_display",
"billing_address",
"billing_address_display",
"tab_additional_costs",
"distribute_additional_costs_based_on",
"additional_costs",
"total_additional_costs",
"tab_other_info",
"more_info",
"status",
"column_break_39",
"per_returned",
"section_break_47",
"amended_from",
"range",
"column_break4",
"represents_company",
"order_status_section",
"status",
"column_break_39",
"per_returned",
"subscription_detail",
"auto_repeat",
"printing_settings",
@@ -84,7 +85,8 @@
"transporter_name",
"column_break5",
"lr_no",
"lr_date"
"lr_date",
"tab_connections"
],
"fields": [
{
@@ -112,7 +114,7 @@
"fieldname": "supplier",
"fieldtype": "Link",
"in_global_search": 1,
"label": "Supplier",
"label": "Job Worker",
"options": "Supplier",
"print_hide": 1,
"print_width": "150px",
@@ -127,7 +129,7 @@
"fieldname": "supplier_name",
"fieldtype": "Data",
"in_global_search": 1,
"label": "Supplier Name",
"label": "Job Worker Name",
"read_only": 1
},
{
@@ -174,15 +176,14 @@
"width": "150px"
},
{
"collapsible": 1,
"fieldname": "section_addresses",
"fieldtype": "Section Break",
"fieldname": "tab_addresses",
"fieldtype": "Tab Break",
"label": "Address and Contact"
},
{
"fieldname": "supplier_address",
"fieldtype": "Link",
"label": "Select Supplier Address",
"label": "Select Job Worker Address",
"options": "Address",
"print_hide": 1
},
@@ -269,7 +270,7 @@
{
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
"label": "Job Worker Warehouse",
"no_copy": 1,
"options": "Warehouse",
"print_hide": 1,
@@ -414,6 +415,7 @@
"width": "50%"
},
{
"collapsible": 1,
"fieldname": "subscription_detail",
"fieldtype": "Section Break",
"label": "Auto Repeat Detail"
@@ -571,10 +573,6 @@
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "section_break_47",
"fieldtype": "Section Break"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
@@ -598,11 +596,9 @@
"options": "Project"
},
{
"collapsible": 1,
"collapsible_depends_on": "total_additional_costs",
"depends_on": "eval:(doc.docstatus == 0 || doc.total_additional_costs)",
"fieldname": "additional_costs_section",
"fieldtype": "Section Break",
"fieldname": "tab_additional_costs",
"fieldtype": "Tab Break",
"label": "Additional Costs"
},
{
@@ -643,7 +639,7 @@
{
"fieldname": "supplier_delivery_note",
"fieldtype": "Data",
"label": "Supplier Delivery Note"
"label": "Job Worker Delivery Note"
},
{
"fieldname": "raw_materials_consumed_section",
@@ -658,6 +654,23 @@
{
"fieldname": "column_break_uinr",
"fieldtype": "Column Break"
},
{
"fieldname": "tab_other_info",
"fieldtype": "Tab Break",
"label": "Other Info"
},
{
"collapsible": 1,
"fieldname": "order_status_section",
"fieldtype": "Section Break",
"label": "Order Status"
},
{
"fieldname": "tab_connections",
"fieldtype": "Tab Break",
"label": "Connections",
"show_dashboard": 1
}
],
"in_create": 1,