Compare commits

...

128 Commits

Author SHA1 Message Date
Abdeali Chharchhoda
2db2c8bce1 refactor: Move PR link filters to client side 2024-11-27 15:52:45 +05:30
Abdeali Chharchhoda
cabe7ea028 Merge branch 'develop' into refactor-payment-request 2024-11-27 15:36:35 +05:30
ruthra kumar
1b45ba81a5 Merge pull request #44377 from ljain112/fix-payment-ref
fix:  correct placeholder index in message
2024-11-27 14:55:00 +05:30
ljain112
d61cb9a4bf fix: correct placeholder index in message 2024-11-27 13:44:33 +05:30
Abdeali Chharchhoda
2dd2083463 Merge branch 'develop' into refactor-payment-request 2024-11-27 12:07:16 +05:30
ruthra kumar
d03dc1b855 Merge pull request #44343 from aerele/process-statement-of-account-email
fix: show cc on the process statement of accounts email
2024-11-27 10:22:17 +05:30
rohitwaghchaure
123e3ef263 feat: provision to disable item attribute (#44358) 2024-11-27 09:46:36 +05:30
Raffael Meyer
e094473c65 feat(Dunning): separate tab "Address & Contact" (#44363) 2024-11-26 21:10:34 +01:00
Raffael Meyer
3f92a57d63 fix: unify company address query in sales transactions (#44361)
* fix: unify company address query in sales transactions

* refactor: get the correct field label
2024-11-26 20:03:06 +01:00
rohitwaghchaure
0fd50b5048 fix: added validation for quality inspection (#44351) 2024-11-26 19:48:42 +05:30
rohitwaghchaure
2ab7ec5437 fix: billed qty and received amount in PO analysis report (#44349) 2024-11-26 18:07:43 +05:30
ruthra kumar
6c5f57b22b Merge pull request #44347 from frappe/l10n_develop
fix: sync translations from crowdin
2024-11-26 17:52:25 +05:30
ruthra kumar
f8655cb26a Merge pull request #44345 from ruthra-kumar/fix_broken_export_in_p_l_statements
fix: broken export on financial statements
2024-11-26 17:48:29 +05:30
ruthra kumar
ea6e022fad test: export functionality on P&L statement 2024-11-26 16:36:18 +05:30
Frappe PR Bot
ce27d37fc3 fix: Persian translations 2024-11-26 16:27:41 +05:30
Frappe PR Bot
60a2d74d74 fix: Turkish translations 2024-11-26 16:27:36 +05:30
ruthra kumar
1da61a43e7 chore: remove debugging statement 2024-11-26 15:48:54 +05:30
ruthra kumar
c66d57d490 fix: broken export on statements 2024-11-26 15:33:04 +05:30
venkat102
2dd5699f6d fix: show cc on the email 2024-11-26 14:58:57 +05:30
Abdeali Chharchhoda
37ceb09955 revert: remove default Payment Request indicator color 2024-11-26 12:10:05 +05:30
ruthra kumar
4983dac27b Merge pull request #44319 from vishakhdesai/hd-ticket-21161
fix: use field precision in Sales and Purchase Order
2024-11-26 11:46:50 +05:30
ruthra kumar
1baed9f46e Merge pull request #44304 from ernestoruiz89/patch-2
chore: Add translations to QI validations in Update stock_controller.py
2024-11-26 11:36:05 +05:30
ruthra kumar
2fcfd41eb6 Merge pull request #44257 from aerele/number-card-dynamic-filter
fix: add company dynamic filters in number cards
2024-11-26 10:53:48 +05:30
ruthra kumar
48d9e9c362 Merge pull request #44320 from Abdeali099/fix-pos-qty
fix: Increase quantity by `1 UOM` when adding an item from the selector in `POS`
2024-11-26 10:29:41 +05:30
rohitwaghchaure
e56a98d5ba fix: Required By date issue (#44326) 2024-11-25 20:28:56 +05:30
rohitwaghchaure
bf28b2c771 fix: item none not found (#44324) 2024-11-25 20:12:12 +05:30
rohitwaghchaure
52f0d67bae Merge pull request #44260 from rohitwaghchaure/fixed-github-40144
feat: available qty at company in sales transactions
2024-11-25 19:58:54 +05:30
Frappe PR Bot
e5bfdf8cfb fix: sync translations from crowdin (#44322) 2024-11-25 11:19:33 +00:00
ruthra kumar
ca0e20b2cf Merge pull request #44321 from ruthra-kumar/clear_unallocated_payments_on_pos
refactor: clear unallocated payments from POS Invoice
2024-11-25 16:40:35 +05:30
Rohit Waghchaure
d8b9aef14f feat: available qty at company in sales transactions 2024-11-25 16:27:46 +05:30
Abdeali Chharchhoda
112b4c705b revert: use + flt(value) instead of direct increment 2024-11-25 16:18:22 +05:30
ruthra kumar
eebd058891 refactor: clear unallocated payments from POS Invoice 2024-11-25 15:56:41 +05:30
Abdeali Chharchhoda
e37a039660 Merge branch 'develop' into fix-pos-qty 2024-11-25 15:10:06 +05:30
Abdeali Chharchhoda
84dcbe6639 fix: Show available stock qty in stock_uom instead of uom 2024-11-25 14:48:41 +05:30
Abdeali Chharchhoda
bbab850135 fix: Increase quantity by 1 UOM when adding an item from the selector in POS 2024-11-25 14:13:26 +05:30
vishakhdesai
1a1e2c7e01 fix: use field precision instead of hardcoded precision in so and po 2024-11-25 12:50:35 +05:30
ruthra kumar
efc22d5615 Merge pull request #44274 from aerele/rfq-price-list-currency
fix: set price_list_currency only if it exists
2024-11-25 12:38:31 +05:30
ruthra kumar
d8af140770 Merge pull request #44163 from aerele/show_child_data_for_company
feat: Show Aggregate Value from Subsidiary Companies
2024-11-25 12:08:47 +05:30
ruthra kumar
8e72e35a21 Merge pull request #44290 from frappe/l10n_develop
fix: sync translations from crowdin
2024-11-25 10:23:20 +05:30
ruthra kumar
f4ad935f15 Merge pull request #44297 from Abdeali099/report-column-label-translation
refactor: added translate function for some columns of report
2024-11-25 10:08:34 +05:30
Frappe PR Bot
7e06e05584 chore: update POT file (#44309) 2024-11-24 16:49:43 +01:00
Frappe PR Bot
026c5b76ff fix: Persian translations 2024-11-24 16:25:10 +05:30
Frappe PR Bot
ae0ed1f56f fix: Swedish translations 2024-11-24 16:25:05 +05:30
David Arnold
a9485b91b0 feat: add more fine-grained deprecation warning control (#44307)
* feat: add more fine-grained deprecation warning control

see: https://github.com/frappe/frappe/wiki/Deprecations

* chore: ensure frappe deprecation dumpster is reused where possible; and loaded to parse PYTHONWARNINGS (!)
2024-11-24 00:37:42 +01:00
David Arnold
f1bd6e492a chore: use local deprecation (#44306) 2024-11-23 22:25:12 +00:00
David Arnold
2e04eeb661 fix: net_amount calculation (#44231)
fix: tax_item_rate calculation
2024-11-23 22:10:33 +00:00
Ernesto Ruiz
6754f15487 chore: Add translations to QI validations in Update stock_controller.py
chore: Add translations to QI validations in Update stock_controller.py
2024-11-23 09:07:18 -06:00
Frappe PR Bot
4289804681 fix: Persian translations 2024-11-23 16:29:52 +05:30
Abdeali Chharchhoda
91955e27c3 fix: Dashboard for Payment Request 2024-11-23 15:10:23 +05:30
Abdeali Chharchhoda
e1c4d6e1e6 refactor: Used object to get payment request status indicator 2024-11-23 14:47:31 +05:30
David Arnold
e9ea9ee57b fix: update calling signature (#44299) 2024-11-23 00:29:00 +00:00
Abdeali Chharchhoda
e545c913b5 refactor: added translate function for some columns of report 2024-11-22 19:50:52 +05:30
Frappe PR Bot
f244ad78df fix: Swedish translations 2024-11-22 16:30:25 +05:30
ruthra kumar
15c607306a Merge pull request #44246 from aerele/validate_for_closed_fiscal_year
fix: Get submitted documents in validate_for_closed_fiscal_year
2024-11-22 15:51:37 +05:30
ruthra kumar
367f56179b Merge pull request #44266 from aerele/pricing-rule-round-free-qty
fix: make free qty round on large transaction qty
2024-11-22 15:50:10 +05:30
ruthra kumar
2a31807407 Merge pull request #44277 from aerele/pos-item-group-filter
fix: filter with item group only if it is mentioned in pos profile
2024-11-22 15:44:44 +05:30
ruthra kumar
c3e5d6336c Merge pull request #44264 from frappe/l10n_develop
fix: sync translations from crowdin
2024-11-22 15:43:54 +05:30
Smit Vora
30f9b2b806 Merge pull request #44263 from vorasmit/fix-toggle-amount 2024-11-22 12:08:58 +05:30
venkat102
09641073e3 fix: filter with item group only if it is mentioned in pos profile 2024-11-22 11:53:07 +05:30
Sugesh393
f0b9cb4019 fix: set price_list_currency only if it exists 2024-11-22 11:36:57 +05:30
ruthra kumar
160bb10db8 Merge pull request #44194 from aerele/tds-tax-on-excess-amount
fix: include current invoice amount when tax_on_excess_amount is checked
2024-11-22 11:06:25 +05:30
Smit Vora
a10e175bc9 test: test case for toggling debit and credit if negative 2024-11-21 19:44:02 +05:30
venkat102
013a6fc6ec test: add unit test to validate free qty round on large transaction qty 2024-11-21 18:37:14 +05:30
Smit Vora
8e759c32c4 fix: toggle debit credit amounts for transaction currency too; minor refactor 2024-11-21 18:30:37 +05:30
venkat102
f9b8165385 fix: make free qty round on large transaction qty 2024-11-21 18:27:15 +05:30
Frappe PR Bot
fbdc40114b fix: Persian translations 2024-11-21 16:34:12 +05:30
Frappe PR Bot
4eb32023f5 fix: Swedish translations 2024-11-21 16:34:07 +05:30
rohitwaghchaure
57293aa18a fix: no permission to read Doctype (#44256) 2024-11-21 13:13:21 +05:30
Sugesh393
29762c4826 fix: remove irrelavent filters 2024-11-21 12:09:24 +05:30
Sugesh393
4e7725de66 fix: add company dynamic filters in number cards 2024-11-21 11:05:49 +05:30
Raffael Meyer
4b53075f84 refactor: use frappe._dict() instead of {} (#44255)
* refactor: use `frappe._dict()` instead of `{}`

A downstream function (`_get_item_tax_template`) expects this to be a frappe._dict and fails otherwise.

* refactor: use `ItemDetails()`
2024-11-21 00:36:20 +01:00
Raffael Meyer
8d8070e8eb fix: use of ctx (#44253)
* fix: rename args to ctx in caller

* fix: use `.get("items")` to avoid conflict with `.items()` method
2024-11-20 19:03:26 +01:00
ruthra kumar
4d96a20205 Merge pull request #44249 from frappe/l10n_develop
fix: sync translations from crowdin
2024-11-20 17:06:57 +05:30
Frappe PR Bot
f5cf417f53 fix: Persian translations 2024-11-20 16:34:49 +05:30
Frappe PR Bot
93fef93a1d fix: Turkish translations 2024-11-20 16:34:41 +05:30
Frappe PR Bot
77cba04b13 fix: Swedish translations 2024-11-20 16:34:36 +05:30
vimalraj27
c607e5f940 fix: Get submitted documents in validate_for_closed_fiscal_year 2024-11-20 13:27:21 +05:30
ruthra kumar
1737269358 Merge pull request #44197 from ljain112/fi-rev-dr-credit-reco
fix: payment reco for jv with negative dr or cr amount
2024-11-20 12:46:57 +05:30
Nihantra C. Patel
9ea8bd7a3c Merge pull request #44240 from Nihantra-Patel/fix_pos_warehouse
fix: non group pos warehouse
2024-11-20 12:10:42 +05:30
Nihantra C. Patel
d526be0394 fix: non group pos warehouse 2024-11-20 12:00:41 +05:30
ruthra kumar
3b9e226d93 Merge pull request #44220 from Abdeali099/payment-request-search-option-query
refactor: Update `Payment Request` search query in PE's reference
2024-11-20 11:50:48 +05:30
rohitwaghchaure
cc571aca8f fix: added Stock UOM field for RM in work order (#44185)
fix: added UOM field for RM in work order
2024-11-20 11:20:47 +05:30
ruthra kumar
8bd7ad5bf0 Merge pull request #44203 from ljain112/fix-so-calender
fix: disable conversion to user tz for sales order calender
2024-11-20 10:46:31 +05:30
ruthra kumar
a971417f25 Merge pull request #44207 from ljain112/validate-sales_person
fix: validate sales team to ensure all sales person are enabled
2024-11-20 10:40:34 +05:30
David Arnold
9673bf85ec refactor: pass typed arguments to get_item_details methods (#44230)
* refactor: pass proper types to get_item_details methods

* chore: excempt previous commit from git blame
2024-11-20 02:31:03 +01:00
David Arnold
4ec23b5525 refactor: make stem utility for normalizing ctx args from dict json-str or Document (#44228) 2024-11-20 00:40:16 +01:00
David Arnold
af2a622afb fix: partial fix for #44291 (#44229)
This is only a partial fix and improves the situaton.

A proper fix will need to have items _always_ track their tax-related
account head, namely for item-wise tax templates as well as default
document taxes that are applied "On Net Total", in order to achive
proper assignment of the net_amount.

Currently there is no way to always identify the orignating account head per item.
2024-11-19 23:15:22 +00:00
David Arnold
daabe847b0 refactor: cleanup args structure akin to some typing (#44226)
* refactor: cleanup args structure akin to some typing

this clarification is a human precondition to being able to fix https://github.com/frappe/erpnext/issues/44219

* chore: excempt previous commit from git blame

* fix: adapt signature
2024-11-19 22:27:57 +00:00
Abdeali Chharchhoda
4ab3499a17 refactor: Update Payment Request search query in PE's reference 2024-11-19 18:00:35 +05:30
ruthra kumar
a9bb6b9970 Merge pull request #44190 from cogk/fix-coa-setup-wizard
fix(setup): Fix typo in COA setup
2024-11-19 17:44:01 +05:30
David Arnold
8b05f954c2 Merge pull request #44215 from blaggacao/fix/item-tax-detail-ro
fix: field read only property on taxes net_amount
2024-11-19 12:58:28 +01:00
Corentin Forler
a245cc6b07 fix(setup): Fix typo in COA setup 2024-11-19 17:18:25 +05:30
ruthra kumar
cb4b4e212c Merge pull request #44213 from frappe/l10n_develop
fix: sync translations from crowdin
2024-11-19 17:11:39 +05:30
Nikolas Beckel
9d31bf7647 fix: check if pricing rule matches with coupon code (#44104)
* fix: check if pricing rule matches with coupon code

* fix: correct linting error
2024-11-19 17:10:55 +05:30
ruthra kumar
64833fccb4 Merge pull request #44214 from ruthra-kumar/wrong_eval_expression
fix: incorrect eval expression
2024-11-19 17:05:51 +05:30
David
b2f8b610fc fix: field read only property on taxes net_amount 2024-11-19 12:29:56 +01:00
ruthra kumar
76ea94d906 fix: incorrect eval expression 2024-11-19 16:36:34 +05:30
Frappe PR Bot
8a12104073 fix: Swedish translations 2024-11-19 16:17:20 +05:30
rohitwaghchaure
b21fb8f8b6 fix: update project cost from timesheet (#44211) 2024-11-19 16:12:22 +05:30
Khaled Bin Amir
a2199db5e5 feat: Add 'ABBR' variable to support company abbreviation in naming series (#43260)
* Add ABBR variable in naming settings

* Add ABBR variable in naming settings

* Update utils.py

* chore: resolve linter

---------

Co-authored-by: ruthra kumar <ruthra@erpnext.com>
2024-11-19 15:15:27 +05:30
hyaray
b3c558f0c0 chore: typo in Manufaturing Settings 2024-11-19 14:54:05 +05:30
ruthra kumar
7ff6901883 Merge pull request #44204 from ruthra-kumar/update_owners
chore: update codeowners
2024-11-19 14:46:59 +05:30
ljain112
548dbb33eb fix: validate sales team to ensure all sales person are enabled 2024-11-19 14:32:43 +05:30
ruthra kumar
ed54b9b8c7 Merge pull request #44145 from ajiragroup/ajiragroup-patch-1
fix: updated label "Is short year" to "Is Short/Long year" for both short and long fiscal years
2024-11-19 14:25:37 +05:30
ruthra kumar
8d4f2eeb07 Merge pull request #44189 from frappe/l10n_develop
fix: sync translations from crowdin
2024-11-19 14:14:53 +05:30
ruthra kumar
3273b808a0 chore: update codeowners 2024-11-19 14:12:36 +05:30
ljain112
cdf098c193 fix: disable conversion to user tz for sales order calender 2024-11-19 12:38:34 +05:30
ljain112
6f9ea6422d fix: added test cases 2024-11-19 12:17:35 +05:30
ruthra kumar
a202f564b1 Merge pull request #44188 from ismxilxrif/develop
chore: update oldest_items.json, change owner back to administrator
2024-11-19 12:02:40 +05:30
ajiragroup
1d6b9b405f refactor: update label and description on short year checkbox
Is short/long year.
2024-11-19 12:01:43 +05:30
Ismail Arif
7ceb24fb4c chore: update oldest_items.json, change owner back to administrator
Signed-off-by: Ismail Arif <38789073+ismxilxrif@users.noreply.github.com>
2024-11-19 09:56:35 +08:00
ljain112
fee79b9445 fix: payment reco for jv with negative dr or cr amount 2024-11-18 20:08:11 +05:30
venkat102
4820273595 test: add unit test for tax on excess amount 2024-11-18 15:48:39 +05:30
venkat102
b74f2896cd fix: include current invoice amount when tax_on_excess_amount is checked 2024-11-18 15:42:01 +05:30
Frappe PR Bot
a712ff603b fix: Esperanto translations 2024-11-18 15:38:31 +05:30
Frappe PR Bot
5122489790 fix: German translations 2024-11-18 15:38:27 +05:30
Frappe PR Bot
3468f27e8b fix: Bosnian translations 2024-11-18 15:38:22 +05:30
Frappe PR Bot
31a2fe4bd0 fix: Persian translations 2024-11-18 15:38:18 +05:30
Frappe PR Bot
94703698c0 fix: Chinese Simplified translations 2024-11-18 15:38:12 +05:30
Frappe PR Bot
92d76b5a63 fix: Turkish translations 2024-11-18 15:38:09 +05:30
Frappe PR Bot
d8c20f4dd8 fix: Swedish translations 2024-11-18 15:38:04 +05:30
Frappe PR Bot
26dd896741 fix: Russian translations 2024-11-18 15:37:59 +05:30
Frappe PR Bot
a1c88a6302 fix: Polish translations 2024-11-18 15:37:54 +05:30
Frappe PR Bot
1fbb5203c6 fix: Hungarian translations 2024-11-18 15:37:50 +05:30
Frappe PR Bot
41c7ce7cfb fix: Arabic translations 2024-11-18 15:37:45 +05:30
Frappe PR Bot
7f0e881894 fix: Spanish translations 2024-11-18 15:37:40 +05:30
Frappe PR Bot
475611697f fix: French translations 2024-11-18 15:37:35 +05:30
l0gesh29
c23af6af41 feat: Show Aggregate Value from Subsidiary Companies 2024-11-15 12:54:04 +05:30
138 changed files with 14733 additions and 11531 deletions

View File

@@ -38,3 +38,7 @@ ec74a5e56617bbd76ac402451468fd4668af543d
# ruff formatting
a308792ee7fda18a681e9181f4fd00b36385bc23
# noisy typing refactoring of get_item_details
7b7211ac79c248a79ba8a999ff34e734d874c0ae
d827ed21adc7b36047e247cbb0dc6388d048a7f9

View File

@@ -3,11 +3,11 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @khushi8112 @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007
erpnext/accounts/ @ruthra-kumar
erpnext/assets/ @khushi8112
erpnext/regional @ruthra-kumar
erpnext/selling @ruthra-kumar
erpnext/support/ @ruthra-kumar
pos*
erpnext/buying/ @rohitwaghchaure
@@ -17,8 +17,8 @@ erpnext/quality_management/ @rohitwaghchaure
erpnext/stock/ @rohitwaghchaure
erpnext/subcontracting @rohitwaghchaure
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure
erpnext/patches/ @ruthra-kumar
.github/ @deepeshgarg007
.github/ @ruthra-kumar
pyproject.toml @akhilnarang

View File

@@ -1,7 +1,9 @@
import functools
import inspect
from typing import TypeVar
import frappe
from frappe.model.document import Document
from frappe.utils.user import is_website_user
__version__ = "16.0.0-dev"
@@ -160,3 +162,34 @@ def check_app_permission():
return False
return True
T = TypeVar("T")
def normalize_ctx_input(T: type) -> callable:
"""
Normalizes the first argument (ctx) of the decorated function by:
- Converting Document objects to dictionaries
- Parsing JSON strings
- Casting the result to the specified type T
"""
def decorator(func: callable):
# conserve annotations for frappe.utils.typing_validations
@functools.wraps(func, assigned=(a for a in functools.WRAPPER_ASSIGNMENTS if a != "__annotations__"))
def wrapper(ctx: T | Document | dict | str, *args, **kwargs):
if isinstance(ctx, Document):
ctx = T(**ctx.as_dict())
elif isinstance(ctx, dict):
ctx = T(**ctx)
else:
ctx = T(**frappe.parse_json(ctx))
return func(ctx, *args, **kwargs)
# set annotations from function
wrapper.__annotations__.update({k: v for k, v in func.__annotations__.items() if k != "ctx"})
return wrapper
return decorator

View File

@@ -13,6 +13,7 @@
"col_break_1",
"description",
"included_in_paid_amount",
"set_by_item_tax_template",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -183,7 +184,8 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Net Amount",
"options": "currency"
"options": "currency",
"read_only": 1
},
{
"fieldname": "base_net_amount",
@@ -193,12 +195,22 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"default": "0",
"fieldname": "set_by_item_tax_template",
"fieldtype": "Check",
"hidden": 1,
"label": "Set by Item Tax Template",
"print_hide": 1,
"read_only": 1,
"report_hide": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-09-24 06:51:07.417348",
"modified": "2024-11-22 19:16:22.346267",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Taxes and Charges",
@@ -207,4 +219,4 @@
"sort_field": "creation",
"sort_order": "ASC",
"states": []
}
}

View File

@@ -34,6 +34,7 @@ class AdvanceTaxesandCharges(Document):
parenttype: DF.Data
rate: DF.Float
row_id: DF.Data | None
set_by_item_tax_template: DF.Check
tax_amount: DF.Currency
total: DF.Currency
# end: auto-generated types

View File

@@ -62,7 +62,7 @@
"unique": 1
},
{
"depends_on": "eval !doc.from_external_ecomm_platform",
"depends_on": "eval: !doc.from_external_ecomm_platform",
"fieldname": "pricing_rule",
"fieldtype": "Link",
"label": "Pricing Rule",
@@ -125,7 +125,7 @@
}
],
"links": [],
"modified": "2024-06-28 06:17:01.833399",
"modified": "2024-11-19 16:35:11.836441",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Coupon Code",
@@ -186,4 +186,4 @@
"states": [],
"title_field": "coupon_name",
"track_changes": 1
}
}

View File

@@ -19,16 +19,6 @@
"currency",
"column_break_11",
"conversion_rate",
"address_and_contact_section",
"customer_address",
"address_display",
"contact_person",
"contact_display",
"column_break_16",
"company_address",
"company_address_display",
"contact_mobile",
"contact_email",
"section_break_6",
"dunning_type",
"column_break_8",
@@ -56,7 +46,21 @@
"income_account",
"column_break_48",
"cost_center",
"amended_from"
"amended_from",
"address_and_contact_tab",
"address_and_contact_section",
"customer_address",
"address_display",
"column_break_vodj",
"contact_person",
"contact_display",
"contact_mobile",
"contact_email",
"section_break_xban",
"column_break_16",
"company_address",
"company_address_display",
"column_break_lqmf"
],
"fields": [
{
@@ -178,10 +182,8 @@
"label": "Rate of Interest (%) Yearly"
},
{
"collapsible": 1,
"fieldname": "address_and_contact_section",
"fieldtype": "Section Break",
"label": "Address and Contact"
"fieldtype": "Section Break"
},
{
"fieldname": "address_display",
@@ -377,11 +379,28 @@
{
"fieldname": "column_break_48",
"fieldtype": "Column Break"
},
{
"fieldname": "address_and_contact_tab",
"fieldtype": "Tab Break",
"label": "Address & Contact"
},
{
"fieldname": "column_break_vodj",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_xban",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_lqmf",
"fieldtype": "Column Break"
}
],
"is_submittable": 1,
"links": [],
"modified": "2024-03-27 13:08:19.176146",
"modified": "2024-11-26 13:46:07.760867",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dunning",

View File

@@ -72,10 +72,10 @@
},
{
"default": "0",
"description": "Less than 12 months.",
"description": "More/Less than 12 months.",
"fieldname": "is_short_year",
"fieldtype": "Check",
"label": "Is Short Year",
"label": "Is Short/Long Year",
"set_only_once": 1
}
],

View File

@@ -523,6 +523,55 @@ class TestJournalEntry(IntegrationTestCase):
self.assertEqual(row.debit_in_account_currency, 100)
self.assertEqual(row.credit_in_account_currency, 100)
def test_toggle_debit_credit_if_negative(self):
from erpnext.accounts.general_ledger import process_gl_map
# Create JV with defaut cost center - _Test Cost Center
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
jv = frappe.new_doc("Journal Entry")
jv.posting_date = nowdate()
jv.company = "_Test Company"
jv.user_remark = "test"
jv.extend(
"accounts",
[
{
"account": "_Test Cash - _TC",
"debit": 100 * -1,
"debit_in_account_currency": 100 * -1,
"exchange_rate": 1,
},
{
"account": "_Test Bank - _TC",
"credit": 100 * -1,
"credit_in_account_currency": 100 * -1,
"exchange_rate": 1,
},
],
)
jv.flags.ignore_validate = True
jv.save()
self.assertEqual(len(jv.accounts), 2)
gl_map = jv.build_gl_map()
for row in gl_map:
if row.account == "_Test Cash - _TC":
self.assertEqual(row.debit, 100 * -1)
self.assertEqual(row.debit_in_account_currency, 100 * -1)
self.assertEqual(row.debit_in_transaction_currency, 100 * -1)
gl_map = process_gl_map(gl_map, False)
for row in gl_map:
if row.account == "_Test Cash - _TC":
self.assertEqual(row.credit, 100)
self.assertEqual(row.credit_in_account_currency, 100)
self.assertEqual(row.credit_in_transaction_currency, 100)
def test_transaction_exchange_rate_on_journals(self):
jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable USD - _TC", 100, save=False)
jv.accounts[0].update({"debit_in_account_currency": 8500, "exchange_rate": 1})

View File

@@ -185,6 +185,10 @@ frappe.ui.form.on("Payment Entry", {
filters: {
reference_doctype: row.reference_doctype,
reference_name: row.reference_name,
company: doc.company,
status: ["!=", "Paid"],
outstanding_amount: [">", 0], // for compatibility with old data
docstatus: 1,
},
};
});

View File

@@ -1848,7 +1848,7 @@ class PaymentEntry(AccountsController):
if paid_amount > total_negative_outstanding:
if total_negative_outstanding == 0:
frappe.msgprint(
_("Cannot {0} from {2} without any negative outstanding invoice").format(
_("Cannot {0} from {1} without any negative outstanding invoice").format(
self.payment_type,
self.party_type,
)

View File

@@ -211,12 +211,14 @@ class PaymentReconciliation(Document):
if self.get("cost_center"):
conditions.append(jea.cost_center == self.cost_center)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
conditions.append(jea[dr_or_cr].gt(0))
account_type = erpnext.get_party_account_type(self.party_type)
if account_type == "Receivable":
dr_or_cr = jea.credit_in_account_currency - jea.debit_in_account_currency
elif account_type == "Payable":
dr_or_cr = jea.debit_in_account_currency - jea.credit_in_account_currency
conditions.append(dr_or_cr.gt(0))
if self.bank_cash_account:
conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%"))
@@ -231,7 +233,7 @@ class PaymentReconciliation(Document):
je.posting_date,
je.remark.as_("remarks"),
jea.name.as_("reference_row"),
jea[dr_or_cr].as_("amount"),
dr_or_cr.as_("amount"),
jea.is_advance,
jea.exchange_rate,
jea.account_currency.as_("currency"),

View File

@@ -641,6 +641,42 @@ class TestPaymentReconciliation(IntegrationTestCase):
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_negative_debit_or_credit_journal_against_invoice(self):
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
# credit debtors account to record a payment
je = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date)
je.accounts[1].party_type = "Customer"
je.accounts[1].party = self.customer
je.accounts[1].credit_in_account_currency = 0
je.accounts[1].debit_in_account_currency = -1 * amount
je.save()
je.submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
# Difference amount should not be calculated for base currency accounts
for row in pr.allocation:
self.assertEqual(flt(row.get("difference_amount")), 0.0)
pr.reconcile()
# assert outstanding
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(si.outstanding_amount, 0)
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_journal_against_journal(self):
transaction_date = nowdate()
sales = "Sales - _PR"
@@ -963,6 +999,100 @@ class TestPaymentReconciliation(IntegrationTestCase):
frappe.db.get_value("Journal Entry", jea_parent.parent, "voucher_type"), "Exchange Gain Or Loss"
)
def test_difference_amount_via_negative_debit_or_credit_journal_entry(self):
# Make Sale Invoice
si = self.create_sales_invoice(
qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
)
si.customer = self.customer4
si.currency = "EUR"
si.conversion_rate = 85
si.debit_to = self.debtors_eur
si.save().submit()
# Make payment using Journal Entry
je1 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 100, nowdate())
je1.multi_currency = 1
je1.accounts[0].exchange_rate = 1
je1.accounts[0].credit_in_account_currency = -8000
je1.accounts[0].credit = -8000
je1.accounts[0].debit_in_account_currency = 0
je1.accounts[0].debit = 0
je1.accounts[1].party_type = "Customer"
je1.accounts[1].party = self.customer4
je1.accounts[1].exchange_rate = 80
je1.accounts[1].credit_in_account_currency = 100
je1.accounts[1].credit = 8000
je1.accounts[1].debit_in_account_currency = 0
je1.accounts[1].debit = 0
je1.save()
je1.submit()
je2 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 200, nowdate())
je2.multi_currency = 1
je2.accounts[0].exchange_rate = 1
je2.accounts[0].credit_in_account_currency = -16000
je2.accounts[0].credit = -16000
je2.accounts[0].debit_in_account_currency = 0
je2.accounts[0].debit = 0
je2.accounts[1].party_type = "Customer"
je2.accounts[1].party = self.customer4
je2.accounts[1].exchange_rate = 80
je2.accounts[1].credit_in_account_currency = 200
je1.accounts[1].credit = 16000
je1.accounts[1].debit_in_account_currency = 0
je1.accounts[1].debit = 0
je2.save()
je2.submit()
pr = self.create_payment_reconciliation()
pr.party = self.customer4
pr.receivable_payable_account = self.debtors_eur
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 2)
# Test exact payment allocation
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[0].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
self.assertEqual(pr.allocation[0].allocated_amount, 100)
self.assertEqual(pr.allocation[0].difference_amount, -500)
# Test partial payment allocation (with excess payment entry)
pr.set("allocation", [])
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.invoices]
payments = [pr.payments[1].as_dict()]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].difference_account = "Exchange Gain/Loss - _PR"
self.assertEqual(pr.allocation[0].allocated_amount, 100)
self.assertEqual(pr.allocation[0].difference_amount, -500)
# Check if difference journal entry gets generated for difference amount after reconciliation
pr.reconcile()
total_credit_amount = frappe.db.get_all(
"Journal Entry Account",
{"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name},
"sum(credit) as amount",
group_by="reference_name",
)[0].amount
# total credit includes the exchange gain/loss amount
self.assertEqual(flt(total_credit_amount, 2), 8500)
jea_parent = frappe.db.get_all(
"Journal Entry Account",
filters={"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name, "credit": 500},
fields=["parent"],
)[0]
self.assertEqual(
frappe.db.get_value("Journal Entry", jea_parent.parent, "voucher_type"), "Exchange Gain Or Loss"
)
def test_difference_amount_via_payment_entry(self):
# Make Sale Invoice
si = self.create_sales_invoice(

View File

@@ -945,21 +945,17 @@ def validate_payment(doc, method=None):
@frappe.whitelist()
def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
# permission checks in `get_list()`
reference_doctype = filters.get("reference_doctype")
reference_name = filters.get("reference_doctype")
filters = frappe._dict(filters)
if not reference_doctype or not reference_name:
if not filters.reference_doctype or not filters.reference_name:
return []
if txt:
filters.name = ["like", f"%{txt}%"]
open_payment_requests = frappe.get_list(
"Payment Request",
filters={
"reference_doctype": filters["reference_doctype"],
"reference_name": filters["reference_name"],
"status": ["!=", "Paid"],
"outstanding_amount": ["!=", 0], # for compatibility with old data
"docstatus": 1,
},
filters=filters,
fields=["name", "grand_total", "outstanding_amount"],
order_by="transaction_date ASC,creation ASC",
)

View File

@@ -0,0 +1,14 @@
from frappe import _
def get_data():
return {
"fieldname": "payment_request",
"internal_links": {
"Payment Entry": ["references", "payment_request"],
"Payment Order": ["references", "payment_order"],
},
"transactions": [
{"label": _("Payment"), "items": ["Payment Entry", "Payment Order"]},
],
}

View File

@@ -1,21 +1,18 @@
const INDICATORS = {
"Partially Paid": "orange",
Cancelled: "red",
Draft: "gray",
Failed: "red",
Initiated: "green",
Paid: "blue",
Requested: "green",
};
frappe.listview_settings["Payment Request"] = {
add_fields: ["status"],
get_indicator: function (doc) {
if (doc.status == "Draft") {
return [__("Draft"), "gray", "status,=,Draft"];
}
if (doc.status == "Requested") {
return [__("Requested"), "green", "status,=,Requested"];
} else if (doc.status == "Initiated") {
return [__("Initiated"), "green", "status,=,Initiated"];
} else if (doc.status == "Partially Paid") {
return [__("Partially Paid"), "orange", "status,=,Partially Paid"];
} else if (doc.status == "Paid") {
return [__("Paid"), "blue", "status,=,Paid"];
} else if (doc.status == "Failed") {
return [__("Failed"), "red", "status,=,Failed"];
} else if (doc.status == "Cancelled") {
return [__("Cancelled"), "red", "status,=,Cancelled"];
}
if (!doc.status || !INDICATORS[doc.status]) return;
return [__(doc.status), INDICATORS[doc.status], `status,=,${doc.status}`];
},
};

View File

@@ -218,11 +218,25 @@ class TestPOSClosingEntry(IntegrationTestCase):
opening_entry = create_opening_entry(pos_profile, test_user.name)
pos_inv = create_pos_invoice(
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
item_code=item_code,
qty=5,
rate=300,
use_serial_batch_fields=1,
batch_no=batch_no,
do_not_submit=True,
)
pos_inv.payments[0].amount = pos_inv.grand_total
pos_inv.submit()
pos_inv2 = create_pos_invoice(
item_code=item_code, qty=5, rate=300, use_serial_batch_fields=1, batch_no=batch_no
item_code=item_code,
qty=5,
rate=300,
use_serial_batch_fields=1,
batch_no=batch_no,
do_not_submit=True,
)
pos_inv2.payments[0].amount = pos_inv2.grand_total
pos_inv2.submit()
batch_qty = frappe.db.get_value("Batch", batch_no, "batch_qty")
self.assertEqual(batch_qty, 10)

View File

@@ -240,6 +240,7 @@ class POSInvoice(SalesInvoice):
from erpnext.accounts.doctype.pricing_rule.utils import update_coupon_code_count
update_coupon_code_count(self.coupon_code, "used")
self.clear_unallocated_mode_of_payments()
def before_cancel(self):
if (
@@ -278,6 +279,12 @@ class POSInvoice(SalesInvoice):
self.delink_serial_and_batch_bundle()
def clear_unallocated_mode_of_payments(self):
self.set("payments", self.get("payments", {"amount": ["not in", [0, None, ""]]}))
sip = frappe.qb.DocType("Sales Invoice Payment")
frappe.qb.from_(sip).delete().where(sip.parent == self.name).where(sip.amount == 0).run()
def delink_serial_and_batch_bundle(self):
for row in self.items:
if row.serial_and_batch_bundle:
@@ -533,7 +540,11 @@ class POSInvoice(SalesInvoice):
def set_pos_fields(self, for_validate=False):
"""Set retail related fields from POS Profiles"""
from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details
from erpnext.stock.get_item_details import (
ItemDetailsCtx,
get_pos_profile,
get_pos_profile_item_details_,
)
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
@@ -602,8 +613,8 @@ class POSInvoice(SalesInvoice):
# set pos values in items
for item in self.get("items"):
if item.get("item_code"):
profile_details = get_pos_profile_item_details(
profile.get("company"), frappe._dict(item.as_dict()), profile
profile_details = get_pos_profile_item_details_(
ItemDetailsCtx(item.as_dict()), profile.get("company"), profile
)
for fname, val in profile_details.items():
if (not for_validate) or (for_validate and not item.get(fname)):

View File

@@ -134,6 +134,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
},
)
inv.insert()
inv.payments[0].amount = inv.grand_total
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
@@ -150,6 +151,7 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
},
)
inv2.insert()
inv2.payments[0].amount = inv.grand_total
inv2.submit()
consolidate_pos_invoices()
@@ -157,16 +159,19 @@ class TestPOSInvoiceMergeLog(IntegrationTestCase):
consolidated_invoice = frappe.get_doc("Sales Invoice", inv.consolidated_invoice)
item_wise_tax_detail = json.loads(consolidated_invoice.get("taxes")[0].item_wise_tax_detail)
tax_data = item_wise_tax_detail.get("_Test Item")
self.assertEqual(tax_data.get("tax_rate"), 9)
self.assertEqual(tax_data.get("tax_amount"), 9)
self.assertEqual(tax_data.get("net_amount"), 100)
tax_data = item_wise_tax_detail.get("_Test Item 2")
self.assertEqual(tax_data.get("tax_rate"), 5)
self.assertEqual(tax_data.get("tax_amount"), 5)
self.assertEqual(tax_data.get("net_amount"), 100)
expected_item_wise_tax_detail = {
"_Test Item": {
"tax_rate": 9,
"tax_amount": 9,
"net_amount": 100,
},
"_Test Item 2": {
"tax_rate": 5,
"tax_amount": 5,
"net_amount": 100,
},
}
self.assertEqual(item_wise_tax_detail, expected_item_wise_tax_detail)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")

View File

@@ -451,6 +451,16 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
get_pricing_rule_items(pricing_rule, other_items=fetch_other_item) or []
)
if pricing_rule.coupon_code_based == 1:
if not args.coupon_code:
return item_details
coupon_code = frappe.db.get_value(
doctype="Coupon Code", filters={"pricing_rule": pricing_rule.name}, fieldname="name"
)
if args.coupon_code != coupon_code:
continue
if pricing_rule.get("suggestion"):
continue
@@ -476,9 +486,6 @@ def get_pricing_rule_for_item(args, doc=None, for_validate=False):
pricing_rule.apply_rule_on_other_items
)
if pricing_rule.coupon_code_based == 1 and args.coupon_code is None:
return item_details
if not pricing_rule.validate_applied_rule:
if pricing_rule.price_or_product_discount == "Price":
apply_price_discount_rule(pricing_rule, item_details, args)

View File

@@ -1147,6 +1147,45 @@ class TestPricingRule(IntegrationTestCase):
so.save()
self.assertEqual(len(so.items), 1)
def test_pricing_rule_for_product_free_item_round_free_qty(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule")
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [
{
"item_code": "_Test Item",
}
],
"selling": 1,
"rate": 0,
"min_qty": 100,
"max_qty": 0,
"price_or_product_discount": "Product",
"same_item": 1,
"free_qty": 10,
"round_free_qty": 1,
"is_recursive": 1,
"recurse_for": 100,
"company": "_Test Company",
}
frappe.get_doc(test_record.copy()).insert()
# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=100)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 10)
so = make_sales_order(item_code="_Test Item", qty=150)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 10)
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2")

View File

@@ -655,7 +655,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty:
qty = math.floor(qty)
qty = (flt(transaction_qty) // pricing_rule.recurse_for) * (pricing_rule.free_qty or 1)
if not qty:
return

View File

@@ -474,6 +474,7 @@ def send_emails(document_name, from_scheduler=False, posting_date=None):
reference_doctype="Process Statement Of Accounts",
reference_name=document_name,
attachments=attachments,
expose_recipients="header",
)
if doc.enable_auto_email and from_scheduler:

View File

@@ -17,6 +17,7 @@
"account_head",
"description",
"is_tax_withholding_account",
"set_by_item_tax_template",
"section_break_10",
"rate",
"accounting_dimensions_section",
@@ -244,7 +245,8 @@
"label": "Net Amount",
"oldfieldname": "tax_amount",
"oldfieldtype": "Currency",
"options": "currency"
"options": "currency",
"read_only": 1
},
{
"fieldname": "base_net_amount",
@@ -253,12 +255,22 @@
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"default": "0",
"fieldname": "set_by_item_tax_template",
"fieldtype": "Check",
"hidden": 1,
"label": "Set by Item Tax Template",
"print_hide": 1,
"read_only": 1,
"report_hide": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-09-24 06:47:25.129901",
"modified": "2024-11-22 19:17:02.377473",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Taxes and Charges",

View File

@@ -42,6 +42,7 @@ class PurchaseTaxesandCharges(Document):
parenttype: DF.Data
rate: DF.Float
row_id: DF.Data | None
set_by_item_tax_template: DF.Check
tax_amount: DF.Currency
tax_amount_after_discount_amount: DF.Currency
total: DF.Currency

View File

@@ -45,7 +45,7 @@ class RepostAccountingLedger(Document):
latest_pcv = (
frappe.db.get_all(
"Period Closing Voucher",
filters={"company": self.company},
filters={"company": self.company, "docstatus": 1},
order_by="period_end_date desc",
pluck="period_end_date",
limit=1,

View File

@@ -740,20 +740,6 @@ frappe.ui.form.on("Sales Invoice", {
};
});
frm.set_query("company_address", function (doc) {
if (!doc.company) {
frappe.throw(__("Please set Company"));
}
return {
query: "frappe.contacts.doctype.address.address.address_query",
filters: {
link_doctype: "Company",
link_name: doc.company,
},
};
});
frm.set_query("pos_profile", function (doc) {
if (!doc.company) {
frappe.throw(__("Please set Company"));

View File

@@ -766,7 +766,11 @@ class SalesInvoice(SellingController):
"Company", self.company, "default_cash_account"
)
from erpnext.stock.get_item_details import get_pos_profile, get_pos_profile_item_details
from erpnext.stock.get_item_details import (
ItemDetailsCtx,
get_pos_profile,
get_pos_profile_item_details_,
)
if not self.pos_profile and not self.flags.ignore_pos_profile:
pos_profile = get_pos_profile(self.company) or {}
@@ -834,8 +838,8 @@ class SalesInvoice(SellingController):
# set pos values in items
for item in self.get("items"):
if item.get("item_code"):
profile_details = get_pos_profile_item_details(
pos, frappe._dict(item.as_dict()), pos, update_data=True
profile_details = get_pos_profile_item_details_(
ItemDetailsCtx(item.as_dict()), pos, pos, update_data=True
)
for fname, val in profile_details.items():
if (not for_validate) or (for_validate and not item.get(fname)):

View File

@@ -90,11 +90,14 @@
"incoming_rate",
"item_tax_rate",
"actual_batch_qty",
"actual_qty",
"section_break_eoec",
"serial_no",
"column_break_ytgd",
"batch_no",
"available_quantity_section",
"actual_qty",
"column_break_ogff",
"company_total_stock",
"edit_references",
"sales_order",
"so_detail",
@@ -676,7 +679,8 @@
"allow_on_submit": 1,
"fieldname": "actual_qty",
"fieldtype": "Float",
"label": "Available Qty at Warehouse",
"label": "Qty (Warehouse)",
"no_copy": 1,
"oldfieldname": "actual_qty",
"oldfieldtype": "Currency",
"print_hide": 1,
@@ -930,12 +934,30 @@
"fieldtype": "Currency",
"label": "Distributed Discount Amount",
"options": "currency"
},
{
"fieldname": "available_quantity_section",
"fieldtype": "Section Break",
"label": "Available Quantity"
},
{
"fieldname": "column_break_ogff",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "company_total_stock",
"fieldtype": "Float",
"label": "Qty (Company)",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-10-28 15:06:40.980995",
"modified": "2024-11-25 16:27:33.287341",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -34,6 +34,7 @@ class SalesInvoiceItem(Document):
base_rate_with_margin: DF.Currency
batch_no: DF.Link | None
brand: DF.Data | None
company_total_stock: DF.Float
conversion_factor: DF.Float
cost_center: DF.Link
customer_item_code: DF.Data | None

View File

@@ -13,6 +13,7 @@
"description",
"included_in_print_rate",
"included_in_paid_amount",
"set_by_item_tax_template",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -221,7 +222,8 @@
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Net Amount",
"options": "currency"
"options": "currency",
"read_only": 1
},
{
"fieldname": "base_net_amount",
@@ -231,13 +233,23 @@
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"read_only": 1
},
{
"default": "0",
"fieldname": "set_by_item_tax_template",
"fieldtype": "Check",
"hidden": 1,
"label": "Set by Item Tax Template",
"print_hide": 1,
"read_only": 1,
"report_hide": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-09-24 06:49:32.034074",
"modified": "2024-11-22 19:17:31.898467",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Taxes and Charges",

View File

@@ -40,6 +40,7 @@ class SalesTaxesandCharges(Document):
parenttype: DF.Data
rate: DF.Float
row_id: DF.Data | None
set_by_item_tax_template: DF.Check
tax_amount: DF.Currency
tax_amount_after_discount_amount: DF.Currency
total: DF.Currency

View File

@@ -568,7 +568,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, vouchers):
if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(
tax_details.tax_on_excess_amount
):
supp_credit_amt = net_total - cumulative_threshold
supp_credit_amt = net_total + tax_withholding_net_total - cumulative_threshold
if ldc and is_valid_certificate(ldc, inv.get("posting_date") or inv.get("transaction_date"), 0):
tds_amount = get_lower_deduction_amount(

View File

@@ -177,6 +177,45 @@ class TestTaxWithholdingCategory(IntegrationTestCase):
for d in reversed(invoices):
d.cancel()
def test_cumulative_threshold_with_tax_on_excess_amount(self):
invoices = []
frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
# Invoice with tax and without exceeding single and cumulative thresholds
for _ in range(2):
pi = create_purchase_invoice(supplier="Test TDS Supplier3", rate=10000, do_not_save=True)
pi.apply_tds = 1
pi.append(
"taxes",
{
"category": "Total",
"charge_type": "Actual",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"tax_amount": 500,
"description": "Test",
"add_deduct_tax": "Add",
},
)
pi.save()
pi.submit()
invoices.append(pi)
# Third Invoice exceeds single threshold and not exceeding cumulative threshold
pi1 = create_purchase_invoice(supplier="Test TDS Supplier3", rate=20000)
pi1.apply_tds = 1
pi1.save()
pi1.submit()
invoices.append(pi1)
# Cumulative threshold is 10,000
# Threshold calculation should be only on the third invoice
self.assertTrue(len(pi1.taxes) > 0)
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
for d in reversed(invoices):
d.cancel()
def test_cumulative_threshold_tcs(self):
frappe.db.set_value(
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"

View File

@@ -316,66 +316,48 @@ def check_if_in_list(gle, gl_map):
def toggle_debit_credit_if_negative(gl_map):
debit_credit_field_map = {
"debit": "credit",
"debit_in_account_currency": "credit_in_account_currency",
"debit_in_transaction_currency": "credit_in_transaction_currency",
}
for entry in gl_map:
# toggle debit, credit if negative entry
if flt(entry.debit) < 0 and flt(entry.credit) < 0 and flt(entry.debit) == flt(entry.credit):
entry.credit *= -1
entry.debit *= -1
for debit_field, credit_field in debit_credit_field_map.items():
debit = flt(entry.get(debit_field))
credit = flt(entry.get(credit_field))
if (
flt(entry.debit_in_account_currency) < 0
and flt(entry.credit_in_account_currency) < 0
and flt(entry.debit_in_account_currency) == flt(entry.credit_in_account_currency)
):
entry.credit_in_account_currency *= -1
entry.debit_in_account_currency *= -1
if debit < 0 and credit < 0 and debit == credit:
debit *= -1
credit *= -1
if flt(entry.debit) < 0:
entry.credit = flt(entry.credit) - flt(entry.debit)
entry.debit = 0.0
if debit < 0:
credit = credit - debit
debit = 0.0
if flt(entry.debit_in_account_currency) < 0:
entry.credit_in_account_currency = flt(entry.credit_in_account_currency) - flt(
entry.debit_in_account_currency
)
entry.debit_in_account_currency = 0.0
if credit < 0:
debit = debit - credit
credit = 0.0
if flt(entry.credit) < 0:
entry.debit = flt(entry.debit) - flt(entry.credit)
entry.credit = 0.0
# update net values
# In some scenarios net value needs to be shown in the ledger
# This method updates net values as debit or credit
if entry.post_net_value and debit and credit:
if debit > credit:
debit = debit - credit
credit = 0.0
if flt(entry.credit_in_account_currency) < 0:
entry.debit_in_account_currency = flt(entry.debit_in_account_currency) - flt(
entry.credit_in_account_currency
)
entry.credit_in_account_currency = 0.0
else:
credit = credit - debit
debit = 0.0
update_net_values(entry)
entry[debit_field] = debit
entry[credit_field] = credit
return gl_map
def update_net_values(entry):
# In some scenarios net value needs to be shown in the ledger
# This method updates net values as debit or credit
if entry.post_net_value and entry.debit and entry.credit:
if entry.debit > entry.credit:
entry.debit = entry.debit - entry.credit
entry.debit_in_account_currency = (
entry.debit_in_account_currency - entry.credit_in_account_currency
)
entry.credit = 0
entry.credit_in_account_currency = 0
else:
entry.credit = entry.credit - entry.debit
entry.credit_in_account_currency = (
entry.credit_in_account_currency - entry.debit_in_account_currency
)
entry.debit = 0
entry.debit_in_account_currency = 0
def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
if not from_repost:
validate_cwip_accounts(gl_map)

View File

@@ -4,13 +4,14 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Purchase Invoice",
"dynamic_filters_json": "[[\"Purchase Invoice\",\"company\",\"=\",\" frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Purchase Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Purchase Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]",
"function": "Sum",
"idx": 0,
"is_public": 1,
"is_standard": 1,
"label": "Total Incoming Bills",
"modified": "2020-07-22 13:06:46.045344",
"modified": "2024-11-20 19:08:37.043777",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Total Incoming Bills",

View File

@@ -4,6 +4,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Payment Entry",
"dynamic_filters_json": "[[\"Payment Entry\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Receive\",false]]",
"function": "Sum",
"idx": 0,

View File

@@ -4,6 +4,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Sales Invoice",
"dynamic_filters_json": "[[\"Sales Invoice\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Sales Invoice\",\"docstatus\",\"=\",\"1\",false],[\"Sales Invoice\",\"posting_date\",\"Timespan\",\"this year\",false]]",
"function": "Sum",
"idx": 0,

View File

@@ -4,6 +4,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Payment Entry",
"dynamic_filters_json": "[[\"Payment Entry\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Payment Entry\",\"docstatus\",\"=\",\"1\",false],[\"Payment Entry\",\"posting_date\",\"Timespan\",\"this year\",false],[\"Payment Entry\",\"payment_type\",\"=\",\"Pay\",false]]",
"function": "Sum",
"idx": 0,

View File

@@ -1013,15 +1013,15 @@ class ReceivablePayableReport:
def get_columns(self):
self.columns = []
self.add_column("Posting Date", fieldtype="Date")
self.add_column(_("Posting Date"), fieldtype="Date")
self.add_column(
label="Party Type",
label=_("Party Type"),
fieldname="party_type",
fieldtype="Data",
width=100,
)
self.add_column(
label="Party",
label=_("Party"),
fieldname="party",
fieldtype="Dynamic Link",
options="party_type",
@@ -1037,10 +1037,10 @@ class ReceivablePayableReport:
if self.party_naming_by == "Naming Series":
if self.account_type == "Payable":
label = "Supplier Name"
label = _("Supplier Name")
fieldname = "supplier_name"
else:
label = "Customer Name"
label = _("Customer Name")
fieldname = "customer_name"
self.add_column(
label=label,
@@ -1066,7 +1066,7 @@ class ReceivablePayableReport:
width=180,
)
self.add_column(label="Due Date", fieldtype="Date")
self.add_column(label=_("Due Date"), fieldtype="Date")
if self.account_type == "Payable":
self.add_column(label=_("Bill No"), fieldname="bill_no", fieldtype="Data")

View File

@@ -263,7 +263,6 @@ def get_report_summary(summary_data, currency):
def get_chart_data(columns, data, currency):
labels = [d.get("label") for d in columns[2:]]
print(data)
datasets = [
{
"name": section.get("section").replace("'", ""),

View File

@@ -619,7 +619,7 @@ def get_cost_centers_with_children(cost_centers):
def get_columns(periodicity, period_list, accumulated_values=1, company=None, cash_flow=False):
columns = [
{
"fieldname": "stub",
"fieldname": "account",
"label": _("Account") if not cash_flow else _("Section"),
"fieldtype": "Link",
"options": "Account",

View File

@@ -2,6 +2,7 @@
# MIT License. See license.txt
import frappe
from frappe.desk.query_report import export_query
from frappe.tests import IntegrationTestCase
from frappe.utils import getdate, today
@@ -90,3 +91,21 @@ class TestProfitAndLossStatement(AccountsTestMixin, IntegrationTestCase):
with self.subTest(current_period_key=current_period_key):
self.assertEqual(acc[current_period_key], 150)
self.assertEqual(acc["total"], 150)
def test_p_and_l_export(self):
self.create_sales_invoice(qty=1, rate=150)
filters = self.get_report_filters()
frappe.local.form_dict = frappe._dict(
{
"report_name": "Profit and Loss Statement",
"file_format_type": "CSV",
"filters": filters,
"visible_idx": [0, 1, 2, 3, 4, 5, 6],
}
)
export_query()
contents = frappe.response["filecontent"].decode()
sales_account = frappe.db.get_value("Company", self.company, "default_income_account")
self.assertIn(sales_account, contents)

View File

@@ -650,6 +650,16 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
if jv_detail.get("reference_type") in advance_payment_doctypes:
update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name))
rev_dr_or_cr = (
"debit_in_account_currency"
if d["dr_or_cr"] == "credit_in_account_currency"
else "credit_in_account_currency"
)
if jv_detail.get(rev_dr_or_cr):
d["dr_or_cr"] = rev_dr_or_cr
d["allocated_amount"] = d["allocated_amount"] * -1
d["unadjusted_amount"] = d["unadjusted_amount"] * -1
if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
# adjust the unreconciled balance
amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])
@@ -1367,6 +1377,14 @@ def parse_naming_series_variable(doc, variable):
company = None
return get_fiscal_year(date=date, company=company)[0]
elif variable == "ABBR":
if doc:
company = doc.get("company") or frappe.db.get_default("company")
else:
company = frappe.db.get_default("company")
return frappe.db.get_value("Company", company, "abbr") if company else ""
@frappe.whitelist()
def get_coa(doctype, parent, is_root=None, chart=None):

View File

@@ -26,9 +26,10 @@ from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.stock.get_item_details import (
ItemDetailsCtx,
get_default_cost_center,
get_default_expense_account,
get_item_warehouse,
get_item_warehouse_,
)
from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.stock.utils import get_incoming_rate
@@ -748,7 +749,7 @@ def get_target_item_details(item_code=None, company=None):
item_group_defaults = get_item_group_defaults(item.name, company)
brand_defaults = get_brand_defaults(item.name, company)
out.cost_center = get_default_cost_center(
frappe._dict({"item_code": item.name, "company": company}),
ItemDetailsCtx({"item_code": item.name, "company": company}),
item_defaults,
item_group_defaults,
brand_defaults,
@@ -785,45 +786,42 @@ def get_target_asset_details(asset=None, company=None):
@frappe.whitelist()
def get_consumed_stock_item_details(args):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_consumed_stock_item_details(ctx: ItemDetailsCtx):
out = frappe._dict()
item = frappe._dict()
if args.item_code:
item = frappe.get_cached_doc("Item", args.item_code)
if ctx.item_code:
item = frappe.get_cached_doc("Item", ctx.item_code)
out.item_name = item.item_name
out.batch_no = None
out.serial_no = ""
out.stock_qty = flt(args.stock_qty) or 1
out.stock_qty = flt(ctx.stock_qty) or 1
out.stock_uom = item.stock_uom
out.warehouse = get_item_warehouse(item, args, overwrite_warehouse=True) if item else None
out.warehouse = get_item_warehouse_(ctx, item, overwrite_warehouse=True) if item else None
# Cost Center
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults)
item_defaults = get_item_defaults(item.name, ctx.company)
item_group_defaults = get_item_group_defaults(item.name, ctx.company)
brand_defaults = get_brand_defaults(item.name, ctx.company)
out.cost_center = get_default_cost_center(ctx, item_defaults, item_group_defaults, brand_defaults)
if args.item_code and out.warehouse:
if ctx.item_code and out.warehouse:
incoming_rate_args = frappe._dict(
{
"item_code": args.item_code,
"item_code": ctx.item_code,
"warehouse": out.warehouse,
"posting_date": args.posting_date,
"posting_time": args.posting_time,
"posting_date": ctx.posting_date,
"posting_time": ctx.posting_time,
"qty": -1 * flt(out.stock_qty),
"voucher_type": args.doctype,
"voucher_no": args.name,
"company": args.company,
"serial_no": args.serial_no,
"batch_no": args.batch_no,
"voucher_type": ctx.doctype,
"voucher_no": ctx.name,
"company": ctx.company,
"serial_no": ctx.serial_no,
"batch_no": ctx.batch_no,
}
)
out.update(get_warehouse_details(incoming_rate_args))
@@ -851,31 +849,28 @@ def get_warehouse_details(args):
@frappe.whitelist()
def get_consumed_asset_details(args):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_consumed_asset_details(ctx):
out = frappe._dict()
asset_details = frappe._dict()
if args.asset:
if ctx.asset:
asset_details = frappe.db.get_value(
"Asset", args.asset, ["asset_name", "item_code", "item_name"], as_dict=1
"Asset", ctx.asset, ["asset_name", "item_code", "item_name"], as_dict=1
)
if not asset_details:
frappe.throw(_("Asset {0} does not exist").format(args.asset))
frappe.throw(_("Asset {0} does not exist").format(ctx.asset))
out.item_code = asset_details.item_code
out.asset_name = asset_details.asset_name
out.item_name = asset_details.item_name
if args.asset:
if ctx.asset:
out.current_asset_value = flt(
get_asset_value_after_depreciation(args.asset, finance_book=args.finance_book)
get_asset_value_after_depreciation(ctx.asset, finance_book=ctx.finance_book)
)
out.asset_value = get_value_after_depreciation_on_disposal_date(
args.asset, args.posting_date, finance_book=args.finance_book
ctx.asset, ctx.posting_date, finance_book=ctx.finance_book
)
else:
out.current_asset_value = 0
@@ -884,7 +879,7 @@ def get_consumed_asset_details(args):
# Account
if asset_details.item_code:
out.fixed_asset_account = get_asset_category_account(
"fixed_asset_account", item=asset_details.item_code, company=args.company
"fixed_asset_account", item=asset_details.item_code, company=ctx.company
)
else:
out.fixed_asset_account = None
@@ -892,37 +887,32 @@ def get_consumed_asset_details(args):
# Cost Center
if asset_details.item_code:
item = frappe.get_cached_doc("Item", asset_details.item_code)
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults)
item_defaults = get_item_defaults(item.name, ctx.company)
item_group_defaults = get_item_group_defaults(item.name, ctx.company)
brand_defaults = get_brand_defaults(item.name, ctx.company)
out.cost_center = get_default_cost_center(ctx, item_defaults, item_group_defaults, brand_defaults)
return out
@frappe.whitelist()
def get_service_item_details(args):
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
@erpnext.normalize_ctx_input(ItemDetailsCtx)
def get_service_item_details(ctx):
out = frappe._dict()
item = frappe._dict()
if args.item_code:
item = frappe.get_cached_doc("Item", args.item_code)
if ctx.item_code:
item = frappe.get_cached_doc("Item", ctx.item_code)
out.item_name = item.item_name
out.qty = flt(args.qty) or 1
out.qty = flt(ctx.qty) or 1
out.uom = item.purchase_uom or item.stock_uom
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
item_defaults = get_item_defaults(item.name, ctx.company)
item_group_defaults = get_item_group_defaults(item.name, ctx.company)
brand_defaults = get_brand_defaults(item.name, ctx.company)
out.expense_account = get_default_expense_account(
args, item_defaults, item_group_defaults, brand_defaults
)
out.cost_center = get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults)
out.expense_account = get_default_expense_account(ctx, item_defaults, item_group_defaults, brand_defaults)
out.cost_center = get_default_cost_center(ctx, item_defaults, item_group_defaults, brand_defaults)
return out

View File

@@ -4,6 +4,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Asset",
"dynamic_filters_json": "[[\"Asset\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[]",
"function": "Sum",
"idx": 0,

View File

@@ -3,6 +3,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Asset",
"dynamic_filters_json": "[[\"Asset\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Asset\",\"creation\",\"Timespan\",\"this year\",false]]",
"function": "Count",
"idx": 0,

View File

@@ -3,6 +3,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Asset",
"dynamic_filters_json": "[[\"Asset\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[]",
"function": "Count",
"idx": 0,

View File

@@ -93,7 +93,10 @@ frappe.ui.form.on("Purchase Order", {
get_materials_from_supplier: function (frm) {
let po_details = [];
if (frm.doc.supplied_items && (flt(frm.doc.per_received, 2) == 100 || frm.doc.status === "Closed")) {
if (
frm.doc.supplied_items &&
(flt(frm.doc.per_received, precision("per_received")) == 100 || frm.doc.status === "Closed")
) {
frm.doc.supplied_items.forEach((d) => {
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
po_details.push(d.name);
@@ -329,8 +332,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
if (!["Closed", "Delivered"].includes(doc.status)) {
if (
this.frm.doc.status !== "Closed" &&
flt(this.frm.doc.per_received, 2) < 100 &&
flt(this.frm.doc.per_billed, 2) < 100
flt(this.frm.doc.per_received, precision("per_received")) < 100 &&
flt(this.frm.doc.per_billed, precision("per_billed")) < 100
) {
if (!this.frm.doc.__onload || this.frm.doc.__onload.can_update_items) {
this.frm.add_custom_button(__("Update Items"), () => {
@@ -344,7 +347,10 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
}
}
if (this.frm.has_perm("submit")) {
if (flt(doc.per_billed, 2) < 100 || flt(doc.per_received, 2) < 100) {
if (
flt(doc.per_billed, precision("per_billed")) < 100 ||
flt(doc.per_received, precision("per_received")) < 100
) {
if (doc.status != "On Hold") {
this.frm.add_custom_button(
__("Hold"),
@@ -382,7 +388,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
}
if (doc.status != "Closed") {
if (doc.status != "On Hold") {
if (flt(doc.per_received) < 100 && allow_receipt) {
if (flt(doc.per_received, precision("per_received")) < 100 && allow_receipt) {
this.frm.add_custom_button(
__("Purchase Receipt"),
() => {
@@ -413,7 +419,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
}
}
// Please do not add precision in the below flt function
if (flt(doc.per_billed) < 100)
if (flt(doc.per_billed, precision("per_billed")) < 100)
this.frm.add_custom_button(
__("Purchase Invoice"),
() => {
@@ -422,7 +428,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
__("Create")
);
if (flt(doc.per_billed, 2) < 100 && doc.status != "Delivered") {
if (flt(doc.per_billed, precision("per_billed")) < 100 && doc.status != "Delivered") {
this.frm.add_custom_button(
__("Payment"),
() => this.make_payment_entry(),
@@ -430,7 +436,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
);
}
if (flt(doc.per_billed, 2) < 100) {
if (flt(doc.per_billed, precision("per_billed")) < 100) {
this.frm.add_custom_button(
__("Payment Request"),
function () {

View File

@@ -18,6 +18,7 @@ def execute(filters=None):
columns = get_columns(filters)
data = get_data(filters)
update_received_amount(data)
if not data:
return [], [], None, []
@@ -40,7 +41,6 @@ def get_data(filters):
po = frappe.qb.DocType("Purchase Order")
po_item = frappe.qb.DocType("Purchase Order Item")
pi_item = frappe.qb.DocType("Purchase Invoice Item")
pr_item = frappe.qb.DocType("Purchase Receipt Item")
query = (
frappe.qb.from_(po)
@@ -48,8 +48,6 @@ def get_data(filters):
.on(po_item.parent == po.name)
.left_join(pi_item)
.on((pi_item.po_detail == po_item.name) & (pi_item.docstatus == 1))
.left_join(pr_item)
.on((pr_item.purchase_order_item == po_item.name) & (pr_item.docstatus == 1))
.select(
po.transaction_date.as_("date"),
po_item.schedule_date.as_("required_date"),
@@ -63,7 +61,6 @@ def get_data(filters):
(po_item.qty - po_item.received_qty).as_("pending_qty"),
Sum(IfNull(pi_item.qty, 0)).as_("billed_qty"),
po_item.base_amount.as_("amount"),
(pr_item.base_amount).as_("received_qty_amount"),
(po_item.billed_amt * IfNull(po.conversion_rate, 1)).as_("billed_amount"),
(po_item.base_amount - (po_item.billed_amt * IfNull(po.conversion_rate, 1))).as_(
"pending_amount"
@@ -95,6 +92,39 @@ def get_data(filters):
return data
def update_received_amount(data):
pr_data = get_received_amount_data(data)
for row in data:
row.received_qty_amount = flt(pr_data.get(row.name))
def get_received_amount_data(data):
pr = frappe.qb.DocType("Purchase Receipt")
pr_item = frappe.qb.DocType("Purchase Receipt Item")
query = (
frappe.qb.from_(pr)
.inner_join(pr_item)
.on(pr_item.parent == pr.name)
.select(
pr_item.purchase_order_item,
Sum(pr_item.base_amount).as_("received_qty_amount"),
)
.where((pr_item.parent == pr.name) & (pr.docstatus == 1))
.groupby(pr_item.purchase_order_item)
)
query = query.where(pr_item.purchase_order_item.isin([row.name for row in data]))
data = query.run()
if not data:
return frappe._dict()
return frappe._dict(data)
def prepare_data(data, filters):
completed, pending = 0, 0
pending_field = "pending_amount"

View File

@@ -62,11 +62,12 @@ from erpnext.setup.utils import get_exchange_rate
from erpnext.stock.doctype.item.item import get_uom_conv_factor
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
from erpnext.stock.get_item_details import (
ItemDetailsCtx,
_get_item_tax_template,
get_conversion_factor,
get_item_details,
get_item_tax_map,
get_item_warehouse,
get_item_warehouse_,
)
from erpnext.utilities.regional import temporary_flag
from erpnext.utilities.transaction_base import TransactionBase
@@ -752,24 +753,28 @@ class AccountsController(TransactionBase):
for item in self.get("items"):
if item.get("item_code"):
args = parent_dict.copy()
args.update(item.as_dict())
ctx: ItemDetailsCtx = ItemDetailsCtx(parent_dict.copy())
ctx.update(item.as_dict())
args["doctype"] = self.doctype
args["name"] = self.name
args["child_doctype"] = item.doctype
args["child_docname"] = item.name
args["ignore_pricing_rule"] = (
self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0
ctx.update(
{
"doctype": self.doctype,
"name": self.name,
"child_doctype": item.doctype,
"child_docname": item.name,
"ignore_pricing_rule": (
self.ignore_pricing_rule if hasattr(self, "ignore_pricing_rule") else 0
),
}
)
if not args.get("transaction_date"):
args["transaction_date"] = args.get("posting_date")
if not ctx.transaction_date:
ctx.transaction_date = ctx.posting_date
if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted
ctx.is_subcontracted = self.is_subcontracted
ret = get_item_details(args, self, for_validate=for_validate, overwrite_warehouse=False)
ret = get_item_details(ctx, 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:
@@ -956,6 +961,7 @@ class AccountsController(TransactionBase):
"account_head": account_head,
"rate": 0,
"description": account_head,
"set_by_item_tax_template": 1,
},
)
@@ -3161,18 +3167,21 @@ def get_supplier_block_status(party_name):
def set_child_tax_template_and_map(item, child_item, parent_doc):
args = {
"item_code": item.item_code,
"posting_date": parent_doc.transaction_date,
"tax_category": parent_doc.get("tax_category"),
"company": parent_doc.get("company"),
}
ctx = ItemDetailsCtx(
{
"item_code": item.item_code,
"posting_date": parent_doc.transaction_date,
"tax_category": parent_doc.get("tax_category"),
"company": parent_doc.get("company"),
}
)
child_item.item_tax_template = _get_item_tax_template(args, item.taxes)
if child_item.get("item_tax_template"):
child_item.item_tax_rate = get_item_tax_map(
parent_doc.get("company"), child_item.item_tax_template, as_json=True
)
child_item.item_tax_template = _get_item_tax_template(ctx, item.taxes)
child_item.item_tax_rate = get_item_tax_map(
doc=parent_doc,
tax_template=child_item.item_tax_template,
as_json=True,
)
def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
@@ -3195,6 +3204,7 @@ def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
"charge_type": "On Net Total",
"account_head": tax_type,
"rate": tax_rate,
"set_by_item_tax_template": 1,
}
)
if parent_doc.doctype == "Purchase Order":
@@ -3218,7 +3228,7 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
child_item.stock_uom = item.stock_uom
child_item.uom = trans_item.get("uom") or item.stock_uom
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
child_item.warehouse = get_item_warehouse_(p_doc, item, overwrite_warehouse=True)
conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
child_item.conversion_factor = flt(trans_item.get("conversion_factor")) or conversion_factor
@@ -3227,7 +3237,7 @@ def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child
child_item.base_rate = 1
child_item.base_amount = 1
if child_doctype == "Sales Order Item":
child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
child_item.warehouse = get_item_warehouse_(p_doc, item, overwrite_warehouse=True)
if not child_item.warehouse:
frappe.throw(
_(

View File

@@ -14,7 +14,7 @@ from frappe.utils import nowdate, today, unique
from pypika import Order
import erpnext
from erpnext.stock.get_item_details import _get_item_tax_template
from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template
# searches for active employees
@@ -813,14 +813,16 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
valid_from = filters.get("valid_from")
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
args = {
"item_code": filters.get("item_code"),
"posting_date": valid_from,
"tax_category": filters.get("tax_category"),
"company": company,
}
ctx = ItemDetailsCtx(
{
"item_code": filters.get("item_code"),
"posting_date": valid_from,
"tax_category": filters.get("tax_category"),
"company": company,
}
)
taxes = _get_item_tax_template(args, taxes, for_validate=True)
taxes = _get_item_tax_template(ctx, taxes, for_validate=True)
return [(d,) for d in set(taxes)]

View File

@@ -21,9 +21,15 @@ class SellingController(StockController):
def onload(self):
super().onload()
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"):
if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice", "Quotation"):
for item in self.get("items") + (self.get("packed_items") or []):
item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True))
company = self.company
item.update(
get_bin_details(
item.item_code, item.warehouse, company=company, include_child_warehouses=True
)
)
def validate(self):
super().validate()
@@ -167,6 +173,9 @@ class SellingController(StockController):
total = 0.0
sales_team = self.get("sales_team")
self.validate_sales_team(sales_team)
for sales_person in sales_team:
self.round_floats_in(sales_person)
@@ -186,6 +195,20 @@ class SellingController(StockController):
if sales_team and total != 100.0:
throw(_("Total allocated percentage for sales team should be 100"))
def validate_sales_team(self, sales_team):
sales_persons = [d.sales_person for d in sales_team]
if not sales_persons:
return
sales_person_status = frappe.db.get_all(
"Sales Person", filters={"name": ["in", sales_persons]}, fields=["name", "enabled"]
)
for row in sales_person_status:
if not row.enabled:
frappe.throw(_("Sales Person <b>{0}</b> is disabled.").format(row.name))
def validate_max_discount(self):
for d in self.get("items"):
if d.item_code:

View File

@@ -822,6 +822,9 @@ class StockController(AccountsController):
self.update_inventory_dimensions(d, sl_dict)
if self.docstatus == 2:
from erpnext.deprecation_dumpster import deprecation_warning
deprecation_warning("unknown", "v16", "No instructions.")
# To handle denormalized serial no records, will br deprecated in v16
for field in ["serial_no", "batch_no"]:
if d.get(field):
@@ -1008,11 +1011,13 @@ class StockController(AccountsController):
def validate_qi_presence(self, row):
"""Check if QI is present on row level. Warn on save and stop on submit if missing."""
if not row.quality_inspection:
msg = f"Row #{row.idx}: Quality Inspection is required for Item {frappe.bold(row.item_code)}"
msg = _("Row #{0}: Quality Inspection is required for Item {1}").format(
row.idx, frappe.bold(row.item_code)
)
if self.docstatus == 1:
frappe.throw(_(msg), title=_("Inspection Required"), exc=QualityInspectionRequiredError)
frappe.throw(msg, title=_("Inspection Required"), exc=QualityInspectionRequiredError)
else:
frappe.msgprint(_(msg), title=_("Inspection Required"), indicator="blue")
frappe.msgprint(msg, title=_("Inspection Required"), indicator="blue")
def validate_qi_submission(self, row):
"""Check if QI is submitted on row level, during submission"""
@@ -1021,11 +1026,13 @@ class StockController(AccountsController):
if qa_docstatus != 1:
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} is not submitted for the item: {row.item_code}"
msg = _("Row #{0}: Quality Inspection {1} is not submitted for the item: {2}").format(
row.idx, link, row.item_code
)
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
frappe.throw(msg, title=_("Inspection Submission"), exc=QualityInspectionNotSubmittedError)
else:
frappe.msgprint(_(msg), alert=True, indicator="orange")
frappe.msgprint(msg, alert=True, indicator="orange")
def validate_qi_rejection(self, row):
"""Check if QI is rejected on row level, during submission"""
@@ -1034,11 +1041,13 @@ class StockController(AccountsController):
if qa_status == "Rejected":
link = frappe.utils.get_link_to_form("Quality Inspection", row.quality_inspection)
msg = f"Row #{row.idx}: Quality Inspection {link} was rejected for item {row.item_code}"
msg = _("Row #{0}: Quality Inspection {1} was rejected for item {2}").format(
row.idx, link, row.item_code
)
if action == "Stop":
frappe.throw(_(msg), title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
frappe.throw(msg, title=_("Inspection Rejected"), exc=QualityInspectionRejectedError)
else:
frappe.msgprint(_(msg), alert=True, indicator="orange")
frappe.msgprint(msg, alert=True, indicator="orange")
def update_blanket_order(self):
blanket_orders = list(set([d.blanket_order for d in self.items if d.blanket_order]))

View File

@@ -335,12 +335,18 @@ class SubcontractingController(StockController):
# Will be deprecated in v16
if row.serial_no and not consumed_bundles.serial_nos:
from erpnext.deprecation_dumpster import deprecation_warning
deprecation_warning("unknown", "v16", "No instructions.")
self.available_materials[key]["serial_no"] = list(
set(self.available_materials[key]["serial_no"]) - set(get_serial_nos(row.serial_no))
)
# Will be deprecated in v16
if row.batch_no and not consumed_bundles.batch_nos:
from erpnext.deprecation_dumpster import deprecation_warning
deprecation_warning("unknown", "v16", "No instructions.")
self.available_materials[key]["batch_no"][row.batch_no] -= row.consumed_qty
def get_available_materials(self):

View File

@@ -8,7 +8,6 @@ import frappe
from frappe import _, scrub
from frappe.model.document import Document
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from frappe.utils.deprecations import deprecated
import erpnext
from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_rate
@@ -18,9 +17,12 @@ from erpnext.controllers.accounts_controller import (
validate_inclusive_tax,
validate_taxes_and_charges,
)
from erpnext.stock.get_item_details import _get_item_tax_template
from erpnext.deprecation_dumpster import deprecated
from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template
from erpnext.utilities.regional import temporary_flag
logger = frappe.logger(__name__)
ItemWiseTaxDetail = frappe._dict
@@ -101,15 +103,17 @@ class calculate_taxes_and_totals:
for item in self.doc.items:
if item.item_code and item.get("item_tax_template"):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
"net_rate": item.net_rate or item.rate,
"base_net_rate": item.base_net_rate or item.base_rate,
"tax_category": self.doc.get("tax_category"),
"posting_date": self.doc.get("posting_date"),
"bill_date": self.doc.get("bill_date"),
"transaction_date": self.doc.get("transaction_date"),
"company": self.doc.get("company"),
}
ctx = ItemDetailsCtx(
{
"net_rate": item.net_rate or item.rate,
"base_net_rate": item.base_net_rate or item.base_rate,
"tax_category": self.doc.get("tax_category"),
"posting_date": self.doc.get("posting_date"),
"bill_date": self.doc.get("bill_date"),
"transaction_date": self.doc.get("transaction_date"),
"company": self.doc.get("company"),
}
)
item_group = item_doc.item_group
item_group_taxes = []
@@ -125,7 +129,7 @@ class calculate_taxes_and_totals:
# No validation if no taxes in item or item group
continue
taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
taxes = _get_item_tax_template(ctx, item_taxes + item_group_taxes, for_validate=True)
if taxes:
if item.item_tax_template not in taxes:
@@ -379,8 +383,10 @@ class calculate_taxes_and_totals:
]
)
logger.debug(f"{self.doc} ...")
for n, item in enumerate(self._items):
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
logger.debug(f" Item {n}: {item.item_code}" + (f" - {item_tax_map}" if item_tax_map else ""))
for i, tax in enumerate(self.doc.get("taxes")):
# tax_amount represents the amount of tax for the current step
current_net_amount, current_tax_amount = self.get_current_tax_and_net_amount(
@@ -447,6 +453,9 @@ class calculate_taxes_and_totals:
self.doc.grand_total - flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment"),
)
logger.debug(
f" net_amount: {current_net_amount:<20} tax_amount: {current_tax_amount:<20} - {tax.description}"
)
def get_tax_amount_if_for_valuation_or_deduction(self, tax_amount, tax):
# if just for valuation, do not add the tax amount in total
@@ -477,25 +486,24 @@ class calculate_taxes_and_totals:
current_net_amount = 0.0
if tax.charge_type == "Actual":
current_net_amount = item.net_amount
# distribute the tax amount proportionally to each item row
actual = flt(tax.tax_amount, tax.precision("tax_amount"))
if tax.get("is_tax_withholding_account") and item.meta.get_field("apply_tds"):
if not item.get("apply_tds") or not self.doc.tax_withholding_net_total:
current_tax_amount = 0.0
current_net_amount = 0.0
else:
current_net_amount = item.net_amount
current_tax_amount = current_net_amount * actual / self.doc.tax_withholding_net_total
current_tax_amount = item.net_amount * actual / self.doc.tax_withholding_net_total
else:
current_net_amount = item.net_amount
current_tax_amount = (
current_net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0
)
elif tax.charge_type == "On Net Total":
current_net_amount = item.net_amount
current_tax_amount = (tax_rate / 100.0) * current_net_amount
if tax.account_head in item_tax_map:
current_net_amount = item.net_amount
current_tax_amount = (tax_rate / 100.0) * item.net_amount
elif tax.charge_type == "On Previous Row Amount":
current_net_amount = self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item
current_tax_amount = (tax_rate / 100.0) * current_net_amount
@@ -559,7 +567,12 @@ class calculate_taxes_and_totals:
tax.base_tax_amount = round(tax.base_tax_amount, 0)
tax.base_tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount, 0)
@deprecated
@deprecated(
f"{__name__}.calculate_taxes_and_totals.manipulate_grand_total_for_inclusive_tax",
"unknown",
"v16",
"No known instructions.",
)
def manipulate_grand_total_for_inclusive_tax(self):
# for backward compatablility - if in case used by an external application
return self.adjust_grand_total_for_inclusive_tax()

View File

@@ -79,7 +79,7 @@ class TestTaxesAndTotals(FrappeTestCase):
"rate": 50,
},
)
self.doc.set_missing_item_details()
calculate_taxes_and_totals(self.doc)
expected_values = {

View File

@@ -3,7 +3,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Opportunity",
"dynamic_filters_json": "[[\"Opportunity\",\"status\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"dynamic_filters_json": "[[\"Opportunity\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Opportunity\",\"company\",\"=\",null,false]]",
"function": "Count",
"idx": 0,

View File

@@ -5,7 +5,7 @@ This file is the final resting place (or should we say, "retirement home"?) for
Each function or method that checks in here comes with its own personalized decorator, complete with:
1. The date it was marked for deprecation (its "over the hill" birthday)
2. The ERPNext version in which it will be removed (its "graduation" to the great codebase in the sky)
2. The ERPNext version at the beginning of which it becomes an error and at the end of which it will be removed (its "graduation" to the great codebase in the sky)
3. A user-facing note on alternative solutions (its "parting wisdom")
Warning: The global namespace herein is more patched up than a sailor's favorite pair of jeans. Proceed with caution and a sense of humor!
@@ -15,52 +15,63 @@ Remember, deprecated doesn't mean useless - it just means these functions are en
Enjoy your stay in the Deprecation Dumpster, where every function gets a second chance to shine (or at least, to not break everything).
"""
import functools
import re
import sys
import warnings
def colorize(text, color_code):
if sys.stdout.isatty():
return f"\033[{color_code}m{text}\033[0m"
return text
from frappe.deprecation_dumpster import Color, _deprecated, colorize
class Color:
RED = 91
YELLOW = 93
CYAN = 96
# we use Warning because DeprecationWarning has python default filters which would exclude them from showing
# see also frappe.__init__ enabling them when a dev_server
class ERPNextDeprecationError(Warning):
"""Deprecated feature in current version.
Raises an error by default but can be configured via PYTHONWARNINGS in an emergency.
"""
class ERPNextDeprecationWarning(Warning):
...
"""Deprecated feature in next version"""
try:
# since python 3.13, PEP 702
from warnings import deprecated as _deprecated
except ImportError:
import functools
import warnings
from collections.abc import Callable
from typing import Optional, TypeVar, Union, overload
class PendingERPNextDeprecationWarning(ERPNextDeprecationWarning):
"""Deprecated feature in develop beyond next version.
T = TypeVar("T", bound=Callable)
Warning ignored by default.
def _deprecated(message: str, category=ERPNextDeprecationWarning, stacklevel=1) -> Callable[[T], T]:
def decorator(func: T) -> T:
@functools.wraps(func)
def wrapper(*args, **kwargs):
if message:
warning_msg = f"{func.__name__} is deprecated.\n{message}"
else:
warning_msg = f"{func.__name__} is deprecated."
warnings.warn(warning_msg, category=category, stacklevel=stacklevel + 1)
return func(*args, **kwargs)
The deprecation decision may still be reverted or deferred at this stage.
Regardless, using the new variant is encouraged and stable.
"""
return wrapper
wrapper.__deprecated__ = True # hint for the type checker
return decorator
warnings.simplefilter("error", ERPNextDeprecationError)
warnings.simplefilter("ignore", PendingERPNextDeprecationWarning)
class V15ERPNextDeprecationWarning(ERPNextDeprecationError):
pass
class V16ERPNextDeprecationWarning(ERPNextDeprecationWarning):
pass
class V17ERPNextDeprecationWarning(PendingERPNextDeprecationWarning):
pass
def __get_deprecation_class(graduation: str | None = None, class_name: str | None = None) -> type:
if graduation:
# Scrub the graduation string to ensure it's a valid class name
cleaned_graduation = re.sub(r"\W|^(?=\d)", "_", graduation.upper())
class_name = f"{cleaned_graduation}ERPNextDeprecationWarning"
current_module = sys.modules[__name__]
try:
return getattr(current_module, class_name)
except AttributeError:
return PendingDeprecationWarning
def deprecated(original: str, marked: str, graduation: str, msg: str, stacklevel: int = 1):
@@ -79,6 +90,7 @@ def deprecated(original: str, marked: str, graduation: str, msg: str, stacklevel
wrapper = _deprecated(
colorize(f"It was marked on {marked} for removal from {graduation} with note: ", Color.RED)
+ colorize(f"{msg}", Color.YELLOW),
category=__get_deprecation_class(graduation),
stacklevel=stacklevel,
)
@@ -103,7 +115,7 @@ def deprecation_warning(marked: str, graduation: str, msg: str):
Color.RED,
)
+ colorize(f"{msg}\n", Color.YELLOW),
category=ERPNextDeprecationWarning,
category=__get_deprecation_class(graduation),
stacklevel=2,
)
@@ -125,3 +137,27 @@ def taxes_and_totals_get_itemised_taxable_amount(items):
itemised_taxable_amount[item_code] += item.net_amount
return itemised_taxable_amount
@deprecated(
"erpnext.stock.get_pos_profile_item_details",
"2024-11-19",
"v16",
"Use erpnext.stock.get_pos_profile_item_details_ with a flipped signature",
)
def get_pos_profile_item_details(company, ctx, pos_profile=None, update_data=False):
from erpnext.stock.get_item_details import get_pos_profile_item_details_
return get_pos_profile_item_details_(ctx, company, pos_profile=pos_profile, update_data=update_data)
@deprecated(
"erpnext.stock.get_item_warehouse",
"2024-11-19",
"v16",
"Use erpnext.stock.get_item_warehouse_ with a flipped signature",
)
def get_item_warehouse(item, ctx, overwrite_warehouse, defaults=None):
from erpnext.stock.get_item_details import get_item_warehouse_
return get_item_warehouse_(ctx, item, overwrite_warehouse, defaults=defaults)

View File

@@ -393,6 +393,7 @@ doc_events = {
# function should expect the variable and doc as arguments
naming_series_variables = {
"FY": "erpnext.accounts.utils.parse_naming_series_variable",
"ABBR": "erpnext.accounts.utils.parse_naming_series_variable",
}
# On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,6 @@ frappe.ui.form.on("BOM", {
item: row.finished_good,
is_active: 1,
docstatus: 1,
track_semi_finished_goods: 0,
},
};
});

View File

@@ -16,7 +16,7 @@ from frappe.website.website_generator import WebsiteGenerator
import erpnext
from erpnext.setup.utils import get_exchange_rate
from erpnext.stock.doctype.item.item import get_item_details
from erpnext.stock.get_item_details import get_conversion_factor, get_price_list_rate
from erpnext.stock.get_item_details import ItemDetailsCtx, get_conversion_factor, get_price_list_rate
form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
@@ -886,7 +886,7 @@ class BOM(WebsiteGenerator):
self.cur_exploded_items = {}
for d in self.get("items"):
if d.bom_no:
self.get_child_exploded_items(d.bom_no, d.stock_qty)
self.get_child_exploded_items(d.bom_no, d.stock_qty, d.operation)
elif d.item_code:
self.add_to_cur_exploded_items(
frappe._dict(
@@ -915,7 +915,7 @@ class BOM(WebsiteGenerator):
else:
self.cur_exploded_items[args.item_code] = args
def get_child_exploded_items(self, bom_no, stock_qty):
def get_child_exploded_items(self, bom_no, stock_qty, operation=None):
"""Add all items from Flat BOM of child BOM"""
# Did not use qty_consumed_per_unit in the query, as it leads to rounding loss
child_fb_items = frappe.db.sql(
@@ -949,7 +949,7 @@ class BOM(WebsiteGenerator):
"item_code": d["item_code"],
"item_name": d["item_name"],
"source_warehouse": d["source_warehouse"],
"operation": d["operation"],
"operation": d["operation"] or operation,
"description": d["description"],
"stock_uom": d["stock_uom"],
"stock_qty": d["qty_consumed_per_unit"] * stock_qty,
@@ -1052,7 +1052,7 @@ def get_bom_item_rate(args, bom_doc):
elif bom_doc.rm_cost_as_per == "Price List":
if not bom_doc.buying_price_list:
frappe.throw(_("Please select Price List"))
bom_args = frappe._dict(
ctx = ItemDetailsCtx(
{
"doctype": "BOM",
"price_list": bom_doc.buying_price_list,
@@ -1070,7 +1070,7 @@ def get_bom_item_rate(args, bom_doc):
}
)
item_doc = frappe.get_cached_doc("Item", args.get("item_code"))
price_list_data = get_price_list_rate(bom_args, item_doc)
price_list_data = get_price_list_rate(ctx, item_doc)
rate = price_list_data.price_list_rate
return flt(rate)

View File

@@ -98,8 +98,6 @@ frappe.ui.form.on("BOM Creator", {
],
primary_action_label: __("Create"),
primary_action: (values) => {
frm.events.validate_dialog_values(frm, values);
values.doctype = frm.doc.doctype;
frappe.db.insert(values).then((doc) => {
frappe.set_route("Form", doc.doctype, doc.name);
@@ -111,18 +109,6 @@ frappe.ui.form.on("BOM Creator", {
dialog.show();
},
validate_dialog_values(frm, values) {
if (values.track_semi_finished_goods) {
if (values.final_operation_time <= 0) {
frappe.throw(__("Operation Time must be greater than 0"));
}
if (!values.workstation && !values.workstation_type) {
frappe.throw(__("Either Workstation or Workstation Type is mandatory"));
}
}
},
set_queries(frm) {
frm.set_query("bom_no", "items", function (doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn);

View File

@@ -39,23 +39,6 @@
"items",
"costing_detail",
"raw_material_cost",
"configuration_section",
"track_operations",
"column_break_obzr",
"track_semi_finished_goods",
"final_product_operation_section",
"operation",
"operation_time",
"column_break_xnlu",
"workstation_type",
"workstation",
"final_product_warehouse_section",
"skip_material_transfer",
"backflush_from_wip_warehouse",
"source_warehouse",
"column_break_buha",
"wip_warehouse",
"fg_warehouse",
"remarks_tab",
"remarks",
"section_break_yixm",
@@ -298,104 +281,6 @@
"label": "Error Log",
"read_only": 1
},
{
"fieldname": "configuration_section",
"fieldtype": "Section Break",
"label": "Operation"
},
{
"default": "0",
"depends_on": "track_operations",
"fieldname": "track_semi_finished_goods",
"fieldtype": "Check",
"label": "Track Semi Finished Goods"
},
{
"fieldname": "column_break_obzr",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "track_operations",
"fieldtype": "Check",
"label": "Track Operations"
},
{
"depends_on": "eval:doc.track_semi_finished_goods === 1",
"fieldname": "final_product_operation_section",
"fieldtype": "Section Break",
"label": "Final Product Operation & Workstation"
},
{
"fieldname": "column_break_xnlu",
"fieldtype": "Column Break"
},
{
"fieldname": "operation",
"fieldtype": "Link",
"label": "Operation",
"options": "Operation"
},
{
"fieldname": "operation_time",
"fieldtype": "Float",
"label": "Operation Time (in mins)"
},
{
"fieldname": "workstation",
"fieldtype": "Link",
"label": "Workstation",
"options": "Workstation"
},
{
"fieldname": "workstation_type",
"fieldtype": "Link",
"label": "Workstation Type",
"options": "Workstation Type"
},
{
"depends_on": "eval:!doc.backflush_from_wip_warehouse",
"fieldname": "source_warehouse",
"fieldtype": "Link",
"label": "Source Warehouse",
"options": "Warehouse"
},
{
"depends_on": "eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"label": "Work In Progress Warehouse",
"options": "Warehouse"
},
{
"fieldname": "fg_warehouse",
"fieldtype": "Link",
"label": "Finished Good Warehouse",
"options": "Warehouse"
},
{
"depends_on": "eval:doc.track_semi_finished_goods === 1",
"fieldname": "final_product_warehouse_section",
"fieldtype": "Section Break",
"label": "Final Product Warehouse"
},
{
"default": "0",
"fieldname": "skip_material_transfer",
"fieldtype": "Check",
"label": "Skip Material Transfer"
},
{
"default": "0",
"depends_on": "eval:doc.skip_material_transfer",
"fieldname": "backflush_from_wip_warehouse",
"fieldtype": "Check",
"label": "Backflush Materials From WIP Warehouse"
},
{
"fieldname": "column_break_buha",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_xvld",
"fieldtype": "Section Break",
@@ -417,7 +302,7 @@
"link_fieldname": "bom_creator"
}
],
"modified": "2024-09-26 17:07:32.111198",
"modified": "2024-11-25 16:41:03.047835",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Creator",

View File

@@ -44,20 +44,16 @@ class BOMCreator(Document):
from erpnext.manufacturing.doctype.bom_creator_item.bom_creator_item import BOMCreatorItem
amended_from: DF.Link | None
backflush_from_wip_warehouse: DF.Check
buying_price_list: DF.Link | None
company: DF.Link
conversion_rate: DF.Float
currency: DF.Link
default_warehouse: DF.Link | None
error_log: DF.Text | None
fg_warehouse: DF.Link | None
item_code: DF.Link
item_group: DF.Link | None
item_name: DF.Data | None
items: DF.Table[BOMCreatorItem]
operation: DF.Link | None
operation_time: DF.Float
plc_conversion_rate: DF.Float
price_list_currency: DF.Link | None
project: DF.Link | None
@@ -67,15 +63,8 @@ class BOMCreator(Document):
rm_cost_as_per: DF.Literal["Valuation Rate", "Last Purchase Rate", "Price List"]
routing: DF.Link | None
set_rate_based_on_warehouse: DF.Check
skip_material_transfer: DF.Check
source_warehouse: DF.Link | None
status: DF.Literal["Draft", "Submitted", "In Progress", "Completed", "Failed", "Cancelled"]
track_operations: DF.Check
track_semi_finished_goods: DF.Check
uom: DF.Link | None
wip_warehouse: DF.Link | None
workstation: DF.Link | None
workstation_type: DF.Link | None
# end: auto-generated types
def before_save(self):
@@ -272,15 +261,9 @@ class BOMCreator(Document):
try:
for d in reverse_tree:
if self.track_operations and self.track_semi_finished_goods and final_product == d:
continue
fg_item_data = production_item_wise_rm.get(d).fg_item_data
self.create_bom(fg_item_data, production_item_wise_rm)
if self.track_operations and self.track_semi_finished_goods:
self.make_bom_for_final_product(production_item_wise_rm)
frappe.msgprint(_("BOMs created successfully"))
except Exception:
traceback = frappe.get_traceback(with_context=True)
@@ -293,81 +276,6 @@ class BOMCreator(Document):
frappe.msgprint(_("BOMs creation failed"))
def make_bom_for_final_product(self, production_item_wise_rm):
bom = frappe.new_doc("BOM")
bom.update(
{
"item": self.item_code,
"bom_type": "Production",
"quantity": self.qty,
"allow_alternative_item": 1,
"bom_creator": self.name,
"bom_creator_item": self.name,
"rm_cost_as_per": "Manual",
"with_operations": 1,
"track_semi_finished_goods": 1,
}
)
for field in BOM_FIELDS:
if self.get(field):
bom.set(field, self.get(field))
for item in self.items:
if not item.is_expandable or not item.operation:
continue
bom.append(
"operations",
{
"operation": item.operation,
"workstation": item.workstation,
"source_warehouse": item.source_warehouse,
"wip_warehouse": item.wip_warehouse,
"fg_warehouse": item.fg_warehouse,
"finished_good": item.item_code,
"finished_good_qty": item.qty,
"bom_no": production_item_wise_rm[(item.item_code, item.name)].bom_no,
"workstation_type": item.workstation_type,
"time_in_mins": item.operation_time,
"is_subcontracted": item.is_subcontracted,
"skip_material_transfer": item.skip_material_transfer,
"backflush_from_wip_warehouse": item.backflush_from_wip_warehouse,
},
)
operation_row = bom.append(
"operations",
{
"operation": self.operation,
"time_in_mins": self.operation_time,
"workstation": self.workstation,
"workstation_type": self.workstation_type,
"finished_good": self.item_code,
"finished_good_qty": self.qty,
"source_warehouse": self.source_warehouse,
"wip_warehouse": self.wip_warehouse,
"fg_warehouse": self.fg_warehouse,
"skip_material_transfer": self.skip_material_transfer,
"backflush_from_wip_warehouse": self.backflush_from_wip_warehouse,
},
)
final_product = (self.item_code, self.name)
items = production_item_wise_rm.get(final_product).get("items")
bom.set_materials_based_on_operation_bom()
for item in items:
item_args = {"operation_row_id": operation_row.idx}
for field in BOM_ITEM_FIELDS:
item_args[field] = item.get(field)
bom.append("items", item_args)
bom.save(ignore_permissions=True)
bom.submit()
def create_bom(self, row, production_item_wise_rm):
bom_creator_item = row.name if row.name != self.name else ""
if frappe.db.exists(
@@ -393,25 +301,7 @@ class BOMCreator(Document):
}
)
if self.track_operations and not self.track_semi_finished_goods:
if row.item_code == self.item_code:
bom.with_operations = 1
bom.transfer_material_against = "Work Order"
for item in self.items:
if not item.operation:
continue
bom.append(
"operations",
{
"operation": item.operation,
"workstation_type": item.workstation_type,
"workstation": item.workstation,
"time_in_mins": item.operation_time,
},
)
elif row.item_code == self.item_code and self.routing:
if row.item_code == self.item_code and (self.routing or self.has_operations()):
bom.routing = self.routing
bom.with_operations = 1
bom.transfer_material_against = "Work Order"
@@ -447,6 +337,13 @@ class BOMCreator(Document):
production_item_wise_rm[(row.item_code, row.name)].bom_no = bom.name
def has_operations(self):
for row in self.items:
if row.operation:
return True
return False
@frappe.whitelist()
def get_default_bom(self, item_code) -> str:
return frappe.get_cached_value("Item", item_code, "default_bom")
@@ -562,14 +459,6 @@ def add_sub_assembly(**kwargs):
"is_expandable": 1,
"stock_uom": item_info.stock_uom,
"operation": bom_item.operation,
"workstation_type": bom_item.workstation_type,
"operation_time": bom_item.operation_time,
"is_subcontracted": bom_item.is_subcontracted,
"workstation": bom_item.workstation,
"source_warehouse": bom_item.source_warehouse,
"wip_warehouse": bom_item.wip_warehouse,
"fg_warehouse": bom_item.fg_warehouse,
"skip_material_transfer": bom_item.skip_material_transfer,
},
)
@@ -579,20 +468,6 @@ def add_sub_assembly(**kwargs):
parent_row_no = [row.idx for row in doc.items if row.name == kwargs.fg_reference_id]
if parent_row_no:
parent_row_no = parent_row_no[0]
doc.items[parent_row_no - 1].update(
{
"operation": bom_item.operation,
"workstation_type": bom_item.workstation_type,
"operation_time": bom_item.operation_time,
"is_subcontracted": bom_item.is_subcontracted,
"workstation": bom_item.workstation,
"source_warehouse": bom_item.source_warehouse,
"wip_warehouse": bom_item.wip_warehouse,
"fg_warehouse": bom_item.fg_warehouse,
"skip_material_transfer": bom_item.skip_material_transfer,
"backflush_from_wip_warehouse": bom_item.backflush_from_wip_warehouse,
}
)
for row in bom_item.get("items"):
row = frappe._dict(row)

View File

@@ -17,17 +17,7 @@
"is_subcontracted",
"operation_section",
"operation",
"operation_time",
"column_break_cbnk",
"workstation_type",
"workstation",
"warehouse_section",
"skip_material_transfer",
"backflush_from_wip_warehouse",
"source_warehouse",
"column_break_xutc",
"wip_warehouse",
"fg_warehouse",
"description_section",
"description",
"quantity_and_rate_section",
@@ -87,13 +77,6 @@
"options": "Item",
"reqd": 1
},
{
"depends_on": "eval:doc.skip_material_transfer && !doc.backflush_from_wip_warehouse",
"fieldname": "source_warehouse",
"fieldtype": "Link",
"label": "Source Warehouse",
"options": "Warehouse"
},
{
"columns": 1,
"default": "0",
@@ -256,59 +239,6 @@
"fieldname": "column_break_cbnk",
"fieldtype": "Column Break"
},
{
"fieldname": "workstation_type",
"fieldtype": "Link",
"label": "Workstation Type",
"options": "Workstation Type"
},
{
"description": "In Mins",
"fieldname": "operation_time",
"fieldtype": "Int",
"label": "Operation Time"
},
{
"fieldname": "workstation",
"fieldtype": "Link",
"label": "Workstation",
"options": "Workstation"
},
{
"fieldname": "warehouse_section",
"fieldtype": "Section Break",
"label": "Warehouse"
},
{
"depends_on": "eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
"fieldname": "wip_warehouse",
"fieldtype": "Link",
"label": "Work In Progress Warehouse",
"options": "Warehouse"
},
{
"fieldname": "column_break_xutc",
"fieldtype": "Column Break"
},
{
"fieldname": "fg_warehouse",
"fieldtype": "Link",
"label": "Finished Good Warehouse",
"options": "Warehouse"
},
{
"default": "0",
"fieldname": "skip_material_transfer",
"fieldtype": "Check",
"label": "Skip Material Transfer"
},
{
"default": "0",
"depends_on": "eval:doc.skip_material_transfer",
"fieldname": "backflush_from_wip_warehouse",
"fieldtype": "Check",
"label": "Backflush Materials From WIP Warehouse"
},
{
"default": "0",
"fieldname": "is_subcontracted",
@@ -320,7 +250,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2024-06-03 18:45:24.339532",
"modified": "2024-11-25 18:13:34.542391",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Creator Item",

View File

@@ -15,7 +15,6 @@ class BOMCreatorItem(Document):
from frappe.types import DF
amount: DF.Currency
backflush_from_wip_warehouse: DF.Check
base_amount: DF.Currency
base_rate: DF.Currency
bom_created: DF.Check
@@ -24,7 +23,6 @@ class BOMCreatorItem(Document):
do_not_explode: DF.Check
fg_item: DF.Link
fg_reference_id: DF.Data | None
fg_warehouse: DF.Link | None
instruction: DF.SmallText | None
is_expandable: DF.Check
is_subcontracted: DF.Check
@@ -32,22 +30,16 @@ class BOMCreatorItem(Document):
item_group: DF.Link | None
item_name: DF.Data | None
operation: DF.Link | None
operation_time: DF.Int
parent: DF.Data
parent_row_no: DF.Data | None
parentfield: DF.Data
parenttype: DF.Data
qty: DF.Float
rate: DF.Currency
skip_material_transfer: DF.Check
source_warehouse: DF.Link | None
sourced_by_supplier: DF.Check
stock_qty: DF.Float
stock_uom: DF.Link | None
uom: DF.Link | None
wip_warehouse: DF.Link | None
workstation: DF.Link | None
workstation_type: DF.Link | None
# end: auto-generated types
pass

View File

@@ -1157,6 +1157,9 @@ class JobCard(Document):
for employee in kwargs.employees:
kwargs.employee = employee.get("employee")
if kwargs.from_time and not kwargs.to_time:
if kwargs.qty:
kwargs.completed_qty = kwargs.qty
row = self.append("time_logs", kwargs)
row.db_update()
self.db_set("status", "Work In Progress")
@@ -1223,6 +1226,10 @@ class JobCard(Document):
if kwargs.auto_submit:
self.submit()
if not self.finished_good:
return
self.make_stock_entry_for_semi_fg_item(kwargs.auto_submit)
frappe.msgprint(
_("Job Card {0} has been completed").format(get_link_to_form("Job Card", self.name))

View File

@@ -154,7 +154,7 @@
{
"fieldname": "raw_materials_consumption_section",
"fieldtype": "Section Break",
"label": "Raw Materials Consumption "
"label": "Raw Materials Consumption"
},
{
"fieldname": "column_break_16",
@@ -267,4 +267,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -53,3 +53,24 @@ class Routing(Document):
)
sequence_id = row.sequence_id
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_operations(doctype, txt, searchfield, start, page_len, filters):
query_filters = {}
if txt:
query_filters = {"operation": ["like", f"%{txt}%"]}
if filters.get("routing"):
query_filters["parent"] = filters.get("routing")
return frappe.get_all(
"BOM Operation",
fields=["operation"],
filters=query_filters,
start=start,
page_length=page_len,
as_list=1,
)

View File

@@ -1645,6 +1645,7 @@ def create_job_card(work_order, row, enable_capacity_planning=False, auto_create
"sequence_id": row.get("sequence_id"),
"hour_rate": row.get("hour_rate"),
"serial_no": row.get("serial_no"),
"time_required": row.get("time_in_mins"),
"source_warehouse": row.get("source_warehouse"),
"target_warehouse": row.get("fg_warehouse"),
"wip_warehouse": work_order.wip_warehouse or row.get("wip_warehouse"),

View File

@@ -16,6 +16,7 @@
"include_item_in_manufacturing",
"qty_section",
"required_qty",
"stock_uom",
"rate",
"amount",
"column_break_11",
@@ -145,11 +146,19 @@
"fieldtype": "Int",
"label": "Operation Row Id",
"read_only": 1
},
{
"fetch_from": "item_code.stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"label": "Stock UOM",
"options": "UOM",
"read_only": 1
}
],
"istable": 1,
"links": [],
"modified": "2024-03-27 13:12:00.429838",
"modified": "2024-11-19 15:48:16.823384",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Item",

View File

@@ -25,6 +25,7 @@ class WorkOrderItem(Document):
item_code: DF.Link | None
item_name: DF.Data | None
operation: DF.Link | None
operation_row_id: DF.Int
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
@@ -32,6 +33,7 @@ class WorkOrderItem(Document):
required_qty: DF.Float
returned_qty: DF.Float
source_warehouse: DF.Link | None
stock_uom: DF.Link | None
transferred_qty: DF.Float
# end: auto-generated types

View File

@@ -509,6 +509,7 @@ def update_job_card(job_card, method, **kwargs):
if kwargs.qty and isinstance(kwargs.qty, str):
kwargs.qty = flt(kwargs.qty)
print(method)
doc = frappe.get_doc("Job Card", job_card)
doc.run_method(method, **kwargs)

View File

@@ -3,6 +3,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Work Order",
"dynamic_filters_json": "[[\"Work Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Work Order\",\"status\",\"=\",\"Completed\"],[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
"function": "Count",
"idx": 0,

View File

@@ -3,6 +3,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Work Order",
"dynamic_filters_json": "[[\"Work Order\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Work Order\",\"docstatus\",\"=\",1],[\"Work Order\",\"creation\",\"between\",[\"2020-06-08\",\"2020-07-08\"]]]",
"function": "Count",
"idx": 0,

View File

@@ -3,6 +3,7 @@
"docstatus": 0,
"doctype": "Number Card",
"document_type": "Job Card",
"dynamic_filters_json": "[[\"Job Card\",\"company\",\"=\",\"frappe.defaults.get_user_default(\\\"Company\\\")\"]]",
"filters_json": "[[\"Job Card\",\"status\",\"!=\",\"Completed\"],[\"Job Card\",\"docstatus\",\"=\",1]]",
"function": "Count",
"idx": 0,

View File

@@ -389,3 +389,4 @@ erpnext.patches.v15_0.update_sub_voucher_type_in_gl_entries
erpnext.patches.v15_0.update_task_assignee_email_field_in_asset_maintenance_log
erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter
erpnext.patches.v15_0.migrate_old_item_wise_tax_detail_data_format
erpnext.patches.v14_0.update_stock_uom_in_work_order_item

View File

@@ -0,0 +1,15 @@
import frappe
def execute():
frappe.db.sql(
"""
UPDATE
`tabWork Order Item`, `tabItem`
SET
`tabWork Order Item`.stock_uom = `tabItem`.stock_uom
WHERE
`tabWork Order Item`.item_code = `tabItem`.name
AND `tabWork Order Item`.docstatus = 1
"""
)

View File

@@ -169,10 +169,14 @@ class Timesheet(Document):
task.save()
tasks.append(data.task)
elif data.project and data.project not in projects:
frappe.get_doc("Project", data.project).update_project()
if data.project and data.project not in projects:
projects.append(data.project)
for project in projects:
project_doc = frappe.get_doc("Project", project)
project_doc.update_project()
project_doc.save()
def validate_dates(self):
for time_log in self.time_logs:
time_log.validate_dates()

View File

@@ -292,7 +292,7 @@ class BOMConfigurator {
});
}
get_sub_assembly_modal_fields(view, is_root = false, read_only = false, show_operations_fields = false) {
get_sub_assembly_modal_fields(view, is_root = false, read_only = false) {
let fields = [
{
label: __("Sub Assembly Item"),
@@ -320,7 +320,7 @@ class BOMConfigurator {
},
];
if (this.frm.doc.track_operations && (is_root || show_operations_fields)) {
if (is_root) {
fields.push(
...[
{ fieldtype: "Section Break" },
@@ -329,105 +329,18 @@ class BOMConfigurator {
fieldname: "operation",
fieldtype: "Link",
options: "Operation",
reqd: 1,
},
{
label: __("Operation Time"),
fieldname: "operation_time",
fieldtype: "Int",
reqd: 1,
},
{
label: __("Is Subcontracted"),
fieldname: "is_subcontracted",
fieldtype: "Check",
},
{ fieldtype: "Column Break" },
{
label: __("Workstation Type"),
fieldname: "workstation_type",
fieldtype: "Link",
options: "Workstation Type",
},
{
label: __("Workstation"),
fieldname: "workstation",
fieldtype: "Link",
options: "Workstation",
},
]
);
get_query() {
let doc = view.events.frm.doc;
if (this.frm.doc.track_semi_finished_goods) {
fields.push(
...[
{ label: __("Default Warehouse"), fieldtype: "Section Break", collapsible: 1 },
{
label: __("Skip Material Transfer"),
fieldname: "skip_material_transfer",
fieldtype: "Check",
},
{
label: __("Backflush Materials From WIP"),
fieldname: "backflush_from_wip_warehouse",
fieldtype: "Check",
depends_on: "eval:doc.skip_material_transfer",
},
{
label: __("Source Warehouse"),
fieldname: "source_warehouse",
fieldtype: "Link",
options: "Warehouse",
depends_on: "eval:!doc.backflush_from_wip_warehouse",
get_query() {
if (doc.routing) {
return {
query: "erpnext.manufacturing.doctype.routing.routing.get_operations",
filters: {
company: view.events.frm.doc.company,
routing: doc.routing,
},
};
},
}
},
{ fieldtype: "Column Break" },
{
label: __("Work In Progress Warehouse"),
fieldname: "wip_warehouse",
fieldtype: "Link",
options: "Warehouse",
depends_on:
"eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: view.events.frm.doc.company,
},
};
},
},
{
label: __("Finished Good Warehouse"),
fieldname: "fg_warehouse",
fieldtype: "Link",
options: "Warehouse",
get_query() {
return {
filters: {
company: view.events.frm.doc.company,
},
};
},
},
]
);
}
} else if (this.frm.doc.routing && is_root) {
fields.push(
...[
{ fieldtype: "Section Break" },
{
label: __("Operation"),
fieldname: "operation",
fieldtype: "Link",
options: "Operation",
},
]
);
@@ -473,7 +386,7 @@ class BOMConfigurator {
convert_to_sub_assembly(node, view) {
let dialog = new frappe.ui.Dialog({
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, true, true),
fields: view.events.get_sub_assembly_modal_fields(view, node.is_root, true),
title: __("Add Sub Assembly"),
});
@@ -556,123 +469,6 @@ class BOMConfigurator {
let qty = node.data.qty || this.frm.doc.qty;
let fields = [{ label: __("Qty"), fieldname: "qty", default: qty, fieldtype: "Float", reqd: 1 }];
if (node.expandable && this.frm.doc.track_operations) {
let data = node.data.operation ? node.data : this.frm.doc;
fields = [
...fields,
...[
{ fieldtype: "Section Break" },
{
label: __("Operation"),
fieldname: "operation",
fieldtype: "Link",
options: "Operation",
default: data.operation,
},
{
label: __("Operation Time"),
fieldname: "operation_time",
fieldtype: "Float",
default: data.operation_time,
},
{
label: __("Is Subcontracted"),
fieldname: "is_subcontracted",
fieldtype: "Check",
hidden: node?.is_root || 0,
default: data.is_subcontracted,
},
{ fieldtype: "Column Break" },
{
label: __("Workstation Type"),
fieldname: "workstation_type",
fieldtype: "Link",
options: "Workstation Type",
default: data.workstation_type,
},
{
label: __("Workstation"),
fieldname: "workstation",
fieldtype: "Link",
options: "Workstation",
default: data.workstation,
get_query() {
let dialog = me.frm.edit_bom_dialog;
let workstation_type = dialog.get_value("workstation_type");
if (workstation_type) {
return {
filters: {
workstation_type: dialog.get_value("workstation_type"),
},
};
}
},
},
{ fieldtype: "Section Break" },
{
label: __("Skip Material Transfer"),
fieldname: "skip_material_transfer",
fieldtype: "Check",
default: data.skip_material_transfer,
},
{
label: __("Backflush Materials From WIP"),
fieldname: "backflush_from_wip_warehouse",
fieldtype: "Check",
depends_on: "eval:doc.skip_material_transfer",
default: data.backflush_from_wip_warehouse,
},
{
label: __("Source Warehouse"),
fieldname: "source_warehouse",
fieldtype: "Link",
options: "Warehouse",
default: data.source_warehouse,
depends_on: "eval:!doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: me.frm.doc.company,
},
};
},
},
{ fieldtype: "Column Break" },
{
label: __("Work In Progress Warehouse"),
fieldname: "wip_warehouse",
fieldtype: "Link",
options: "Warehouse",
default: data.wip_warehouse,
depends_on: "eval:!doc.skip_material_transfer || doc.backflush_from_wip_warehouse",
get_query() {
return {
filters: {
company: me.frm.doc.company,
},
};
},
},
{
label: __("Finished Good Warehouse"),
fieldname: "fg_warehouse",
fieldtype: "Link",
options: "Warehouse",
default: data.fg_warehouse,
get_query() {
return {
filters: {
company: me.frm.doc.company,
},
};
},
},
],
];
}
this.frm.edit_bom_dialog = frappe.prompt(
fields,
(data) => {

View File

@@ -336,6 +336,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
child.charge_type = "On Net Total";
child.account_head = tax;
child.rate = 0;
child.set_by_item_tax_template = true;
}
});
}
@@ -447,7 +448,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
get_current_tax_amount(item, tax, item_tax_map) {
var tax_rate = this._get_tax_rate(tax, item_tax_map);
var current_tax_amount = 0.0;
var current_net_amount = 0.0;
// To set row_id by default as previous row.
if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
@@ -462,20 +462,16 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
if(tax.charge_type == "Actual") {
// distribute the tax amount proportionally to each item row
var actual = flt(tax.tax_amount, precision("tax_amount", tax));
current_net_amount = item.net_amount
current_tax_amount = this.frm.doc.net_total ?
((item.net_amount / this.frm.doc.net_total) * actual) : 0.0;
} else if(tax.charge_type == "On Net Total") {
current_net_amount = item.net_amount
current_tax_amount = (tax_rate / 100.0) * item.net_amount;
} else if(tax.charge_type == "On Previous Row Amount") {
current_net_amount = this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount_for_current_item
current_tax_amount = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount_for_current_item;
} else if(tax.charge_type == "On Previous Row Total") {
current_net_amount = this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item
current_tax_amount = (tax_rate / 100.0) *
this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_for_current_item;
} else if (tax.charge_type == "On Item Quantity") {

View File

@@ -531,7 +531,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
child: item,
args: {
doc: me.frm.doc,
args: {
ctx: {
item_code: item.item_code,
barcode: item.barcode,
serial_no: item.serial_no,
@@ -751,6 +751,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
child.charge_type = "On Net Total";
child.account_head = tax;
child.rate = 0;
child.set_by_item_tax_template = true;
}
});
}
@@ -1381,7 +1382,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if(child.service_start_date) {
frappe.call({
"method": "erpnext.stock.get_item_details.calculate_service_end_date",
args: {"args": child},
args: {ctx: child},
callback: function(r) {
frappe.model.set_value(cdt, cdn, "service_end_date", r.message.service_end_date);
}
@@ -1548,7 +1549,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.call({
method: "erpnext.stock.get_item_details.get_batch_based_item_price",
args: {
params: params,
pctx: params,
item_code: row.item_code,
},
callback: function(r) {
@@ -1910,12 +1911,18 @@ 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, doc: me.frm.doc },
args: { ctx: args, doc: me.frm.doc },
callback: function(r) {
if (!r.exc) {
frappe.run_serially([
() => me.frm.set_value("price_list_currency", r.message.parent.price_list_currency),
() => me.frm.set_value("plc_conversion_rate", r.message.parent.plc_conversion_rate),
() => {
if (r.message.parent.price_list_currency)
me.frm.set_value("price_list_currency", r.message.parent.price_list_currency);
},
() => {
if (r.message.parent.plc_conversion_rate)
me.frm.set_value("plc_conversion_rate", r.message.parent.plc_conversion_rate);
},
() => {
if(args.items.length) {
me._set_values_for_item_list(r.message.children);
@@ -2076,7 +2083,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_tax_info",
args: {
company: me.frm.doc.company,
doc: me.frm.doc,
tax_category: cstr(me.frm.doc.tax_category),
item_codes: item_codes,
item_rates: item_rates,
@@ -2107,7 +2114,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_tax_map",
args: {
company: me.frm.doc.company,
doc: me.frm.doc,
item_tax_template: item.item_tax_template,
as_json: true
},
@@ -2527,7 +2534,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.call({
method: "erpnext.stock.get_item_details.get_blanket_order_details",
args: {
args:{
ctx:{
item_code: item.item_code,
customer: doc.customer,
supplier: doc.supplier,

View File

@@ -33,7 +33,7 @@ erpnext.financial_statements = {
return value;
} else if (frappe.query_report.get_filter_value("selected_view") == "Margin" && data) {
if (column.fieldname == "stub" && data.account_name == __("Income")) {
if (column.fieldname == "account" && data.account_name == __("Income")) {
//Taking the total income from each column (for all the financial years) as the base (100%)
this.baseData = row;
}
@@ -52,7 +52,7 @@ erpnext.financial_statements = {
}
}
if (data && column.fieldname == "stub") {
if (data && column.fieldname == "account") {
// first column
value = data.section_name || data.account_name || value;

View File

@@ -77,9 +77,13 @@ $.extend(erpnext.queries, {
},
company_address_query: function (doc) {
if (!doc.company) {
frappe.throw(__("Please set {0}", [__(frappe.meta.get_label(doc.doctype, "company", doc.name))]));
}
return {
query: "frappe.contacts.doctype.address.address.address_query",
filters: { is_your_company_address: 1, link_doctype: "Company", link_name: doc.company || "" },
filters: { link_doctype: "Company", link_name: doc.company },
};
},

View File

@@ -52,6 +52,7 @@ erpnext.sales_common = {
me.frm.set_query("customer_address", erpnext.queries.address_query);
me.frm.set_query("shipping_address_name", erpnext.queries.address_query);
me.frm.set_query("dispatch_address_name", erpnext.queries.dispatch_address_query);
me.frm.set_query("company_address", erpnext.queries.company_address_query);
erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);

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