mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-07 07:20:26 +00:00
Merge branch 'version-14-hotfix' into develop-ritvik-duplicate-seral-nos
This commit is contained in:
@@ -56,36 +56,41 @@ frappe.treeview_settings["Account"] = {
|
||||
accounts = nodes;
|
||||
}
|
||||
|
||||
const get_balances = frappe.call({
|
||||
method: 'erpnext.accounts.utils.get_account_balances',
|
||||
args: {
|
||||
accounts: accounts,
|
||||
company: cur_tree.args.company
|
||||
},
|
||||
});
|
||||
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
|
||||
if(value) {
|
||||
|
||||
get_balances.then(r => {
|
||||
if (!r.message || r.message.length == 0) return;
|
||||
const get_balances = frappe.call({
|
||||
method: 'erpnext.accounts.utils.get_account_balances',
|
||||
args: {
|
||||
accounts: accounts,
|
||||
company: cur_tree.args.company
|
||||
},
|
||||
});
|
||||
|
||||
for (let account of r.message) {
|
||||
get_balances.then(r => {
|
||||
if (!r.message || r.message.length == 0) return;
|
||||
|
||||
const node = cur_tree.nodes && cur_tree.nodes[account.value];
|
||||
if (!node || node.is_root) continue;
|
||||
for (let account of r.message) {
|
||||
|
||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||
const balance = account.balance_in_account_currency || account.balance;
|
||||
const dr_or_cr = balance > 0 ? "Dr": "Cr";
|
||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||
const node = cur_tree.nodes && cur_tree.nodes[account.value];
|
||||
if (!node || node.is_root) continue;
|
||||
|
||||
if (account.balance!==undefined) {
|
||||
node.parent && node.parent.find('.balance-area').remove();
|
||||
$('<span class="balance-area pull-right">'
|
||||
+ (account.balance_in_account_currency ?
|
||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||
+ format(account.balance, account.company_currency)
|
||||
+ " " + dr_or_cr
|
||||
+ '</span>').insertBefore(node.$ul);
|
||||
}
|
||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||
const balance = account.balance_in_account_currency || account.balance;
|
||||
const dr_or_cr = balance > 0 ? "Dr": "Cr";
|
||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||
|
||||
if (account.balance!==undefined) {
|
||||
node.parent && node.parent.find('.balance-area').remove();
|
||||
$('<span class="balance-area pull-right">'
|
||||
+ (account.balance_in_account_currency ?
|
||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||
+ format(account.balance, account.company_currency)
|
||||
+ " " + dr_or_cr
|
||||
+ '</span>').insertBefore(node.$ul);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -66,7 +66,9 @@
|
||||
"report_settings_sb",
|
||||
"banking_tab",
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching"
|
||||
"enable_fuzzy_matching",
|
||||
"tab_break_dpet",
|
||||
"show_balance_in_coa"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -416,6 +418,17 @@
|
||||
"fieldname": "ignore_account_closing_balance",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Account Closing Balance"
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_break_dpet",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "show_balance_in_coa",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Balances in Chart Of Accounts"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
|
||||
@@ -809,6 +809,11 @@ class PaymentEntry(AccountsController):
|
||||
flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
|
||||
)
|
||||
|
||||
# on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated
|
||||
# for base currency transactions
|
||||
if d.exchange_rate is None:
|
||||
d.exchange_rate = 1
|
||||
|
||||
allocated_amount_in_pe_exchange_rate = flt(
|
||||
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
|
||||
)
|
||||
|
||||
@@ -634,6 +634,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
|
||||
create_gain_loss_journal(
|
||||
company,
|
||||
today(),
|
||||
inv.party_type,
|
||||
inv.party,
|
||||
inv.account,
|
||||
|
||||
@@ -45,6 +45,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_debit_to_acc()
|
||||
self.validate_write_off_account()
|
||||
self.validate_change_amount()
|
||||
self.validate_duplicate_serial_and_batch_no()
|
||||
self.validate_change_account()
|
||||
self.validate_item_cost_centers()
|
||||
self.validate_warehouse()
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"column_break_21",
|
||||
"start_date",
|
||||
"section_break_33",
|
||||
"pdf_name",
|
||||
"subject",
|
||||
"column_break_28",
|
||||
"cc_to",
|
||||
@@ -273,7 +274,7 @@
|
||||
"fieldname": "help_text",
|
||||
"fieldtype": "HTML",
|
||||
"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",
|
||||
@@ -368,10 +369,15 @@
|
||||
"fieldname": "based_on_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Based On Payment Terms"
|
||||
},
|
||||
{
|
||||
"fieldname": "pdf_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "PDF Name"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2023-06-23 10:13:15.051950",
|
||||
"modified": "2023-08-28 12:59:53.071334",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -26,7 +26,13 @@ class ProcessStatementOfAccounts(Document):
|
||||
if not self.subject:
|
||||
self.subject = "Statement Of Accounts for {{ customer.customer_name }}"
|
||||
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.body)
|
||||
@@ -57,11 +63,6 @@ def get_report_pdf(doc, consolidated=True):
|
||||
|
||||
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":
|
||||
col, res = get_soa(filters)
|
||||
for x in [0, -2, -1]:
|
||||
@@ -69,8 +70,11 @@ def get_report_pdf(doc, consolidated=True):
|
||||
if len(res) == 3:
|
||||
continue
|
||||
else:
|
||||
filters.update(get_ar_filters(doc, entry))
|
||||
ar_res = get_ar_soa(filters)
|
||||
col, res = ar_res[0], ar_res[1]
|
||||
if not res:
|
||||
continue
|
||||
|
||||
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
||||
|
||||
@@ -139,6 +143,7 @@ def get_ar_filters(doc, entry):
|
||||
return {
|
||||
"report_date": doc.posting_date if doc.posting_date else None,
|
||||
"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,
|
||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||
"sales_person": doc.sales_person if doc.sales_person else None,
|
||||
@@ -362,16 +367,20 @@ def download_statements(document_name):
|
||||
|
||||
|
||||
@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)
|
||||
report = get_report_pdf(doc, consolidated=False)
|
||||
|
||||
if report:
|
||||
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)
|
||||
context = get_context(customer, doc)
|
||||
if not recipients:
|
||||
continue
|
||||
|
||||
subject = frappe.render_template(doc.subject, 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:
|
||||
new_to_date = getdate(today())
|
||||
new_to_date = getdate(posting_date or today())
|
||||
if doc.frequency == "Weekly":
|
||||
new_to_date = add_days(new_to_date, 7)
|
||||
else:
|
||||
@@ -399,8 +408,11 @@ def send_emails(document_name, from_scheduler=False):
|
||||
doc.add_comment(
|
||||
"Comment", "Emails sent on: " + frappe.utils.format_datetime(frappe.utils.now())
|
||||
)
|
||||
doc.db_set("to_date", new_to_date, commit=True)
|
||||
doc.db_set("from_date", new_from_date, commit=True)
|
||||
if doc.report == "General Ledger":
|
||||
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
|
||||
else:
|
||||
return False
|
||||
@@ -410,7 +422,8 @@ def send_emails(document_name, from_scheduler=False):
|
||||
def send_auto_email():
|
||||
selected = frappe.get_list(
|
||||
"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:
|
||||
send_emails(entry.name, from_scheduler=True)
|
||||
|
||||
@@ -8,9 +8,24 @@
|
||||
}
|
||||
</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>
|
||||
<h4 class="text-center">
|
||||
{{ filters.customer }}
|
||||
{{ filters.customer_name }}
|
||||
</h4>
|
||||
<h6 class="text-center">
|
||||
{% if (filters.tax_id) %}
|
||||
@@ -341,4 +356,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if terms_and_conditions %}
|
||||
<div>
|
||||
{{ terms_and_conditions }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<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
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
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):
|
||||
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
|
||||
|
||||
@@ -269,9 +269,7 @@ class PurchaseInvoice(BuyingController):
|
||||
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
asset_items = [d.is_fixed_asset for d in self.items if d.is_fixed_asset]
|
||||
if len(asset_items) > 0:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
asset_received_but_not_billed = None
|
||||
|
||||
if self.update_stock:
|
||||
self.validate_item_code()
|
||||
@@ -365,6 +363,8 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
@@ -978,8 +978,9 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
arbnb_account = None
|
||||
eiiav_account = None
|
||||
asset_eiiav_currency = None
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
@@ -991,6 +992,8 @@ class PurchaseInvoice(BuyingController):
|
||||
"Asset Received But Not Billed",
|
||||
"Fixed Asset",
|
||||
]:
|
||||
if not arbnb_account:
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = arbnb_account
|
||||
|
||||
if not self.update_stock:
|
||||
@@ -1013,7 +1016,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if item.item_tax_amount:
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -1056,7 +1062,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -1076,47 +1085,46 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
# When update stock is checked
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if self.update_stock:
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
if not eiiav_account:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
return gl_entries
|
||||
|
||||
|
||||
@@ -1153,7 +1153,7 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
|
||||
item.enable_deferred_expense = 1
|
||||
item.deferred_expense_account = deferred_account
|
||||
item.item_defaults[0].deferred_expense_account = deferred_account
|
||||
item.save()
|
||||
|
||||
pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
|
||||
|
||||
@@ -15,9 +15,11 @@ def get_data():
|
||||
},
|
||||
"internal_links": {
|
||||
"Sales Order": ["items", "sales_order"],
|
||||
"Delivery Note": ["items", "delivery_note"],
|
||||
"Timesheet": ["timesheets", "time_sheet"],
|
||||
},
|
||||
"internal_and_external_links": {
|
||||
"Delivery Note": ["items", "delivery_note"],
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Payment"),
|
||||
|
||||
@@ -2322,7 +2322,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item.enable_deferred_revenue = 1
|
||||
item.deferred_revenue_account = deferred_account
|
||||
item.item_defaults[0].deferred_revenue_account = deferred_account
|
||||
item.no_of_months = 12
|
||||
item.save()
|
||||
|
||||
@@ -3102,7 +3102,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
item = create_item("_Test Item for Deferred Accounting")
|
||||
item.enable_deferred_expense = 1
|
||||
item.deferred_revenue_account = deferred_account
|
||||
item.item_defaults[0].deferred_revenue_account = deferred_account
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(
|
||||
|
||||
@@ -694,3 +694,23 @@ class TestSubscription(unittest.TestCase):
|
||||
# Check the currency of the created invoice
|
||||
currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].invoice, "currency")
|
||||
self.assertEqual(currency, "USD")
|
||||
|
||||
def test_plan_rate_for_midmonth_start_date(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Supplier"
|
||||
subscription.party = "_Test Supplier"
|
||||
subscription.generate_invoice_at_period_start = 1
|
||||
subscription.follow_calendar_months = 1
|
||||
subscription.generate_new_invoices_past_due_date = 1
|
||||
subscription.start_date = "2023-04-08"
|
||||
subscription.end_date = "2024-02-27"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
|
||||
subscription.save()
|
||||
|
||||
subscription.process()
|
||||
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
pi = frappe.get_doc("Purchase Invoice", subscription.invoices[0].invoice)
|
||||
self.assertEqual(pi.total, 55333.33)
|
||||
|
||||
subscription.delete()
|
||||
|
||||
@@ -57,18 +57,17 @@ def get_plan_rate(
|
||||
prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
|
||||
|
||||
if prorate:
|
||||
prorate_factor = flt(
|
||||
date_diff(start_date, get_first_day(start_date))
|
||||
/ date_diff(get_last_day(start_date), get_first_day(start_date)),
|
||||
1,
|
||||
)
|
||||
|
||||
prorate_factor += flt(
|
||||
date_diff(get_last_day(end_date), end_date)
|
||||
/ date_diff(get_last_day(end_date), get_first_day(end_date)),
|
||||
1,
|
||||
)
|
||||
|
||||
cost -= plan.cost * prorate_factor
|
||||
|
||||
cost -= plan.cost * get_prorate_factor(start_date, end_date)
|
||||
return cost
|
||||
|
||||
|
||||
def get_prorate_factor(start_date, end_date):
|
||||
total_days_to_skip = date_diff(start_date, get_first_day(start_date))
|
||||
total_days_in_month = int(get_last_day(start_date).strftime("%d"))
|
||||
prorate_factor = flt(total_days_to_skip / total_days_in_month)
|
||||
|
||||
total_days_to_skip = date_diff(get_last_day(end_date), end_date)
|
||||
total_days_in_month = int(get_last_day(end_date).strftime("%d"))
|
||||
prorate_factor += flt(total_days_to_skip / total_days_in_month)
|
||||
|
||||
return prorate_factor
|
||||
|
||||
@@ -467,6 +467,10 @@ class ReceivablePayableReport(object):
|
||||
original_row = frappe._dict(row)
|
||||
row.payment_terms = []
|
||||
|
||||
# Cr Note's don't have Payment Terms
|
||||
if not payment_terms_details:
|
||||
return
|
||||
|
||||
# Advance allocated during invoicing is not considered in payment terms
|
||||
# Deduct that from paid amount pre allocation
|
||||
row.paid -= flt(payment_terms_details[0].total_advance)
|
||||
|
||||
@@ -81,7 +81,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
|
||||
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||
item = frappe.get_doc("Item", self.item)
|
||||
item.enable_deferred_revenue = 1
|
||||
item.deferred_revenue_account = self.deferred_revenue_account
|
||||
item.item_defaults[0].deferred_revenue_account = self.deferred_revenue_account
|
||||
item.no_of_months = 3
|
||||
item.save()
|
||||
|
||||
@@ -150,7 +150,7 @@ class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
|
||||
self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
|
||||
item = frappe.get_doc("Item", self.item)
|
||||
item.enable_deferred_expense = 1
|
||||
item.deferred_expense_account = self.deferred_expense_account
|
||||
item.item_defaults[0].deferred_expense_account = self.deferred_expense_account
|
||||
item.no_of_months_exp = 3
|
||||
item.save()
|
||||
|
||||
|
||||
@@ -272,20 +272,19 @@ def get_conditions(filters):
|
||||
if match_conditions:
|
||||
conditions.append(match_conditions)
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if not dimension.disabled:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if not dimension.disabled:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
for opts in (
|
||||
("company", " and company=%(company)s"),
|
||||
("company", " and `tabPurchase Invoice`.company=%(company)s"),
|
||||
("supplier", " and `tabPurchase Invoice`.supplier = %(supplier)s"),
|
||||
("item_code", " and `tabPurchase Invoice Item`.item_code = %(item_code)s"),
|
||||
("from_date", " and `tabPurchase Invoice`.posting_date>=%(from_date)s"),
|
||||
|
||||
@@ -332,7 +332,7 @@ def get_conditions(filters, additional_conditions=None):
|
||||
conditions = ""
|
||||
|
||||
for opts in (
|
||||
("company", " and company=%(company)s"),
|
||||
("company", " and `tabSales Invoice`.company=%(company)s"),
|
||||
("customer", " and `tabSales Invoice`.customer = %(customer)s"),
|
||||
("item_code", " and `tabSales Invoice Item`.item_code = %(item_code)s"),
|
||||
("from_date", " and `tabSales Invoice`.posting_date>=%(from_date)s"),
|
||||
|
||||
@@ -324,12 +324,22 @@ def get_journal_entry_party_map(journal_entries):
|
||||
|
||||
|
||||
def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
common_fields = ["name", "tax_withholding_category"]
|
||||
common_fields = ["name"]
|
||||
fields_dict = {
|
||||
"Purchase Invoice": ["base_tax_withholding_net_total", "grand_total", "base_total"],
|
||||
"Purchase Invoice": [
|
||||
"tax_withholding_category",
|
||||
"base_tax_withholding_net_total",
|
||||
"grand_total",
|
||||
"base_total",
|
||||
],
|
||||
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
||||
"Payment Entry": ["paid_amount", "paid_amount_after_tax", "base_paid_amount"],
|
||||
"Journal Entry": ["total_amount"],
|
||||
"Payment Entry": [
|
||||
"tax_withholding_category",
|
||||
"paid_amount",
|
||||
"paid_amount_after_tax",
|
||||
"base_paid_amount",
|
||||
],
|
||||
"Journal Entry": ["tax_withholding_category", "total_amount"],
|
||||
}
|
||||
|
||||
entries = frappe.get_all(
|
||||
|
||||
@@ -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
|
||||
@@ -1858,6 +1858,7 @@ class QueryPaymentLedger(object):
|
||||
|
||||
def create_gain_loss_journal(
|
||||
company,
|
||||
posting_date,
|
||||
party_type,
|
||||
party,
|
||||
party_account,
|
||||
@@ -1876,7 +1877,7 @@ def create_gain_loss_journal(
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = "Exchange Gain Or Loss"
|
||||
journal_entry.company = company
|
||||
journal_entry.posting_date = nowdate()
|
||||
journal_entry.posting_date = posting_date or nowdate()
|
||||
journal_entry.multi_currency = 1
|
||||
journal_entry.is_system_generated = True
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class Asset(AccountsController):
|
||||
self.validate_item()
|
||||
self.validate_cost_center()
|
||||
self.set_missing_values()
|
||||
self.validate_finance_books()
|
||||
if not self.split_from:
|
||||
self.prepare_depreciation_data()
|
||||
self.validate_gross_and_purchase_amount()
|
||||
@@ -206,6 +207,27 @@ class Asset(AccountsController):
|
||||
finance_books = get_item_details(self.item_code, self.asset_category)
|
||||
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):
|
||||
if not self.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 = (
|
||||
flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
|
||||
) / date_diff(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||
* 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
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
* 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(
|
||||
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
|
||||
|
||||
to_date = get_last_day(
|
||||
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:
|
||||
return (
|
||||
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(row.expected_value_after_useful_life)
|
||||
) / date_diff(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||
* row.frequency_of_depreciation,
|
||||
get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked - 1)
|
||||
* row.frequency_of_depreciation,
|
||||
)
|
||||
),
|
||||
add_days(
|
||||
get_last_day(add_months(row.depreciation_start_date, -1 * row.frequency_of_depreciation)), 1
|
||||
),
|
||||
row.depreciation_start_date,
|
||||
)
|
||||
to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
|
||||
from_date = add_months(
|
||||
row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
|
||||
|
||||
to_date = get_last_day(
|
||||
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:
|
||||
return (
|
||||
flt(asset.gross_purchase_amount)
|
||||
|
||||
@@ -743,18 +743,18 @@ class TestDepreciationMethods(AssetSetup):
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
["2023-01-31", 1019.18, 1019.18],
|
||||
["2023-02-28", 920.55, 1939.73],
|
||||
["2023-03-31", 1019.18, 2958.91],
|
||||
["2023-04-30", 986.3, 3945.21],
|
||||
["2023-05-31", 1019.18, 4964.39],
|
||||
["2023-06-30", 986.3, 5950.69],
|
||||
["2023-07-31", 1019.18, 6969.87],
|
||||
["2023-08-31", 1019.18, 7989.05],
|
||||
["2023-09-30", 986.3, 8975.35],
|
||||
["2023-10-31", 1019.18, 9994.53],
|
||||
["2023-11-30", 986.3, 10980.83],
|
||||
["2023-12-31", 1019.17, 12000.0],
|
||||
["2023-01-31", 1021.98, 1021.98],
|
||||
["2023-02-28", 923.08, 1945.06],
|
||||
["2023-03-31", 1021.98, 2967.04],
|
||||
["2023-04-30", 989.01, 3956.05],
|
||||
["2023-05-31", 1021.98, 4978.03],
|
||||
["2023-06-30", 989.01, 5967.04],
|
||||
["2023-07-31", 1021.98, 6989.02],
|
||||
["2023-08-31", 1021.98, 8011.0],
|
||||
["2023-09-30", 989.01, 9000.01],
|
||||
["2023-10-31", 1021.98, 10021.99],
|
||||
["2023-11-30", 989.01, 11011.0],
|
||||
["2023-12-31", 989.0, 12000.0],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
@@ -1332,6 +1332,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
asset.append(
|
||||
"finance_books",
|
||||
{
|
||||
"finance_book": "Test Finance Book 1",
|
||||
"depreciation_method": "Straight Line",
|
||||
"frequency_of_depreciation": 1,
|
||||
"total_number_of_depreciations": 3,
|
||||
@@ -1342,6 +1343,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
asset.append(
|
||||
"finance_books",
|
||||
{
|
||||
"finance_book": "Test Finance Book 2",
|
||||
"depreciation_method": "Straight Line",
|
||||
"frequency_of_depreciation": 1,
|
||||
"total_number_of_depreciations": 6,
|
||||
@@ -1352,6 +1354,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
asset.append(
|
||||
"finance_books",
|
||||
{
|
||||
"finance_book": "Test Finance Book 3",
|
||||
"depreciation_method": "Straight Line",
|
||||
"frequency_of_depreciation": 12,
|
||||
"total_number_of_depreciations": 3,
|
||||
@@ -1381,6 +1384,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
asset.append(
|
||||
"finance_books",
|
||||
{
|
||||
"finance_book": "Test Finance Book 1",
|
||||
"depreciation_method": "Straight Line",
|
||||
"frequency_of_depreciation": 12,
|
||||
"total_number_of_depreciations": 3,
|
||||
@@ -1391,6 +1395,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
asset.append(
|
||||
"finance_books",
|
||||
{
|
||||
"finance_book": "Test Finance Book 2",
|
||||
"depreciation_method": "Straight Line",
|
||||
"frequency_of_depreciation": 12,
|
||||
"total_number_of_depreciations": 6,
|
||||
@@ -1647,6 +1652,15 @@ def create_asset_data():
|
||||
if not frappe.db.exists("Location", "Test Location"):
|
||||
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):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -80,14 +80,16 @@ def calculate_next_due_date(
|
||||
next_due_date = add_days(start_date, 7)
|
||||
if periodicity == "Monthly":
|
||||
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":
|
||||
next_due_date = add_years(start_date, 1)
|
||||
if periodicity == "2 Yearly":
|
||||
next_due_date = add_years(start_date, 2)
|
||||
if periodicity == "3 Yearly":
|
||||
next_due_date = add_years(start_date, 3)
|
||||
if periodicity == "Quarterly":
|
||||
next_due_date = add_months(start_date, 3)
|
||||
if end_date and (
|
||||
(start_date and start_date >= end_date)
|
||||
or (last_completion_date and last_completion_date >= end_date)
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"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
|
||||
},
|
||||
{
|
||||
@@ -153,4 +153,4 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
{
|
||||
"fieldname": "parent_location",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Parent Location",
|
||||
"options": "Location",
|
||||
"search_index": 1
|
||||
@@ -141,11 +142,11 @@
|
||||
],
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-08 16:11:11.375701",
|
||||
"modified": "2023-08-29 12:49:33.290527",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Location",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By fieldname",
|
||||
"nsm_parent_field": "parent_location",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
@@ -224,5 +225,6 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1171,6 +1171,7 @@
|
||||
"depends_on": "is_internal_supplier",
|
||||
"fieldname": "set_from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Set From Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -1271,7 +1272,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-24 11:16:41.195340",
|
||||
"modified": "2023-09-13 16:21:07.361700",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -878,6 +878,7 @@
|
||||
"depends_on": "eval:parent.is_internal_supplier",
|
||||
"fieldname": "from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "From Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -902,7 +903,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-29 16:47:41.364387",
|
||||
"modified": "2023-09-13 16:22:40.825092",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -195,6 +195,9 @@ class TestSupplier(FrappeTestCase):
|
||||
def create_supplier(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
if not args.supplier_name:
|
||||
args.supplier_name = frappe.generate_hash()
|
||||
|
||||
if frappe.db.exists("Supplier", args.supplier_name):
|
||||
return frappe.get_doc("Supplier", args.supplier_name)
|
||||
|
||||
@@ -202,6 +205,7 @@ def create_supplier(**args):
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": args.supplier_name,
|
||||
"default_currency": args.default_currency,
|
||||
"supplier_group": args.supplier_group or "Services",
|
||||
"supplier_type": args.supplier_type or "Company",
|
||||
"tax_withholding_category": args.tax_withholding_category,
|
||||
|
||||
@@ -7,7 +7,7 @@ import copy
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Coalesce, Sum
|
||||
from frappe.utils import date_diff, flt, getdate
|
||||
from frappe.utils import cint, date_diff, flt, getdate
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -47,8 +47,10 @@ def get_data(filters):
|
||||
mr.transaction_date.as_("date"),
|
||||
mr_item.schedule_date.as_("required_date"),
|
||||
mr_item.item_code.as_("item_code"),
|
||||
Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"),
|
||||
Coalesce(mr_item.stock_uom, "").as_("uom"),
|
||||
Sum(Coalesce(mr_item.qty, 0)).as_("qty"),
|
||||
Sum(Coalesce(mr_item.stock_qty, 0)).as_("stock_qty"),
|
||||
Coalesce(mr_item.uom, "").as_("uom"),
|
||||
Coalesce(mr_item.stock_uom, "").as_("stock_uom"),
|
||||
Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"),
|
||||
Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
|
||||
(Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_(
|
||||
@@ -96,7 +98,7 @@ def get_conditions(filters, query, mr, mr_item):
|
||||
|
||||
|
||||
def update_qty_columns(row_to_update, data_row):
|
||||
fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
|
||||
fields = ["qty", "stock_qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
|
||||
for field in fields:
|
||||
row_to_update[field] += flt(data_row[field])
|
||||
|
||||
@@ -104,16 +106,20 @@ def update_qty_columns(row_to_update, data_row):
|
||||
def prepare_data(data, filters):
|
||||
"""Prepare consolidated Report data and Chart data"""
|
||||
material_request_map, item_qty_map = {}, {}
|
||||
precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
|
||||
for row in data:
|
||||
# item wise map for charts
|
||||
if not row["item_code"] in item_qty_map:
|
||||
item_qty_map[row["item_code"]] = {
|
||||
"qty": row["qty"],
|
||||
"ordered_qty": row["ordered_qty"],
|
||||
"received_qty": row["received_qty"],
|
||||
"qty_to_receive": row["qty_to_receive"],
|
||||
"qty_to_order": row["qty_to_order"],
|
||||
"qty": flt(row["stock_qty"], precision),
|
||||
"stock_qty": flt(row["stock_qty"], precision),
|
||||
"stock_uom": row["stock_uom"],
|
||||
"uom": row["uom"],
|
||||
"ordered_qty": flt(row["ordered_qty"], precision),
|
||||
"received_qty": flt(row["received_qty"], precision),
|
||||
"qty_to_receive": flt(row["qty_to_receive"], precision),
|
||||
"qty_to_order": flt(row["qty_to_order"], precision),
|
||||
}
|
||||
else:
|
||||
item_entry = item_qty_map[row["item_code"]]
|
||||
@@ -200,21 +206,34 @@ def get_columns(filters):
|
||||
{"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
|
||||
{"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200},
|
||||
{
|
||||
"label": _("Stock UOM"),
|
||||
"label": _("UOM"),
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Data",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Stock UOM"),
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Data",
|
||||
"width": 100,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Stock Qty"),
|
||||
"label": _("Qty"),
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
"width": 140,
|
||||
"convertible": "qty",
|
||||
},
|
||||
{
|
||||
"label": _("Qty in Stock UOM"),
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"width": 140,
|
||||
"convertible": "qty",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1042,8 +1042,10 @@ class AccountsController(TransactionBase):
|
||||
self.name,
|
||||
arg.get("referenced_row"),
|
||||
):
|
||||
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
posting_date,
|
||||
arg.get("party_type"),
|
||||
arg.get("party"),
|
||||
party_account,
|
||||
@@ -1123,6 +1125,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
self.posting_date,
|
||||
self.party_type,
|
||||
self.party,
|
||||
party_account,
|
||||
|
||||
@@ -162,10 +162,13 @@ class BuyingController(SubcontractingController):
|
||||
purchase_doc_field = (
|
||||
"purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice"
|
||||
)
|
||||
not_cancelled_asset = [
|
||||
d.name
|
||||
for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
|
||||
]
|
||||
not_cancelled_asset = []
|
||||
if self.return_against:
|
||||
not_cancelled_asset = [
|
||||
d.name
|
||||
for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
|
||||
]
|
||||
|
||||
if self.is_return and len(not_cancelled_asset):
|
||||
frappe.throw(
|
||||
_(
|
||||
|
||||
@@ -634,7 +634,6 @@ def get_applicable_shipping_rules(party=None, quotation=None):
|
||||
shipping_rules = get_shipping_rules(quotation)
|
||||
|
||||
if shipping_rules:
|
||||
rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label")
|
||||
# we need this in sorted order as per the position of the rule in the settings page
|
||||
return [[rule, rule] for rule in shipping_rules]
|
||||
|
||||
|
||||
@@ -263,12 +263,13 @@
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.repay_from_salary",
|
||||
"fetch_from": "against_loan.payment_account",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Repayment Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_36",
|
||||
@@ -294,7 +295,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-21 10:10:07.742298",
|
||||
"modified": "2023-09-04 15:44:29.148766",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
|
||||
@@ -408,6 +408,16 @@ class LoanRepayment(AccountsController):
|
||||
else:
|
||||
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:
|
||||
gle_map.append(
|
||||
self.get_gl_dict(
|
||||
@@ -455,6 +465,8 @@ class LoanRepayment(AccountsController):
|
||||
"remarks": _(remarks),
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(self.posting_date),
|
||||
"party_type": payment_party_type,
|
||||
"party": payment_party,
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -493,6 +505,7 @@ def create_repayment_entry(
|
||||
amount_paid,
|
||||
penalty_amount=None,
|
||||
payroll_payable_account=None,
|
||||
process_payroll_accounting_entry_based_on_employee=0,
|
||||
):
|
||||
|
||||
lr = frappe.get_doc(
|
||||
@@ -509,6 +522,7 @@ def create_repayment_entry(
|
||||
"amount_paid": amount_paid,
|
||||
"loan_type": loan_type,
|
||||
"payroll_payable_account": payroll_payable_account,
|
||||
"process_payroll_accounting_entry_based_on_employee": process_payroll_accounting_entry_based_on_employee,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
@@ -10,22 +10,25 @@
|
||||
"warehouse",
|
||||
"item_name",
|
||||
"material_request_type",
|
||||
"actual_qty",
|
||||
"ordered_qty",
|
||||
"quantity",
|
||||
"required_bom_qty",
|
||||
"column_break_4",
|
||||
"quantity",
|
||||
"schedule_date",
|
||||
"uom",
|
||||
"conversion_factor",
|
||||
"projected_qty",
|
||||
"reserved_qty_for_production",
|
||||
"safety_stock",
|
||||
"item_details",
|
||||
"description",
|
||||
"min_order_qty",
|
||||
"section_break_8",
|
||||
"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": [
|
||||
{
|
||||
@@ -65,7 +68,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"columns": 2,
|
||||
"fieldname": "quantity",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
@@ -80,12 +83,12 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Available Qty",
|
||||
"label": "Qty In Stock",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -176,11 +179,27 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Conversion Factor",
|
||||
"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,
|
||||
"links": [],
|
||||
"modified": "2023-05-03 12:43:29.895754",
|
||||
"modified": "2023-09-12 12:09:08.358326",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Material Request Plan Item",
|
||||
|
||||
@@ -725,7 +725,7 @@ class ProductionPlan(Document):
|
||||
|
||||
# key for Sales Order:Material Request Type:Customer
|
||||
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:
|
||||
# make a new MR for the combination
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# See license.txt
|
||||
import frappe
|
||||
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.manufacturing.doctype.production_plan.production_plan import (
|
||||
@@ -58,6 +58,9 @@ class TestProductionPlan(FrappeTestCase):
|
||||
pln = create_production_plan(item_code="Test Production Item 1")
|
||||
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.reload()
|
||||
self.assertTrue(pln.status, "Material Requested")
|
||||
@@ -71,6 +74,13 @@ class TestProductionPlan(FrappeTestCase):
|
||||
|
||||
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()
|
||||
work_orders = frappe.get_all(
|
||||
"Work Order", fields=["name"], filters={"production_plan": pln.name}, as_list=1
|
||||
|
||||
@@ -339,5 +339,6 @@ erpnext.patches.v14_0.update_closing_balances #15-07-2023
|
||||
execute:frappe.defaults.clear_default("fiscal_year")
|
||||
execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
|
||||
erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow
|
||||
erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
||||
@@ -46,6 +46,17 @@ def execute():
|
||||
for doctype in doctypes:
|
||||
frappe.delete_doc("DocType", doctype, ignore_missing=True)
|
||||
|
||||
titles = [
|
||||
"Fees",
|
||||
"Student Admission",
|
||||
"Grant Application",
|
||||
"Chapter",
|
||||
"Certification Application",
|
||||
]
|
||||
items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name")
|
||||
for item in items:
|
||||
frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True)
|
||||
|
||||
frappe.delete_doc("Module Def", "Education", ignore_missing=True, force=True)
|
||||
|
||||
click.secho(
|
||||
|
||||
@@ -41,7 +41,7 @@ def execute():
|
||||
for card in cards:
|
||||
frappe.delete_doc("Number Card", card, ignore_missing=True, force=True)
|
||||
|
||||
titles = ["Lab Test", "Prescription", "Patient Appointment"]
|
||||
titles = ["Lab Test", "Prescription", "Patient Appointment", "Patient"]
|
||||
items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name")
|
||||
for item in items:
|
||||
frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
try:
|
||||
item_dict = get_deferred_accounts()
|
||||
add_to_item_defaults(item_dict)
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.log_error("Failed to migrate deferred accounts in Item Defaults.")
|
||||
|
||||
|
||||
def get_deferred_accounts():
|
||||
item = frappe.qb.DocType("Item")
|
||||
return (
|
||||
frappe.qb.from_(item)
|
||||
.select(item.name, item.deferred_expense_account, item.deferred_revenue_account)
|
||||
.where((item.enable_deferred_expense == 1) | (item.enable_deferred_revenue == 1))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
|
||||
def add_to_item_defaults(item_dict):
|
||||
for item in item_dict:
|
||||
add_company_wise_item_default(item, "deferred_expense_account")
|
||||
add_company_wise_item_default(item, "deferred_revenue_account")
|
||||
|
||||
|
||||
def add_company_wise_item_default(item, account_type):
|
||||
company = frappe.get_cached_value("Account", item[account_type], "company")
|
||||
if company and item[account_type]:
|
||||
item_defaults = frappe.get_cached_value("Item", item["name"], "item_defaults")
|
||||
for item_row in item_defaults:
|
||||
if item_row.company == company:
|
||||
frappe.set_value("Item Default", item_row.name, account_type, item[account_type])
|
||||
break
|
||||
else:
|
||||
item_defaults.append({"company": company, account_type: item[account_type]})
|
||||
frappe.set_value("Item", item["name"], "item_defaults", item_defaults)
|
||||
@@ -84,6 +84,7 @@ class Project(Document):
|
||||
issue=task_details.issue,
|
||||
is_group=task_details.is_group,
|
||||
color=task_details.color,
|
||||
template_task=task_details.name,
|
||||
)
|
||||
).insert()
|
||||
|
||||
@@ -103,9 +104,13 @@ class Project(Document):
|
||||
return date
|
||||
|
||||
def dependency_mapping(self, template_tasks, project_tasks):
|
||||
for template_task in template_tasks:
|
||||
project_task = list(filter(lambda x: x.subject == template_task.subject, project_tasks))[0]
|
||||
project_task = frappe.get_doc("Task", project_task.name)
|
||||
for project_task in project_tasks:
|
||||
if project_task.get("template_task"):
|
||||
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_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)
|
||||
)
|
||||
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.save()
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, getdate, nowdate
|
||||
|
||||
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"]
|
||||
|
||||
|
||||
class TestProject(unittest.TestCase):
|
||||
class TestProject(FrappeTestCase):
|
||||
def test_project_with_template_having_no_parent_and_depend_tasks(self):
|
||||
project_name = "Test Project with Template - No Parent and Dependend Tasks"
|
||||
frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
|
||||
@@ -155,6 +154,50 @@ class TestProject(unittest.TestCase):
|
||||
so.reload()
|
||||
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):
|
||||
|
||||
|
||||
@@ -52,13 +52,15 @@
|
||||
"company",
|
||||
"lft",
|
||||
"rgt",
|
||||
"old_parent"
|
||||
"old_parent",
|
||||
"template_task"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Subject",
|
||||
"reqd": 1,
|
||||
@@ -138,6 +140,7 @@
|
||||
"fieldname": "parent_task",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Parent Task",
|
||||
"options": "Task",
|
||||
"search_index": 1
|
||||
@@ -382,6 +385,12 @@
|
||||
"fieldtype": "Date",
|
||||
"label": "Completed On",
|
||||
"mandatory_depends_on": "eval: doc.status == \"Completed\""
|
||||
},
|
||||
{
|
||||
"fieldname": "template_task",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Template Task"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-check",
|
||||
@@ -389,7 +398,7 @@
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"max_attachments": 5,
|
||||
"modified": "2022-06-23 16:58:47.005241",
|
||||
"modified": "2023-09-06 13:52:05.861175",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Task",
|
||||
|
||||
@@ -119,19 +119,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
});
|
||||
|
||||
if(this.frm.fields_dict["items"].grid.get_field('batch_no')) {
|
||||
this.frm.set_query("batch_no", "items", function(doc, cdt, cdn) {
|
||||
if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
|
||||
this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
|
||||
return me.set_query_for_batch(doc, cdt, cdn);
|
||||
});
|
||||
|
||||
let batch_field = this.frm.get_docfield('items', 'batch_no');
|
||||
if (batch_field) {
|
||||
batch_field.get_route_options_for_new_doc = (row) => {
|
||||
return {
|
||||
'item': row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if(
|
||||
@@ -196,14 +187,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
});
|
||||
}
|
||||
|
||||
let batch_no_field = this.frm.get_docfield("items", "batch_no");
|
||||
if (batch_no_field) {
|
||||
batch_no_field.get_route_options_for_new_doc = function(row) {
|
||||
return {
|
||||
"item": row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) {
|
||||
this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) {
|
||||
@@ -257,6 +240,17 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
|
||||
let batch_field = this.frm.get_docfield('items', 'batch_no');
|
||||
if (batch_field) {
|
||||
batch_field.get_route_options_for_new_doc = (row) => {
|
||||
return {
|
||||
'item': row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is_return() {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
{
|
||||
"fieldname": "parent_quality_procedure",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Parent Procedure",
|
||||
"options": "Quality Procedure"
|
||||
},
|
||||
@@ -115,7 +116,7 @@
|
||||
"link_fieldname": "procedure"
|
||||
}
|
||||
],
|
||||
"modified": "2020-10-26 15:25:39.316088",
|
||||
"modified": "2023-08-29 12:49:53.963370",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Quality Management",
|
||||
"name": "Quality Procedure",
|
||||
@@ -149,5 +150,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1998,6 +1998,61 @@ class TestSalesOrder(FrappeTestCase):
|
||||
self.assertEqual(len(dn.packed_items), 1)
|
||||
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2")
|
||||
|
||||
@change_settings("Selling Settings", {"editable_bundle_item_rates": 1})
|
||||
def test_expired_rate_for_packed_item(self):
|
||||
bundle = "_Test Product Bundle 1"
|
||||
packed_item = "_Packed Item 1"
|
||||
|
||||
# test Update Items with product bundle
|
||||
for product_bundle in [bundle]:
|
||||
if not frappe.db.exists("Item", product_bundle):
|
||||
bundle_item = make_item(product_bundle, {"is_stock_item": 0})
|
||||
bundle_item.append(
|
||||
"item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"}
|
||||
)
|
||||
bundle_item.save(ignore_permissions=True)
|
||||
|
||||
for product_bundle in [packed_item]:
|
||||
if not frappe.db.exists("Item", product_bundle):
|
||||
make_item(product_bundle, {"is_stock_item": 0, "stock_uom": "Nos"})
|
||||
|
||||
make_product_bundle(bundle, [packed_item], 1)
|
||||
|
||||
for scenario in [
|
||||
{"valid_upto": add_days(nowdate(), -1), "expected_rate": 0.0},
|
||||
{"valid_upto": add_days(nowdate(), 1), "expected_rate": 111.0},
|
||||
]:
|
||||
with self.subTest(scenario=scenario):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"item_code": packed_item,
|
||||
"selling": 1,
|
||||
"price_list": "_Test Price List",
|
||||
"valid_from": add_days(nowdate(), -1),
|
||||
"valid_upto": scenario.get("valid_upto"),
|
||||
"price_list_rate": 111,
|
||||
}
|
||||
).save()
|
||||
|
||||
so = frappe.new_doc("Sales Order")
|
||||
so.transaction_date = nowdate()
|
||||
so.delivery_date = nowdate()
|
||||
so.set_warehouse = ""
|
||||
so.company = "_Test Company"
|
||||
so.customer = "_Test Customer"
|
||||
so.currency = "INR"
|
||||
so.selling_price_list = "_Test Price List"
|
||||
so.append("items", {"item_code": bundle, "qty": 1})
|
||||
so.save()
|
||||
|
||||
self.assertEqual(len(so.items), 1)
|
||||
self.assertEqual(len(so.packed_items), 1)
|
||||
self.assertEqual(so.items[0].item_code, bundle)
|
||||
self.assertEqual(so.packed_items[0].item_code, packed_item)
|
||||
self.assertEqual(so.items[0].rate, scenario.get("expected_rate"))
|
||||
self.assertEqual(so.packed_items[0].rate, scenario.get("expected_rate"))
|
||||
|
||||
|
||||
def automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
|
||||
@@ -25,18 +25,15 @@
|
||||
"label": "Department",
|
||||
"oldfieldname": "department_name",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parent_department",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Parent Department",
|
||||
"options": "Department",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Department"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@@ -44,9 +41,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@@ -54,17 +49,13 @@
|
||||
"fieldname": "is_group",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Group",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Is Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "lft",
|
||||
@@ -72,9 +63,7 @@
|
||||
"hidden": 1,
|
||||
"label": "lft",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rgt",
|
||||
@@ -82,9 +71,7 @@
|
||||
"hidden": 1,
|
||||
"label": "rgt",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "old_parent",
|
||||
@@ -92,22 +79,18 @@
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Old Parent",
|
||||
"print_hide": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-10 12:28:00.563272",
|
||||
"modified": "2023-08-28 17:26:46.826501",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Department",
|
||||
@@ -147,12 +130,12 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
@@ -66,5 +66,8 @@ def make_employee(user, company=None, **kwargs):
|
||||
employee.insert()
|
||||
return employee.name
|
||||
else:
|
||||
frappe.db.set_value("Employee", {"employee_name": user}, "status", "Active")
|
||||
return frappe.get_value("Employee", {"employee_name": user}, "name")
|
||||
employee = frappe.get_doc("Employee", {"employee_name": user})
|
||||
employee.update(kwargs)
|
||||
employee.status = "Active"
|
||||
employee.save()
|
||||
return employee.name
|
||||
|
||||
@@ -138,6 +138,7 @@ class DeliveryNote(SellingController):
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_duplicate_serial_nos()
|
||||
|
||||
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||
|
||||
@@ -412,6 +413,21 @@ class DeliveryNote(SellingController):
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
def validate_duplicate_serial_nos(self):
|
||||
serial_nos = []
|
||||
for item in self.items:
|
||||
if not item.serial_no:
|
||||
continue
|
||||
|
||||
for serial_no in item.serial_no.split("\n"):
|
||||
if serial_no in serial_nos:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Serial No {1} is already selected.").format(item.idx, serial_no),
|
||||
title=_("Duplicate Serial No"),
|
||||
)
|
||||
else:
|
||||
serial_nos.append(serial_no)
|
||||
|
||||
|
||||
def update_billed_amount_based_on_so(so_detail, update_modified=True):
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
@@ -11,10 +11,12 @@ def get_data():
|
||||
},
|
||||
"internal_links": {
|
||||
"Sales Order": ["items", "against_sales_order"],
|
||||
"Sales Invoice": ["items", "against_sales_invoice"],
|
||||
"Material Request": ["items", "material_request"],
|
||||
"Purchase Order": ["items", "purchase_order"],
|
||||
},
|
||||
"internal_and_external_links": {
|
||||
"Sales Invoice": ["items", "against_sales_invoice"],
|
||||
},
|
||||
"transactions": [
|
||||
{"label": _("Related"), "items": ["Sales Invoice", "Packing Slip", "Delivery Trip"]},
|
||||
{"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]},
|
||||
|
||||
@@ -1211,6 +1211,38 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
|
||||
self.assertTrue(return_dn.docstatus == 1)
|
||||
|
||||
def test_duplicate_serial_no_in_delivery_note(self):
|
||||
# Step - 1: Create Serial Item
|
||||
serial_item = make_item(
|
||||
properties={
|
||||
"is_stock_item": 1,
|
||||
"has_serial_no": 1,
|
||||
"serial_no_series": frappe.generate_hash("", 10) + ".###",
|
||||
}
|
||||
).name
|
||||
|
||||
# Step - 2: Inward Stock
|
||||
se = make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=4)
|
||||
|
||||
# Step - 3: Create Delivery Note with Duplicare Serial Nos
|
||||
serial_nos = se.items[0].serial_no.split("\n")
|
||||
dn = create_delivery_note(
|
||||
item_code=serial_item,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
qty=2,
|
||||
do_not_save=True,
|
||||
)
|
||||
dn.items[0].serial_no = "\n".join(serial_nos[:2])
|
||||
dn.append("items", dn.items[0].as_dict())
|
||||
|
||||
# Test - 1: ValidationError should be raised
|
||||
self.assertRaises(frappe.ValidationError, dn.save)
|
||||
|
||||
# Step - 4: Submit Delivery Note with unique Serial Nos
|
||||
dn.items[1].serial_no = "\n".join(serial_nos[2:])
|
||||
dn.save()
|
||||
dn.submit()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
frappe.provide("erpnext.item");
|
||||
|
||||
const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
|
||||
const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
|
||||
|
||||
frappe.ui.form.on("Item", {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('attribute', 'numeric_values', 'numeric_values');
|
||||
@@ -347,18 +350,20 @@ $.extend(erpnext.item, {
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict['deferred_revenue_account'].get_query = function() {
|
||||
frm.fields_dict["item_defaults"].grid.get_field("deferred_revenue_account").get_query = function(doc, cdt, cdn) {
|
||||
return {
|
||||
filters: {
|
||||
"company": locals[cdt][cdn].company,
|
||||
'root_type': 'Liability',
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frm.fields_dict['deferred_expense_account'].get_query = function() {
|
||||
frm.fields_dict["item_defaults"].grid.get_field("deferred_expense_account").get_query = function(doc, cdt, cdn) {
|
||||
return {
|
||||
filters: {
|
||||
"company": locals[cdt][cdn].company,
|
||||
'root_type': 'Asset',
|
||||
"is_group": 0
|
||||
}
|
||||
@@ -894,7 +899,13 @@ function open_form(frm, doctype, child_doctype, parentfield) {
|
||||
let new_child_doc = frappe.model.add_child(new_doc, child_doctype, parentfield);
|
||||
new_child_doc.item_code = frm.doc.name;
|
||||
new_child_doc.item_name = frm.doc.item_name;
|
||||
new_child_doc.uom = frm.doc.stock_uom;
|
||||
if (in_list(SALES_DOCTYPES, doctype) && frm.doc.sales_uom) {
|
||||
new_child_doc.uom = frm.doc.sales_uom;
|
||||
} else if (in_list(PURCHASE_DOCTYPES, doctype) && frm.doc.purchase_uom) {
|
||||
new_child_doc.uom = frm.doc.purchase_uom;
|
||||
} else {
|
||||
new_child_doc.uom = frm.doc.stock_uom;
|
||||
}
|
||||
new_child_doc.description = frm.doc.description;
|
||||
if (!new_child_doc.qty) {
|
||||
new_child_doc.qty = 1.0;
|
||||
|
||||
@@ -70,6 +70,13 @@
|
||||
"variant_based_on",
|
||||
"attributes",
|
||||
"accounting",
|
||||
"deferred_accounting_section",
|
||||
"enable_deferred_expense",
|
||||
"no_of_months_exp",
|
||||
"column_break_9s9o",
|
||||
"enable_deferred_revenue",
|
||||
"no_of_months",
|
||||
"section_break_avcp",
|
||||
"item_defaults",
|
||||
"purchasing_tab",
|
||||
"purchase_uom",
|
||||
@@ -85,10 +92,6 @@
|
||||
"delivered_by_supplier",
|
||||
"column_break2",
|
||||
"supplier_items",
|
||||
"deferred_expense_section",
|
||||
"enable_deferred_expense",
|
||||
"deferred_expense_account",
|
||||
"no_of_months_exp",
|
||||
"foreign_trade_details",
|
||||
"country_of_origin",
|
||||
"column_break_59",
|
||||
@@ -99,10 +102,6 @@
|
||||
"is_sales_item",
|
||||
"column_break3",
|
||||
"max_discount",
|
||||
"deferred_revenue",
|
||||
"enable_deferred_revenue",
|
||||
"deferred_revenue_account",
|
||||
"no_of_months",
|
||||
"customer_details",
|
||||
"customer_items",
|
||||
"item_tax_section_break",
|
||||
@@ -657,20 +656,6 @@
|
||||
"oldfieldname": "max_discount",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "deferred_revenue",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Deferred Revenue"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_deferred_revenue",
|
||||
"fieldname": "deferred_revenue_account",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Deferred Revenue Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_deferred_revenue",
|
||||
@@ -681,21 +666,7 @@
|
||||
"depends_on": "enable_deferred_revenue",
|
||||
"fieldname": "no_of_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "No of Months"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "deferred_expense_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Deferred Expense"
|
||||
},
|
||||
{
|
||||
"depends_on": "enable_deferred_expense",
|
||||
"fieldname": "deferred_expense_account",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Deferred Expense Account",
|
||||
"options": "Account"
|
||||
"label": "No of Months (Revenue)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -904,6 +875,20 @@
|
||||
"fieldname": "accounting",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Accounting"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9s9o",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_avcp",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "deferred_accounting_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Deferred Accounting"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-tag",
|
||||
@@ -912,7 +897,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2023-07-14 17:18:18.658942",
|
||||
"modified": "2023-09-11 13:46:32.688051",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
"selling_defaults",
|
||||
"selling_cost_center",
|
||||
"column_break_12",
|
||||
"income_account"
|
||||
"income_account",
|
||||
"deferred_accounting_defaults_section",
|
||||
"deferred_expense_account",
|
||||
"column_break_kwad",
|
||||
"deferred_revenue_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -108,11 +112,34 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Provisional Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "deferred_accounting_defaults_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Deferred Accounting Defaults"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: parent.enable_deferred_expense",
|
||||
"fieldname": "deferred_expense_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Deferred Expense Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: parent.enable_deferred_revenue",
|
||||
"fieldname": "deferred_revenue_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Deferred Revenue Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_kwad",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-10 20:18:54.148195",
|
||||
"modified": "2023-09-04 12:33:14.607267",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Default",
|
||||
|
||||
@@ -296,6 +296,7 @@
|
||||
"depends_on": "eval:doc.material_request_type == 'Material Transfer'",
|
||||
"fieldname": "set_from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Set Source Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -356,7 +357,7 @@
|
||||
"idx": 70,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-25 17:19:31.662662",
|
||||
"modified": "2023-09-15 12:07:24.789471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Material Request",
|
||||
|
||||
@@ -706,6 +706,7 @@ def raise_work_orders(material_request):
|
||||
)
|
||||
|
||||
wo_order.set_work_order_operations()
|
||||
wo_order.flags.ignore_mandatory = True
|
||||
wo_order.save()
|
||||
|
||||
work_orders.append(wo_order.name)
|
||||
|
||||
@@ -207,6 +207,9 @@ def update_packed_item_price_data(pi_row, item_data, doc):
|
||||
"conversion_rate": doc.get("conversion_rate"),
|
||||
}
|
||||
)
|
||||
if not row_data.get("transaction_date"):
|
||||
row_data.update({"transaction_date": doc.get("transaction_date")})
|
||||
|
||||
rate = get_price_list_rate(row_data, item_doc).get("price_list_rate")
|
||||
|
||||
pi_row.rate = rate or item_data.get("valuation_rate") or 0.0
|
||||
|
||||
@@ -605,7 +605,7 @@ class PurchaseReceipt(BuyingController):
|
||||
account=provisional_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=0.0,
|
||||
credit=multiplication_factor * item.amount,
|
||||
credit=multiplication_factor * item.base_amount,
|
||||
remarks=remarks,
|
||||
against_account=expense_account,
|
||||
account_currency=credit_currency,
|
||||
@@ -619,7 +619,7 @@ class PurchaseReceipt(BuyingController):
|
||||
gl_entries=gl_entries,
|
||||
account=expense_account,
|
||||
cost_center=item.cost_center,
|
||||
debit=multiplication_factor * item.amount,
|
||||
debit=multiplication_factor * item.base_amount,
|
||||
credit=0.0,
|
||||
remarks=remarks,
|
||||
against_account=provisional_account,
|
||||
|
||||
@@ -2024,6 +2024,49 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
ste7.reload()
|
||||
self.assertEqual(ste7.items[0].valuation_rate, valuation_rate)
|
||||
|
||||
def test_purchase_receipt_provisional_accounting(self):
|
||||
# Step - 1: Create Supplier with Default Currency as USD
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
|
||||
supplier = create_supplier(default_currency="USD")
|
||||
|
||||
# Step - 2: Setup Company for Provisional Accounting
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
|
||||
provisional_account = create_account(
|
||||
account_name="Provision Account",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
company = frappe.get_doc("Company", "_Test Company")
|
||||
company.enable_provisional_accounting_for_non_stock_items = 1
|
||||
company.default_provisional_account = provisional_account
|
||||
company.save()
|
||||
|
||||
# Step - 3: Create Non-Stock Item
|
||||
item = make_item(properties={"is_stock_item": 0})
|
||||
|
||||
# Step - 4: Create Purchase Receipt
|
||||
pr = make_purchase_receipt(
|
||||
qty=2,
|
||||
item_code=item.name,
|
||||
company=company.name,
|
||||
supplier=supplier.name,
|
||||
currency=supplier.default_currency,
|
||||
)
|
||||
|
||||
# Test - 1: Total and Base Total should not be the same as the currency is different
|
||||
self.assertNotEqual(flt(pr.total, 2), flt(pr.base_total, 2))
|
||||
self.assertEqual(flt(pr.total * pr.conversion_rate, 2), flt(pr.base_total, 2))
|
||||
|
||||
# Test - 2: Sum of Debit or Credit should be equal to Purchase Receipt Base Total
|
||||
amount = frappe.db.get_value("GL Entry", {"docstatus": 1, "voucher_no": pr.name}, ["sum(debit)"])
|
||||
expected_amount = pr.base_total
|
||||
self.assertEqual(amount, expected_amount)
|
||||
|
||||
company.enable_provisional_accounting_for_non_stock_items = 0
|
||||
company.save()
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -6,6 +6,14 @@ cur_frm.cscript.refresh = cur_frm.cscript.inspection_type;
|
||||
frappe.ui.form.on("Quality Inspection", {
|
||||
|
||||
setup: function(frm) {
|
||||
frm.set_query("reference_name", function() {
|
||||
return {
|
||||
filters: {
|
||||
"docstatus": ["!=", 2],
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("batch_no", function() {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -101,15 +101,6 @@ frappe.ui.form.on('Stock Entry', {
|
||||
}
|
||||
});
|
||||
|
||||
let batch_field = frm.get_docfield('items', 'batch_no');
|
||||
if (batch_field) {
|
||||
batch_field.get_route_options_for_new_doc = (row) => {
|
||||
return {
|
||||
'item': row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
frm.add_fetch("bom_no", "inspection_required", "inspection_required");
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
|
||||
@@ -345,6 +336,15 @@ frappe.ui.form.on('Stock Entry', {
|
||||
if(!check_should_not_attach_bom_items(frm.doc.bom_no)) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
}
|
||||
|
||||
let batch_field = frm.get_docfield('items', 'batch_no');
|
||||
if (batch_field) {
|
||||
batch_field.get_route_options_for_new_doc = (row) => {
|
||||
return {
|
||||
'item': row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
get_items_from_transit_entry: function(frm) {
|
||||
|
||||
@@ -729,7 +729,11 @@ def get_default_discount_account(args, item):
|
||||
def get_default_deferred_account(args, item, fieldname=None):
|
||||
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
|
||||
return (
|
||||
item.get(fieldname)
|
||||
frappe.get_cached_value(
|
||||
"Item Default",
|
||||
{"parent": args.item_code, "company": args.get("company")},
|
||||
fieldname,
|
||||
)
|
||||
or args.get(fieldname)
|
||||
or frappe.get_cached_value("Company", args.company, "default_" + fieldname)
|
||||
)
|
||||
|
||||
@@ -323,8 +323,10 @@ class StockBalanceReport(object):
|
||||
for field in ["item_code", "brand"]:
|
||||
if not self.filters.get(field):
|
||||
continue
|
||||
|
||||
query = query.where(item_table[field] == self.filters.get(field))
|
||||
elif field == "item_code":
|
||||
query = query.where(item_table.name == self.filters.get(field))
|
||||
else:
|
||||
query = query.where(item_table[field] == self.filters.get(field))
|
||||
|
||||
return query
|
||||
|
||||
|
||||
@@ -75,15 +75,6 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let batch_no_field = frm.get_docfield('items', 'batch_no');
|
||||
if (batch_no_field) {
|
||||
batch_no_field.get_route_options_for_new_doc = function(row) {
|
||||
return {
|
||||
'item': row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
refresh: (frm) => {
|
||||
@@ -148,6 +139,15 @@ frappe.ui.form.on('Subcontracting Receipt', {
|
||||
|
||||
frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM');
|
||||
}
|
||||
|
||||
let batch_no_field = frm.get_docfield('items', 'batch_no');
|
||||
if (batch_no_field) {
|
||||
batch_no_field.get_route_options_for_new_doc = function(row) {
|
||||
return {
|
||||
'item': row.doc.item_code
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
set_warehouse: (frm) => {
|
||||
@@ -202,4 +202,4 @@ let set_missing_values = (frm) => {
|
||||
if (!r.exc) frm.refresh();
|
||||
},
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@@ -176,10 +176,9 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
|
||||
rm_supp_cost.pop(item.name)
|
||||
|
||||
if item.recalculate_rate:
|
||||
item.rate = (
|
||||
flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
|
||||
)
|
||||
item.rate = (
|
||||
flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
|
||||
)
|
||||
|
||||
item.received_qty = item.qty + flt(item.rejected_qty)
|
||||
item.amount = item.qty * item.rate
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"rate_and_amount",
|
||||
"rate",
|
||||
"amount",
|
||||
"recalculate_rate",
|
||||
"column_break_19",
|
||||
"rm_cost_per_qty",
|
||||
"service_cost_per_qty",
|
||||
@@ -196,7 +195,6 @@
|
||||
"options": "currency",
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval: doc.recalculate_rate",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
@@ -466,18 +464,12 @@
|
||||
"fieldname": "accounting_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "recalculate_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Recalculate Rate"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-06 18:44:45.599761",
|
||||
"modified": "2023-09-03 17:04:21.214316",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
||||
Reference in New Issue
Block a user