Merge branch 'develop' into bank-trans-party-automatch
This commit is contained in:
commit
6fe5264ae2
@ -204,8 +204,11 @@ class Account(NestedSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_account_currency(self):
|
def validate_account_currency(self):
|
||||||
|
self.currency_explicitly_specified = True
|
||||||
|
|
||||||
if not self.account_currency:
|
if not self.account_currency:
|
||||||
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||||
|
self.currency_explicitly_specified = False
|
||||||
|
|
||||||
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
|
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
|
||||||
|
|
||||||
@ -251,8 +254,10 @@ class Account(NestedSet):
|
|||||||
{
|
{
|
||||||
"company": company,
|
"company": company,
|
||||||
# parent account's currency should be passed down to child account's curreny
|
# parent account's currency should be passed down to child account's curreny
|
||||||
# if it is None, it picks it up from default company currency, which might be unintended
|
# if currency explicitly specified by user, child will inherit. else, default currency will be used.
|
||||||
"account_currency": erpnext.get_company_currency(company),
|
"account_currency": self.account_currency
|
||||||
|
if self.currency_explicitly_specified
|
||||||
|
else erpnext.get_company_currency(company),
|
||||||
"parent_account": parent_acc_name_map[company],
|
"parent_account": parent_acc_name_map[company],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -5,10 +5,13 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.test_runner import make_test_records
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.account import merge_account, update_account_number
|
from erpnext.accounts.doctype.account.account import merge_account, update_account_number
|
||||||
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
|
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
|
||||||
|
|
||||||
|
test_dependencies = ["Company"]
|
||||||
|
|
||||||
|
|
||||||
class TestAccount(unittest.TestCase):
|
class TestAccount(unittest.TestCase):
|
||||||
def test_rename_account(self):
|
def test_rename_account(self):
|
||||||
@ -188,6 +191,58 @@ class TestAccount(unittest.TestCase):
|
|||||||
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
|
||||||
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
|
||||||
|
|
||||||
|
def test_account_currency_sync(self):
|
||||||
|
"""
|
||||||
|
In a parent->child company setup, child should inherit parent account currency if explicitly specified.
|
||||||
|
"""
|
||||||
|
|
||||||
|
make_test_records("Company")
|
||||||
|
|
||||||
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
|
def create_bank_account():
|
||||||
|
acc = frappe.new_doc("Account")
|
||||||
|
acc.account_name = "_Test Bank JPY"
|
||||||
|
|
||||||
|
acc.parent_account = "Temporary Accounts - _TC6"
|
||||||
|
acc.company = "_Test Company 6"
|
||||||
|
return acc
|
||||||
|
|
||||||
|
acc = create_bank_account()
|
||||||
|
# Explicitly set currency
|
||||||
|
acc.account_currency = "JPY"
|
||||||
|
acc.insert()
|
||||||
|
self.assertTrue(
|
||||||
|
frappe.db.exists(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "_Test Bank JPY",
|
||||||
|
"account_currency": "JPY",
|
||||||
|
"company": "_Test Company 7",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.delete_doc("Account", "_Test Bank JPY - _TC6")
|
||||||
|
frappe.delete_doc("Account", "_Test Bank JPY - _TC7")
|
||||||
|
|
||||||
|
acc = create_bank_account()
|
||||||
|
# default currency is used
|
||||||
|
acc.insert()
|
||||||
|
self.assertTrue(
|
||||||
|
frappe.db.exists(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "_Test Bank JPY",
|
||||||
|
"account_currency": "USD",
|
||||||
|
"company": "_Test Company 7",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.delete_doc("Account", "_Test Bank JPY - _TC6")
|
||||||
|
frappe.delete_doc("Account", "_Test Bank JPY - _TC7")
|
||||||
|
|
||||||
def test_child_company_account_rename_sync(self):
|
def test_child_company_account_rename_sync(self):
|
||||||
frappe.local.flags.pop("ignore_root_company_validation", None)
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
|
|
||||||
@ -297,7 +352,7 @@ def _make_test_records(verbose=None):
|
|||||||
# fixed asset depreciation
|
# fixed asset depreciation
|
||||||
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
||||||
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
||||||
["_Test Depreciations", "Expenses", 0, None, None],
|
["_Test Depreciations", "Expenses", 0, "Depreciation", None],
|
||||||
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
||||||
# Receivable / Payable Account
|
# Receivable / Payable Account
|
||||||
["_Test Receivable", "Current Assets", 0, "Receivable", None],
|
["_Test Receivable", "Current Assets", 0, "Receivable", None],
|
||||||
|
@ -56,7 +56,7 @@ class BankClearance(Document):
|
|||||||
select
|
select
|
||||||
"Payment Entry" as payment_document, name as payment_entry,
|
"Payment Entry" as payment_document, name as payment_entry,
|
||||||
reference_no as cheque_number, reference_date as cheque_date,
|
reference_no as cheque_number, reference_date as cheque_date,
|
||||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
||||||
if(paid_from=%(account)s, 0, received_amount) as debit,
|
if(paid_from=%(account)s, 0, received_amount) as debit,
|
||||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||||
|
@ -53,19 +53,20 @@ class BankStatementImport(DataImport):
|
|||||||
if "Bank Account" not in json.dumps(preview["columns"]):
|
if "Bank Account" not in json.dumps(preview["columns"]):
|
||||||
frappe.throw(_("Please add the Bank Account column"))
|
frappe.throw(_("Please add the Bank Account column"))
|
||||||
|
|
||||||
from frappe.utils.background_jobs import is_job_queued
|
from frappe.utils.background_jobs import is_job_enqueued
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
if not is_job_queued(self.name):
|
job_id = f"bank_statement_import::{self.name}"
|
||||||
|
if not is_job_enqueued(job_id):
|
||||||
enqueue(
|
enqueue(
|
||||||
start_import,
|
start_import,
|
||||||
queue="default",
|
queue="default",
|
||||||
timeout=6000,
|
timeout=6000,
|
||||||
event="data_import",
|
event="data_import",
|
||||||
job_name=self.name,
|
job_id=job_id,
|
||||||
data_import=self.name,
|
data_import=self.name,
|
||||||
bank_account=self.bank_account,
|
bank_account=self.bank_account,
|
||||||
import_file_path=self.import_file,
|
import_file_path=self.import_file,
|
||||||
|
@ -330,10 +330,13 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif payment_entry.payment_document == "Journal Entry":
|
elif payment_entry.payment_document == "Journal Entry":
|
||||||
return frappe.db.get_value(
|
return abs(
|
||||||
"Journal Entry Account",
|
frappe.db.get_value(
|
||||||
{"parent": payment_entry.payment_entry, "account": gl_bank_account},
|
"Journal Entry Account",
|
||||||
"sum(credit_in_account_currency)",
|
{"parent": payment_entry.payment_entry, "account": gl_bank_account},
|
||||||
|
"sum(debit_in_account_currency-credit_in_account_currency)",
|
||||||
|
)
|
||||||
|
or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
elif payment_entry.payment_document == "Expense Claim":
|
elif payment_entry.payment_document == "Expense Claim":
|
||||||
|
@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
|
|||||||
frappe.ui.form.on("Journal Entry", {
|
frappe.ui.form.on("Journal Entry", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
frm.add_fetch("bank_account", "account", "account");
|
frm.add_fetch("bank_account", "account", "account");
|
||||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger"];
|
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset Depreciation Schedule'];
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
|
@ -69,6 +69,7 @@ class JournalEntry(AccountsController):
|
|||||||
self.validate_empty_accounts_table()
|
self.validate_empty_accounts_table()
|
||||||
self.set_account_and_party_balance()
|
self.set_account_and_party_balance()
|
||||||
self.validate_inter_company_accounts()
|
self.validate_inter_company_accounts()
|
||||||
|
self.validate_depr_entry_voucher_type()
|
||||||
|
|
||||||
if self.docstatus == 0:
|
if self.docstatus == 0:
|
||||||
self.apply_tax_withholding()
|
self.apply_tax_withholding()
|
||||||
@ -130,6 +131,13 @@ class JournalEntry(AccountsController):
|
|||||||
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
||||||
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
||||||
|
|
||||||
|
def validate_depr_entry_voucher_type(self):
|
||||||
|
if (
|
||||||
|
any(d.account_type == "Depreciation" for d in self.get("accounts"))
|
||||||
|
and self.voucher_type != "Depreciation Entry"
|
||||||
|
):
|
||||||
|
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
|
||||||
|
|
||||||
def validate_stock_accounts(self):
|
def validate_stock_accounts(self):
|
||||||
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
||||||
for account in stock_accounts:
|
for account in stock_accounts:
|
||||||
@ -233,25 +241,30 @@ class JournalEntry(AccountsController):
|
|||||||
self.remove(d)
|
self.remove(d)
|
||||||
|
|
||||||
def update_asset_value(self):
|
def update_asset_value(self):
|
||||||
if self.voucher_type != "Depreciation Entry":
|
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
|
||||||
return
|
return
|
||||||
|
|
||||||
processed_assets = []
|
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if (
|
if (
|
||||||
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
|
d.reference_type == "Asset"
|
||||||
|
and d.reference_name
|
||||||
|
and d.account_type == "Depreciation"
|
||||||
|
and d.debit
|
||||||
):
|
):
|
||||||
processed_assets.append(d.reference_name)
|
|
||||||
|
|
||||||
asset = frappe.get_doc("Asset", d.reference_name)
|
asset = frappe.get_doc("Asset", d.reference_name)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
continue
|
fb_idx = 1
|
||||||
|
if self.finance_book:
|
||||||
depr_value = d.debit or d.credit
|
for fb_row in asset.get("finance_books"):
|
||||||
|
if fb_row.finance_book == self.finance_book:
|
||||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
|
fb_idx = fb_row.idx
|
||||||
|
break
|
||||||
|
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||||
|
fb_row.value_after_depreciation -= d.debit
|
||||||
|
fb_row.db_update()
|
||||||
|
else:
|
||||||
|
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
@ -316,42 +329,47 @@ class JournalEntry(AccountsController):
|
|||||||
if self.voucher_type != "Depreciation Entry":
|
if self.voucher_type != "Depreciation Entry":
|
||||||
return
|
return
|
||||||
|
|
||||||
processed_assets = []
|
|
||||||
|
|
||||||
for d in self.get("accounts"):
|
for d in self.get("accounts"):
|
||||||
if (
|
if (
|
||||||
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
|
d.reference_type == "Asset"
|
||||||
|
and d.reference_name
|
||||||
|
and d.account_type == "Depreciation"
|
||||||
|
and d.debit
|
||||||
):
|
):
|
||||||
processed_assets.append(d.reference_name)
|
|
||||||
|
|
||||||
asset = frappe.get_doc("Asset", d.reference_name)
|
asset = frappe.get_doc("Asset", d.reference_name)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
je_found = False
|
je_found = False
|
||||||
|
|
||||||
for row in asset.get("finance_books"):
|
for fb_row in asset.get("finance_books"):
|
||||||
if je_found:
|
if je_found:
|
||||||
break
|
break
|
||||||
|
|
||||||
depr_schedule = get_depr_schedule(asset.name, "Active", row.finance_book)
|
depr_schedule = get_depr_schedule(asset.name, "Active", fb_row.finance_book)
|
||||||
|
|
||||||
for s in depr_schedule or []:
|
for s in depr_schedule or []:
|
||||||
if s.journal_entry == self.name:
|
if s.journal_entry == self.name:
|
||||||
s.db_set("journal_entry", None)
|
s.db_set("journal_entry", None)
|
||||||
|
|
||||||
row.value_after_depreciation += s.depreciation_amount
|
fb_row.value_after_depreciation += d.debit
|
||||||
row.db_update()
|
fb_row.db_update()
|
||||||
|
|
||||||
asset.set_status()
|
|
||||||
|
|
||||||
je_found = True
|
je_found = True
|
||||||
break
|
break
|
||||||
|
if not je_found:
|
||||||
|
fb_idx = 1
|
||||||
|
if self.finance_book:
|
||||||
|
for fb_row in asset.get("finance_books"):
|
||||||
|
if fb_row.finance_book == self.finance_book:
|
||||||
|
fb_idx = fb_row.idx
|
||||||
|
break
|
||||||
|
|
||||||
|
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||||
|
fb_row.value_after_depreciation += d.debit
|
||||||
|
fb_row.db_update()
|
||||||
else:
|
else:
|
||||||
depr_value = d.debit or d.credit
|
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
|
||||||
|
asset.set_status()
|
||||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
|
|
||||||
|
|
||||||
asset.set_status()
|
|
||||||
|
|
||||||
def unlink_inter_company_jv(self):
|
def unlink_inter_company_jv(self):
|
||||||
if (
|
if (
|
||||||
|
@ -2,6 +2,21 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on("Journal Entry Template", {
|
frappe.ui.form.on("Journal Entry Template", {
|
||||||
|
onload: function(frm) {
|
||||||
|
if(frm.is_new()) {
|
||||||
|
frappe.call({
|
||||||
|
type: "GET",
|
||||||
|
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
|
||||||
|
callback: function(r){
|
||||||
|
if(r.message) {
|
||||||
|
frm.set_df_property("naming_series", "options", r.message.split("\n"));
|
||||||
|
frm.set_value("naming_series", r.message.split("\n")[0]);
|
||||||
|
frm.refresh_field("naming_series");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
refresh: function(frm) {
|
refresh: function(frm) {
|
||||||
frappe.model.set_default_values(frm.doc);
|
frappe.model.set_default_values(frm.doc);
|
||||||
|
|
||||||
@ -19,18 +34,6 @@ frappe.ui.form.on("Journal Entry Template", {
|
|||||||
|
|
||||||
return { filters: filters };
|
return { filters: filters };
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.call({
|
|
||||||
type: "GET",
|
|
||||||
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
|
|
||||||
callback: function(r){
|
|
||||||
if(r.message){
|
|
||||||
frm.set_df_property("naming_series", "options", r.message.split("\n"));
|
|
||||||
frm.set_value("naming_series", r.message.split("\n")[0]);
|
|
||||||
frm.refresh_field("naming_series");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
voucher_type: function(frm) {
|
voucher_type: function(frm) {
|
||||||
var add_accounts = function(doc, r) {
|
var add_accounts = function(doc, r) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils.background_jobs import is_job_queued
|
from frappe.utils.background_jobs import is_job_enqueued
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.account import merge_account
|
from erpnext.accounts.doctype.account.account import merge_account
|
||||||
|
|
||||||
@ -17,13 +17,14 @@ class LedgerMerge(Document):
|
|||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
if not is_job_queued(self.name):
|
job_id = f"ledger_merge::{self.name}"
|
||||||
|
if not is_job_enqueued(job_id):
|
||||||
enqueue(
|
enqueue(
|
||||||
start_merge,
|
start_merge,
|
||||||
queue="default",
|
queue="default",
|
||||||
timeout=6000,
|
timeout=6000,
|
||||||
event="ledger_merge",
|
event="ledger_merge",
|
||||||
job_name=self.name,
|
job_id=job_id,
|
||||||
docname=self.name,
|
docname=self.name,
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,7 @@ import frappe
|
|||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt, nowdate
|
from frappe.utils import flt, nowdate
|
||||||
from frappe.utils.background_jobs import enqueue, is_job_queued
|
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_accounting_dimensions,
|
get_accounting_dimensions,
|
||||||
@ -212,13 +212,15 @@ class OpeningInvoiceCreationTool(Document):
|
|||||||
if is_scheduler_inactive() and not frappe.flags.in_test:
|
if is_scheduler_inactive() and not frappe.flags.in_test:
|
||||||
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
|
||||||
|
|
||||||
if not is_job_queued(self.name):
|
job_id = f"opening_invoice::{self.name}"
|
||||||
|
|
||||||
|
if not is_job_enqueued(job_id):
|
||||||
enqueue(
|
enqueue(
|
||||||
start_import,
|
start_import,
|
||||||
queue="default",
|
queue="default",
|
||||||
timeout=6000,
|
timeout=6000,
|
||||||
event="opening_invoice_creation",
|
event="opening_invoice_creation",
|
||||||
job_name=self.name,
|
job_id=job_id,
|
||||||
invoices=invoices,
|
invoices=invoices,
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||||
)
|
)
|
||||||
|
@ -904,7 +904,7 @@ frappe.ui.form.on('Payment Entry', {
|
|||||||
function(d) { return flt(d.amount) }));
|
function(d) { return flt(d.amount) }));
|
||||||
|
|
||||||
frm.set_value("difference_amount", difference_amount - total_deductions +
|
frm.set_value("difference_amount", difference_amount - total_deductions +
|
||||||
frm.doc.base_total_taxes_and_charges);
|
flt(frm.doc.base_total_taxes_and_charges));
|
||||||
|
|
||||||
frm.events.hide_unhide_fields(frm);
|
frm.events.hide_unhide_fields(frm);
|
||||||
},
|
},
|
||||||
|
@ -654,6 +654,28 @@ class PaymentEntry(AccountsController):
|
|||||||
self.precision("base_received_amount"),
|
self.precision("base_received_amount"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def calculate_base_allocated_amount_for_reference(self, d) -> float:
|
||||||
|
base_allocated_amount = 0
|
||||||
|
if d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
|
||||||
|
# When referencing Sales/Purchase Order, use the source/target exchange rate depending on payment type.
|
||||||
|
# This is so there are no Exchange Gain/Loss generated for such doctypes
|
||||||
|
|
||||||
|
exchange_rate = 1
|
||||||
|
if self.payment_type == "Receive":
|
||||||
|
exchange_rate = self.source_exchange_rate
|
||||||
|
elif self.payment_type == "Pay":
|
||||||
|
exchange_rate = self.target_exchange_rate
|
||||||
|
|
||||||
|
base_allocated_amount += flt(
|
||||||
|
flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
base_allocated_amount += flt(
|
||||||
|
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
|
||||||
|
)
|
||||||
|
|
||||||
|
return base_allocated_amount
|
||||||
|
|
||||||
def set_total_allocated_amount(self):
|
def set_total_allocated_amount(self):
|
||||||
if self.payment_type == "Internal Transfer":
|
if self.payment_type == "Internal Transfer":
|
||||||
return
|
return
|
||||||
@ -662,9 +684,7 @@ class PaymentEntry(AccountsController):
|
|||||||
for d in self.get("references"):
|
for d in self.get("references"):
|
||||||
if d.allocated_amount:
|
if d.allocated_amount:
|
||||||
total_allocated_amount += flt(d.allocated_amount)
|
total_allocated_amount += flt(d.allocated_amount)
|
||||||
base_total_allocated_amount += flt(
|
base_total_allocated_amount += self.calculate_base_allocated_amount_for_reference(d)
|
||||||
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
|
|
||||||
)
|
|
||||||
|
|
||||||
self.total_allocated_amount = abs(total_allocated_amount)
|
self.total_allocated_amount = abs(total_allocated_amount)
|
||||||
self.base_total_allocated_amount = abs(base_total_allocated_amount)
|
self.base_total_allocated_amount = abs(base_total_allocated_amount)
|
||||||
@ -881,9 +901,7 @@ class PaymentEntry(AccountsController):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
allocated_amount_in_company_currency = flt(
|
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
|
||||||
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("paid_amount")
|
|
||||||
)
|
|
||||||
|
|
||||||
gle.update(
|
gle.update(
|
||||||
{
|
{
|
||||||
@ -1693,7 +1711,10 @@ def get_payment_entry(
|
|||||||
):
|
):
|
||||||
reference_doc = None
|
reference_doc = None
|
||||||
doc = frappe.get_doc(dt, dn)
|
doc = frappe.get_doc(dt, dn)
|
||||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= 99.99:
|
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||||
|
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
|
||||||
|
100.0 + over_billing_allowance
|
||||||
|
):
|
||||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||||
|
|
||||||
if not party_type:
|
if not party_type:
|
||||||
@ -1712,6 +1733,13 @@ def get_payment_entry(
|
|||||||
# bank or cash
|
# bank or cash
|
||||||
bank = get_bank_cash_account(doc, bank_account)
|
bank = get_bank_cash_account(doc, bank_account)
|
||||||
|
|
||||||
|
# if default bank or cash account is not set in company master and party has default company bank account, fetch it
|
||||||
|
if party_type in ["Customer", "Supplier"] and not bank:
|
||||||
|
party_bank_account = get_party_bank_account(party_type, doc.get(scrub(party_type)))
|
||||||
|
if party_bank_account:
|
||||||
|
account = frappe.db.get_value("Bank Account", party_bank_account, "account")
|
||||||
|
bank = get_bank_cash_account(doc, account)
|
||||||
|
|
||||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
|
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
|
||||||
)
|
)
|
||||||
@ -1928,19 +1956,27 @@ def set_paid_amount_and_received_amount(
|
|||||||
paid_amount = received_amount = 0
|
paid_amount = received_amount = 0
|
||||||
if party_account_currency == bank.account_currency:
|
if party_account_currency == bank.account_currency:
|
||||||
paid_amount = received_amount = abs(outstanding_amount)
|
paid_amount = received_amount = abs(outstanding_amount)
|
||||||
elif payment_type == "Receive":
|
|
||||||
paid_amount = abs(outstanding_amount)
|
|
||||||
if bank_amount:
|
|
||||||
received_amount = bank_amount
|
|
||||||
else:
|
|
||||||
received_amount = paid_amount * doc.get("conversion_rate", 1)
|
|
||||||
else:
|
else:
|
||||||
received_amount = abs(outstanding_amount)
|
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
|
||||||
if bank_amount:
|
if payment_type == "Receive":
|
||||||
paid_amount = bank_amount
|
paid_amount = abs(outstanding_amount)
|
||||||
|
if bank_amount:
|
||||||
|
received_amount = bank_amount
|
||||||
|
else:
|
||||||
|
if company_currency != bank.account_currency:
|
||||||
|
received_amount = paid_amount / doc.get("conversion_rate", 1)
|
||||||
|
else:
|
||||||
|
received_amount = paid_amount * doc.get("conversion_rate", 1)
|
||||||
else:
|
else:
|
||||||
# if party account currency and bank currency is different then populate paid amount as well
|
received_amount = abs(outstanding_amount)
|
||||||
paid_amount = received_amount * doc.get("conversion_rate", 1)
|
if bank_amount:
|
||||||
|
paid_amount = bank_amount
|
||||||
|
else:
|
||||||
|
if company_currency != bank.account_currency:
|
||||||
|
paid_amount = received_amount / doc.get("conversion_rate", 1)
|
||||||
|
else:
|
||||||
|
# if party account currency and bank currency is different then populate paid amount as well
|
||||||
|
paid_amount = received_amount * doc.get("conversion_rate", 1)
|
||||||
|
|
||||||
return paid_amount, received_amount
|
return paid_amount, received_amount
|
||||||
|
|
||||||
|
@ -51,6 +51,38 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
||||||
self.assertEqual(so_advance_paid, 0)
|
self.assertEqual(so_advance_paid, 0)
|
||||||
|
|
||||||
|
def test_payment_against_sales_order_usd_to_inr(self):
|
||||||
|
so = make_sales_order(
|
||||||
|
customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True
|
||||||
|
)
|
||||||
|
so.conversion_rate = 50
|
||||||
|
so.submit()
|
||||||
|
pe = get_payment_entry("Sales Order", so.name)
|
||||||
|
pe.source_exchange_rate = 55
|
||||||
|
pe.received_amount = 5500
|
||||||
|
pe.insert()
|
||||||
|
pe.submit()
|
||||||
|
|
||||||
|
# there should be no difference amount
|
||||||
|
pe.reload()
|
||||||
|
self.assertEqual(pe.difference_amount, 0)
|
||||||
|
self.assertEqual(pe.deductions, [])
|
||||||
|
|
||||||
|
expected_gle = dict(
|
||||||
|
(d[0], d)
|
||||||
|
for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], ["Cash - _TC", 5500.0, 0, None]]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.validate_gl_entries(pe.name, expected_gle)
|
||||||
|
|
||||||
|
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
||||||
|
self.assertEqual(so_advance_paid, 100)
|
||||||
|
|
||||||
|
pe.cancel()
|
||||||
|
|
||||||
|
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
||||||
|
self.assertEqual(so_advance_paid, 0)
|
||||||
|
|
||||||
def test_payment_entry_for_blocked_supplier_invoice(self):
|
def test_payment_entry_for_blocked_supplier_invoice(self):
|
||||||
supplier = frappe.get_doc("Supplier", "_Test Supplier")
|
supplier = frappe.get_doc("Supplier", "_Test Supplier")
|
||||||
supplier.on_hold = 1
|
supplier.on_hold = 1
|
||||||
|
@ -205,7 +205,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(frappe.ValidationError, jv1.submit)
|
self.assertRaises(frappe.ValidationError, jv1.submit)
|
||||||
|
|
||||||
def test_closing_balance_with_dimensions(self):
|
def test_closing_balance_with_dimensions_and_test_reposting_entry(self):
|
||||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||||
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
||||||
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
|
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
|
||||||
@ -299,6 +299,24 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
|||||||
self.assertEqual(cc2_closing_balance.credit, 500)
|
self.assertEqual(cc2_closing_balance.credit, 500)
|
||||||
self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500)
|
self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500)
|
||||||
|
|
||||||
|
warehouse = frappe.db.get_value("Warehouse", {"company": company}, "name")
|
||||||
|
|
||||||
|
repost_doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Repost Item Valuation",
|
||||||
|
"company": company,
|
||||||
|
"posting_date": "2020-03-15",
|
||||||
|
"based_on": "Item and Warehouse",
|
||||||
|
"item_code": "Test Item 1",
|
||||||
|
"warehouse": warehouse,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, repost_doc.save)
|
||||||
|
|
||||||
|
repost_doc.posting_date = today()
|
||||||
|
repost_doc.save()
|
||||||
|
|
||||||
def make_period_closing_voucher(self, posting_date=None, submit=True):
|
def make_period_closing_voucher(self, posting_date=None, submit=True):
|
||||||
surplus_account = create_account()
|
surplus_account = create_account()
|
||||||
cost_center = create_cost_center("Test Cost Center 1")
|
cost_center = create_cost_center("Test Cost Center 1")
|
||||||
|
@ -9,7 +9,7 @@ from frappe import _
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.model.mapper import map_child_doc, map_doc
|
from frappe.model.mapper import map_child_doc, map_doc
|
||||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||||
from frappe.utils.background_jobs import enqueue, is_job_queued
|
from frappe.utils.background_jobs import enqueue, is_job_enqueued
|
||||||
from frappe.utils.scheduler import is_scheduler_inactive
|
from frappe.utils.scheduler import is_scheduler_inactive
|
||||||
|
|
||||||
|
|
||||||
@ -483,15 +483,15 @@ def enqueue_job(job, **kwargs):
|
|||||||
|
|
||||||
closing_entry = kwargs.get("closing_entry") or {}
|
closing_entry = kwargs.get("closing_entry") or {}
|
||||||
|
|
||||||
job_name = closing_entry.get("name")
|
job_id = "pos_invoice_merge::" + str(closing_entry.get("name"))
|
||||||
if not is_job_queued(job_name):
|
if not is_job_enqueued(job_id):
|
||||||
enqueue(
|
enqueue(
|
||||||
job,
|
job,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
queue="long",
|
queue="long",
|
||||||
timeout=10000,
|
timeout=10000,
|
||||||
event="processing_merge_logs",
|
event="processing_merge_logs",
|
||||||
job_name=job_name,
|
job_id=job_id,
|
||||||
now=frappe.conf.developer_mode or frappe.flags.in_test
|
now=frappe.conf.developer_mode or frappe.flags.in_test
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -164,7 +164,7 @@ def trigger_reconciliation_for_queued_docs():
|
|||||||
Fetch queued docs and start reconciliation process for each one
|
Fetch queued docs and start reconciliation process for each one
|
||||||
"""
|
"""
|
||||||
if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
|
if not frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments"):
|
||||||
frappe.throw(
|
frappe.msgprint(
|
||||||
_("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format(
|
_("Auto Reconciliation of Payments has been disabled. Enable it through {0}").format(
|
||||||
get_link_to_form("Accounts Settings", "Accounts Settings")
|
get_link_to_form("Accounts Settings", "Accounts Settings")
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
||||||
<div>
|
<div>
|
||||||
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party_name[0] }}</b></h5>
|
{% if filters.party[0] == filters.party_name[0] %}
|
||||||
|
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party_name[0] }}</b></h5>
|
||||||
|
{% else %}
|
||||||
|
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party[0] }}</b></h5>
|
||||||
|
<h5 style="float: left; margin-left:15px">{{ _("Customer Name: ") }} <b>{{filters.party_name[0] }}</b></h5>
|
||||||
|
{% endif %}
|
||||||
<h5 style="float: right;">
|
<h5 style="float: right;">
|
||||||
{{ _("Date: ") }}
|
{{ _("Date: ") }}
|
||||||
<b>{{ frappe.format(filters.from_date, 'Date')}}
|
<b>{{ frappe.format(filters.from_date, 'Date')}}
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
"terms_and_conditions",
|
"terms_and_conditions",
|
||||||
"section_break_1",
|
"section_break_1",
|
||||||
"enable_auto_email",
|
"enable_auto_email",
|
||||||
|
"column_break_ocfq",
|
||||||
|
"sender",
|
||||||
"section_break_18",
|
"section_break_18",
|
||||||
"frequency",
|
"frequency",
|
||||||
"filter_duration",
|
"filter_duration",
|
||||||
@ -298,10 +300,20 @@
|
|||||||
"fieldname": "show_net_values_in_party_account",
|
"fieldname": "show_net_values_in_party_account",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Show Net Values in Party Account"
|
"label": "Show Net Values in Party Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "sender",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Sender",
|
||||||
|
"options": "Email Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_ocfq",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-10 17:44:17.165991",
|
"modified": "2023-04-26 12:46:43.645455",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts",
|
"name": "Process Statement Of Accounts",
|
||||||
|
@ -334,7 +334,7 @@ def send_emails(document_name, from_scheduler=False):
|
|||||||
queue="short",
|
queue="short",
|
||||||
method=frappe.sendmail,
|
method=frappe.sendmail,
|
||||||
recipients=recipients,
|
recipients=recipients,
|
||||||
sender=frappe.session.user,
|
sender=doc.sender or frappe.session.user,
|
||||||
cc=cc,
|
cc=cc,
|
||||||
subject=subject,
|
subject=subject,
|
||||||
message=message,
|
message=message,
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "billing_email",
|
"fieldname": "billing_email",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Data",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Billing Email"
|
"label": "Billing Email"
|
||||||
},
|
},
|
||||||
@ -41,7 +41,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-13 00:12:34.508086",
|
"modified": "2023-04-26 13:02:41.964499",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Process Statement Of Accounts Customer",
|
"name": "Process Statement Of Accounts Customer",
|
||||||
|
@ -303,7 +303,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
|
|
||||||
apply_tds(frm) {
|
apply_tds(frm) {
|
||||||
var me = this;
|
var me = this;
|
||||||
|
me.frm.set_value("tax_withheld_vouchers", []);
|
||||||
if (!me.frm.doc.apply_tds) {
|
if (!me.frm.doc.apply_tds) {
|
||||||
me.frm.set_value("tax_withholding_category", '');
|
me.frm.set_value("tax_withholding_category", '');
|
||||||
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
|
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
|
||||||
|
@ -89,6 +89,7 @@
|
|||||||
"column_break8",
|
"column_break8",
|
||||||
"grand_total",
|
"grand_total",
|
||||||
"rounding_adjustment",
|
"rounding_adjustment",
|
||||||
|
"use_company_roundoff_cost_center",
|
||||||
"rounded_total",
|
"rounded_total",
|
||||||
"in_words",
|
"in_words",
|
||||||
"total_advance",
|
"total_advance",
|
||||||
@ -1368,6 +1369,7 @@
|
|||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"print_width": "50px",
|
"print_width": "50px",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"width": "50px"
|
"width": "50px"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1559,13 +1561,19 @@
|
|||||||
"fieldname": "only_include_allocated_payments",
|
"fieldname": "only_include_allocated_payments",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Only Include Allocated Payments"
|
"label": "Only Include Allocated Payments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "use_company_roundoff_cost_center",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Use Company Default Round Off Cost Center"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-03 22:57:14.074982",
|
"modified": "2023-04-29 12:57:50.832598",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
@ -978,7 +978,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
def make_precision_loss_gl_entry(self, gl_entries):
|
def make_precision_loss_gl_entry(self, gl_entries):
|
||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
self.company, "Purchase Invoice", self.name
|
self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
|
||||||
)
|
)
|
||||||
|
|
||||||
precision_loss = self.get("base_net_total") - flt(
|
precision_loss = self.get("base_net_total") - flt(
|
||||||
@ -992,7 +992,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"account": round_off_account,
|
"account": round_off_account,
|
||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"credit": precision_loss,
|
"credit": precision_loss,
|
||||||
"cost_center": self.cost_center or round_off_cost_center,
|
"cost_center": round_off_cost_center
|
||||||
|
if self.use_company_roundoff_cost_center
|
||||||
|
else self.cost_center or round_off_cost_center,
|
||||||
"remarks": _("Net total calculation precision loss"),
|
"remarks": _("Net total calculation precision loss"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1386,7 +1388,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
|
not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
|
||||||
):
|
):
|
||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
self.company, "Purchase Invoice", self.name
|
self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
|
||||||
)
|
)
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@ -1396,7 +1398,9 @@ class PurchaseInvoice(BuyingController):
|
|||||||
"against": self.supplier,
|
"against": self.supplier,
|
||||||
"debit_in_account_currency": self.rounding_adjustment,
|
"debit_in_account_currency": self.rounding_adjustment,
|
||||||
"debit": self.base_rounding_adjustment,
|
"debit": self.base_rounding_adjustment,
|
||||||
"cost_center": self.cost_center or round_off_cost_center,
|
"cost_center": round_off_cost_center
|
||||||
|
if self.use_company_roundoff_cost_center
|
||||||
|
else (self.cost_center or round_off_cost_center),
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
|
@ -79,6 +79,7 @@
|
|||||||
"column_break5",
|
"column_break5",
|
||||||
"grand_total",
|
"grand_total",
|
||||||
"rounding_adjustment",
|
"rounding_adjustment",
|
||||||
|
"use_company_roundoff_cost_center",
|
||||||
"rounded_total",
|
"rounded_total",
|
||||||
"in_words",
|
"in_words",
|
||||||
"total_advance",
|
"total_advance",
|
||||||
@ -2135,6 +2136,12 @@
|
|||||||
"fieldname": "only_include_allocated_payments",
|
"fieldname": "only_include_allocated_payments",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Only Include Allocated Payments"
|
"label": "Only Include Allocated Payments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "use_company_roundoff_cost_center",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Use Company default Cost Center for Round off"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
@ -2147,7 +2154,7 @@
|
|||||||
"link_fieldname": "consolidated_invoice"
|
"link_fieldname": "consolidated_invoice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-04-03 22:55:14.206473",
|
"modified": "2023-04-28 14:15:59.901154",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice",
|
"name": "Sales Invoice",
|
||||||
|
@ -1464,7 +1464,7 @@ class SalesInvoice(SellingController):
|
|||||||
and not self.is_internal_transfer()
|
and not self.is_internal_transfer()
|
||||||
):
|
):
|
||||||
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
|
||||||
self.company, "Sales Invoice", self.name
|
self.company, "Sales Invoice", self.name, self.use_company_roundoff_cost_center
|
||||||
)
|
)
|
||||||
|
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
@ -1476,7 +1476,9 @@ class SalesInvoice(SellingController):
|
|||||||
self.rounding_adjustment, self.precision("rounding_adjustment")
|
self.rounding_adjustment, self.precision("rounding_adjustment")
|
||||||
),
|
),
|
||||||
"credit": flt(self.base_rounding_adjustment, self.precision("base_rounding_adjustment")),
|
"credit": flt(self.base_rounding_adjustment, self.precision("base_rounding_adjustment")),
|
||||||
"cost_center": self.cost_center or round_off_cost_center,
|
"cost_center": round_off_cost_center
|
||||||
|
if self.use_company_roundoff_cost_center
|
||||||
|
else (self.cost_center or round_off_cost_center),
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
|
@ -124,6 +124,7 @@
|
|||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -147,6 +148,7 @@
|
|||||||
"fieldname": "amount",
|
"fieldname": "amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Amount",
|
"label": "Amount",
|
||||||
|
"options": "Company:company:default_currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -215,7 +215,7 @@ def get_tax_row_for_tds(tax_details, tax_amount):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_lower_deduction_certificate(tax_details, pan_no):
|
def get_lower_deduction_certificate(company, tax_details, pan_no):
|
||||||
ldc_name = frappe.db.get_value(
|
ldc_name = frappe.db.get_value(
|
||||||
"Lower Deduction Certificate",
|
"Lower Deduction Certificate",
|
||||||
{
|
{
|
||||||
@ -223,6 +223,7 @@ def get_lower_deduction_certificate(tax_details, pan_no):
|
|||||||
"tax_withholding_category": tax_details.tax_withholding_category,
|
"tax_withholding_category": tax_details.tax_withholding_category,
|
||||||
"valid_from": (">=", tax_details.from_date),
|
"valid_from": (">=", tax_details.from_date),
|
||||||
"valid_upto": ("<=", tax_details.to_date),
|
"valid_upto": ("<=", tax_details.to_date),
|
||||||
|
"company": company,
|
||||||
},
|
},
|
||||||
"name",
|
"name",
|
||||||
)
|
)
|
||||||
@ -255,7 +256,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
|||||||
tax_amount = 0
|
tax_amount = 0
|
||||||
|
|
||||||
if party_type == "Supplier":
|
if party_type == "Supplier":
|
||||||
ldc = get_lower_deduction_certificate(tax_details, pan_no)
|
ldc = get_lower_deduction_certificate(inv.company, tax_details, pan_no)
|
||||||
if tax_deducted:
|
if tax_deducted:
|
||||||
net_total = inv.tax_withholding_net_total
|
net_total = inv.tax_withholding_net_total
|
||||||
if ldc:
|
if ldc:
|
||||||
@ -301,7 +302,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
|||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
if not tax_details.get("consider_party_ledger_amount") and doctype != "Sales Invoice":
|
if doctype != "Sales Invoice":
|
||||||
filters.update(
|
filters.update(
|
||||||
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
||||||
)
|
)
|
||||||
|
@ -110,9 +110,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
|||||||
invoices.append(pi1)
|
invoices.append(pi1)
|
||||||
|
|
||||||
# Cumulative threshold is 30000
|
# Cumulative threshold is 30000
|
||||||
# Threshold calculation should be on both the invoices
|
# Threshold calculation should be only on the Second invoice
|
||||||
# TDS should be applied only on 1000
|
# Second didn't breach, no TDS should be applied
|
||||||
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
self.assertEqual(pi1.taxes, [])
|
||||||
|
|
||||||
for d in reversed(invoices):
|
for d in reversed(invoices):
|
||||||
d.cancel()
|
d.cancel()
|
||||||
|
@ -475,7 +475,9 @@ def update_accounting_dimensions(round_off_gle):
|
|||||||
round_off_gle[dimension] = dimension_values.get(dimension)
|
round_off_gle[dimension] = dimension_values.get(dimension)
|
||||||
|
|
||||||
|
|
||||||
def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
|
def get_round_off_account_and_cost_center(
|
||||||
|
company, voucher_type, voucher_no, use_company_default=False
|
||||||
|
):
|
||||||
round_off_account, round_off_cost_center = frappe.get_cached_value(
|
round_off_account, round_off_cost_center = frappe.get_cached_value(
|
||||||
"Company", company, ["round_off_account", "round_off_cost_center"]
|
"Company", company, ["round_off_account", "round_off_cost_center"]
|
||||||
) or [None, None]
|
) or [None, None]
|
||||||
@ -483,7 +485,7 @@ def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
|
|||||||
meta = frappe.get_meta(voucher_type)
|
meta = frappe.get_meta(voucher_type)
|
||||||
|
|
||||||
# Give first preference to parent cost center for round off GLE
|
# Give first preference to parent cost center for round off GLE
|
||||||
if meta.has_field("cost_center"):
|
if not use_company_default and meta.has_field("cost_center"):
|
||||||
parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
|
parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
|
||||||
if parent_cost_center:
|
if parent_cost_center:
|
||||||
round_off_cost_center = parent_cost_center
|
round_off_cost_center = parent_cost_center
|
||||||
|
@ -114,28 +114,6 @@ def get_assets(filters):
|
|||||||
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||||
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
||||||
from (SELECT a.asset_category,
|
from (SELECT a.asset_category,
|
||||||
ifnull(sum(case when ds.schedule_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
|
||||||
ds.depreciation_amount
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as accumulated_depreciation_as_on_from_date,
|
|
||||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
|
||||||
and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then
|
|
||||||
ds.depreciation_amount
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as depreciation_eliminated_during_the_period,
|
|
||||||
ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s
|
|
||||||
and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then
|
|
||||||
ds.depreciation_amount
|
|
||||||
else
|
|
||||||
0
|
|
||||||
end), 0) as depreciation_amount_during_the_period
|
|
||||||
from `tabAsset` a, `tabAsset Depreciation Schedule` ads, `tabDepreciation Schedule` ds
|
|
||||||
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and ads.asset = a.name and ads.docstatus=1 and ads.name = ds.parent and ifnull(ds.journal_entry, '') != ''
|
|
||||||
group by a.asset_category
|
|
||||||
union
|
|
||||||
SELECT a.asset_category,
|
|
||||||
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||||
gle.debit
|
gle.debit
|
||||||
else
|
else
|
||||||
@ -160,7 +138,7 @@ def get_assets(filters):
|
|||||||
aca.parent = a.asset_category and aca.company_name = %(company)s
|
aca.parent = a.asset_category and aca.company_name = %(company)s
|
||||||
join `tabCompany` company on
|
join `tabCompany` company on
|
||||||
company.name = %(company)s
|
company.name = %(company)s
|
||||||
where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||||
group by a.asset_category
|
group by a.asset_category
|
||||||
union
|
union
|
||||||
SELECT a.asset_category,
|
SELECT a.asset_category,
|
||||||
|
@ -80,7 +80,7 @@ def get_entries(filters):
|
|||||||
payment_entries = frappe.db.sql(
|
payment_entries = frappe.db.sql(
|
||||||
"""SELECT
|
"""SELECT
|
||||||
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
|
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
|
||||||
if(paid_from=%(account)s, paid_amount * -1, received_amount)
|
if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount)
|
||||||
FROM
|
FROM
|
||||||
`tabPayment Entry`
|
`tabPayment Entry`
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -538,13 +538,21 @@ def apply_additional_conditions(doctype, query, from_date, ignore_closing_entrie
|
|||||||
query = query.where(gl_entry.cost_center.isin(filters.cost_center))
|
query = query.where(gl_entry.cost_center.isin(filters.cost_center))
|
||||||
|
|
||||||
if filters.get("include_default_book_entries"):
|
if filters.get("include_default_book_entries"):
|
||||||
|
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||||
|
|
||||||
|
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||||
|
frappe.throw(
|
||||||
|
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||||
|
)
|
||||||
|
|
||||||
query = query.where(
|
query = query.where(
|
||||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
|
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||||
| (gl_entry.finance_book.isnull())
|
| (gl_entry.finance_book.isnull())
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query = query.where(
|
query = query.where(
|
||||||
(gl_entry.finance_book.isin([cstr(filters.company_fb), ""])) | (gl_entry.finance_book.isnull())
|
(gl_entry.finance_book.isin([cstr(filters.finance_book), ""]))
|
||||||
|
| (gl_entry.finance_book.isnull())
|
||||||
)
|
)
|
||||||
|
|
||||||
if accounting_dimensions:
|
if accounting_dimensions:
|
||||||
|
@ -176,7 +176,8 @@ frappe.query_reports["General Ledger"] = {
|
|||||||
{
|
{
|
||||||
"fieldname": "include_default_book_entries",
|
"fieldname": "include_default_book_entries",
|
||||||
"label": __("Include Default Book Entries"),
|
"label": __("Include Default Book Entries"),
|
||||||
"fieldtype": "Check"
|
"fieldtype": "Check",
|
||||||
|
"default": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "show_cancelled_entries",
|
"fieldname": "show_cancelled_entries",
|
||||||
|
@ -244,13 +244,23 @@ def get_conditions(filters):
|
|||||||
if filters.get("project"):
|
if filters.get("project"):
|
||||||
conditions.append("project in %(project)s")
|
conditions.append("project in %(project)s")
|
||||||
|
|
||||||
if filters.get("finance_book"):
|
if filters.get("include_default_book_entries"):
|
||||||
if filters.get("include_default_book_entries"):
|
if filters.get("finance_book"):
|
||||||
conditions.append(
|
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
filters.get("company_fb")
|
||||||
)
|
):
|
||||||
|
frappe.throw(
|
||||||
|
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||||
else:
|
else:
|
||||||
conditions.append("finance_book in (%(finance_book)s)")
|
conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
|
||||||
|
else:
|
||||||
|
if filters.get("finance_book"):
|
||||||
|
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||||
|
else:
|
||||||
|
conditions.append("(finance_book in ('') OR finance_book IS NULL)")
|
||||||
|
|
||||||
if not filters.get("show_cancelled_entries"):
|
if not filters.get("show_cancelled_entries"):
|
||||||
conditions.append("is_cancelled = 0")
|
conditions.append("is_cancelled = 0")
|
||||||
|
@ -73,7 +73,7 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
|
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
|
||||||
column._options = "Sales Invoice";
|
column._options = "Sales Invoice";
|
||||||
} else {
|
} else {
|
||||||
column._options = "Item";
|
column._options = "";
|
||||||
}
|
}
|
||||||
value = default_formatter(value, row, column, data);
|
value = default_formatter(value, row, column, data);
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ def get_columns(group_wise_columns, filters):
|
|||||||
"label": _("Warehouse"),
|
"label": _("Warehouse"),
|
||||||
"fieldname": "warehouse",
|
"fieldname": "warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "warehouse",
|
"options": "Warehouse",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
"qty": {"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 80},
|
"qty": {"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 80},
|
||||||
@ -305,7 +305,8 @@ def get_columns(group_wise_columns, filters):
|
|||||||
"sales_person": {
|
"sales_person": {
|
||||||
"label": _("Sales Person"),
|
"label": _("Sales Person"),
|
||||||
"fieldname": "sales_person",
|
"fieldname": "sales_person",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Link",
|
||||||
|
"options": "Sales Person",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
"allocated_amount": {
|
"allocated_amount": {
|
||||||
@ -326,14 +327,14 @@ def get_columns(group_wise_columns, filters):
|
|||||||
"label": _("Customer Group"),
|
"label": _("Customer Group"),
|
||||||
"fieldname": "customer_group",
|
"fieldname": "customer_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "customer",
|
"options": "Customer Group",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
"territory": {
|
"territory": {
|
||||||
"label": _("Territory"),
|
"label": _("Territory"),
|
||||||
"fieldname": "territory",
|
"fieldname": "territory",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "territory",
|
"options": "Territory",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
"monthly": {
|
"monthly": {
|
||||||
|
@ -19,14 +19,19 @@ def execute(filters=None):
|
|||||||
return _execute(filters)
|
return _execute(filters)
|
||||||
|
|
||||||
|
|
||||||
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
|
def _execute(
|
||||||
|
filters=None,
|
||||||
|
additional_table_columns=None,
|
||||||
|
additional_query_columns=None,
|
||||||
|
additional_conditions=None,
|
||||||
|
):
|
||||||
if not filters:
|
if not filters:
|
||||||
filters = {}
|
filters = {}
|
||||||
columns = get_columns(additional_table_columns, filters)
|
columns = get_columns(additional_table_columns, filters)
|
||||||
|
|
||||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||||
|
|
||||||
item_list = get_items(filters, additional_query_columns)
|
item_list = get_items(filters, additional_query_columns, additional_conditions)
|
||||||
if item_list:
|
if item_list:
|
||||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||||
|
|
||||||
@ -328,7 +333,7 @@ def get_columns(additional_table_columns, filters):
|
|||||||
return columns
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def get_conditions(filters):
|
def get_conditions(filters, additional_conditions=None):
|
||||||
conditions = ""
|
conditions = ""
|
||||||
|
|
||||||
for opts in (
|
for opts in (
|
||||||
@ -341,6 +346,9 @@ def get_conditions(filters):
|
|||||||
if filters.get(opts[0]):
|
if filters.get(opts[0]):
|
||||||
conditions += opts[1]
|
conditions += opts[1]
|
||||||
|
|
||||||
|
if additional_conditions:
|
||||||
|
conditions += additional_conditions
|
||||||
|
|
||||||
if filters.get("mode_of_payment"):
|
if filters.get("mode_of_payment"):
|
||||||
conditions += """ and exists(select name from `tabSales Invoice Payment`
|
conditions += """ and exists(select name from `tabSales Invoice Payment`
|
||||||
where parent=`tabSales Invoice`.name
|
where parent=`tabSales Invoice`.name
|
||||||
@ -376,8 +384,8 @@ def get_group_by_conditions(filters, doctype):
|
|||||||
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
|
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
|
||||||
|
|
||||||
|
|
||||||
def get_items(filters, additional_query_columns):
|
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters, additional_conditions)
|
||||||
|
|
||||||
if additional_query_columns:
|
if additional_query_columns:
|
||||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import flt
|
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
@ -66,12 +65,6 @@ def get_result(
|
|||||||
else:
|
else:
|
||||||
total_amount_credited += entry.credit
|
total_amount_credited += entry.credit
|
||||||
|
|
||||||
## Check if ldc is applied and show rate as per ldc
|
|
||||||
actual_rate = (tds_deducted / total_amount_credited) * 100
|
|
||||||
|
|
||||||
if flt(actual_rate) < flt(rate):
|
|
||||||
rate = actual_rate
|
|
||||||
|
|
||||||
if tds_deducted:
|
if tds_deducted:
|
||||||
row = {
|
row = {
|
||||||
"pan"
|
"pan"
|
||||||
|
@ -248,8 +248,15 @@ def get_opening_balance(
|
|||||||
opening_balance = opening_balance.where(closing_balance.project == filters.project)
|
opening_balance = opening_balance.where(closing_balance.project == filters.project)
|
||||||
|
|
||||||
if filters.get("include_default_book_entries"):
|
if filters.get("include_default_book_entries"):
|
||||||
|
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||||
|
|
||||||
|
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||||
|
frappe.throw(
|
||||||
|
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||||
|
)
|
||||||
|
|
||||||
opening_balance = opening_balance.where(
|
opening_balance = opening_balance.where(
|
||||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
|
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||||
| (closing_balance.finance_book.isnull())
|
| (closing_balance.finance_book.isnull())
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -1374,10 +1374,7 @@ def get_stock_and_account_balance(account=None, posting_date=None, company=None)
|
|||||||
if wh_details.account == account and not wh_details.is_group
|
if wh_details.account == account and not wh_details.is_group
|
||||||
]
|
]
|
||||||
|
|
||||||
total_stock_value = 0.0
|
total_stock_value = get_stock_value_on(related_warehouses, posting_date)
|
||||||
for warehouse in related_warehouses:
|
|
||||||
value = get_stock_value_on(warehouse, posting_date)
|
|
||||||
total_stock_value += value
|
|
||||||
|
|
||||||
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
|
precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
|
||||||
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
|
return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
|
||||||
|
@ -783,7 +783,7 @@ def make_journal_entry(asset_name):
|
|||||||
je.voucher_type = "Depreciation Entry"
|
je.voucher_type = "Depreciation Entry"
|
||||||
je.naming_series = depreciation_series
|
je.naming_series = depreciation_series
|
||||||
je.company = asset.company
|
je.company = asset.company
|
||||||
je.remark = "Depreciation Entry against asset {0}".format(asset_name)
|
je.remark = _("Depreciation Entry against asset {0}").format(asset_name)
|
||||||
|
|
||||||
je.append(
|
je.append(
|
||||||
"accounts",
|
"accounts",
|
||||||
|
@ -157,6 +157,7 @@ def make_depreciation_entry(asset_depr_schedule_name, date=None):
|
|||||||
je.append("accounts", debit_entry)
|
je.append("accounts", debit_entry)
|
||||||
|
|
||||||
je.flags.ignore_permissions = True
|
je.flags.ignore_permissions = True
|
||||||
|
je.flags.planned_depr_entry = True
|
||||||
je.save()
|
je.save()
|
||||||
if not je.meta.get_workflow():
|
if not je.meta.get_workflow():
|
||||||
je.submit()
|
je.submit()
|
||||||
|
@ -691,7 +691,7 @@ class TestDepreciationMethods(AssetSetup):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(asset.status, "Draft")
|
self.assertEqual(asset.status, "Draft")
|
||||||
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
|
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
|
||||||
schedules = [
|
schedules = [
|
||||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||||
for d in get_depr_schedule(asset.name, "Draft")
|
for d in get_depr_schedule(asset.name, "Draft")
|
||||||
@ -1511,7 +1511,7 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(asset.status, "Submitted")
|
self.assertEqual(asset.status, "Submitted")
|
||||||
self.assertEqual(asset.get("value_after_depreciation"), 100000)
|
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||||
|
|
||||||
jv = make_journal_entry(
|
jv = make_journal_entry(
|
||||||
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||||
@ -1524,12 +1524,68 @@ class TestDepreciationBasics(AssetSetup):
|
|||||||
jv.submit()
|
jv.submit()
|
||||||
|
|
||||||
asset.reload()
|
asset.reload()
|
||||||
self.assertEqual(asset.get("value_after_depreciation"), 99900)
|
self.assertEqual(asset.get_value_after_depreciation(), 99900)
|
||||||
|
|
||||||
jv.cancel()
|
jv.cancel()
|
||||||
|
|
||||||
asset.reload()
|
asset.reload()
|
||||||
self.assertEqual(asset.get("value_after_depreciation"), 100000)
|
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||||
|
|
||||||
|
def test_manual_depreciation_for_depreciable_asset(self):
|
||||||
|
asset = create_asset(
|
||||||
|
item_code="Macbook Pro",
|
||||||
|
calculate_depreciation=1,
|
||||||
|
purchase_date="2020-01-30",
|
||||||
|
available_for_use_date="2020-01-30",
|
||||||
|
expected_value_after_useful_life=10000,
|
||||||
|
total_number_of_depreciations=10,
|
||||||
|
frequency_of_depreciation=1,
|
||||||
|
submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(asset.status, "Submitted")
|
||||||
|
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||||
|
|
||||||
|
jv = make_journal_entry(
|
||||||
|
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||||
|
)
|
||||||
|
for d in jv.accounts:
|
||||||
|
d.reference_type = "Asset"
|
||||||
|
d.reference_name = asset.name
|
||||||
|
jv.voucher_type = "Depreciation Entry"
|
||||||
|
jv.insert()
|
||||||
|
jv.submit()
|
||||||
|
|
||||||
|
asset.reload()
|
||||||
|
self.assertEqual(asset.get_value_after_depreciation(), 99900)
|
||||||
|
|
||||||
|
jv.cancel()
|
||||||
|
|
||||||
|
asset.reload()
|
||||||
|
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||||
|
|
||||||
|
def test_manual_depreciation_with_incorrect_jv_voucher_type(self):
|
||||||
|
asset = create_asset(
|
||||||
|
item_code="Macbook Pro",
|
||||||
|
calculate_depreciation=1,
|
||||||
|
purchase_date="2020-01-30",
|
||||||
|
available_for_use_date="2020-01-30",
|
||||||
|
expected_value_after_useful_life=10000,
|
||||||
|
total_number_of_depreciations=10,
|
||||||
|
frequency_of_depreciation=1,
|
||||||
|
submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
jv = make_journal_entry(
|
||||||
|
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||||
|
)
|
||||||
|
for d in jv.accounts:
|
||||||
|
d.reference_type = "Asset"
|
||||||
|
d.reference_name = asset.name
|
||||||
|
d.account_type = "Depreciation"
|
||||||
|
jv.voucher_type = "Journal Entry"
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, jv.insert)
|
||||||
|
|
||||||
|
|
||||||
def create_asset_data():
|
def create_asset_data():
|
||||||
|
@ -337,7 +337,7 @@ class AssetDepreciationSchedule(Document):
|
|||||||
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
|
depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
|
||||||
skip_row = True
|
skip_row = True
|
||||||
|
|
||||||
if depreciation_amount > 0:
|
if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
|
||||||
self.add_depr_schedule_row(
|
self.add_depr_schedule_row(
|
||||||
schedule_date,
|
schedule_date,
|
||||||
depreciation_amount,
|
depreciation_amount,
|
||||||
@ -521,9 +521,11 @@ def get_straight_line_or_manual_depr_amount(asset, row):
|
|||||||
)
|
)
|
||||||
# if the Depreciation Schedule is being prepared for the first time
|
# if the Depreciation Schedule is being prepared for the first time
|
||||||
else:
|
else:
|
||||||
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
|
return (
|
||||||
row.total_number_of_depreciations
|
flt(asset.gross_purchase_amount)
|
||||||
)
|
- flt(asset.opening_accumulated_depreciation)
|
||||||
|
- flt(row.expected_value_after_useful_life)
|
||||||
|
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||||
|
|
||||||
|
|
||||||
def get_wdv_or_dd_depr_amount(
|
def get_wdv_or_dd_depr_amount(
|
||||||
|
@ -150,7 +150,9 @@ class AssetValueAdjustment(Document):
|
|||||||
if d.depreciation_method in ("Straight Line", "Manual"):
|
if d.depreciation_method in ("Straight Line", "Manual"):
|
||||||
end_date = max(s.schedule_date for s in depr_schedule)
|
end_date = max(s.schedule_date for s in depr_schedule)
|
||||||
total_days = date_diff(end_date, self.date)
|
total_days = date_diff(end_date, self.date)
|
||||||
rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
|
rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt(
|
||||||
|
total_days
|
||||||
|
)
|
||||||
from_date = self.date
|
from_date = self.date
|
||||||
else:
|
else:
|
||||||
no_of_depreciations = len([s.name for s in depr_schedule if not s.journal_entry])
|
no_of_depreciations = len([s.name for s in depr_schedule if not s.journal_entry])
|
||||||
|
@ -94,11 +94,11 @@ frappe.query_reports["Fixed Asset Register"] = {
|
|||||||
label: __("Finance Book"),
|
label: __("Finance Book"),
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Finance Book",
|
options: "Finance Book",
|
||||||
depends_on: "eval: doc.only_depreciable_assets == 1",
|
depends_on: "eval: doc.filter_by_finance_book == 1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname:"only_depreciable_assets",
|
fieldname:"filter_by_finance_book",
|
||||||
label: __("Only depreciable assets"),
|
label: __("Filter by Finance Book"),
|
||||||
fieldtype: "Check"
|
fieldtype: "Check"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -45,8 +45,6 @@ def get_conditions(filters):
|
|||||||
filters.year_end_date = getdate(fiscal_year.year_end_date)
|
filters.year_end_date = getdate(fiscal_year.year_end_date)
|
||||||
|
|
||||||
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
|
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
|
||||||
if filters.get("only_depreciable_assets"):
|
|
||||||
conditions["calculate_depreciation"] = filters.get("only_depreciable_assets")
|
|
||||||
if filters.get("only_existing_assets"):
|
if filters.get("only_existing_assets"):
|
||||||
conditions["is_existing_asset"] = filters.get("only_existing_assets")
|
conditions["is_existing_asset"] = filters.get("only_existing_assets")
|
||||||
if filters.get("asset_category"):
|
if filters.get("asset_category"):
|
||||||
@ -106,7 +104,7 @@ def get_data(filters):
|
|||||||
|
|
||||||
assets_linked_to_fb = None
|
assets_linked_to_fb = None
|
||||||
|
|
||||||
if filters.only_depreciable_assets:
|
if filters.filter_by_finance_book:
|
||||||
assets_linked_to_fb = frappe.db.get_all(
|
assets_linked_to_fb = frappe.db.get_all(
|
||||||
doctype="Asset Finance Book",
|
doctype="Asset Finance Book",
|
||||||
filters={"finance_book": filters.finance_book or ("is", "not set")},
|
filters={"finance_book": filters.finance_book or ("is", "not set")},
|
||||||
|
@ -1171,6 +1171,7 @@
|
|||||||
"depends_on": "is_internal_supplier",
|
"depends_on": "is_internal_supplier",
|
||||||
"fieldname": "set_from_warehouse",
|
"fieldname": "set_from_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Set From Warehouse",
|
"label": "Set From Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
@ -1271,7 +1272,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-04-14 16:42:29.448464",
|
"modified": "2023-05-07 20:18:09.196799",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
@ -43,7 +43,7 @@ frappe.listview_settings['Purchase Order'] = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
listview.page.add_action_item(__("Advance Payment"), ()=>{
|
listview.page.add_action_item(__("Advance Payment"), ()=>{
|
||||||
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Advance Payment");
|
erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Payment Entry");
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -392,6 +392,9 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate_inter_company_reference(self):
|
def validate_inter_company_reference(self):
|
||||||
|
if self.get("is_return"):
|
||||||
|
return
|
||||||
|
|
||||||
if self.doctype not in ("Purchase Invoice", "Purchase Receipt"):
|
if self.doctype not in ("Purchase Invoice", "Purchase Receipt"):
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -1662,7 +1665,10 @@ class AccountsController(TransactionBase):
|
|||||||
)
|
)
|
||||||
self.append("payment_schedule", data)
|
self.append("payment_schedule", data)
|
||||||
|
|
||||||
if not automatically_fetch_payment_terms:
|
if not (
|
||||||
|
automatically_fetch_payment_terms
|
||||||
|
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
|
||||||
|
):
|
||||||
for d in self.get("payment_schedule"):
|
for d in self.get("payment_schedule"):
|
||||||
if d.invoice_portion:
|
if d.invoice_portion:
|
||||||
d.payment_amount = flt(
|
d.payment_amount = flt(
|
||||||
@ -1676,6 +1682,9 @@ class AccountsController(TransactionBase):
|
|||||||
d.base_payment_amount = flt(
|
d.base_payment_amount = flt(
|
||||||
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.fetch_payment_terms_from_order(po_or_so, doctype)
|
||||||
|
self.ignore_default_payment_terms_template = 1
|
||||||
|
|
||||||
def get_order_details(self):
|
def get_order_details(self):
|
||||||
if self.doctype == "Sales Invoice":
|
if self.doctype == "Sales Invoice":
|
||||||
@ -1901,12 +1910,14 @@ class AccountsController(TransactionBase):
|
|||||||
reconcilation_entry.party = secondary_party
|
reconcilation_entry.party = secondary_party
|
||||||
reconcilation_entry.reference_type = self.doctype
|
reconcilation_entry.reference_type = self.doctype
|
||||||
reconcilation_entry.reference_name = self.name
|
reconcilation_entry.reference_name = self.name
|
||||||
reconcilation_entry.cost_center = self.cost_center
|
reconcilation_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(
|
||||||
|
self.company
|
||||||
|
)
|
||||||
|
|
||||||
advance_entry.account = primary_account
|
advance_entry.account = primary_account
|
||||||
advance_entry.party_type = primary_party_type
|
advance_entry.party_type = primary_party_type
|
||||||
advance_entry.party = primary_party
|
advance_entry.party = primary_party
|
||||||
advance_entry.cost_center = self.cost_center
|
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||||
advance_entry.is_advance = "Yes"
|
advance_entry.is_advance = "Yes"
|
||||||
|
|
||||||
if self.doctype == "Sales Invoice":
|
if self.doctype == "Sales Invoice":
|
||||||
|
@ -53,13 +53,17 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
doctype = "Lead"
|
doctype = "Lead"
|
||||||
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
|
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
|
||||||
|
|
||||||
|
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||||
|
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""select {fields} from `tabLead`
|
"""select {fields} from `tabLead`
|
||||||
where docstatus < 2
|
where docstatus < 2
|
||||||
and ifnull(status, '') != 'Converted'
|
and ifnull(status, '') != 'Converted'
|
||||||
and ({key} like %(txt)s
|
and ({key} like %(txt)s
|
||||||
or lead_name like %(txt)s
|
or lead_name like %(txt)s
|
||||||
or company_name like %(txt)s)
|
or company_name like %(txt)s
|
||||||
|
or {scond})
|
||||||
{mcond}
|
{mcond}
|
||||||
order by
|
order by
|
||||||
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
|
(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
|
||||||
@ -68,7 +72,12 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
idx desc,
|
idx desc,
|
||||||
name, lead_name
|
name, lead_name
|
||||||
limit %(page_len)s offset %(start)s""".format(
|
limit %(page_len)s offset %(start)s""".format(
|
||||||
**{"fields": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
|
**{
|
||||||
|
"fields": ", ".join(fields),
|
||||||
|
"key": searchfield,
|
||||||
|
"scond": searchfields,
|
||||||
|
"mcond": get_match_cond(doctype),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
|
||||||
)
|
)
|
||||||
@ -576,7 +585,9 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters):
|
def get_filtered_dimensions(
|
||||||
|
doctype, txt, searchfield, start, page_len, filters, reference_doctype=None
|
||||||
|
):
|
||||||
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
|
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
|
||||||
get_dimension_filter_map,
|
get_dimension_filter_map,
|
||||||
)
|
)
|
||||||
@ -617,7 +628,12 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
|
|||||||
query_filters.append(["name", query_selector, dimensions])
|
query_filters.append(["name", query_selector, dimensions])
|
||||||
|
|
||||||
output = frappe.get_list(
|
output = frappe.get_list(
|
||||||
doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1
|
doctype,
|
||||||
|
fields=fields,
|
||||||
|
filters=query_filters,
|
||||||
|
or_filters=or_filters,
|
||||||
|
as_list=1,
|
||||||
|
reference_doctype=reference_doctype,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [tuple(d) for d in set(output)]
|
return [tuple(d) for d in set(output)]
|
||||||
|
@ -168,7 +168,7 @@ class SellingController(StockController):
|
|||||||
self.round_floats_in(sales_person)
|
self.round_floats_in(sales_person)
|
||||||
|
|
||||||
sales_person.allocated_amount = flt(
|
sales_person.allocated_amount = flt(
|
||||||
self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
|
flt(self.amount_eligible_for_commission) * sales_person.allocated_percentage / 100.0,
|
||||||
self.precision("allocated_amount", sales_person),
|
self.precision("allocated_amount", sales_person),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -329,9 +329,10 @@ class StockController(AccountsController):
|
|||||||
"""Create batches if required. Called before submit"""
|
"""Create batches if required. Called before submit"""
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
if d.get(warehouse_field) and not d.batch_no:
|
if d.get(warehouse_field) and not d.batch_no:
|
||||||
has_batch_no, create_new_batch = frappe.db.get_value(
|
has_batch_no, create_new_batch = frappe.get_cached_value(
|
||||||
"Item", d.item_code, ["has_batch_no", "create_new_batch"]
|
"Item", d.item_code, ["has_batch_no", "create_new_batch"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_batch_no and create_new_batch:
|
if has_batch_no and create_new_batch:
|
||||||
d.batch_no = (
|
d.batch_no = (
|
||||||
frappe.get_doc(
|
frappe.get_doc(
|
||||||
@ -414,7 +415,7 @@ class StockController(AccountsController):
|
|||||||
"voucher_no": self.name,
|
"voucher_no": self.name,
|
||||||
"voucher_detail_no": d.name,
|
"voucher_detail_no": d.name,
|
||||||
"actual_qty": (self.docstatus == 1 and 1 or -1) * flt(d.get("stock_qty")),
|
"actual_qty": (self.docstatus == 1 and 1 or -1) * flt(d.get("stock_qty")),
|
||||||
"stock_uom": frappe.db.get_value(
|
"stock_uom": frappe.get_cached_value(
|
||||||
"Item", args.get("item_code") or d.get("item_code"), "stock_uom"
|
"Item", args.get("item_code") or d.get("item_code"), "stock_uom"
|
||||||
),
|
),
|
||||||
"incoming_rate": 0,
|
"incoming_rate": 0,
|
||||||
@ -441,7 +442,43 @@ class StockController(AccountsController):
|
|||||||
if not dimension:
|
if not dimension:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if row.get(dimension.source_fieldname):
|
if self.doctype in [
|
||||||
|
"Purchase Invoice",
|
||||||
|
"Purchase Receipt",
|
||||||
|
"Sales Invoice",
|
||||||
|
"Delivery Note",
|
||||||
|
"Stock Entry",
|
||||||
|
]:
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
sl_dict.actual_qty > 0
|
||||||
|
and not self.get("is_return")
|
||||||
|
or sl_dict.actual_qty < 0
|
||||||
|
and self.get("is_return")
|
||||||
|
)
|
||||||
|
and self.doctype in ["Purchase Invoice", "Purchase Receipt"]
|
||||||
|
) or (
|
||||||
|
(
|
||||||
|
sl_dict.actual_qty < 0
|
||||||
|
and not self.get("is_return")
|
||||||
|
or sl_dict.actual_qty > 0
|
||||||
|
and self.get("is_return")
|
||||||
|
)
|
||||||
|
and self.doctype in ["Sales Invoice", "Delivery Note", "Stock Entry"]
|
||||||
|
):
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||||
|
else:
|
||||||
|
fieldname_start_with = "to"
|
||||||
|
if self.doctype in ["Purchase Invoice", "Purchase Receipt"]:
|
||||||
|
fieldname_start_with = "from"
|
||||||
|
|
||||||
|
fieldname = f"{fieldname_start_with}_{dimension.source_fieldname}"
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(fieldname)
|
||||||
|
|
||||||
|
if not sl_dict.get(dimension.target_fieldname):
|
||||||
|
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||||
|
|
||||||
|
elif row.get(dimension.source_fieldname):
|
||||||
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
sl_dict[dimension.target_fieldname] = row.get(dimension.source_fieldname)
|
||||||
|
|
||||||
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
|
if not sl_dict.get(dimension.target_fieldname) and dimension.fetch_from_parent:
|
||||||
@ -609,7 +646,7 @@ class StockController(AccountsController):
|
|||||||
def validate_customer_provided_item(self):
|
def validate_customer_provided_item(self):
|
||||||
for d in self.get("items"):
|
for d in self.get("items"):
|
||||||
# Customer Provided parts will have zero valuation rate
|
# Customer Provided parts will have zero valuation rate
|
||||||
if frappe.db.get_value("Item", d.item_code, "is_customer_provided_item"):
|
if frappe.get_cached_value("Item", d.item_code, "is_customer_provided_item"):
|
||||||
d.allow_zero_valuation_rate = 1
|
d.allow_zero_valuation_rate = 1
|
||||||
|
|
||||||
def set_rate_of_stock_uom(self):
|
def set_rate_of_stock_uom(self):
|
||||||
@ -722,7 +759,7 @@ class StockController(AccountsController):
|
|||||||
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def repost_future_sle_and_gle(self):
|
def repost_future_sle_and_gle(self, force=False):
|
||||||
args = frappe._dict(
|
args = frappe._dict(
|
||||||
{
|
{
|
||||||
"posting_date": self.posting_date,
|
"posting_date": self.posting_date,
|
||||||
@ -733,7 +770,10 @@ class StockController(AccountsController):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if future_sle_exists(args) or repost_required_for_queue(self):
|
if self.docstatus == 2:
|
||||||
|
force = True
|
||||||
|
|
||||||
|
if force or future_sle_exists(args) or repost_required_for_queue(self):
|
||||||
item_based_reposting = cint(
|
item_based_reposting = cint(
|
||||||
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
|
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
|
||||||
)
|
)
|
||||||
@ -894,9 +934,6 @@ def future_sle_exists(args, sl_entries=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
for d in data:
|
for d in data:
|
||||||
if key not in frappe.local.future_sle:
|
|
||||||
frappe.local.future_sle[key] = frappe._dict({})
|
|
||||||
|
|
||||||
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
|
frappe.local.future_sle[key][(d.item_code, d.warehouse)] = d.total_row
|
||||||
|
|
||||||
return len(data)
|
return len(data)
|
||||||
@ -919,7 +956,7 @@ def validate_future_sle_not_exists(args, key, sl_entries=None):
|
|||||||
|
|
||||||
def get_cached_data(args, key):
|
def get_cached_data(args, key):
|
||||||
if key not in frappe.local.future_sle:
|
if key not in frappe.local.future_sle:
|
||||||
return False
|
frappe.local.future_sle[key] = frappe._dict({})
|
||||||
|
|
||||||
if args.get("item_code"):
|
if args.get("item_code"):
|
||||||
item_key = (args.get("item_code"), args.get("warehouse"))
|
item_key = (args.get("item_code"), args.get("warehouse"))
|
||||||
|
@ -741,7 +741,7 @@ class SubcontractingController(StockController):
|
|||||||
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
sco_doc = frappe.get_doc("Subcontracting Order", sco)
|
||||||
sco_doc.update_status()
|
sco_doc.update_status()
|
||||||
|
|
||||||
def set_missing_values_in_additional_costs(self):
|
def calculate_additional_costs(self):
|
||||||
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
|
self.total_additional_costs = sum(flt(item.amount) for item in self.get("additional_costs"))
|
||||||
|
|
||||||
if self.total_additional_costs:
|
if self.total_additional_costs:
|
||||||
|
@ -36,7 +36,7 @@ class TestSubcontractingController(FrappeTestCase):
|
|||||||
sco.remove_empty_rows()
|
sco.remove_empty_rows()
|
||||||
self.assertEqual((len_before - 1), len(sco.service_items))
|
self.assertEqual((len_before - 1), len(sco.service_items))
|
||||||
|
|
||||||
def test_set_missing_values_in_additional_costs(self):
|
def test_calculate_additional_costs(self):
|
||||||
sco = get_subcontracting_order(do_not_submit=1)
|
sco = get_subcontracting_order(do_not_submit=1)
|
||||||
|
|
||||||
rate_without_additional_cost = sco.items[0].rate
|
rate_without_additional_cost = sco.items[0].rate
|
||||||
|
@ -59,7 +59,7 @@ class Opportunity(TransactionBase, CRMNote):
|
|||||||
if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
|
if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
|
||||||
try:
|
try:
|
||||||
value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
|
value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
|
||||||
self.db_set(field, value)
|
self.set(field, value)
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -165,6 +165,7 @@
|
|||||||
"fieldname": "slide_3_content_align",
|
"fieldname": "slide_3_content_align",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Content Align",
|
"label": "Content Align",
|
||||||
|
"options": "Left\nCentre\nRight",
|
||||||
"reqd": 0
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -214,6 +215,7 @@
|
|||||||
"fieldname": "slide_4_content_align",
|
"fieldname": "slide_4_content_align",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Content Align",
|
"label": "Content Align",
|
||||||
|
"options": "Left\nCentre\nRight",
|
||||||
"reqd": 0
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -263,6 +265,7 @@
|
|||||||
"fieldname": "slide_5_content_align",
|
"fieldname": "slide_5_content_align",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Content Align",
|
"label": "Content Align",
|
||||||
|
"options": "Left\nCentre\nRight",
|
||||||
"reqd": 0
|
"reqd": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -274,7 +277,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 2,
|
"idx": 2,
|
||||||
"modified": "2021-02-24 15:57:05.889709",
|
"modified": "2023-05-12 15:03:57.604060",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "E-commerce",
|
"module": "E-commerce",
|
||||||
"name": "Hero Slider",
|
"name": "Hero Slider",
|
||||||
|
@ -48,7 +48,8 @@ frappe.ui.form.on("BOM", {
|
|||||||
return {
|
return {
|
||||||
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
|
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
|
||||||
filters: {
|
filters: {
|
||||||
"item_code": doc.item
|
"include_item_in_manufacturing": 1,
|
||||||
|
"is_fixed_asset": 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -652,7 +653,7 @@ frappe.ui.form.on("BOM Operation", "operation", function(frm, cdt, cdn) {
|
|||||||
|
|
||||||
frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) {
|
frappe.ui.form.on("BOM Operation", "workstation", function(frm, cdt, cdn) {
|
||||||
var d = locals[cdt][cdn];
|
var d = locals[cdt][cdn];
|
||||||
|
if(!d.workstation) return;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
"method": "frappe.client.get",
|
"method": "frappe.client.get",
|
||||||
args: {
|
args: {
|
||||||
|
@ -1317,7 +1317,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
if not field in searchfields
|
if not field in searchfields
|
||||||
]
|
]
|
||||||
|
|
||||||
query_filters = {"disabled": 0, "end_of_life": (">", today())}
|
query_filters = {"disabled": 0, "ifnull(end_of_life, '3099-12-31')": (">", today())}
|
||||||
|
|
||||||
or_cond_filters = {}
|
or_cond_filters = {}
|
||||||
if txt:
|
if txt:
|
||||||
@ -1339,8 +1339,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
if not has_variants:
|
if not has_variants:
|
||||||
query_filters["has_variants"] = 0
|
query_filters["has_variants"] = 0
|
||||||
|
|
||||||
if filters and filters.get("is_stock_item"):
|
if filters:
|
||||||
query_filters["is_stock_item"] = 1
|
for fieldname, value in filters.items():
|
||||||
|
query_filters[fieldname] = value
|
||||||
|
|
||||||
return frappe.get_list(
|
return frappe.get_list(
|
||||||
"Item",
|
"Item",
|
||||||
|
@ -698,6 +698,45 @@ class TestBOM(FrappeTestCase):
|
|||||||
bom.update_cost()
|
bom.update_cost()
|
||||||
self.assertFalse(bom.flags.cost_updated)
|
self.assertFalse(bom.flags.cost_updated)
|
||||||
|
|
||||||
|
def test_do_not_include_manufacturing_and_fixed_items(self):
|
||||||
|
from erpnext.manufacturing.doctype.bom.bom import item_query
|
||||||
|
|
||||||
|
if not frappe.db.exists("Asset Category", "Computers-Test"):
|
||||||
|
doc = frappe.get_doc({"doctype": "Asset Category", "asset_category_name": "Computers-Test"})
|
||||||
|
doc.flags.ignore_mandatory = True
|
||||||
|
doc.insert()
|
||||||
|
|
||||||
|
for item_code, properties in {
|
||||||
|
"_Test RM Item 1 Do Not Include In Manufacture": {
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"include_item_in_manufacturing": 0,
|
||||||
|
},
|
||||||
|
"_Test RM Item 2 Fixed Asset Item": {
|
||||||
|
"is_fixed_asset": 1,
|
||||||
|
"is_stock_item": 0,
|
||||||
|
"asset_category": "Computers-Test",
|
||||||
|
},
|
||||||
|
"_Test RM Item 3 Manufacture Item": {"is_stock_item": 1, "include_item_in_manufacturing": 1},
|
||||||
|
}.items():
|
||||||
|
make_item(item_code, properties)
|
||||||
|
|
||||||
|
data = item_query(
|
||||||
|
"Item",
|
||||||
|
txt="_Test RM Item",
|
||||||
|
searchfield="name",
|
||||||
|
start=0,
|
||||||
|
page_len=20000,
|
||||||
|
filters={"include_item_in_manufacturing": 1, "is_fixed_asset": 0},
|
||||||
|
)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for row in data:
|
||||||
|
items.append(row[0])
|
||||||
|
|
||||||
|
self.assertTrue("_Test RM Item 1 Do Not Include In Manufacture" not in items)
|
||||||
|
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
|
||||||
|
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
|
||||||
|
|
||||||
|
|
||||||
def get_default_bom(item_code="_Test FG Item 2"):
|
def get_default_bom(item_code="_Test FG Item 2"):
|
||||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||||
|
@ -87,6 +87,12 @@ class JobCard(Document):
|
|||||||
frappe.db.get_value("Work Order Operation", self.operation_id, "completed_qty")
|
frappe.db.get_value("Work Order Operation", self.operation_id, "completed_qty")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
over_production_percentage = flt(
|
||||||
|
frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
|
||||||
|
)
|
||||||
|
|
||||||
|
wo_qty = wo_qty + (wo_qty * over_production_percentage / 100)
|
||||||
|
|
||||||
job_card_qty = frappe.get_all(
|
job_card_qty = frappe.get_all(
|
||||||
"Job Card",
|
"Job Card",
|
||||||
fields=["sum(for_quantity)"],
|
fields=["sum(for_quantity)"],
|
||||||
@ -101,8 +107,17 @@ class JobCard(Document):
|
|||||||
job_card_qty = flt(job_card_qty[0][0]) if job_card_qty else 0
|
job_card_qty = flt(job_card_qty[0][0]) if job_card_qty else 0
|
||||||
|
|
||||||
if job_card_qty and ((job_card_qty - completed_qty) > wo_qty):
|
if job_card_qty and ((job_card_qty - completed_qty) > wo_qty):
|
||||||
msg = f"""Job Card quantity cannot be greater than
|
form_link = get_link_to_form("Manufacturing Settings", "Manufacturing Settings")
|
||||||
Work Order quantity for the operation {self.operation}"""
|
|
||||||
|
msg = f"""
|
||||||
|
Qty To Manufacture in the job card
|
||||||
|
cannot be greater than Qty To Manufacture in the
|
||||||
|
work order for the operation {bold(self.operation)}.
|
||||||
|
<br><br><b>Solution: </b> Either you can reduce the
|
||||||
|
Qty To Manufacture in the job card or set the
|
||||||
|
'Overproduction Percentage For Work Order'
|
||||||
|
in the {form_link}."""
|
||||||
|
|
||||||
frappe.throw(_(msg), title=_("Extra Job Card Quantity"))
|
frappe.throw(_(msg), title=_("Extra Job Card Quantity"))
|
||||||
|
|
||||||
def set_sub_operations(self):
|
def set_sub_operations(self):
|
||||||
@ -547,6 +562,7 @@ class JobCard(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def update_work_order_data(self, for_quantity, time_in_mins, wo):
|
def update_work_order_data(self, for_quantity, time_in_mins, wo):
|
||||||
|
workstation_hour_rate = frappe.get_value("Workstation", self.workstation, "hour_rate")
|
||||||
jc = frappe.qb.DocType("Job Card")
|
jc = frappe.qb.DocType("Job Card")
|
||||||
jctl = frappe.qb.DocType("Job Card Time Log")
|
jctl = frappe.qb.DocType("Job Card Time Log")
|
||||||
|
|
||||||
@ -572,6 +588,7 @@ class JobCard(Document):
|
|||||||
if data.get("workstation") != self.workstation:
|
if data.get("workstation") != self.workstation:
|
||||||
# workstations can change in a job card
|
# workstations can change in a job card
|
||||||
data.workstation = self.workstation
|
data.workstation = self.workstation
|
||||||
|
data.hour_rate = flt(workstation_hour_rate)
|
||||||
|
|
||||||
wo.flags.ignore_validate_update_after_submit = True
|
wo.flags.ignore_validate_update_after_submit = True
|
||||||
wo.update_operation_status()
|
wo.update_operation_status()
|
||||||
|
@ -272,6 +272,42 @@ class TestJobCard(FrappeTestCase):
|
|||||||
transfer_entry_2.insert()
|
transfer_entry_2.insert()
|
||||||
self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit)
|
self.assertRaises(JobCardOverTransferError, transfer_entry_2.submit)
|
||||||
|
|
||||||
|
@change_settings("Manufacturing Settings", {"job_card_excess_transfer": 0})
|
||||||
|
def test_job_card_excess_material_transfer_with_no_reference(self):
|
||||||
|
|
||||||
|
self.transfer_material_against = "Job Card"
|
||||||
|
self.source_warehouse = "Stores - _TC"
|
||||||
|
|
||||||
|
self.generate_required_stock(self.work_order)
|
||||||
|
|
||||||
|
job_card_name = frappe.db.get_value("Job Card", {"work_order": self.work_order.name})
|
||||||
|
|
||||||
|
# fully transfer both RMs
|
||||||
|
transfer_entry_1 = make_stock_entry_from_jc(job_card_name)
|
||||||
|
row = transfer_entry_1.items[0]
|
||||||
|
|
||||||
|
# Add new row without reference of the job card item
|
||||||
|
transfer_entry_1.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": row.item_code,
|
||||||
|
"item_name": row.item_name,
|
||||||
|
"item_group": row.item_group,
|
||||||
|
"qty": row.qty,
|
||||||
|
"uom": row.uom,
|
||||||
|
"conversion_factor": row.conversion_factor,
|
||||||
|
"stock_uom": row.stock_uom,
|
||||||
|
"basic_rate": row.basic_rate,
|
||||||
|
"basic_amount": row.basic_amount,
|
||||||
|
"expense_account": row.expense_account,
|
||||||
|
"cost_center": row.cost_center,
|
||||||
|
"s_warehouse": row.s_warehouse,
|
||||||
|
"t_warehouse": row.t_warehouse,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertRaises(frappe.ValidationError, transfer_entry_1.insert)
|
||||||
|
|
||||||
def test_job_card_partial_material_transfer(self):
|
def test_job_card_partial_material_transfer(self):
|
||||||
"Test partial material transfer against Job Card"
|
"Test partial material transfer against Job Card"
|
||||||
self.transfer_material_against = "Job Card"
|
self.transfer_material_against = "Job Card"
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"column_break_4",
|
"column_break_4",
|
||||||
"quantity",
|
"quantity",
|
||||||
"uom",
|
"uom",
|
||||||
|
"conversion_factor",
|
||||||
"projected_qty",
|
"projected_qty",
|
||||||
"reserved_qty_for_production",
|
"reserved_qty_for_production",
|
||||||
"safety_stock",
|
"safety_stock",
|
||||||
@ -169,11 +170,17 @@
|
|||||||
"label": "Qty As Per BOM",
|
"label": "Qty As Per BOM",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "conversion_factor",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Conversion Factor",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-11-26 14:59:25.879631",
|
"modified": "2023-05-03 12:43:29.895754",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Material Request Plan Item",
|
"name": "Material Request Plan Item",
|
||||||
|
@ -336,10 +336,6 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
get_items_for_material_requests(frm, warehouses) {
|
get_items_for_material_requests(frm, warehouses) {
|
||||||
let set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
|
|
||||||
'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
|
|
||||||
'reserved_qty_for_production', 'material_request_type'];
|
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
|
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
|
||||||
freeze: true,
|
freeze: true,
|
||||||
@ -352,11 +348,11 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
frm.set_value('mr_items', []);
|
frm.set_value('mr_items', []);
|
||||||
r.message.forEach(row => {
|
r.message.forEach(row => {
|
||||||
let d = frm.add_child('mr_items');
|
let d = frm.add_child('mr_items');
|
||||||
set_fields.forEach(field => {
|
for (let field in row) {
|
||||||
if (row[field]) {
|
if (field !== 'name') {
|
||||||
d[field] = row[field];
|
d[field] = row[field];
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
refresh_field('mr_items');
|
refresh_field('mr_items');
|
||||||
|
@ -28,6 +28,7 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
|||||||
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
|
||||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||||
from erpnext.stock.get_item_details import get_conversion_factor
|
from erpnext.stock.get_item_details import get_conversion_factor
|
||||||
|
from erpnext.stock.utils import get_or_make_bin
|
||||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||||
|
|
||||||
|
|
||||||
@ -398,9 +399,20 @@ class ProductionPlan(Document):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self.db_set("status", self.status)
|
self.db_set("status", self.status)
|
||||||
|
|
||||||
|
def on_submit(self):
|
||||||
|
self.update_bin_qty()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
self.delete_draft_work_order()
|
self.delete_draft_work_order()
|
||||||
|
self.update_bin_qty()
|
||||||
|
|
||||||
|
def update_bin_qty(self):
|
||||||
|
for d in self.mr_items:
|
||||||
|
if d.warehouse:
|
||||||
|
bin_name = get_or_make_bin(d.item_code, d.warehouse)
|
||||||
|
bin = frappe.get_doc("Bin", bin_name, for_update=True)
|
||||||
|
bin.update_reserved_qty_for_production_plan()
|
||||||
|
|
||||||
def delete_draft_work_order(self):
|
def delete_draft_work_order(self):
|
||||||
for d in frappe.get_all(
|
for d in frappe.get_all(
|
||||||
@ -575,6 +587,7 @@ class ProductionPlan(Document):
|
|||||||
"production_plan_sub_assembly_item": row.name,
|
"production_plan_sub_assembly_item": row.name,
|
||||||
"bom": row.bom_no,
|
"bom": row.bom_no,
|
||||||
"production_plan": self.name,
|
"production_plan": self.name,
|
||||||
|
"fg_item_qty": row.qty,
|
||||||
}
|
}
|
||||||
|
|
||||||
for field in [
|
for field in [
|
||||||
@ -1068,6 +1081,7 @@ def get_material_request_items(
|
|||||||
"item_code": row.item_code,
|
"item_code": row.item_code,
|
||||||
"item_name": row.item_name,
|
"item_name": row.item_name,
|
||||||
"quantity": required_qty / conversion_factor,
|
"quantity": required_qty / conversion_factor,
|
||||||
|
"conversion_factor": conversion_factor,
|
||||||
"required_bom_qty": total_qty,
|
"required_bom_qty": total_qty,
|
||||||
"stock_uom": row.get("stock_uom"),
|
"stock_uom": row.get("stock_uom"),
|
||||||
"warehouse": warehouse
|
"warehouse": warehouse
|
||||||
@ -1474,3 +1488,34 @@ def set_default_warehouses(row, default_warehouses):
|
|||||||
for field in ["wip_warehouse", "fg_warehouse"]:
|
for field in ["wip_warehouse", "fg_warehouse"]:
|
||||||
if not row.get(field):
|
if not row.get(field):
|
||||||
row[field] = default_warehouses.get(field)
|
row[field] = default_warehouses.get(field)
|
||||||
|
|
||||||
|
|
||||||
|
def get_reserved_qty_for_production_plan(item_code, warehouse):
|
||||||
|
from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production
|
||||||
|
|
||||||
|
table = frappe.qb.DocType("Production Plan")
|
||||||
|
child = frappe.qb.DocType("Material Request Plan Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.inner_join(child)
|
||||||
|
.on(table.name == child.parent)
|
||||||
|
.select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
|
||||||
|
.where(
|
||||||
|
(table.docstatus == 1)
|
||||||
|
& (child.item_code == item_code)
|
||||||
|
& (child.warehouse == warehouse)
|
||||||
|
& (table.status.notin(["Completed", "Closed"]))
|
||||||
|
)
|
||||||
|
).run()
|
||||||
|
|
||||||
|
if not query:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
reserved_qty_for_production_plan = flt(query[0][0])
|
||||||
|
|
||||||
|
reserved_qty_for_production = flt(
|
||||||
|
get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
return reserved_qty_for_production_plan - reserved_qty_for_production
|
||||||
|
@ -307,6 +307,43 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier")
|
self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier")
|
||||||
|
|
||||||
|
def test_production_plan_for_subcontracting_po(self):
|
||||||
|
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
|
||||||
|
|
||||||
|
bom_tree_1 = {"Test Laptop 1": {"Test Motherboard 1": {"Test Motherboard Wires 1": {}}}}
|
||||||
|
create_nested_bom(bom_tree_1, prefix="")
|
||||||
|
|
||||||
|
item_doc = frappe.get_doc("Item", "Test Motherboard 1")
|
||||||
|
company = "_Test Company"
|
||||||
|
|
||||||
|
item_doc.is_sub_contracted_item = 1
|
||||||
|
for row in item_doc.item_defaults:
|
||||||
|
if row.company == company and not row.default_supplier:
|
||||||
|
row.default_supplier = "_Test Supplier"
|
||||||
|
|
||||||
|
if not item_doc.item_defaults:
|
||||||
|
item_doc.append("item_defaults", {"company": company, "default_supplier": "_Test Supplier"})
|
||||||
|
|
||||||
|
item_doc.save()
|
||||||
|
|
||||||
|
plan = create_production_plan(
|
||||||
|
item_code="Test Laptop 1", planned_qty=10, use_multi_level_bom=1, do_not_submit=True
|
||||||
|
)
|
||||||
|
plan.get_sub_assembly_items()
|
||||||
|
plan.set_default_supplier_for_subcontracting_order()
|
||||||
|
plan.submit()
|
||||||
|
|
||||||
|
self.assertEqual(plan.sub_assembly_items[0].supplier, "_Test Supplier")
|
||||||
|
plan.make_work_order()
|
||||||
|
|
||||||
|
po = frappe.db.get_value("Purchase Order Item", {"production_plan": plan.name}, "parent")
|
||||||
|
po_doc = frappe.get_doc("Purchase Order", po)
|
||||||
|
self.assertEqual(po_doc.supplier, "_Test Supplier")
|
||||||
|
self.assertEqual(po_doc.items[0].qty, 10.0)
|
||||||
|
self.assertEqual(po_doc.items[0].fg_item_qty, 10.0)
|
||||||
|
self.assertEqual(po_doc.items[0].fg_item_qty, 10.0)
|
||||||
|
self.assertEqual(po_doc.items[0].fg_item, "Test Motherboard 1")
|
||||||
|
|
||||||
def test_production_plan_combine_subassembly(self):
|
def test_production_plan_combine_subassembly(self):
|
||||||
"""
|
"""
|
||||||
Test combining Sub assembly items belonging to the same BOM in Prod Plan.
|
Test combining Sub assembly items belonging to the same BOM in Prod Plan.
|
||||||
@ -868,6 +905,27 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
for item_code in mr_items:
|
for item_code in mr_items:
|
||||||
self.assertTrue(item_code in validate_mr_items)
|
self.assertTrue(item_code in validate_mr_items)
|
||||||
|
|
||||||
|
def test_resered_qty_for_production_plan_for_material_requests(self):
|
||||||
|
from erpnext.stock.utils import get_or_make_bin
|
||||||
|
|
||||||
|
bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
|
||||||
|
before_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
||||||
|
|
||||||
|
pln = create_production_plan(item_code="Test Production Item 1")
|
||||||
|
|
||||||
|
bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
|
||||||
|
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
||||||
|
|
||||||
|
self.assertEqual(after_qty - before_qty, 1)
|
||||||
|
|
||||||
|
pln = frappe.get_doc("Production Plan", pln.name)
|
||||||
|
pln.cancel()
|
||||||
|
|
||||||
|
bin_name = get_or_make_bin("Raw Material Item 1", "_Test Warehouse - _TC")
|
||||||
|
after_qty = flt(frappe.db.get_value("Bin", bin_name, "reserved_qty_for_production_plan"))
|
||||||
|
|
||||||
|
self.assertEqual(after_qty, before_qty)
|
||||||
|
|
||||||
|
|
||||||
def create_production_plan(**args):
|
def create_production_plan(**args):
|
||||||
"""
|
"""
|
||||||
|
@ -50,7 +50,7 @@ frappe.ui.form.on('BOM Operation', {
|
|||||||
|
|
||||||
workstation: function(frm, cdt, cdn) {
|
workstation: function(frm, cdt, cdn) {
|
||||||
const d = locals[cdt][cdn];
|
const d = locals[cdt][cdn];
|
||||||
|
if(!d.workstation) return;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
"method": "frappe.client.get",
|
"method": "frappe.client.get",
|
||||||
args: {
|
args: {
|
||||||
|
@ -1649,6 +1649,14 @@ class TestWorkOrder(FrappeTestCase):
|
|||||||
job_card2 = frappe.copy_doc(job_card_doc)
|
job_card2 = frappe.copy_doc(job_card_doc)
|
||||||
self.assertRaises(frappe.ValidationError, job_card2.save)
|
self.assertRaises(frappe.ValidationError, job_card2.save)
|
||||||
|
|
||||||
|
frappe.db.set_single_value(
|
||||||
|
"Manufacturing Settings", "overproduction_percentage_for_work_order", 100
|
||||||
|
)
|
||||||
|
|
||||||
|
job_card2 = frappe.copy_doc(job_card_doc)
|
||||||
|
job_card2.time_logs = []
|
||||||
|
job_card2.save()
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_workstation_type_check():
|
def prepare_data_for_workstation_type_check():
|
||||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||||
|
@ -558,12 +558,19 @@ class WorkOrder(Document):
|
|||||||
and self.production_plan_item
|
and self.production_plan_item
|
||||||
and not self.production_plan_sub_assembly_item
|
and not self.production_plan_sub_assembly_item
|
||||||
):
|
):
|
||||||
qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
|
table = frappe.qb.DocType("Work Order")
|
||||||
|
|
||||||
if self.docstatus == 1:
|
query = (
|
||||||
qty += self.qty
|
frappe.qb.from_(table)
|
||||||
elif self.docstatus == 2:
|
.select(Sum(table.qty))
|
||||||
qty -= self.qty
|
.where(
|
||||||
|
(table.production_plan == self.production_plan)
|
||||||
|
& (table.production_plan_item == self.production_plan_item)
|
||||||
|
& (table.docstatus == 1)
|
||||||
|
)
|
||||||
|
).run()
|
||||||
|
|
||||||
|
qty = flt(query[0][0]) if query else 0
|
||||||
|
|
||||||
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
|
frappe.db.set_value("Production Plan Item", self.production_plan_item, "ordered_qty", qty)
|
||||||
|
|
||||||
@ -1476,12 +1483,14 @@ def create_pick_list(source_name, target_doc=None, for_qty=None):
|
|||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
|
def get_reserved_qty_for_production(
|
||||||
|
item_code: str, warehouse: str, check_production_plan: bool = False
|
||||||
|
) -> float:
|
||||||
"""Get total reserved quantity for any item in specified warehouse"""
|
"""Get total reserved quantity for any item in specified warehouse"""
|
||||||
wo = frappe.qb.DocType("Work Order")
|
wo = frappe.qb.DocType("Work Order")
|
||||||
wo_item = frappe.qb.DocType("Work Order Item")
|
wo_item = frappe.qb.DocType("Work Order Item")
|
||||||
|
|
||||||
return (
|
query = (
|
||||||
frappe.qb.from_(wo)
|
frappe.qb.from_(wo)
|
||||||
.from_(wo_item)
|
.from_(wo_item)
|
||||||
.select(
|
.select(
|
||||||
@ -1502,7 +1511,12 @@ def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
|
|||||||
| (wo_item.required_qty > wo_item.consumed_qty)
|
| (wo_item.required_qty > wo_item.consumed_qty)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).run()[0][0] or 0.0
|
)
|
||||||
|
|
||||||
|
if check_production_plan:
|
||||||
|
query = query.where(wo.production_plan.isnotnull())
|
||||||
|
|
||||||
|
return query.run()[0][0] or 0.0
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
@ -69,7 +69,7 @@ def get_columns(filters):
|
|||||||
"label": _("Id"),
|
"label": _("Id"),
|
||||||
"fieldname": "name",
|
"fieldname": "name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Work Order",
|
"options": "Quality Inspection",
|
||||||
"width": 100,
|
"width": 100,
|
||||||
},
|
},
|
||||||
{"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},
|
{"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},
|
||||||
|
@ -326,9 +326,10 @@ erpnext.patches.v13_0.update_docs_link
|
|||||||
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
|
||||||
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
|
||||||
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
||||||
erpnext.patches.v14_0.update_closing_balances
|
erpnext.patches.v14_0.update_closing_balances #10-05-2023
|
||||||
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
execute:frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||||
# below migration patches should always run last
|
# below migration patches should always run last
|
||||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||||
execute:frappe.delete_doc_if_exists("Report", "Tax Detail")
|
execute:frappe.delete_doc_if_exists("Report", "Tax Detail")
|
||||||
erpnext.patches.v15_0.enable_all_leads
|
erpnext.patches.v15_0.enable_all_leads
|
||||||
|
erpnext.patches.v14_0.update_company_in_ldc
|
||||||
|
@ -7,4 +7,6 @@ def execute():
|
|||||||
frappe.reload_doc("manufacturing", "doctype", "work_order")
|
frappe.reload_doc("manufacturing", "doctype", "work_order")
|
||||||
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
|
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
|
||||||
|
|
||||||
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
|
frappe.db.sql(
|
||||||
|
"""UPDATE `tabWork Order Item` SET amount = ifnull(rate, 0.0) * ifnull(required_qty, 0.0)"""
|
||||||
|
)
|
||||||
|
@ -11,6 +11,8 @@ from erpnext.accounts.utils import get_fiscal_year
|
|||||||
|
|
||||||
|
|
||||||
def execute():
|
def execute():
|
||||||
|
frappe.db.truncate("Account Closing Balance")
|
||||||
|
|
||||||
company_wise_order = {}
|
company_wise_order = {}
|
||||||
get_opening_entries = True
|
get_opening_entries = True
|
||||||
for pcv in frappe.db.get_all(
|
for pcv in frappe.db.get_all(
|
||||||
@ -26,8 +28,29 @@ def execute():
|
|||||||
pcv_doc.year_start_date = get_fiscal_year(
|
pcv_doc.year_start_date = get_fiscal_year(
|
||||||
pcv.posting_date, pcv.fiscal_year, company=pcv.company
|
pcv.posting_date, pcv.fiscal_year, company=pcv.company
|
||||||
)[1]
|
)[1]
|
||||||
gl_entries = pcv_doc.get_gl_entries()
|
|
||||||
closing_entries = pcv_doc.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
gl_entries = frappe.db.get_all(
|
||||||
|
"GL Entry", filters={"voucher_no": pcv.name, "is_cancelled": 0}, fields=["*"]
|
||||||
|
)
|
||||||
|
for entry in gl_entries:
|
||||||
|
entry["is_period_closing_voucher_entry"] = 1
|
||||||
|
entry["closing_date"] = pcv_doc.posting_date
|
||||||
|
entry["period_closing_voucher"] = pcv_doc.name
|
||||||
|
|
||||||
|
closing_entries = frappe.db.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters={
|
||||||
|
"is_cancelled": 0,
|
||||||
|
"voucher_no": ["!=", pcv.name],
|
||||||
|
"posting_date": ["<=", pcv.posting_date],
|
||||||
|
},
|
||||||
|
fields=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in closing_entries:
|
||||||
|
entry["closing_date"] = pcv_doc.posting_date
|
||||||
|
entry["period_closing_voucher"] = pcv_doc.name
|
||||||
|
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
|
make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
|
||||||
company_wise_order[pcv.company].append(pcv.posting_date)
|
company_wise_order[pcv.company].append(pcv.posting_date)
|
||||||
get_opening_entries = False
|
get_opening_entries = False
|
||||||
|
14
erpnext/patches/v14_0/update_company_in_ldc.py
Normal file
14
erpnext/patches/v14_0/update_company_in_ldc.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: MIT. See LICENSE
|
||||||
|
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
from erpnext import get_default_company
|
||||||
|
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
company = get_default_company()
|
||||||
|
if company:
|
||||||
|
for d in frappe.get_all("Lower Deduction Certificate", pluck="name"):
|
||||||
|
frappe.db.set_value("Lower Deduction Certificate", d, "company", company, update_modified=False)
|
@ -374,6 +374,7 @@ def make_sales_invoice(source_name, item_code=None, customer=None, currency=None
|
|||||||
billing_rate = billing_amount / hours
|
billing_rate = billing_amount / hours
|
||||||
|
|
||||||
target.company = timesheet.company
|
target.company = timesheet.company
|
||||||
|
target.project = timesheet.parent_project
|
||||||
if customer:
|
if customer:
|
||||||
target.customer = customer
|
target.customer = customer
|
||||||
|
|
||||||
|
@ -91,6 +91,12 @@ frappe.ui.form.on("Sales Invoice", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Purchase Invoice', {
|
frappe.ui.form.on('Purchase Invoice', {
|
||||||
|
setup: (frm) => {
|
||||||
|
frm.make_methods = {
|
||||||
|
'Landed Cost Voucher': function () { frm.trigger('create_landedcost_voucher') },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
mode_of_payment: function(frm) {
|
mode_of_payment: function(frm) {
|
||||||
get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){
|
get_payment_mode_account(frm, frm.doc.mode_of_payment, function(account){
|
||||||
frm.set_value('cash_bank_account', account);
|
frm.set_value('cash_bank_account', account);
|
||||||
@ -99,6 +105,20 @@ frappe.ui.form.on('Purchase Invoice', {
|
|||||||
|
|
||||||
payment_terms_template: function() {
|
payment_terms_template: function() {
|
||||||
cur_frm.trigger("disable_due_date");
|
cur_frm.trigger("disable_due_date");
|
||||||
|
},
|
||||||
|
|
||||||
|
create_landedcost_voucher: function (frm) {
|
||||||
|
let lcv = frappe.model.get_new_doc('Landed Cost Voucher');
|
||||||
|
lcv.company = frm.doc.company;
|
||||||
|
|
||||||
|
let lcv_receipt = frappe.model.get_new_doc('Landed Cost Purchase Invoice');
|
||||||
|
lcv_receipt.receipt_document_type = 'Purchase Invoice';
|
||||||
|
lcv_receipt.receipt_document = frm.doc.name;
|
||||||
|
lcv_receipt.supplier = frm.doc.supplier;
|
||||||
|
lcv_receipt.grand_total = frm.doc.grand_total;
|
||||||
|
lcv.purchase_receipts = [lcv_receipt];
|
||||||
|
|
||||||
|
frappe.set_route("Form", lcv.doctype, lcv.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
|
|
||||||
_calculate_taxes_and_totals() {
|
_calculate_taxes_and_totals() {
|
||||||
const is_quotation = this.frm.doc.doctype == "Quotation";
|
const is_quotation = this.frm.doc.doctype == "Quotation";
|
||||||
this.frm.doc._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
|
this.frm._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
|
||||||
|
|
||||||
this.validate_conversion_rate();
|
this.validate_conversion_rate();
|
||||||
this.calculate_item_values();
|
this.calculate_item_values();
|
||||||
@ -125,7 +125,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
calculate_item_values() {
|
calculate_item_values() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if (!this.discount_amount_applied) {
|
if (!this.discount_amount_applied) {
|
||||||
for (const item of this.frm.doc._items || []) {
|
for (const item of this.frm._items || []) {
|
||||||
frappe.model.round_floats_in(item);
|
frappe.model.round_floats_in(item);
|
||||||
item.net_rate = item.rate;
|
item.net_rate = item.rate;
|
||||||
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
|
item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
|
||||||
@ -209,7 +209,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
});
|
});
|
||||||
if(has_inclusive_tax==false) return;
|
if(has_inclusive_tax==false) return;
|
||||||
|
|
||||||
$.each(me.frm.doc._items || [], function(n, item) {
|
$.each(me.frm._items || [], function(n, item) {
|
||||||
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
||||||
var cumulated_tax_fraction = 0.0;
|
var cumulated_tax_fraction = 0.0;
|
||||||
var total_inclusive_tax_amount_per_qty = 0;
|
var total_inclusive_tax_amount_per_qty = 0;
|
||||||
@ -280,13 +280,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
var me = this;
|
var me = this;
|
||||||
this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
|
this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
|
||||||
|
|
||||||
$.each(this.frm.doc._items || [], function(i, item) {
|
$.each(this.frm._items || [], function(i, item) {
|
||||||
me.frm.doc.total += item.amount;
|
me.frm.doc.total += item.amount;
|
||||||
me.frm.doc.total_qty += item.qty;
|
me.frm.doc.total_qty += item.qty;
|
||||||
me.frm.doc.base_total += item.base_amount;
|
me.frm.doc.base_total += item.base_amount;
|
||||||
me.frm.doc.net_total += item.net_amount;
|
me.frm.doc.net_total += item.net_amount;
|
||||||
me.frm.doc.base_net_total += item.base_net_amount;
|
me.frm.doc.base_net_total += item.base_net_amount;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
calculate_shipping_charges() {
|
calculate_shipping_charges() {
|
||||||
@ -333,7 +333,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$.each(this.frm.doc._items || [], function(n, item) {
|
$.each(this.frm._items || [], function(n, item) {
|
||||||
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
|
||||||
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||||
// tax_amount represents the amount of tax for the current step
|
// tax_amount represents the amount of tax for the current step
|
||||||
@ -342,7 +342,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
// Adjust divisional loss to the last item
|
// Adjust divisional loss to the last item
|
||||||
if (tax.charge_type == "Actual") {
|
if (tax.charge_type == "Actual") {
|
||||||
actual_tax_dict[tax.idx] -= current_tax_amount;
|
actual_tax_dict[tax.idx] -= current_tax_amount;
|
||||||
if (n == me.frm.doc._items.length - 1) {
|
if (n == me.frm._items.length - 1) {
|
||||||
current_tax_amount += actual_tax_dict[tax.idx];
|
current_tax_amount += actual_tax_dict[tax.idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,7 +379,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set precision in the last item iteration
|
// set precision in the last item iteration
|
||||||
if (n == me.frm.doc._items.length - 1) {
|
if (n == me.frm._items.length - 1) {
|
||||||
me.round_off_totals(tax);
|
me.round_off_totals(tax);
|
||||||
me.set_in_company_currency(tax,
|
me.set_in_company_currency(tax,
|
||||||
["tax_amount", "tax_amount_after_discount_amount"]);
|
["tax_amount", "tax_amount_after_discount_amount"]);
|
||||||
@ -602,7 +602,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
|
|
||||||
_cleanup() {
|
_cleanup() {
|
||||||
this.frm.doc.base_in_words = this.frm.doc.in_words = "";
|
this.frm.doc.base_in_words = this.frm.doc.in_words = "";
|
||||||
let items = this.frm.doc._items;
|
let items = this.frm._items;
|
||||||
|
|
||||||
if(items && items.length) {
|
if(items && items.length) {
|
||||||
if(!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) {
|
if(!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) {
|
||||||
@ -659,7 +659,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
var net_total = 0;
|
var net_total = 0;
|
||||||
// calculate item amount after Discount Amount
|
// calculate item amount after Discount Amount
|
||||||
if (total_for_discount_amount) {
|
if (total_for_discount_amount) {
|
||||||
$.each(this.frm.doc._items || [], function(i, item) {
|
$.each(this.frm._items || [], function(i, item) {
|
||||||
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
|
distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
|
||||||
item.net_amount = flt(item.net_amount - distributed_amount,
|
item.net_amount = flt(item.net_amount - distributed_amount,
|
||||||
precision("base_amount", item));
|
precision("base_amount", item));
|
||||||
@ -667,7 +667,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
|||||||
|
|
||||||
// discount amount rounding loss adjustment if no taxes
|
// discount amount rounding loss adjustment if no taxes
|
||||||
if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
|
if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
|
||||||
&& i == (me.frm.doc._items || []).length - 1) {
|
&& i == (me.frm._items || []).length - 1) {
|
||||||
var discount_amount_loss = flt(me.frm.doc.net_total - net_total
|
var discount_amount_loss = flt(me.frm.doc.net_total - net_total
|
||||||
- me.frm.doc.discount_amount, precision("net_total"));
|
- me.frm.doc.discount_amount, precision("net_total"));
|
||||||
item.net_amount = flt(item.net_amount + discount_amount_loss,
|
item.net_amount = flt(item.net_amount + discount_amount_loss,
|
||||||
|
@ -68,7 +68,7 @@ erpnext.timesheet.control_timer = function(frm, dialog, row, timestamp=0) {
|
|||||||
// New activity if no activities found
|
// New activity if no activities found
|
||||||
var args = dialog.get_values();
|
var args = dialog.get_values();
|
||||||
if(!args) return;
|
if(!args) return;
|
||||||
if (frm.doc.time_logs.length <= 1 && !frm.doc.time_logs[0].activity_type && !frm.doc.time_logs[0].from_time) {
|
if (frm.doc.time_logs.length == 1 && !frm.doc.time_logs[0].activity_type && !frm.doc.time_logs[0].from_time) {
|
||||||
frm.doc.time_logs = [];
|
frm.doc.time_logs = [];
|
||||||
}
|
}
|
||||||
row = frappe.model.add_child(frm.doc, "Timesheet Detail", "time_logs");
|
row = frappe.model.add_child(frm.doc, "Timesheet Detail", "time_logs");
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"tax_withholding_category",
|
"tax_withholding_category",
|
||||||
"fiscal_year",
|
"fiscal_year",
|
||||||
"column_break_3",
|
"column_break_3",
|
||||||
|
"company",
|
||||||
"certificate_no",
|
"certificate_no",
|
||||||
"section_break_3",
|
"section_break_3",
|
||||||
"supplier",
|
"supplier",
|
||||||
@ -123,11 +124,18 @@
|
|||||||
"label": "Tax Withholding Category",
|
"label": "Tax Withholding Category",
|
||||||
"options": "Tax Withholding Category",
|
"options": "Tax Withholding Category",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-10-23 18:33:38.962622",
|
"modified": "2023-04-18 08:25:35.302081",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Regional",
|
"module": "Regional",
|
||||||
"name": "Lower Deduction Certificate",
|
"name": "Lower Deduction Certificate",
|
||||||
@ -136,5 +144,6 @@
|
|||||||
"permissions": [],
|
"permissions": [],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
@ -617,11 +617,15 @@ def get_credit_limit(customer, company):
|
|||||||
|
|
||||||
if not credit_limit:
|
if not credit_limit:
|
||||||
customer_group = frappe.get_cached_value("Customer", customer, "customer_group")
|
customer_group = frappe.get_cached_value("Customer", customer, "customer_group")
|
||||||
credit_limit = frappe.db.get_value(
|
|
||||||
|
result = frappe.db.get_values(
|
||||||
"Customer Credit Limit",
|
"Customer Credit Limit",
|
||||||
{"parent": customer_group, "parenttype": "Customer Group", "company": company},
|
{"parent": customer_group, "parenttype": "Customer Group", "company": company},
|
||||||
"credit_limit",
|
fieldname=["credit_limit", "bypass_credit_limit_check"],
|
||||||
|
as_dict=True,
|
||||||
)
|
)
|
||||||
|
if result and not result[0].bypass_credit_limit_check:
|
||||||
|
credit_limit = result[0].credit_limit
|
||||||
|
|
||||||
if not credit_limit:
|
if not credit_limit:
|
||||||
credit_limit = frappe.get_cached_value("Company", company, "credit_limit")
|
credit_limit = frappe.get_cached_value("Company", company, "credit_limit")
|
||||||
|
@ -286,6 +286,18 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
|
|||||||
target.commission_rate = frappe.get_value(
|
target.commission_rate = frappe.get_value(
|
||||||
"Sales Partner", source.referral_sales_partner, "commission_rate"
|
"Sales Partner", source.referral_sales_partner, "commission_rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# sales team
|
||||||
|
for d in customer.get("sales_team", []):
|
||||||
|
target.append(
|
||||||
|
"sales_team",
|
||||||
|
{
|
||||||
|
"sales_person": d.sales_person,
|
||||||
|
"allocated_percentage": d.allocated_percentage or None,
|
||||||
|
"commission_rate": d.commission_rate,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
target.flags.ignore_permissions = ignore_permissions
|
target.flags.ignore_permissions = ignore_permissions
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("calculate_taxes_and_totals")
|
target.run_method("calculate_taxes_and_totals")
|
||||||
|
@ -264,7 +264,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// payment request
|
// payment request
|
||||||
if(flt(doc.per_billed)<100) {
|
if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
|
||||||
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
|
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
|
||||||
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
|
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
|
||||||
}
|
}
|
||||||
|
@ -547,7 +547,7 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
# qty is for packed items, because packed items don't have stock_qty field
|
# qty is for packed items, because packed items don't have stock_qty field
|
||||||
qty = source.get("qty")
|
qty = source.get("qty")
|
||||||
target.project = source_parent.project
|
target.project = source_parent.project
|
||||||
target.qty = qty - requested_item_qty.get(source.name, 0)
|
target.qty = qty - requested_item_qty.get(source.name, 0) - source.delivered_qty
|
||||||
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
|
target.stock_qty = flt(target.qty) * flt(target.conversion_factor)
|
||||||
|
|
||||||
args = target.as_dict().copy()
|
args = target.as_dict().copy()
|
||||||
@ -581,7 +581,7 @@ def make_material_request(source_name, target_doc=None):
|
|||||||
"doctype": "Material Request Item",
|
"doctype": "Material Request Item",
|
||||||
"field_map": {"name": "sales_order_item", "parent": "sales_order"},
|
"field_map": {"name": "sales_order_item", "parent": "sales_order"},
|
||||||
"condition": lambda doc: not frappe.db.exists("Product Bundle", doc.item_code)
|
"condition": lambda doc: not frappe.db.exists("Product Bundle", doc.item_code)
|
||||||
and doc.stock_qty > requested_item_qty.get(doc.name, 0),
|
and (doc.stock_qty - doc.delivered_qty) > requested_item_qty.get(doc.name, 0),
|
||||||
"postprocess": update_item,
|
"postprocess": update_item,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -620,6 +620,8 @@ def make_project(source_name, target_doc=None):
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
||||||
|
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
|
||||||
|
|
||||||
def set_missing_values(source, target):
|
def set_missing_values(source, target):
|
||||||
target.run_method("set_missing_values")
|
target.run_method("set_missing_values")
|
||||||
target.run_method("set_po_nos")
|
target.run_method("set_po_nos")
|
||||||
@ -634,6 +636,8 @@ def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
|
|||||||
if target.company_address:
|
if target.company_address:
|
||||||
target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
|
target.update(get_fetch_values("Delivery Note", "company_address", target.company_address))
|
||||||
|
|
||||||
|
make_packing_list(target)
|
||||||
|
|
||||||
def update_item(source, target, source_parent):
|
def update_item(source, target, source_parent):
|
||||||
target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
|
target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
|
||||||
target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
|
target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
|
||||||
@ -1340,8 +1344,9 @@ def get_work_order_items(sales_order, for_raw_material_request=0):
|
|||||||
.select(Sum(wo.qty))
|
.select(Sum(wo.qty))
|
||||||
.where(
|
.where(
|
||||||
(wo.production_item == i.item_code)
|
(wo.production_item == i.item_code)
|
||||||
& (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
|
& (wo.sales_order == so.name)
|
||||||
& (wo.docstatus.lte(2))
|
& (wo.sales_order_item == i.name)
|
||||||
|
& (wo.docstatus.lt(2))
|
||||||
)
|
)
|
||||||
.run()[0][0]
|
.run()[0][0]
|
||||||
)
|
)
|
||||||
|
@ -57,7 +57,7 @@ frappe.listview_settings['Sales Order'] = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
listview.page.add_action_item(__("Advance Payment"), ()=>{
|
listview.page.add_action_item(__("Advance Payment"), ()=>{
|
||||||
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Advance Payment");
|
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Payment Entry");
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1878,6 +1878,106 @@ class TestSalesOrder(FrappeTestCase):
|
|||||||
self.assertEqual(pe.references[1].reference_name, so.name)
|
self.assertEqual(pe.references[1].reference_name, so.name)
|
||||||
self.assertEqual(pe.references[1].allocated_amount, 300)
|
self.assertEqual(pe.references[1].allocated_amount, 300)
|
||||||
|
|
||||||
|
def test_delivered_item_material_request(self):
|
||||||
|
"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
|
||||||
|
from erpnext.manufacturing.doctype.work_order.work_order import (
|
||||||
|
make_stock_entry as make_se_from_wo,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.material_request.material_request import raise_work_orders
|
||||||
|
|
||||||
|
so = make_sales_order(
|
||||||
|
item_list=[
|
||||||
|
{"item_code": "_Test FG Item", "qty": 10, "rate": 100, "warehouse": "Work In Progress - _TC"}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
make_stock_entry(
|
||||||
|
item_code="_Test FG Item", target="Work In Progress - _TC", qty=4, basic_rate=100
|
||||||
|
)
|
||||||
|
|
||||||
|
dn = make_delivery_note(so.name)
|
||||||
|
dn.items[0].qty = 4
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
self.assertEqual(so.items[0].delivered_qty, 4)
|
||||||
|
|
||||||
|
mr = make_material_request(so.name)
|
||||||
|
mr.material_request_type = "Purchase"
|
||||||
|
mr.schedule_date = today()
|
||||||
|
mr.save()
|
||||||
|
|
||||||
|
self.assertEqual(mr.items[0].qty, 6)
|
||||||
|
|
||||||
|
def test_packed_items_for_partial_sales_order(self):
|
||||||
|
# test Update Items with product bundle
|
||||||
|
for product_bundle in [
|
||||||
|
"_Test Product Bundle Item Partial 1",
|
||||||
|
"_Test Product Bundle Item Partial 2",
|
||||||
|
]:
|
||||||
|
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 Partial 1", "_Packed Item Partial 2"]:
|
||||||
|
if not frappe.db.exists("Item", product_bundle):
|
||||||
|
make_item(product_bundle, {"is_stock_item": 1, "stock_uom": "Nos"})
|
||||||
|
|
||||||
|
make_stock_entry(item=product_bundle, target="_Test Warehouse - _TC", qty=2, rate=10)
|
||||||
|
|
||||||
|
make_product_bundle("_Test Product Bundle Item Partial 1", ["_Packed Item Partial 1"], 1)
|
||||||
|
|
||||||
|
make_product_bundle("_Test Product Bundle Item Partial 2", ["_Packed Item Partial 2"], 1)
|
||||||
|
|
||||||
|
so = make_sales_order(
|
||||||
|
item_code="_Test Product Bundle Item Partial 1",
|
||||||
|
warehouse="_Test Warehouse - _TC",
|
||||||
|
qty=1,
|
||||||
|
uom="Nos",
|
||||||
|
stock_uom="Nos",
|
||||||
|
conversion_factor=1,
|
||||||
|
transaction_date=nowdate(),
|
||||||
|
delivery_note=nowdate(),
|
||||||
|
do_not_submit=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
so.append(
|
||||||
|
"items",
|
||||||
|
{
|
||||||
|
"item_code": "_Test Product Bundle Item Partial 2",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 1,
|
||||||
|
"uom": "Nos",
|
||||||
|
"stock_uom": "Nos",
|
||||||
|
"conversion_factor": 1,
|
||||||
|
"delivery_note": nowdate(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
so.save()
|
||||||
|
so.submit()
|
||||||
|
|
||||||
|
dn = make_delivery_note(so.name)
|
||||||
|
dn.remove(dn.items[1])
|
||||||
|
dn.save()
|
||||||
|
dn.submit()
|
||||||
|
|
||||||
|
self.assertEqual(len(dn.items), 1)
|
||||||
|
self.assertEqual(len(dn.packed_items), 1)
|
||||||
|
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 1")
|
||||||
|
|
||||||
|
so.load_from_db()
|
||||||
|
|
||||||
|
dn = make_delivery_note(so.name)
|
||||||
|
dn.save()
|
||||||
|
|
||||||
|
self.assertEqual(len(dn.items), 1)
|
||||||
|
self.assertEqual(len(dn.packed_items), 1)
|
||||||
|
self.assertEqual(dn.items[0].item_code, "_Test Product Bundle Item Partial 2")
|
||||||
|
|
||||||
|
|
||||||
def automatically_fetch_payment_terms(enable=1):
|
def automatically_fetch_payment_terms(enable=1):
|
||||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||||
|
@ -299,7 +299,8 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
|||||||
}
|
}
|
||||||
|
|
||||||
batch_no(doc, cdt, cdn) {
|
batch_no(doc, cdt, cdn) {
|
||||||
var me = this;
|
super.batch_no(doc, cdt, cdn);
|
||||||
|
|
||||||
var item = frappe.get_doc(cdt, cdn);
|
var item = frappe.get_doc(cdt, cdn);
|
||||||
|
|
||||||
if (item.serial_no) {
|
if (item.serial_no) {
|
||||||
@ -378,10 +379,6 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
batch_no(doc, cdt, cdn) {
|
|
||||||
super.batch_no(doc, cdt, cdn);
|
|
||||||
}
|
|
||||||
|
|
||||||
qty(doc, cdt, cdn) {
|
qty(doc, cdt, cdn) {
|
||||||
super.qty(doc, cdt, cdn);
|
super.qty(doc, cdt, cdn);
|
||||||
|
|
||||||
|
@ -80,5 +80,30 @@
|
|||||||
"chart_of_accounts": "Standard",
|
"chart_of_accounts": "Standard",
|
||||||
"enable_perpetual_inventory": 1,
|
"enable_perpetual_inventory": 1,
|
||||||
"default_holiday_list": "_Test Holiday List"
|
"default_holiday_list": "_Test Holiday List"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abbr": "_TC6",
|
||||||
|
"company_name": "_Test Company 6",
|
||||||
|
"is_group": 1,
|
||||||
|
"country": "India",
|
||||||
|
"default_currency": "INR",
|
||||||
|
"doctype": "Company",
|
||||||
|
"domain": "Manufacturing",
|
||||||
|
"chart_of_accounts": "Standard",
|
||||||
|
"default_holiday_list": "_Test Holiday List",
|
||||||
|
"enable_perpetual_inventory": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"abbr": "_TC7",
|
||||||
|
"company_name": "_Test Company 7",
|
||||||
|
"parent_company": "_Test Company 6",
|
||||||
|
"is_group": 1,
|
||||||
|
"country": "United States",
|
||||||
|
"default_currency": "USD",
|
||||||
|
"doctype": "Company",
|
||||||
|
"domain": "Manufacturing",
|
||||||
|
"chart_of_accounts": "Standard",
|
||||||
|
"default_holiday_list": "_Test Holiday List",
|
||||||
|
"enable_perpetual_inventory": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -257,7 +257,9 @@ def get_employee_email(employee_doc):
|
|||||||
|
|
||||||
def get_holiday_list_for_employee(employee, raise_exception=True):
|
def get_holiday_list_for_employee(employee, raise_exception=True):
|
||||||
if employee:
|
if employee:
|
||||||
holiday_list, company = frappe.db.get_value("Employee", employee, ["holiday_list", "company"])
|
holiday_list, company = frappe.get_cached_value(
|
||||||
|
"Employee", employee, ["holiday_list", "company"]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
holiday_list = ""
|
holiday_list = ""
|
||||||
company = frappe.db.get_single_value("Global Defaults", "default_company")
|
company = frappe.db.get_single_value("Global Defaults", "default_company")
|
||||||
|
@ -115,6 +115,8 @@ def is_holiday(holiday_list, date=None):
|
|||||||
if date is None:
|
if date is None:
|
||||||
date = today()
|
date = today()
|
||||||
if holiday_list:
|
if holiday_list:
|
||||||
return bool(frappe.get_all("Holiday List", dict(name=holiday_list, holiday_date=date)))
|
return bool(
|
||||||
|
frappe.db.exists("Holiday", {"parent": holiday_list, "holiday_date": date}, cache=True)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -22,24 +22,18 @@
|
|||||||
"creation": "2021-11-22 12:19:15.888642",
|
"creation": "2021-11-22 12:19:15.888642",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Module Onboarding",
|
"doctype": "Module Onboarding",
|
||||||
"documentation_url": "https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/company-setup",
|
"documentation_url": "https://docs.erpnext.com/docs/v14/user/manual/en/setting-up/company-setup",
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"modified": "2022-06-07 14:31:00.575193",
|
"modified": "2023-05-16 13:13:24.043792",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Setup",
|
"module": "Setup",
|
||||||
"name": "Home",
|
"name": "Home",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"steps": [
|
"steps": [
|
||||||
{
|
|
||||||
"step": "Company Set Up"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"step": "Navigation Help"
|
"step": "Navigation Help"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"step": "Data import"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"step": "Create an Item"
|
"step": "Create an Item"
|
||||||
},
|
},
|
||||||
@ -51,12 +45,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"step": "Create a Quotation"
|
"step": "Create a Quotation"
|
||||||
},
|
|
||||||
{
|
|
||||||
"step": "Letterhead"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"subtitle": "Company, Item, Customer, Supplier, Navigation Help, Data Import, Letter Head, Quotation",
|
"subtitle": "Item, Customer, Supplier, Navigation Help and Quotation",
|
||||||
"success_message": "Masters are all set up!",
|
"success_message": "You're ready to start your journey with ERPNext",
|
||||||
"title": "Let's Set Up Some Masters"
|
"title": "Let's begin your journey with ERPNext"
|
||||||
}
|
}
|
@ -5,11 +5,11 @@
|
|||||||
"description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n",
|
"description": "# Set Up a Company\n\nA company is a legal entity for which you will set up your books of account and create accounting transactions. In ERPNext, you can create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company.\n",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-12-15 14:22:18.317423",
|
"modified": "2023-05-15 09:18:42.895537",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Company Set Up",
|
"name": "Company Set Up",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
"description": "# Create a Customer\n\nThe Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
|
"description": "# Create a Customer\n\nThe Customer master is at the heart of your sales transactions. Customers are linked in Quotations, Sales Orders, Invoices, and Payments. Customers can be either numbered or identified by name (you would typically do this based on the number of customers you have).\n\nThrough Customer\u2019s master, you can effectively track essentials like:\n - Customer\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-12-15 14:20:31.197564",
|
"modified": "2023-05-16 12:54:54.112364",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Create a Customer",
|
"name": "Create a Customer",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_document": "Customer",
|
"reference_document": "Customer",
|
||||||
"show_form_tour": 0,
|
"show_form_tour": 0,
|
||||||
"show_full_form": 0,
|
"show_full_form": 0,
|
||||||
"title": "Manage Customers",
|
"title": "Create a Customer",
|
||||||
"validate_action": 1
|
"validate_action": 1
|
||||||
}
|
}
|
@ -5,11 +5,11 @@
|
|||||||
"description": "# Create a Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.",
|
"description": "# Create a Quotation\n\nLet\u2019s get started with business transactions by creating your first Quotation. You can create a Quotation for an existing customer or a prospect. It will be an approved document, with items you sell and the proposed price + taxes applied. After completing the instructions, you will get a Quotation in a ready to share print format.",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-12-15 14:21:31.675330",
|
"modified": "2023-05-15 09:18:42.984170",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Create a Quotation",
|
"name": "Create a Quotation",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
"description": "# Create a Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
|
"description": "# Create a Supplier\n\nAlso known as Vendor, is a master at the center of your purchase transactions. Suppliers are linked in Request for Quotation, Purchase Orders, Receipts, and Payments. Suppliers can be either numbered or identified by name.\n\nThrough Supplier\u2019s master, you can effectively track essentials like:\n - Supplier\u2019s multiple address and contacts\n - Account Receivables\n - Credit Limit and Credit Period\n",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-12-15 14:21:23.518301",
|
"modified": "2023-05-16 12:55:08.610113",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Create a Supplier",
|
"name": "Create a Supplier",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_document": "Supplier",
|
"reference_document": "Supplier",
|
||||||
"show_form_tour": 0,
|
"show_form_tour": 0,
|
||||||
"show_full_form": 0,
|
"show_full_form": 0,
|
||||||
"title": "Manage Suppliers",
|
"title": "Create a Supplier",
|
||||||
"validate_action": 1
|
"validate_action": 1
|
||||||
}
|
}
|
@ -6,18 +6,18 @@
|
|||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"form_tour": "Item General",
|
"form_tour": "Item General",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"intro_video_url": "",
|
"intro_video_url": "",
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-12-15 14:19:56.297772",
|
"modified": "2023-05-16 12:56:40.355878",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Create an Item",
|
"name": "Create an Item",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"reference_document": "Item",
|
"reference_document": "Item",
|
||||||
"show_form_tour": 1,
|
"show_form_tour": 1,
|
||||||
"show_full_form": 1,
|
"show_full_form": 0,
|
||||||
"title": "Manage Items",
|
"title": "Create an Item",
|
||||||
"validate_action": 1
|
"validate_action": 1
|
||||||
}
|
}
|
@ -5,11 +5,11 @@
|
|||||||
"description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc).",
|
"description": "# Import Data from Spreadsheet\n\nIn ERPNext, you can easily migrate your historical data using spreadsheets. You can use it for migrating not just masters (like Customer, Supplier, Items), but also for transactions like (outstanding invoices, opening stock and accounting entries, etc).",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2022-06-07 14:28:51.390813",
|
"modified": "2023-05-15 09:18:42.962231",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Data import",
|
"name": "Data import",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
"description": "# Create a Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n",
|
"description": "# Create a Letter Head\n\nA Letter Head contains your organization's name, logo, address, etc which appears at the header and footer portion in documents. You can learn more about Setting up Letter Head in ERPNext here.\n",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2021-12-15 14:21:39.037742",
|
"modified": "2023-05-15 09:18:42.995184",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Letterhead",
|
"name": "Letterhead",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
"action": "Watch Video",
|
"action": "Watch Video",
|
||||||
"action_label": "Learn about Navigation options",
|
"action_label": "Learn about Navigation options",
|
||||||
"creation": "2021-11-22 12:09:52.233872",
|
"creation": "2021-11-22 12:09:52.233872",
|
||||||
"description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or awesome bar\u2019s shortcut.\n",
|
"description": "# Navigation in ERPNext\n\nEase of navigating and browsing around the ERPNext is one of our core strengths. In the following video, you will learn how to reach a specific feature in ERPNext via module page or AwesomeBar.",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Onboarding Step",
|
"doctype": "Onboarding Step",
|
||||||
"idx": 0,
|
"idx": 1,
|
||||||
"is_complete": 0,
|
"is_complete": 0,
|
||||||
"is_single": 0,
|
"is_single": 0,
|
||||||
"is_skipped": 0,
|
"is_skipped": 0,
|
||||||
"modified": "2022-06-07 14:28:00.901082",
|
"modified": "2023-05-16 12:53:25.939908",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Navigation Help",
|
"name": "Navigation Help",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
@ -21,6 +21,10 @@ def boot_session(bootinfo):
|
|||||||
bootinfo.sysdefaults.allow_stale = cint(
|
bootinfo.sysdefaults.allow_stale = cint(
|
||||||
frappe.db.get_single_value("Accounts Settings", "allow_stale")
|
frappe.db.get_single_value("Accounts Settings", "allow_stale")
|
||||||
)
|
)
|
||||||
|
bootinfo.sysdefaults.over_billing_allowance = frappe.db.get_single_value(
|
||||||
|
"Accounts Settings", "over_billing_allowance"
|
||||||
|
)
|
||||||
|
|
||||||
bootinfo.sysdefaults.quotation_valid_till = cint(
|
bootinfo.sysdefaults.quotation_valid_till = cint(
|
||||||
frappe.db.get_single_value("CRM Settings", "default_valid_till")
|
frappe.db.get_single_value("CRM Settings", "default_valid_till")
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"projected_qty",
|
"projected_qty",
|
||||||
"reserved_qty_for_production",
|
"reserved_qty_for_production",
|
||||||
"reserved_qty_for_sub_contract",
|
"reserved_qty_for_sub_contract",
|
||||||
|
"reserved_qty_for_production_plan",
|
||||||
"ma_rate",
|
"ma_rate",
|
||||||
"stock_uom",
|
"stock_uom",
|
||||||
"fcfs_rate",
|
"fcfs_rate",
|
||||||
@ -165,13 +166,19 @@
|
|||||||
"oldfieldname": "stock_value",
|
"oldfieldname": "stock_value",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "reserved_qty_for_production_plan",
|
||||||
|
"fieldtype": "Float",
|
||||||
|
"label": "Reserved Qty for Production Plan",
|
||||||
|
"read_only": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hide_toolbar": 1,
|
"hide_toolbar": 1,
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"in_create": 1,
|
"in_create": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-03-30 07:22:23.868602",
|
"modified": "2023-05-02 23:26:21.806965",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Bin",
|
"name": "Bin",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user