mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-09 08:22:25 +00:00
Compare commits
128 Commits
mergify/bp
...
refactor-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2db2c8bce1 | ||
|
|
cabe7ea028 | ||
|
|
1b45ba81a5 | ||
|
|
d61cb9a4bf | ||
|
|
2dd2083463 | ||
|
|
d03dc1b855 | ||
|
|
123e3ef263 | ||
|
|
e094473c65 | ||
|
|
3f92a57d63 | ||
|
|
0fd50b5048 | ||
|
|
2ab7ec5437 | ||
|
|
6c5f57b22b | ||
|
|
f8655cb26a | ||
|
|
ea6e022fad | ||
|
|
ce27d37fc3 | ||
|
|
60a2d74d74 | ||
|
|
1da61a43e7 | ||
|
|
c66d57d490 | ||
|
|
2dd5699f6d | ||
|
|
37ceb09955 | ||
|
|
4983dac27b | ||
|
|
1baed9f46e | ||
|
|
2fcfd41eb6 | ||
|
|
48d9e9c362 | ||
|
|
e56a98d5ba | ||
|
|
bf28b2c771 | ||
|
|
52f0d67bae | ||
|
|
e5bfdf8cfb | ||
|
|
ca0e20b2cf | ||
|
|
d8b9aef14f | ||
|
|
112b4c705b | ||
|
|
eebd058891 | ||
|
|
e37a039660 | ||
|
|
84dcbe6639 | ||
|
|
bbab850135 | ||
|
|
1a1e2c7e01 | ||
|
|
efc22d5615 | ||
|
|
d8af140770 | ||
|
|
8e72e35a21 | ||
|
|
f4ad935f15 | ||
|
|
7e06e05584 | ||
|
|
026c5b76ff | ||
|
|
ae0ed1f56f | ||
|
|
a9485b91b0 | ||
|
|
f1bd6e492a | ||
|
|
2e04eeb661 | ||
|
|
6754f15487 | ||
|
|
4289804681 | ||
|
|
91955e27c3 | ||
|
|
e1c4d6e1e6 | ||
|
|
e9ea9ee57b | ||
|
|
e545c913b5 | ||
|
|
f244ad78df | ||
|
|
15c607306a | ||
|
|
367f56179b | ||
|
|
2a31807407 | ||
|
|
c3e5d6336c | ||
|
|
30f9b2b806 | ||
|
|
09641073e3 | ||
|
|
f0b9cb4019 | ||
|
|
160bb10db8 | ||
|
|
a10e175bc9 | ||
|
|
013a6fc6ec | ||
|
|
8e759c32c4 | ||
|
|
f9b8165385 | ||
|
|
fbdc40114b | ||
|
|
4eb32023f5 | ||
|
|
57293aa18a | ||
|
|
29762c4826 | ||
|
|
4e7725de66 | ||
|
|
4b53075f84 | ||
|
|
8d8070e8eb | ||
|
|
4d96a20205 | ||
|
|
f5cf417f53 | ||
|
|
93fef93a1d | ||
|
|
77cba04b13 | ||
|
|
c607e5f940 | ||
|
|
1737269358 | ||
|
|
9ea8bd7a3c | ||
|
|
d526be0394 | ||
|
|
3b9e226d93 | ||
|
|
cc571aca8f | ||
|
|
8bd7ad5bf0 | ||
|
|
a971417f25 | ||
|
|
9673bf85ec | ||
|
|
4ec23b5525 | ||
|
|
af2a622afb | ||
|
|
daabe847b0 | ||
|
|
4ab3499a17 | ||
|
|
a9bb6b9970 | ||
|
|
8b05f954c2 | ||
|
|
a245cc6b07 | ||
|
|
cb4b4e212c | ||
|
|
9d31bf7647 | ||
|
|
64833fccb4 | ||
|
|
b2f8b610fc | ||
|
|
76ea94d906 | ||
|
|
8a12104073 | ||
|
|
b21fb8f8b6 | ||
|
|
a2199db5e5 | ||
|
|
b3c558f0c0 | ||
|
|
7ff6901883 | ||
|
|
548dbb33eb | ||
|
|
ed54b9b8c7 | ||
|
|
8d4f2eeb07 | ||
|
|
3273b808a0 | ||
|
|
cdf098c193 | ||
|
|
6f9ea6422d | ||
|
|
a202f564b1 | ||
|
|
1d6b9b405f | ||
|
|
7ceb24fb4c | ||
|
|
fee79b9445 | ||
|
|
4820273595 | ||
|
|
b74f2896cd | ||
|
|
a712ff603b | ||
|
|
5122489790 | ||
|
|
3468f27e8b | ||
|
|
31a2fe4bd0 | ||
|
|
94703698c0 | ||
|
|
92d76b5a63 | ||
|
|
d8c20f4dd8 | ||
|
|
26dd896741 | ||
|
|
a1c88a6302 | ||
|
|
1fbb5203c6 | ||
|
|
41c7ce7cfb | ||
|
|
7f0e881894 | ||
|
|
475611697f | ||
|
|
c23af6af41 |
@@ -38,3 +38,7 @@ ec74a5e56617bbd76ac402451468fd4668af543d
|
||||
|
||||
# ruff formatting
|
||||
a308792ee7fda18a681e9181f4fd00b36385bc23
|
||||
|
||||
# noisy typing refactoring of get_item_details
|
||||
7b7211ac79c248a79ba8a999ff34e734d874c0ae
|
||||
d827ed21adc7b36047e247cbb0dc6388d048a7f9
|
||||
|
||||
16
CODEOWNERS
16
CODEOWNERS
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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"]},
|
||||
],
|
||||
}
|
||||
@@ -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}`];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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`")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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("'", ""),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
_(
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -79,7 +79,7 @@ class TestTaxesAndTotals(FrappeTestCase):
|
||||
"rate": 50,
|
||||
},
|
||||
)
|
||||
|
||||
self.doc.set_missing_item_details()
|
||||
calculate_taxes_and_totals(self.doc)
|
||||
|
||||
expected_values = {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
1622
erpnext/locale/ar.po
1622
erpnext/locale/ar.po
File diff suppressed because it is too large
Load Diff
1620
erpnext/locale/bs.po
1620
erpnext/locale/bs.po
File diff suppressed because it is too large
Load Diff
1624
erpnext/locale/de.po
1624
erpnext/locale/de.po
File diff suppressed because it is too large
Load Diff
1624
erpnext/locale/eo.po
1624
erpnext/locale/eo.po
File diff suppressed because it is too large
Load Diff
1624
erpnext/locale/es.po
1624
erpnext/locale/es.po
File diff suppressed because it is too large
Load Diff
1880
erpnext/locale/fa.po
1880
erpnext/locale/fa.po
File diff suppressed because it is too large
Load Diff
1622
erpnext/locale/fr.po
1622
erpnext/locale/fr.po
File diff suppressed because it is too large
Load Diff
1620
erpnext/locale/hu.po
1620
erpnext/locale/hu.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1620
erpnext/locale/pl.po
1620
erpnext/locale/pl.po
File diff suppressed because it is too large
Load Diff
1622
erpnext/locale/ru.po
1622
erpnext/locale/ru.po
File diff suppressed because it is too large
Load Diff
1790
erpnext/locale/sv.po
1790
erpnext/locale/sv.po
File diff suppressed because it is too large
Load Diff
1628
erpnext/locale/tr.po
1628
erpnext/locale/tr.po
File diff suppressed because it is too large
Load Diff
1622
erpnext/locale/zh.po
1622
erpnext/locale/zh.po
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,6 @@ frappe.ui.form.on("BOM", {
|
||||
item: row.finished_good,
|
||||
is_active: 1,
|
||||
docstatus: 1,
|
||||
track_semi_finished_goods: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
15
erpnext/patches/v14_0/update_stock_uom_in_work_order_item.py
Normal file
15
erpnext/patches/v14_0/update_stock_uom_in_work_order_item.py
Normal 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
|
||||
"""
|
||||
)
|
||||
@@ -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()
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user