mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 11:19:09 +00:00
Merge pull request #37043 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -49,6 +49,7 @@
|
|||||||
"column_break_21",
|
"column_break_21",
|
||||||
"start_date",
|
"start_date",
|
||||||
"section_break_33",
|
"section_break_33",
|
||||||
|
"pdf_name",
|
||||||
"subject",
|
"subject",
|
||||||
"column_break_28",
|
"column_break_28",
|
||||||
"cc_to",
|
"cc_to",
|
||||||
@@ -273,7 +274,7 @@
|
|||||||
"fieldname": "help_text",
|
"fieldname": "help_text",
|
||||||
"fieldtype": "HTML",
|
"fieldtype": "HTML",
|
||||||
"label": "Help Text",
|
"label": "Help Text",
|
||||||
"options": "<br>\n<h4>Note</h4>\n<ul>\n<li>\nYou can use <a href=\"https://jinja.palletsprojects.com/en/2.11.x/\" target=\"_blank\">Jinja tags</a> in <b>Subject</b> and <b>Body</b> fields for dynamic values.\n</li><li>\n All fields in this doctype are available under the <b>doc</b> object and all fields for the customer to whom the mail will go to is available under the <b>customer</b> object.\n</li></ul>\n<h4> Examples</h4>\n<!-- {% raw %} -->\n<ul>\n <li><b>Subject</b>:<br><br><pre><code>Statement Of Accounts for {{ customer.name }}</code></pre><br></li>\n <li><b>Body</b>: <br><br>\n<pre><code>Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.</code> </pre></li>\n</ul>\n<!-- {% endraw %} -->"
|
"options": "<br>\n<h4>Note</h4>\n<ul>\n<li>\nYou can use <a href=\"https://jinja.palletsprojects.com/en/2.11.x/\" target=\"_blank\">Jinja tags</a> in <b>Subject</b> and <b>Body</b> fields for dynamic values.\n</li><li>\n All fields in this doctype are available under the <b>doc</b> object and all fields for the customer to whom the mail will go to is available under the <b>customer</b> object.\n</li></ul>\n<h4> Examples</h4>\n<!-- {% raw %} -->\n<ul>\n <li><b>Subject</b>:<br><br><pre><code>Statement Of Accounts for {{ customer.customer_name }}</code></pre><br></li>\n <li><b>Body</b>: <br><br>\n<pre><code>Hello {{ customer.customer_name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.</code> </pre></li>\n</ul>\n<!-- {% endraw %} -->"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
@@ -368,10 +369,15 @@
|
|||||||
"fieldname": "based_on_payment_terms",
|
"fieldname": "based_on_payment_terms",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Based On Payment Terms"
|
"label": "Based On Payment Terms"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "pdf_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "PDF Name"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-23 10:13:15.051950",
|
"modified": "2023-08-28 12:59:53.071334",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts",
|
"name": "Process Statement Of Accounts",
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ class ProcessStatementOfAccounts(Document):
|
|||||||
if not self.subject:
|
if not self.subject:
|
||||||
self.subject = "Statement Of Accounts for {{ customer.customer_name }}"
|
self.subject = "Statement Of Accounts for {{ customer.customer_name }}"
|
||||||
if not self.body:
|
if not self.body:
|
||||||
self.body = "Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}."
|
if self.report == "General Ledger":
|
||||||
|
body_str = " from {{ doc.from_date }} to {{ doc.to_date }}."
|
||||||
|
else:
|
||||||
|
body_str = " until {{ doc.posting_date }}."
|
||||||
|
self.body = "Hello {{ customer.customer_name }},<br>PFA your Statement Of Accounts" + body_str
|
||||||
|
if not self.pdf_name:
|
||||||
|
self.pdf_name = "{{ customer.customer_name }}"
|
||||||
|
|
||||||
validate_template(self.subject)
|
validate_template(self.subject)
|
||||||
validate_template(self.body)
|
validate_template(self.body)
|
||||||
@@ -57,11 +63,6 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
|
|
||||||
filters = get_common_filters(doc)
|
filters = get_common_filters(doc)
|
||||||
|
|
||||||
if doc.report == "General Ledger":
|
|
||||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
|
||||||
else:
|
|
||||||
filters.update(get_ar_filters(doc, entry))
|
|
||||||
|
|
||||||
if doc.report == "General Ledger":
|
if doc.report == "General Ledger":
|
||||||
col, res = get_soa(filters)
|
col, res = get_soa(filters)
|
||||||
for x in [0, -2, -1]:
|
for x in [0, -2, -1]:
|
||||||
@@ -69,8 +70,11 @@ def get_report_pdf(doc, consolidated=True):
|
|||||||
if len(res) == 3:
|
if len(res) == 3:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
filters.update(get_ar_filters(doc, entry))
|
||||||
ar_res = get_ar_soa(filters)
|
ar_res = get_ar_soa(filters)
|
||||||
col, res = ar_res[0], ar_res[1]
|
col, res = ar_res[0], ar_res[1]
|
||||||
|
if not res:
|
||||||
|
continue
|
||||||
|
|
||||||
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
||||||
|
|
||||||
@@ -139,6 +143,7 @@ def get_ar_filters(doc, entry):
|
|||||||
return {
|
return {
|
||||||
"report_date": doc.posting_date if doc.posting_date else None,
|
"report_date": doc.posting_date if doc.posting_date else None,
|
||||||
"customer": entry.customer,
|
"customer": entry.customer,
|
||||||
|
"customer_name": entry.customer_name if entry.customer_name else None,
|
||||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||||
"sales_person": doc.sales_person if doc.sales_person else None,
|
"sales_person": doc.sales_person if doc.sales_person else None,
|
||||||
@@ -362,16 +367,20 @@ def download_statements(document_name):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def send_emails(document_name, from_scheduler=False):
|
def send_emails(document_name, from_scheduler=False, posting_date=None):
|
||||||
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
|
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
|
||||||
report = get_report_pdf(doc, consolidated=False)
|
report = get_report_pdf(doc, consolidated=False)
|
||||||
|
|
||||||
if report:
|
if report:
|
||||||
for customer, report_pdf in report.items():
|
for customer, report_pdf in report.items():
|
||||||
attachments = [{"fname": customer + ".pdf", "fcontent": report_pdf}]
|
context = get_context(customer, doc)
|
||||||
|
filename = frappe.render_template(doc.pdf_name, context)
|
||||||
|
attachments = [{"fname": filename + ".pdf", "fcontent": report_pdf}]
|
||||||
|
|
||||||
recipients, cc = get_recipients_and_cc(customer, doc)
|
recipients, cc = get_recipients_and_cc(customer, doc)
|
||||||
context = get_context(customer, doc)
|
if not recipients:
|
||||||
|
continue
|
||||||
|
|
||||||
subject = frappe.render_template(doc.subject, context)
|
subject = frappe.render_template(doc.subject, context)
|
||||||
message = frappe.render_template(doc.body, context)
|
message = frappe.render_template(doc.body, context)
|
||||||
|
|
||||||
@@ -390,7 +399,7 @@ def send_emails(document_name, from_scheduler=False):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if doc.enable_auto_email and from_scheduler:
|
if doc.enable_auto_email and from_scheduler:
|
||||||
new_to_date = getdate(today())
|
new_to_date = getdate(posting_date or today())
|
||||||
if doc.frequency == "Weekly":
|
if doc.frequency == "Weekly":
|
||||||
new_to_date = add_days(new_to_date, 7)
|
new_to_date = add_days(new_to_date, 7)
|
||||||
else:
|
else:
|
||||||
@@ -399,8 +408,11 @@ def send_emails(document_name, from_scheduler=False):
|
|||||||
doc.add_comment(
|
doc.add_comment(
|
||||||
"Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())
|
"Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())
|
||||||
)
|
)
|
||||||
doc.db_set("to_date", new_to_date, commit=True)
|
if doc.report == "General Ledger":
|
||||||
doc.db_set("from_date", new_from_date, commit=True)
|
doc.db_set("to_date", new_to_date, commit=True)
|
||||||
|
doc.db_set("from_date", new_from_date, commit=True)
|
||||||
|
else:
|
||||||
|
doc.db_set("posting_date", new_to_date, commit=True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -410,7 +422,8 @@ def send_emails(document_name, from_scheduler=False):
|
|||||||
def send_auto_email():
|
def send_auto_email():
|
||||||
selected = frappe.get_list(
|
selected = frappe.get_list(
|
||||||
"Process Statement Of Accounts",
|
"Process Statement Of Accounts",
|
||||||
filters={"to_date": format_date(today()), "enable_auto_email": 1},
|
filters={"enable_auto_email": 1},
|
||||||
|
or_filters={"to_date": format_date(today()), "posting_date": format_date(today())},
|
||||||
)
|
)
|
||||||
for entry in selected:
|
for entry in selected:
|
||||||
send_emails(entry.name, from_scheduler=True)
|
send_emails(entry.name, from_scheduler=True)
|
||||||
|
|||||||
@@ -8,9 +8,24 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<div id="header-html" class="hidden-pdf">
|
||||||
|
{% if letter_head.content %}
|
||||||
|
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||||
|
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div id="footer-html" class="visible-pdf">
|
||||||
|
{% if letter_head.footer %}
|
||||||
|
<div class="letter-head-footer">
|
||||||
|
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
|
||||||
|
{{ letter_head.footer }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{{ filters.customer }}
|
{{ filters.customer_name }}
|
||||||
</h4>
|
</h4>
|
||||||
<h6 class="text-center">
|
<h6 class="text-center">
|
||||||
{% if (filters.tax_id) %}
|
{% if (filters.tax_id) %}
|
||||||
@@ -341,4 +356,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if terms_and_conditions %}
|
||||||
|
<div>
|
||||||
|
{{ terms_and_conditions }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
|
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
|
||||||
|
|||||||
@@ -1,9 +1,42 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
|
||||||
# import frappe
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.utils import add_days, getdate, today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts import (
|
||||||
|
send_emails,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
|
|
||||||
class TestProcessStatementOfAccounts(unittest.TestCase):
|
class TestProcessStatementOfAccounts(unittest.TestCase):
|
||||||
pass
|
def setUp(self):
|
||||||
|
self.si = create_sales_invoice()
|
||||||
|
self.process_soa = create_process_soa()
|
||||||
|
|
||||||
|
def test_auto_email_for_process_soa_ar(self):
|
||||||
|
send_emails(self.process_soa.name, from_scheduler=True)
|
||||||
|
self.process_soa.load_from_db()
|
||||||
|
self.assertEqual(self.process_soa.posting_date, getdate(add_days(today(), 7)))
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
||||||
|
|
||||||
|
|
||||||
|
def create_process_soa():
|
||||||
|
frappe.delete_doc_if_exists("Process Statement Of Accounts", "Test Process SOA")
|
||||||
|
process_soa = frappe.new_doc("Process Statement Of Accounts")
|
||||||
|
soa_dict = {
|
||||||
|
"name": "Test Process SOA",
|
||||||
|
"company": "_Test Company",
|
||||||
|
}
|
||||||
|
process_soa.update(soa_dict)
|
||||||
|
process_soa.set("customers", [{"customer": "_Test Customer"}])
|
||||||
|
process_soa.enable_auto_email = 1
|
||||||
|
process_soa.frequency = "Weekly"
|
||||||
|
process_soa.report = "Accounts Receivable"
|
||||||
|
process_soa.save()
|
||||||
|
return process_soa
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ def get_conditions(filters):
|
|||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
for opts in (
|
for opts in (
|
||||||
("company", " and company=%(company)s"),
|
("company", " and `tabPurchase Invoice`.company=%(company)s"),
|
||||||
("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
|
("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
|
||||||
("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
|
("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
|
||||||
("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
|
("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ def get_conditions(filters, additional_conditions=None):
|
|||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
for opts in (
|
for opts in (
|
||||||
("company", " and company=%(company)s"),
|
("company", " and `tabSales Invoice`.company=%(company)s"),
|
||||||
("customer", " and `tabSales Invoice`.customer = %(customer)s"),
|
("customer", " and `tabSales Invoice`.customer = %(customer)s"),
|
||||||
("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
|
("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
|
||||||
("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
|
("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
|
from frappe.utils import today
|
||||||
|
|
||||||
|
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||||
|
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||||
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
from erpnext.accounts.doctype.tax_withholding_category.test_tax_withholding_category import (
|
||||||
|
create_tax_withholding_category,
|
||||||
|
)
|
||||||
|
from erpnext.accounts.report.tds_payable_monthly.tds_payable_monthly import execute
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
|
class TestTdsPayableMonthly(AccountsTestMixin, FrappeTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.create_company()
|
||||||
|
self.clear_old_entries()
|
||||||
|
create_tax_accounts()
|
||||||
|
create_tcs_category()
|
||||||
|
|
||||||
|
def test_tax_withholding_for_customers(self):
|
||||||
|
si = create_sales_invoice(rate=1000)
|
||||||
|
pe = create_tcs_payment_entry()
|
||||||
|
filters = frappe._dict(
|
||||||
|
company="_Test Company", party_type="Customer", from_date=today(), to_date=today()
|
||||||
|
)
|
||||||
|
result = execute(filters)[1]
|
||||||
|
expected_values = [
|
||||||
|
[pe.name, "TCS", 0.075, 2550, 0.53, 2550.53],
|
||||||
|
[si.name, "TCS", 0.075, 1000, 0.53, 1000.53],
|
||||||
|
]
|
||||||
|
self.check_expected_values(result, expected_values)
|
||||||
|
|
||||||
|
def check_expected_values(self, result, expected_values):
|
||||||
|
for i in range(len(result)):
|
||||||
|
voucher = frappe._dict(result[i])
|
||||||
|
voucher_expected_values = expected_values[i]
|
||||||
|
self.assertEqual(voucher.ref_no, voucher_expected_values[0])
|
||||||
|
self.assertEqual(voucher.section_code, voucher_expected_values[1])
|
||||||
|
self.assertEqual(voucher.rate, voucher_expected_values[2])
|
||||||
|
self.assertEqual(voucher.base_total, voucher_expected_values[3])
|
||||||
|
self.assertEqual(voucher.tax_amount, voucher_expected_values[4])
|
||||||
|
self.assertEqual(voucher.grand_total, voucher_expected_values[5])
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.clear_old_entries()
|
||||||
|
|
||||||
|
|
||||||
|
def create_tax_accounts():
|
||||||
|
account_names = ["TCS", "TDS"]
|
||||||
|
for account in account_names:
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"company": "_Test Company",
|
||||||
|
"account_name": account,
|
||||||
|
"parent_account": "Duties and Taxes - _TC",
|
||||||
|
"report_type": "Balance Sheet",
|
||||||
|
"root_type": "Liability",
|
||||||
|
}
|
||||||
|
).insert(ignore_if_duplicate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_tcs_category():
|
||||||
|
fiscal_year = get_fiscal_year(today(), company="_Test Company")
|
||||||
|
from_date = fiscal_year[1]
|
||||||
|
to_date = fiscal_year[2]
|
||||||
|
|
||||||
|
tax_category = create_tax_withholding_category(
|
||||||
|
category_name="TCS",
|
||||||
|
rate=0.075,
|
||||||
|
from_date=from_date,
|
||||||
|
to_date=to_date,
|
||||||
|
account="TCS - _TC",
|
||||||
|
cumulative_threshold=300,
|
||||||
|
)
|
||||||
|
|
||||||
|
customer = frappe.get_doc("Customer", "_Test Customer")
|
||||||
|
customer.tax_withholding_category = "TCS"
|
||||||
|
customer.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_tcs_payment_entry():
|
||||||
|
payment_entry = create_payment_entry(
|
||||||
|
payment_type="Receive",
|
||||||
|
party_type="Customer",
|
||||||
|
party="_Test Customer",
|
||||||
|
paid_from="Debtors - _TC",
|
||||||
|
paid_to="Cash - _TC",
|
||||||
|
paid_amount=2550,
|
||||||
|
)
|
||||||
|
|
||||||
|
payment_entry.append(
|
||||||
|
"taxes",
|
||||||
|
{
|
||||||
|
"account_head": "TCS - _TC",
|
||||||
|
"charge_type": "Actual",
|
||||||
|
"tax_amount": 0.53,
|
||||||
|
"add_deduct_tax": "Add",
|
||||||
|
"description": "Test",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
payment_entry.submit()
|
||||||
|
return payment_entry
|
||||||
@@ -40,6 +40,7 @@ class Asset(AccountsController):
|
|||||||
self.validate_item()
|
self.validate_item()
|
||||||
self.validate_cost_center()
|
self.validate_cost_center()
|
||||||
self.set_missing_values()
|
self.set_missing_values()
|
||||||
|
self.validate_finance_books()
|
||||||
if not self.split_from:
|
if not self.split_from:
|
||||||
self.prepare_depreciation_data()
|
self.prepare_depreciation_data()
|
||||||
self.validate_gross_and_purchase_amount()
|
self.validate_gross_and_purchase_amount()
|
||||||
@@ -206,6 +207,27 @@ class Asset(AccountsController):
|
|||||||
finance_books = get_item_details(self.item_code, self.asset_category)
|
finance_books = get_item_details(self.item_code, self.asset_category)
|
||||||
self.set("finance_books", finance_books)
|
self.set("finance_books", finance_books)
|
||||||
|
|
||||||
|
def validate_finance_books(self):
|
||||||
|
if not self.calculate_depreciation or len(self.finance_books) == 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
finance_books = set()
|
||||||
|
|
||||||
|
for d in self.finance_books:
|
||||||
|
if d.finance_book in finance_books:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{}: Please use a different Finance Book.").format(d.idx),
|
||||||
|
title=_("Duplicate Finance Book"),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
finance_books.add(d.finance_book)
|
||||||
|
|
||||||
|
if not d.finance_book:
|
||||||
|
frappe.throw(
|
||||||
|
_("Row #{}: Finance Book should not be empty since you're using multiple.").format(d.idx),
|
||||||
|
title=_("Missing Finance Book"),
|
||||||
|
)
|
||||||
|
|
||||||
def validate_asset_values(self):
|
def validate_asset_values(self):
|
||||||
if not self.asset_category:
|
if not self.asset_category:
|
||||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
@@ -1363,26 +1385,41 @@ def get_straight_line_or_manual_depr_amount(
|
|||||||
daily_depr_amount = (
|
daily_depr_amount = (
|
||||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||||
) / date_diff(
|
) / date_diff(
|
||||||
add_months(
|
get_last_day(
|
||||||
row.depreciation_start_date,
|
add_months(
|
||||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
row.depreciation_start_date,
|
||||||
* row.frequency_of_depreciation,
|
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||||
),
|
* row.frequency_of_depreciation,
|
||||||
add_months(
|
|
||||||
row.depreciation_start_date,
|
|
||||||
flt(
|
|
||||||
row.total_number_of_depreciations
|
|
||||||
- asset.number_of_depreciations_booked
|
|
||||||
- number_of_pending_depreciations
|
|
||||||
)
|
)
|
||||||
* row.frequency_of_depreciation,
|
),
|
||||||
|
add_days(
|
||||||
|
get_last_day(
|
||||||
|
add_months(
|
||||||
|
row.depreciation_start_date,
|
||||||
|
flt(
|
||||||
|
row.total_number_of_depreciations
|
||||||
|
- asset.number_of_depreciations_booked
|
||||||
|
- number_of_pending_depreciations
|
||||||
|
- 1
|
||||||
|
)
|
||||||
|
* row.frequency_of_depreciation,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
|
||||||
from_date = add_months(
|
to_date = get_last_day(
|
||||||
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
|
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
||||||
)
|
)
|
||||||
return daily_depr_amount * date_diff(to_date, from_date)
|
from_date = add_days(
|
||||||
|
get_last_day(
|
||||||
|
add_months(row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation)
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
|
||||||
else:
|
else:
|
||||||
return (
|
return (
|
||||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||||
@@ -1395,18 +1432,29 @@ def get_straight_line_or_manual_depr_amount(
|
|||||||
- flt(asset.opening_accumulated_depreciation)
|
- flt(asset.opening_accumulated_depreciation)
|
||||||
- flt(row.expected_value_after_useful_life)
|
- flt(row.expected_value_after_useful_life)
|
||||||
) / date_diff(
|
) / date_diff(
|
||||||
add_months(
|
get_last_day(
|
||||||
row.depreciation_start_date,
|
add_months(
|
||||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
row.depreciation_start_date,
|
||||||
* row.frequency_of_depreciation,
|
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||||
|
* row.frequency_of_depreciation,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
add_days(
|
||||||
|
get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1
|
||||||
),
|
),
|
||||||
row.depreciation_start_date,
|
|
||||||
)
|
)
|
||||||
to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
|
||||||
from_date = add_months(
|
to_date = get_last_day(
|
||||||
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
|
add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
||||||
)
|
)
|
||||||
return daily_depr_amount * date_diff(to_date, from_date)
|
from_date = add_days(
|
||||||
|
get_last_day(
|
||||||
|
add_months(row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation)
|
||||||
|
),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
return daily_depr_amount * (date_diff(to_date, from_date) + 1)
|
||||||
else:
|
else:
|
||||||
return (
|
return (
|
||||||
flt(asset.gross_purchase_amount)
|
flt(asset.gross_purchase_amount)
|
||||||
|
|||||||
@@ -743,18 +743,18 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
)
|
)
|
||||||
|
|
||||||
expected_schedules = [
|
expected_schedules = [
|
||||||
["2023-01-31", 1019.18, 1019.18],
|
["2023-01-31", 1021.98, 1021.98],
|
||||||
["2023-02-28", 920.55, 1939.73],
|
["2023-02-28", 923.08, 1945.06],
|
||||||
["2023-03-31", 1019.18, 2958.91],
|
["2023-03-31", 1021.98, 2967.04],
|
||||||
["2023-04-30", 986.3, 3945.21],
|
["2023-04-30", 989.01, 3956.05],
|
||||||
["2023-05-31", 1019.18, 4964.39],
|
["2023-05-31", 1021.98, 4978.03],
|
||||||
["2023-06-30", 986.3, 5950.69],
|
["2023-06-30", 989.01, 5967.04],
|
||||||
["2023-07-31", 1019.18, 6969.87],
|
["2023-07-31", 1021.98, 6989.02],
|
||||||
["2023-08-31", 1019.18, 7989.05],
|
["2023-08-31", 1021.98, 8011.0],
|
||||||
["2023-09-30", 986.3, 8975.35],
|
["2023-09-30", 989.01, 9000.01],
|
||||||
["2023-10-31", 1019.18, 9994.53],
|
["2023-10-31", 1021.98, 10021.99],
|
||||||
["2023-11-30", 986.3, 10980.83],
|
["2023-11-30", 989.01, 11011.0],
|
||||||
["2023-12-31", 1019.17, 12000.0],
|
["2023-12-31", 989.0, 12000.0],
|
||||||
]
|
]
|
||||||
|
|
||||||
schedules = [
|
schedules = [
|
||||||
@@ -1332,6 +1332,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset.append(
|
asset.append(
|
||||||
"finance_books",
|
"finance_books",
|
||||||
{
|
{
|
||||||
|
"finance_book": "Test Finance Book 1",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"frequency_of_depreciation": 1,
|
"frequency_of_depreciation": 1,
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
@@ -1342,6 +1343,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset.append(
|
asset.append(
|
||||||
"finance_books",
|
"finance_books",
|
||||||
{
|
{
|
||||||
|
"finance_book": "Test Finance Book 2",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"frequency_of_depreciation": 1,
|
"frequency_of_depreciation": 1,
|
||||||
"total_number_of_depreciations": 6,
|
"total_number_of_depreciations": 6,
|
||||||
@@ -1352,6 +1354,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset.append(
|
asset.append(
|
||||||
"finance_books",
|
"finance_books",
|
||||||
{
|
{
|
||||||
|
"finance_book": "Test Finance Book 3",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"frequency_of_depreciation": 12,
|
"frequency_of_depreciation": 12,
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
@@ -1381,6 +1384,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset.append(
|
asset.append(
|
||||||
"finance_books",
|
"finance_books",
|
||||||
{
|
{
|
||||||
|
"finance_book": "Test Finance Book 1",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"frequency_of_depreciation": 12,
|
"frequency_of_depreciation": 12,
|
||||||
"total_number_of_depreciations": 3,
|
"total_number_of_depreciations": 3,
|
||||||
@@ -1391,6 +1395,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
asset.append(
|
asset.append(
|
||||||
"finance_books",
|
"finance_books",
|
||||||
{
|
{
|
||||||
|
"finance_book": "Test Finance Book 2",
|
||||||
"depreciation_method": "Straight Line",
|
"depreciation_method": "Straight Line",
|
||||||
"frequency_of_depreciation": 12,
|
"frequency_of_depreciation": 12,
|
||||||
"total_number_of_depreciations": 6,
|
"total_number_of_depreciations": 6,
|
||||||
@@ -1647,6 +1652,15 @@ def create_asset_data():
|
|||||||
if not frappe.db.exists("Location", "Test Location"):
|
if not frappe.db.exists("Location", "Test Location"):
|
||||||
frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
|
frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Finance Book", "Test Finance Book 1"):
|
||||||
|
frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 1"}).insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Finance Book", "Test Finance Book 2"):
|
||||||
|
frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 2"}).insert()
|
||||||
|
|
||||||
|
if not frappe.db.exists("Finance Book", "Test Finance Book 3"):
|
||||||
|
frappe.get_doc({"doctype": "Finance Book", "finance_book_name": "Test Finance Book 3"}).insert()
|
||||||
|
|
||||||
|
|
||||||
def create_asset(**args):
|
def create_asset(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -80,14 +80,16 @@ def calculate_next_due_date(
|
|||||||
next_due_date = add_days(start_date, 7)
|
next_due_date = add_days(start_date, 7)
|
||||||
if periodicity == "Monthly":
|
if periodicity == "Monthly":
|
||||||
next_due_date = add_months(start_date, 1)
|
next_due_date = add_months(start_date, 1)
|
||||||
|
if periodicity == "Quarterly":
|
||||||
|
next_due_date = add_months(start_date, 3)
|
||||||
|
if periodicity == "Half-yearly":
|
||||||
|
next_due_date = add_months(start_date, 6)
|
||||||
if periodicity == "Yearly":
|
if periodicity == "Yearly":
|
||||||
next_due_date = add_years(start_date, 1)
|
next_due_date = add_years(start_date, 1)
|
||||||
if periodicity == "2 Yearly":
|
if periodicity == "2 Yearly":
|
||||||
next_due_date = add_years(start_date, 2)
|
next_due_date = add_years(start_date, 2)
|
||||||
if periodicity == "3 Yearly":
|
if periodicity == "3 Yearly":
|
||||||
next_due_date = add_years(start_date, 3)
|
next_due_date = add_years(start_date, 3)
|
||||||
if periodicity == "Quarterly":
|
|
||||||
next_due_date = add_months(start_date, 3)
|
|
||||||
if end_date and (
|
if end_date and (
|
||||||
(start_date and start_date >= end_date)
|
(start_date and start_date >= end_date)
|
||||||
or (last_completion_date and last_completion_date >= end_date)
|
or (last_completion_date and last_completion_date >= end_date)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Periodicity",
|
"label": "Periodicity",
|
||||||
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly",
|
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly\n2 Yearly\n3 Yearly",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -408,6 +408,16 @@ class LoanRepayment(AccountsController):
|
|||||||
else:
|
else:
|
||||||
payment_account = self.payment_account
|
payment_account = self.payment_account
|
||||||
|
|
||||||
|
payment_party_type = ""
|
||||||
|
payment_party = ""
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasattr(self, "process_payroll_accounting_entry_based_on_employee")
|
||||||
|
and self.process_payroll_accounting_entry_based_on_employee
|
||||||
|
):
|
||||||
|
payment_party_type = "Employee"
|
||||||
|
payment_party = self.applicant
|
||||||
|
|
||||||
if self.total_penalty_paid:
|
if self.total_penalty_paid:
|
||||||
gle_map.append(
|
gle_map.append(
|
||||||
self.get_gl_dict(
|
self.get_gl_dict(
|
||||||
@@ -455,6 +465,8 @@ class LoanRepayment(AccountsController):
|
|||||||
"remarks": _(remarks),
|
"remarks": _(remarks),
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center,
|
||||||
"posting_date": getdate(self.posting_date),
|
"posting_date": getdate(self.posting_date),
|
||||||
|
"party_type": payment_party_type,
|
||||||
|
"party": payment_party,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -493,6 +505,7 @@ def create_repayment_entry(
|
|||||||
amount_paid,
|
amount_paid,
|
||||||
penalty_amount=None,
|
penalty_amount=None,
|
||||||
payroll_payable_account=None,
|
payroll_payable_account=None,
|
||||||
|
process_payroll_accounting_entry_based_on_employee=0,
|
||||||
):
|
):
|
||||||
|
|
||||||
lr = frappe.get_doc(
|
lr = frappe.get_doc(
|
||||||
@@ -509,6 +522,7 @@ def create_repayment_entry(
|
|||||||
"amount_paid": amount_paid,
|
"amount_paid": amount_paid,
|
||||||
"loan_type": loan_type,
|
"loan_type": loan_type,
|
||||||
"payroll_payable_account": payroll_payable_account,
|
"payroll_payable_account": payroll_payable_account,
|
||||||
|
"process_payroll_accounting_entry_based_on_employee": process_payroll_accounting_entry_based_on_employee,
|
||||||
}
|
}
|
||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
|
|||||||
@@ -10,22 +10,25 @@
|
|||||||
"warehouse",
|
"warehouse",
|
||||||
"item_name",
|
"item_name",
|
||||||
"material_request_type",
|
"material_request_type",
|
||||||
"actual_qty",
|
"quantity",
|
||||||
"ordered_qty",
|
|
||||||
"required_bom_qty",
|
"required_bom_qty",
|
||||||
"column_break_4",
|
"column_break_4",
|
||||||
"quantity",
|
"schedule_date",
|
||||||
"uom",
|
"uom",
|
||||||
"conversion_factor",
|
"conversion_factor",
|
||||||
"projected_qty",
|
|
||||||
"reserved_qty_for_production",
|
|
||||||
"safety_stock",
|
|
||||||
"item_details",
|
"item_details",
|
||||||
"description",
|
"description",
|
||||||
"min_order_qty",
|
"min_order_qty",
|
||||||
"section_break_8",
|
"section_break_8",
|
||||||
"sales_order",
|
"sales_order",
|
||||||
"requested_qty"
|
"bin_qty_section",
|
||||||
|
"actual_qty",
|
||||||
|
"requested_qty",
|
||||||
|
"reserved_qty_for_production",
|
||||||
|
"column_break_yhelv",
|
||||||
|
"ordered_qty",
|
||||||
|
"projected_qty",
|
||||||
|
"safety_stock"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -65,7 +68,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 1,
|
"columns": 2,
|
||||||
"fieldname": "quantity",
|
"fieldname": "quantity",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -80,12 +83,12 @@
|
|||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"columns": 2,
|
"columns": 1,
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "actual_qty",
|
"fieldname": "actual_qty",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Available Qty",
|
"label": "Qty In Stock",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
@@ -176,11 +179,27 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"label": "Conversion Factor",
|
"label": "Conversion Factor",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"columns": 1,
|
||||||
|
"fieldname": "schedule_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Required By"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "bin_qty_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "BIN Qty"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_yhelv",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-03 12:43:29.895754",
|
"modified": "2023-09-12 12:09:08.358326",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Material Request Plan Item",
|
"name": "Material Request Plan Item",
|
||||||
|
|||||||
@@ -725,7 +725,7 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
# key for Sales Order:Material Request Type:Customer
|
# key for Sales Order:Material Request Type:Customer
|
||||||
key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
|
key = "{}:{}:{}".format(item.sales_order, material_request_type, item_doc.customer or "")
|
||||||
schedule_date = add_days(nowdate(), cint(item_doc.lead_time_days))
|
schedule_date = item.schedule_date or add_days(nowdate(), cint(item_doc.lead_time_days))
|
||||||
|
|
||||||
if not key in material_request_map:
|
if not key in material_request_map:
|
||||||
# make a new MR for the combination
|
# make a new MR for the combination
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# See license.txt
|
# See license.txt
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_to_date, flt, now_datetime, nowdate
|
from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate
|
||||||
|
|
||||||
from erpnext.controllers.item_variant import create_variant
|
from erpnext.controllers.item_variant import create_variant
|
||||||
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
||||||
@@ -58,6 +58,9 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
pln = create_production_plan(item_code="Test Production Item 1")
|
pln = create_production_plan(item_code="Test Production Item 1")
|
||||||
self.assertTrue(len(pln.mr_items), 2)
|
self.assertTrue(len(pln.mr_items), 2)
|
||||||
|
|
||||||
|
for row in pln.mr_items:
|
||||||
|
row.schedule_date = add_to_date(nowdate(), days=10)
|
||||||
|
|
||||||
pln.make_material_request()
|
pln.make_material_request()
|
||||||
pln.reload()
|
pln.reload()
|
||||||
self.assertTrue(pln.status, "Material Requested")
|
self.assertTrue(pln.status, "Material Requested")
|
||||||
@@ -71,6 +74,13 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertTrue(len(material_requests), 2)
|
self.assertTrue(len(material_requests), 2)
|
||||||
|
|
||||||
|
for row in material_requests:
|
||||||
|
mr_schedule_date = getdate(frappe.db.get_value("Material Request", row[0], "schedule_date"))
|
||||||
|
|
||||||
|
expected_date = getdate(add_to_date(nowdate(), days=10))
|
||||||
|
|
||||||
|
self.assertEqual(mr_schedule_date, expected_date)
|
||||||
|
|
||||||
pln.make_work_order()
|
pln.make_work_order()
|
||||||
work_orders = frappe.get_all(
|
work_orders = frappe.get_all(
|
||||||
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
|
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class Project(Document):
|
|||||||
issue=task_details.issue,
|
issue=task_details.issue,
|
||||||
is_group=task_details.is_group,
|
is_group=task_details.is_group,
|
||||||
color=task_details.color,
|
color=task_details.color,
|
||||||
|
template_task=task_details.name,
|
||||||
)
|
)
|
||||||
).insert()
|
).insert()
|
||||||
|
|
||||||
@@ -103,9 +104,13 @@ class Project(Document):
|
|||||||
return date
|
return date
|
||||||
|
|
||||||
def dependency_mapping(self, template_tasks, project_tasks):
|
def dependency_mapping(self, template_tasks, project_tasks):
|
||||||
for template_task in template_tasks:
|
for project_task in project_tasks:
|
||||||
project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0]
|
if project_task.get("template_task"):
|
||||||
project_task = frappe.get_doc("Task", project_task.name)
|
template_task = frappe.get_doc("Task", project_task.template_task)
|
||||||
|
else:
|
||||||
|
template_task = list(filter(lambda x: x.subject == project_task.subject, template_tasks))[0]
|
||||||
|
template_task = frappe.get_doc("Task", template_task.name)
|
||||||
|
|
||||||
self.check_depends_on_value(template_task, project_task, project_tasks)
|
self.check_depends_on_value(template_task, project_task, project_tasks)
|
||||||
self.check_for_parent_tasks(template_task, project_task, project_tasks)
|
self.check_for_parent_tasks(template_task, project_task, project_tasks)
|
||||||
|
|
||||||
@@ -117,6 +122,7 @@ class Project(Document):
|
|||||||
filter(lambda x: x.subject == child_task_subject, project_tasks)
|
filter(lambda x: x.subject == child_task_subject, project_tasks)
|
||||||
)
|
)
|
||||||
if len(corresponding_project_task):
|
if len(corresponding_project_task):
|
||||||
|
project_task.reload() # reload, as it might have been updated in the previous iteration
|
||||||
project_task.append("depends_on", {"task": corresponding_project_task[0].name})
|
project_task.append("depends_on", {"task": corresponding_project_task[0].name})
|
||||||
project_task.save()
|
project_task.save()
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.tests.utils import FrappeTestCase
|
||||||
from frappe.utils import add_days, getdate, nowdate
|
from frappe.utils import add_days, getdate, nowdate
|
||||||
|
|
||||||
from erpnext.projects.doctype.project_template.test_project_template import make_project_template
|
from erpnext.projects.doctype.project_template.test_project_template import make_project_template
|
||||||
@@ -15,7 +14,7 @@ test_records = frappe.get_test_records("Project")
|
|||||||
test_ignore = ["Sales Order"]
|
test_ignore = ["Sales Order"]
|
||||||
|
|
||||||
|
|
||||||
class TestProject(unittest.TestCase):
|
class TestProject(FrappeTestCase):
|
||||||
def test_project_with_template_having_no_parent_and_depend_tasks(self):
|
def test_project_with_template_having_no_parent_and_depend_tasks(self):
|
||||||
project_name = "Test Project with Template - No Parent and Dependend Tasks"
|
project_name = "Test Project with Template - No Parent and Dependend Tasks"
|
||||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||||
@@ -155,6 +154,50 @@ class TestProject(unittest.TestCase):
|
|||||||
so.reload()
|
so.reload()
|
||||||
self.assertFalse(so.project)
|
self.assertFalse(so.project)
|
||||||
|
|
||||||
|
def test_project_with_template_tasks_having_common_name(self):
|
||||||
|
# Step - 1: Create Template Parent Tasks
|
||||||
|
template_parent_task1 = create_task(subject="Parent Task - 1", is_template=1, is_group=1)
|
||||||
|
template_parent_task2 = create_task(subject="Parent Task - 2", is_template=1, is_group=1)
|
||||||
|
template_parent_task3 = create_task(subject="Parent Task - 1", is_template=1, is_group=1)
|
||||||
|
|
||||||
|
# Step - 2: Create Template Child Tasks
|
||||||
|
template_task1 = create_task(
|
||||||
|
subject="Task - 1", is_template=1, parent_task=template_parent_task1.name
|
||||||
|
)
|
||||||
|
template_task2 = create_task(
|
||||||
|
subject="Task - 2", is_template=1, parent_task=template_parent_task2.name
|
||||||
|
)
|
||||||
|
template_task3 = create_task(
|
||||||
|
subject="Task - 1", is_template=1, parent_task=template_parent_task3.name
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step - 3: Create Project Template
|
||||||
|
template_tasks = [
|
||||||
|
template_parent_task1,
|
||||||
|
template_task1,
|
||||||
|
template_parent_task2,
|
||||||
|
template_task2,
|
||||||
|
template_parent_task3,
|
||||||
|
template_task3,
|
||||||
|
]
|
||||||
|
project_template = make_project_template(
|
||||||
|
"Project template with common Task Subject", template_tasks
|
||||||
|
)
|
||||||
|
|
||||||
|
# Step - 4: Create Project against the Project Template
|
||||||
|
project = get_project("Project with common Task Subject", project_template)
|
||||||
|
project_tasks = frappe.get_all(
|
||||||
|
"Task", {"project": project.name}, ["subject", "parent_task", "is_group"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test - 1: No. of Project Tasks should be equal to No. of Template Tasks
|
||||||
|
self.assertEquals(len(project_tasks), len(template_tasks))
|
||||||
|
|
||||||
|
# Test - 2: All child Project Tasks should have Parent Task linked
|
||||||
|
for pt in project_tasks:
|
||||||
|
if not pt.is_group:
|
||||||
|
self.assertIsNotNone(pt.parent_task)
|
||||||
|
|
||||||
|
|
||||||
def get_project(name, template):
|
def get_project(name, template):
|
||||||
|
|
||||||
|
|||||||
@@ -52,13 +52,15 @@
|
|||||||
"company",
|
"company",
|
||||||
"lft",
|
"lft",
|
||||||
"rgt",
|
"rgt",
|
||||||
"old_parent"
|
"old_parent",
|
||||||
|
"template_task"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Subject",
|
"label": "Subject",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
"fieldname": "parent_task",
|
"fieldname": "parent_task",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Parent Task",
|
"label": "Parent Task",
|
||||||
"options": "Task",
|
"options": "Task",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
@@ -382,6 +385,12 @@
|
|||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Completed On",
|
"label": "Completed On",
|
||||||
"mandatory_depends_on": "eval: doc.status == \"Completed\""
|
"mandatory_depends_on": "eval: doc.status == \"Completed\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "template_task",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Template Task"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-check",
|
"icon": "fa fa-check",
|
||||||
@@ -389,7 +398,7 @@
|
|||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2022-06-23 16:58:47.005241",
|
"modified": "2023-09-06 13:52:05.861175",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Task",
|
"name": "Task",
|
||||||
|
|||||||
@@ -66,5 +66,8 @@ def make_employee(user, company=None, **kwargs):
|
|||||||
employee.insert()
|
employee.insert()
|
||||||
return employee.name
|
return employee.name
|
||||||
else:
|
else:
|
||||||
frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active")
|
employee = frappe.get_doc("Employee", {"employee_name": user})
|
||||||
return frappe.get_value("Employee", {"employee_name": user}, "name")
|
employee.update(kwargs)
|
||||||
|
employee.status = "Active"
|
||||||
|
employee.save()
|
||||||
|
return employee.name
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
|
|||||||
frappe.ui.form.on("Quality Inspection", {
|
frappe.ui.form.on("Quality Inspection", {
|
||||||
|
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
frm.set_query("reference_name", function() {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
"docstatus": ["!=", 2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
frm.set_query("batch_no", function() {
|
frm.set_query("batch_no", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
|||||||
@@ -323,8 +323,10 @@ class StockBalanceReport(object):
|
|||||||
for field in ["item_code", "brand"]:
|
for field in ["item_code", "brand"]:
|
||||||
if not self.filters.get(field):
|
if not self.filters.get(field):
|
||||||
continue
|
continue
|
||||||
|
elif field == "item_code":
|
||||||
query = query.where(item_table[field] == self.filters.get(field))
|
query = query.where(item_table.name == self.filters.get(field))
|
||||||
|
else:
|
||||||
|
query = query.where(item_table[field] == self.filters.get(field))
|
||||||
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user