mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-28 17:34:47 +00:00
Merge pull request #37339 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
"transaction_date",
|
"transaction_date",
|
||||||
"posting_date",
|
"posting_date",
|
||||||
"fiscal_year",
|
"fiscal_year",
|
||||||
|
"year_start_date",
|
||||||
"amended_from",
|
"amended_from",
|
||||||
"company",
|
"company",
|
||||||
"column_break1",
|
"column_break1",
|
||||||
@@ -100,16 +101,22 @@
|
|||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"label": "Error Message",
|
"label": "Error Message",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "year_start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Year Start Date"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-07-20 14:51:04.714154",
|
"modified": "2023-09-11 20:19:11.810533",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Period Closing Voucher",
|
"name": "Period Closing Voucher",
|
||||||
|
"naming_rule": "Expression (old style)",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -144,5 +151,6 @@
|
|||||||
"search_fields": "posting_date, fiscal_year",
|
"search_fields": "posting_date, fiscal_year",
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"title_field": "closing_account_head"
|
"title_field": "closing_account_head"
|
||||||
}
|
}
|
||||||
@@ -95,15 +95,23 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
|
|
||||||
self.check_if_previous_year_closed()
|
self.check_if_previous_year_closed()
|
||||||
|
|
||||||
pce = frappe.db.sql(
|
pcv = frappe.qb.DocType("Period Closing Voucher")
|
||||||
"""select name from `tabPeriod Closing Voucher`
|
existing_entry = (
|
||||||
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
|
frappe.qb.from_(pcv)
|
||||||
(self.posting_date, self.fiscal_year, self.company),
|
.select(pcv.name)
|
||||||
|
.where(
|
||||||
|
(pcv.posting_date >= self.posting_date)
|
||||||
|
& (pcv.fiscal_year == self.fiscal_year)
|
||||||
|
& (pcv.docstatus == 1)
|
||||||
|
& (pcv.company == self.company)
|
||||||
|
)
|
||||||
|
.run()
|
||||||
)
|
)
|
||||||
if pce and pce[0][0]:
|
|
||||||
|
if existing_entry and existing_entry[0][0]:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Another Period Closing Entry {0} has been made after {1}").format(
|
_("Another Period Closing Entry {0} has been made after {1}").format(
|
||||||
pce[0][0], self.posting_date
|
existing_entry[0][0], self.posting_date
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -130,18 +138,27 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
process_gl_entries,
|
process_gl_entries,
|
||||||
gl_entries=gl_entries,
|
gl_entries=gl_entries,
|
||||||
|
voucher_name=self.name,
|
||||||
|
timeout=3000,
|
||||||
|
)
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
process_closing_entries,
|
||||||
|
gl_entries=gl_entries,
|
||||||
closing_entries=closing_entries,
|
closing_entries=closing_entries,
|
||||||
voucher_name=self.name,
|
voucher_name=self.name,
|
||||||
company=self.company,
|
company=self.company,
|
||||||
closing_date=self.posting_date,
|
closing_date=self.posting_date,
|
||||||
queue="long",
|
timeout=3000,
|
||||||
)
|
)
|
||||||
|
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
_("The GL Entries will be processed in the background, it can take a few minutes."),
|
||||||
alert=True,
|
alert=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
process_gl_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
process_gl_entries(gl_entries, self.name)
|
||||||
|
process_closing_entries(gl_entries, closing_entries, self.name, self.company, self.posting_date)
|
||||||
|
|
||||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||||
closing_entries = []
|
closing_entries = []
|
||||||
@@ -322,17 +339,12 @@ class PeriodClosingVoucher(AccountsController):
|
|||||||
return query.run(as_dict=1)
|
return query.run(as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
def process_gl_entries(gl_entries, voucher_name):
|
||||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
|
||||||
make_closing_entries,
|
|
||||||
)
|
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
make_gl_entries(gl_entries, merge_entries=False)
|
make_gl_entries(gl_entries, merge_entries=False)
|
||||||
|
|
||||||
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
|
||||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Completed")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
@@ -340,6 +352,19 @@ def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closi
|
|||||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||||
|
|
||||||
|
|
||||||
|
def process_closing_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||||
|
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||||
|
make_closing_entries,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if gl_entries + closing_entries:
|
||||||
|
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||||
|
except Exception as e:
|
||||||
|
frappe.db.rollback()
|
||||||
|
frappe.log_error(e)
|
||||||
|
|
||||||
|
|
||||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from frappe.utils import add_months, today
|
|||||||
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
from erpnext.accounts.doctype.finance_book.test_finance_book import create_finance_book
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.utils import get_fiscal_year, now
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|
||||||
|
|
||||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ def get_ar_filters(doc, entry):
|
|||||||
return {
|
return {
|
||||||
"report_date": doc.posting_date if doc.posting_date else None,
|
"report_date": doc.posting_date if doc.posting_date else None,
|
||||||
"party_type": "Customer",
|
"party_type": "Customer",
|
||||||
"party": entry.customer,
|
"party": [entry.customer],
|
||||||
"customer_name": entry.customer_name if entry.customer_name else None,
|
"customer_name": entry.customer_name if entry.customer_name else None,
|
||||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||||
|
|||||||
@@ -1377,6 +1377,7 @@
|
|||||||
"depends_on": "eval:doc.is_subcontracted",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Warehouse",
|
"options": "Warehouse",
|
||||||
@@ -1574,7 +1575,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-04 17:23:59.145031",
|
"modified": "2023-10-01 21:01:47.282533",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -95,18 +95,11 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
"options": "Payment Terms Template"
|
"options": "Payment Terms Template"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_type",
|
"fieldname":"party_type",
|
||||||
"label": __("Party Type"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Party Type",
|
options: get_party_type_options(),
|
||||||
get_query: () => {
|
on_change: function() {
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'account_type': 'Payable'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
on_change: () => {
|
|
||||||
frappe.query_report.set_filter_value('party', "");
|
frappe.query_report.set_filter_value('party', "");
|
||||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||||
}
|
}
|
||||||
@@ -114,8 +107,15 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"party",
|
"fieldname":"party",
|
||||||
"label": __("Party"),
|
"label": __("Party"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "party_type",
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "supplier_group",
|
"fieldname": "supplier_group",
|
||||||
@@ -164,3 +164,15 @@ frappe.query_reports["Accounts Payable"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
erpnext.utils.add_dimensions('Accounts Payable', 9);
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"party_type": "Supplier",
|
"party_type": "Supplier",
|
||||||
"party": self.supplier,
|
"party": [self.supplier],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range1": 30,
|
||||||
"range2": 60,
|
"range2": 60,
|
||||||
|
|||||||
@@ -72,18 +72,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_type",
|
"fieldname":"party_type",
|
||||||
"label": __("Party Type"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Party Type",
|
options: get_party_type_options(),
|
||||||
get_query: () => {
|
on_change: function() {
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'account_type': 'Payable'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
on_change: () => {
|
|
||||||
frappe.query_report.set_filter_value('party', "");
|
frappe.query_report.set_filter_value('party', "");
|
||||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||||
}
|
}
|
||||||
@@ -91,8 +84,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"party",
|
"fieldname":"party",
|
||||||
"label": __("Party"),
|
"label": __("Party"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "party_type",
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"payment_terms_template",
|
"fieldname":"payment_terms_template",
|
||||||
@@ -122,3 +122,15 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
erpnext.utils.add_dimensions('Accounts Payable Summary', 9);
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Payable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
// License: GNU General Public License v3. See license.txt
|
// License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
frappe.provide("erpnext.utils");
|
||||||
|
|
||||||
frappe.query_reports["Accounts Receivable"] = {
|
frappe.query_reports["Accounts Receivable"] = {
|
||||||
"filters": [
|
"filters": [
|
||||||
{
|
{
|
||||||
@@ -38,19 +40,11 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_type",
|
"fieldname":"party_type",
|
||||||
"label": __("Party Type"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Party Type",
|
options: get_party_type_options(),
|
||||||
"Default": "Customer",
|
on_change: function() {
|
||||||
get_query: () => {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'account_type': 'Receivable'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
on_change: () => {
|
|
||||||
frappe.query_report.set_filter_value('party', "");
|
frappe.query_report.set_filter_value('party', "");
|
||||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||||
}
|
}
|
||||||
@@ -58,8 +52,15 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"party",
|
"fieldname":"party",
|
||||||
"label": __("Party"),
|
"label": __("Party"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "party_type",
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_account",
|
"fieldname": "party_account",
|
||||||
@@ -192,3 +193,16 @@ frappe.query_reports["Accounts Receivable"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
erpnext.utils.add_dimensions('Accounts Receivable', 9);
|
||||||
|
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
@@ -801,7 +801,7 @@ class ReceivablePayableReport(object):
|
|||||||
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
||||||
|
|
||||||
if self.filters.get("party"):
|
if self.filters.get("party"):
|
||||||
self.qb_selection_filter.append(self.filters.party == self.ple.party)
|
self.qb_selection_filter.append(self.ple.party.isin(self.filters.party))
|
||||||
|
|
||||||
if self.filters.party_account:
|
if self.filters.party_account:
|
||||||
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
||||||
|
|||||||
@@ -573,7 +573,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
filters = {
|
filters = {
|
||||||
"company": self.company,
|
"company": self.company,
|
||||||
"party_type": "Customer",
|
"party_type": "Customer",
|
||||||
"party": self.customer,
|
"party": [self.customer],
|
||||||
"report_date": today(),
|
"report_date": today(),
|
||||||
"range1": 30,
|
"range1": 30,
|
||||||
"range2": 60,
|
"range2": 60,
|
||||||
@@ -605,3 +605,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
|||||||
for field in expected:
|
for field in expected:
|
||||||
with self.subTest(field=field):
|
with self.subTest(field=field):
|
||||||
self.assertEqual(report_output.get(field), expected.get(field))
|
self.assertEqual(report_output.get(field), expected.get(field))
|
||||||
|
|
||||||
|
def test_multi_select_party_filter(self):
|
||||||
|
self.customer1 = self.customer
|
||||||
|
self.create_customer("_Test Customer 2")
|
||||||
|
self.customer2 = self.customer
|
||||||
|
self.create_customer("_Test Customer 3")
|
||||||
|
self.customer3 = self.customer
|
||||||
|
|
||||||
|
filters = {
|
||||||
|
"company": self.company,
|
||||||
|
"party_type": "Customer",
|
||||||
|
"party": [self.customer1, self.customer3],
|
||||||
|
"report_date": today(),
|
||||||
|
"range1": 30,
|
||||||
|
"range2": 60,
|
||||||
|
"range3": 90,
|
||||||
|
"range4": 120,
|
||||||
|
}
|
||||||
|
|
||||||
|
si1 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
si1.customer = self.customer1
|
||||||
|
si1.save().submit()
|
||||||
|
|
||||||
|
si2 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
si2.customer = self.customer2
|
||||||
|
si2.save().submit()
|
||||||
|
|
||||||
|
si3 = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||||
|
si3.customer = self.customer3
|
||||||
|
si3.save().submit()
|
||||||
|
|
||||||
|
# check invoice grand total and invoiced column's value for 3 payment terms
|
||||||
|
report = execute(filters)
|
||||||
|
|
||||||
|
expected_output = {self.customer1, self.customer3}
|
||||||
|
self.assertEqual(len(report[1]), 2)
|
||||||
|
output_for = set([x.party for x in report[1]])
|
||||||
|
self.assertEqual(output_for, expected_output)
|
||||||
|
|||||||
@@ -72,19 +72,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "party_type",
|
"fieldname":"party_type",
|
||||||
"label": __("Party Type"),
|
"label": __("Party Type"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Autocomplete",
|
||||||
"options": "Party Type",
|
options: get_party_type_options(),
|
||||||
"Default": "Customer",
|
on_change: function() {
|
||||||
get_query: () => {
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
'account_type': 'Receivable'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
on_change: () => {
|
|
||||||
frappe.query_report.set_filter_value('party', "");
|
frappe.query_report.set_filter_value('party', "");
|
||||||
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
frappe.query_report.toggle_filter_display('customer_group', frappe.query_report.get_filter_value('party_type') !== "Customer");
|
||||||
}
|
}
|
||||||
@@ -92,8 +84,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
{
|
{
|
||||||
"fieldname":"party",
|
"fieldname":"party",
|
||||||
"label": __("Party"),
|
"label": __("Party"),
|
||||||
"fieldtype": "Dynamic Link",
|
"fieldtype": "MultiSelectList",
|
||||||
"options": "party_type",
|
get_data: function(txt) {
|
||||||
|
if (!frappe.query_report.filters) return;
|
||||||
|
|
||||||
|
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||||
|
if (!party_type) return;
|
||||||
|
|
||||||
|
return frappe.db.get_link_options(party_type, txt);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"customer_group",
|
"fieldname":"customer_group",
|
||||||
@@ -151,3 +150,15 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
|
erpnext.utils.add_dimensions('Accounts Receivable Summary', 9);
|
||||||
|
|
||||||
|
function get_party_type_options() {
|
||||||
|
let options = [];
|
||||||
|
frappe.db.get_list(
|
||||||
|
"Party Type", {filters:{"account_type": "Receivable"}, fields:['name']}
|
||||||
|
).then((res) => {
|
||||||
|
res.forEach((party_type) => {
|
||||||
|
options.push(party_type.name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
@@ -99,13 +99,11 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
|
|
||||||
# Add all amount columns
|
# Add all amount columns
|
||||||
for k in list(self.party_total[d.party]):
|
for k in list(self.party_total[d.party]):
|
||||||
if k not in ["currency", "sales_person"]:
|
if isinstance(self.party_total[d.party][k], float):
|
||||||
|
self.party_total[d.party][k] += d.get(k) or 0.0
|
||||||
self.party_total[d.party][k] += d.get(k, 0.0)
|
|
||||||
|
|
||||||
# set territory, customer_group, sales person etc
|
# set territory, customer_group, sales person etc
|
||||||
self.set_party_details(d)
|
self.set_party_details(d)
|
||||||
self.party_total[d.party].update({"party_type": d.party_type})
|
|
||||||
|
|
||||||
def init_party_total(self, row):
|
def init_party_total(self, row):
|
||||||
self.party_total.setdefault(
|
self.party_total.setdefault(
|
||||||
@@ -124,6 +122,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
"total_due": 0.0,
|
"total_due": 0.0,
|
||||||
"future_amount": 0.0,
|
"future_amount": 0.0,
|
||||||
"sales_person": [],
|
"sales_person": [],
|
||||||
|
"party_type": row.party_type,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -133,13 +132,12 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
|||||||
|
|
||||||
for key in ("territory", "customer_group", "supplier_group"):
|
for key in ("territory", "customer_group", "supplier_group"):
|
||||||
if row.get(key):
|
if row.get(key):
|
||||||
self.party_total[row.party][key] = row.get(key)
|
self.party_total[row.party][key] = row.get(key, "")
|
||||||
|
|
||||||
if row.sales_person:
|
if row.sales_person:
|
||||||
self.party_total[row.party].sales_person.append(row.sales_person)
|
self.party_total[row.party].sales_person.append(row.get("sales_person", ""))
|
||||||
|
|
||||||
if self.filters.sales_partner:
|
if self.filters.sales_partner:
|
||||||
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner")
|
self.party_total[row.party]["default_sales_partner"] = row.get("default_sales_partner", "")
|
||||||
|
|
||||||
def get_columns(self):
|
def get_columns(self):
|
||||||
self.columns = []
|
self.columns = []
|
||||||
|
|||||||
@@ -287,7 +287,8 @@ frappe.ui.form.on('Asset', {
|
|||||||
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
method: "erpnext.assets.doctype.asset.asset.get_item_details",
|
||||||
args: {
|
args: {
|
||||||
item_code: frm.doc.item_code,
|
item_code: frm.doc.item_code,
|
||||||
asset_category: frm.doc.asset_category
|
asset_category: frm.doc.asset_category,
|
||||||
|
gross_purchase_amount: frm.doc.gross_purchase_amount
|
||||||
},
|
},
|
||||||
callback: function(r, rt) {
|
callback: function(r, rt) {
|
||||||
if(r.message) {
|
if(r.message) {
|
||||||
@@ -504,7 +505,21 @@ frappe.ui.form.on('Asset', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
set_salvage_value_percentage_or_expected_value_after_useful_life: function(frm, row, salvage_value_percentage_changed, expected_value_after_useful_life_changed) {
|
||||||
|
if (expected_value_after_useful_life_changed) {
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||||
|
const new_salvage_value_percentage = flt((row.expected_value_after_useful_life * 100) / frm.doc.gross_purchase_amount, precision("salvage_value_percentage", row));
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "salvage_value_percentage", new_salvage_value_percentage);
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||||
|
} else if (salvage_value_percentage_changed) {
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = true;
|
||||||
|
const new_expected_value_after_useful_life = flt(frm.doc.gross_purchase_amount * (row.salvage_value_percentage / 100), precision('gross_purchase_amount'));
|
||||||
|
frappe.model.set_value(row.doctype, row.name, "expected_value_after_useful_life", new_expected_value_after_useful_life);
|
||||||
|
frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on('Asset Finance Book', {
|
frappe.ui.form.on('Asset Finance Book', {
|
||||||
@@ -516,9 +531,19 @@ frappe.ui.form.on('Asset Finance Book', {
|
|||||||
|
|
||||||
expected_value_after_useful_life: function(frm, cdt, cdn) {
|
expected_value_after_useful_life: function(frm, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
|
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||||
|
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, false, true);
|
||||||
|
}
|
||||||
frm.events.set_depreciation_rate(frm, row);
|
frm.events.set_depreciation_rate(frm, row);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
salvage_value_percentage: function(frm, cdt, cdn) {
|
||||||
|
const row = locals[cdt][cdn];
|
||||||
|
if (!frappe.flags.from_set_salvage_value_percentage_or_expected_value_after_useful_life) {
|
||||||
|
frm.events.set_salvage_value_percentage_or_expected_value_after_useful_life(frm, row, true, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
frequency_of_depreciation: function(frm, cdt, cdn) {
|
frequency_of_depreciation: function(frm, cdt, cdn) {
|
||||||
const row = locals[cdt][cdn];
|
const row = locals[cdt][cdn];
|
||||||
frm.events.set_depreciation_rate(frm, row);
|
frm.events.set_depreciation_rate(frm, row);
|
||||||
|
|||||||
@@ -204,7 +204,9 @@ class Asset(AccountsController):
|
|||||||
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
self.asset_category = frappe.get_cached_value("Item", self.item_code, "asset_category")
|
||||||
|
|
||||||
if self.item_code and not self.get("finance_books"):
|
if self.item_code and not self.get("finance_books"):
|
||||||
finance_books = get_item_details(self.item_code, self.asset_category)
|
finance_books = get_item_details(
|
||||||
|
self.item_code, self.asset_category, self.gross_purchase_amount
|
||||||
|
)
|
||||||
self.set("finance_books", finance_books)
|
self.set("finance_books", finance_books)
|
||||||
|
|
||||||
def validate_finance_books(self):
|
def validate_finance_books(self):
|
||||||
@@ -1195,7 +1197,7 @@ def transfer_asset(args):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_item_details(item_code, asset_category):
|
def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||||
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
||||||
books = []
|
books = []
|
||||||
for d in asset_category_doc.finance_books:
|
for d in asset_category_doc.finance_books:
|
||||||
@@ -1205,7 +1207,11 @@ def get_item_details(item_code, asset_category):
|
|||||||
"depreciation_method": d.depreciation_method,
|
"depreciation_method": d.depreciation_method,
|
||||||
"total_number_of_depreciations": d.total_number_of_depreciations,
|
"total_number_of_depreciations": d.total_number_of_depreciations,
|
||||||
"frequency_of_depreciation": d.frequency_of_depreciation,
|
"frequency_of_depreciation": d.frequency_of_depreciation,
|
||||||
"start_date": nowdate(),
|
"daily_depreciation": d.daily_depreciation,
|
||||||
|
"salvage_value_percentage": d.salvage_value_percentage,
|
||||||
|
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||||
|
* flt(d.salvage_value_percentage / 100),
|
||||||
|
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"column_break_5",
|
"column_break_5",
|
||||||
"frequency_of_depreciation",
|
"frequency_of_depreciation",
|
||||||
"depreciation_start_date",
|
"depreciation_start_date",
|
||||||
|
"salvage_value_percentage",
|
||||||
"expected_value_after_useful_life",
|
"expected_value_after_useful_life",
|
||||||
"value_after_depreciation",
|
"value_after_depreciation",
|
||||||
"rate_of_depreciation"
|
"rate_of_depreciation"
|
||||||
@@ -87,12 +88,17 @@
|
|||||||
"fieldname": "daily_depreciation",
|
"fieldname": "daily_depreciation",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Daily Depreciation"
|
"label": "Daily Depreciation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "salvage_value_percentage",
|
||||||
|
"fieldtype": "Percent",
|
||||||
|
"label": "Salvage Value Percentage"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-08-10 18:56:09.022246",
|
"modified": "2023-09-29 15:39:52.740594",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset Finance Book",
|
"name": "Asset Finance Book",
|
||||||
|
|||||||
@@ -475,6 +475,7 @@
|
|||||||
"depends_on": "eval:doc.is_subcontracted",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
"options": "Warehouse"
|
"options": "Warehouse"
|
||||||
},
|
},
|
||||||
@@ -1272,7 +1273,7 @@
|
|||||||
"idx": 105,
|
"idx": 105,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-09-13 16:21:07.361700",
|
"modified": "2023-10-01 20:58:07.851037",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Purchase Order",
|
"name": "Purchase Order",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ frappe.ui.form.on("Supplier", {
|
|||||||
}, __("View"));
|
}, __("View"));
|
||||||
|
|
||||||
frm.add_custom_button(__('Accounts Payable'), function () {
|
frm.add_custom_button(__('Accounts Payable'), function () {
|
||||||
frappe.set_route('query-report', 'Accounts Payable', { supplier: frm.doc.name });
|
frappe.set_route('query-report', 'Accounts Payable', { party_type: "Supplier", party: frm.doc.name });
|
||||||
}, __("View"));
|
}, __("View"));
|
||||||
|
|
||||||
frm.add_custom_button(__('Bank Account'), function () {
|
frm.add_custom_button(__('Bank Account'), function () {
|
||||||
|
|||||||
@@ -35,8 +35,12 @@ def get_data(filters):
|
|||||||
sq_item.parent,
|
sq_item.parent,
|
||||||
sq_item.item_code,
|
sq_item.item_code,
|
||||||
sq_item.qty,
|
sq_item.qty,
|
||||||
|
sq.currency,
|
||||||
sq_item.stock_qty,
|
sq_item.stock_qty,
|
||||||
sq_item.amount,
|
sq_item.amount,
|
||||||
|
sq_item.base_rate,
|
||||||
|
sq_item.base_amount,
|
||||||
|
sq.price_list_currency,
|
||||||
sq_item.uom,
|
sq_item.uom,
|
||||||
sq_item.stock_uom,
|
sq_item.stock_uom,
|
||||||
sq_item.request_for_quotation,
|
sq_item.request_for_quotation,
|
||||||
@@ -105,7 +109,11 @@ def prepare_data(supplier_quotation_data, filters):
|
|||||||
"qty": data.get("qty"),
|
"qty": data.get("qty"),
|
||||||
"price": flt(data.get("amount") * exchange_rate, float_precision),
|
"price": flt(data.get("amount") * exchange_rate, float_precision),
|
||||||
"uom": data.get("uom"),
|
"uom": data.get("uom"),
|
||||||
|
"price_list_currency": data.get("price_list_currency"),
|
||||||
|
"currency": data.get("currency"),
|
||||||
"stock_uom": data.get("stock_uom"),
|
"stock_uom": data.get("stock_uom"),
|
||||||
|
"base_amount": flt(data.get("base_amount"), float_precision),
|
||||||
|
"base_rate": flt(data.get("base_rate"), float_precision),
|
||||||
"request_for_quotation": data.get("request_for_quotation"),
|
"request_for_quotation": data.get("request_for_quotation"),
|
||||||
"valid_till": data.get("valid_till"),
|
"valid_till": data.get("valid_till"),
|
||||||
"lead_time_days": data.get("lead_time_days"),
|
"lead_time_days": data.get("lead_time_days"),
|
||||||
@@ -183,6 +191,8 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
|||||||
|
|
||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
|
currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||||
|
|
||||||
group_by_columns = [
|
group_by_columns = [
|
||||||
{
|
{
|
||||||
"fieldname": "supplier_name",
|
"fieldname": "supplier_name",
|
||||||
@@ -203,11 +213,18 @@ def get_columns(filters):
|
|||||||
columns = [
|
columns = [
|
||||||
{"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90},
|
{"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 90},
|
||||||
{"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80},
|
{"fieldname": "qty", "label": _("Quantity"), "fieldtype": "Float", "width": 80},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"label": _("Currency"),
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"options": "Currency",
|
||||||
|
"width": 110,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "price",
|
"fieldname": "price",
|
||||||
"label": _("Price"),
|
"label": _("Price"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "currency",
|
||||||
"width": 110,
|
"width": 110,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -221,9 +238,23 @@ def get_columns(filters):
|
|||||||
"fieldname": "price_per_unit",
|
"fieldname": "price_per_unit",
|
||||||
"label": _("Price per Unit (Stock UOM)"),
|
"label": _("Price per Unit (Stock UOM)"),
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "Company:company:default_currency",
|
"options": "currency",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_amount",
|
||||||
|
"label": _("Price ({0})").format(currency),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "price_list_currency",
|
||||||
|
"width": 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_rate",
|
||||||
|
"label": _("Price Per Unit ({0})").format(currency),
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"options": "price_list_currency",
|
||||||
|
"width": 180,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quotation",
|
"fieldname": "quotation",
|
||||||
"label": _("Supplier Quotation"),
|
"label": _("Supplier Quotation"),
|
||||||
|
|||||||
@@ -1406,7 +1406,7 @@ class AccountsController(TransactionBase):
|
|||||||
"account": self.additional_discount_account,
|
"account": self.additional_discount_account,
|
||||||
"against": supplier_or_customer,
|
"against": supplier_or_customer,
|
||||||
dr_or_cr: self.base_discount_amount,
|
dr_or_cr: self.base_discount_amount,
|
||||||
"cost_center": self.cost_center,
|
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
|
||||||
},
|
},
|
||||||
item=self,
|
item=self,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -282,7 +282,9 @@ class SellingController(StockController):
|
|||||||
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
|
last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1)
|
||||||
|
|
||||||
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
|
if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom):
|
||||||
throw_message(item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate")
|
throw_message(
|
||||||
|
item.idx, item.item_name, last_valuation_rate_in_sales_uom, "valuation rate (Moving Average)"
|
||||||
|
)
|
||||||
|
|
||||||
def get_item_list(self):
|
def get_item_list(self):
|
||||||
il = []
|
il = []
|
||||||
|
|||||||
@@ -228,7 +228,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "If enabled, the system won't create material requests for the available items.",
|
"description": "If enabled, the system will create material requests even if the stock exists in the 'Raw Materials Warehouse'.",
|
||||||
"fieldname": "ignore_existing_ordered_qty",
|
"fieldname": "ignore_existing_ordered_qty",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Available Stock"
|
"label": "Ignore Available Stock"
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-28 13:37:43.926686",
|
"modified": "2023-09-29 11:41:03.246059",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import json
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
from frappe.query_builder import Case
|
||||||
from frappe.query_builder.functions import IfNull, Sum
|
from frappe.query_builder.functions import IfNull, Sum
|
||||||
from frappe.utils import (
|
from frappe.utils import (
|
||||||
add_days,
|
add_days,
|
||||||
@@ -1616,18 +1617,33 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
|
|||||||
table = frappe.qb.DocType("Production Plan")
|
table = frappe.qb.DocType("Production Plan")
|
||||||
child = frappe.qb.DocType("Material Request Plan Item")
|
child = frappe.qb.DocType("Material Request Plan Item")
|
||||||
|
|
||||||
|
completed_production_plans = get_completed_production_plans()
|
||||||
|
|
||||||
|
case = Case()
|
||||||
query = (
|
query = (
|
||||||
frappe.qb.from_(table)
|
frappe.qb.from_(table)
|
||||||
.inner_join(child)
|
.inner_join(child)
|
||||||
.on(table.name == child.parent)
|
.on(table.name == child.parent)
|
||||||
.select(Sum(child.quantity * IfNull(child.conversion_factor, 1.0)))
|
.select(
|
||||||
|
Sum(
|
||||||
|
child.quantity
|
||||||
|
* IfNull(
|
||||||
|
case.when(child.material_request_type == "Purchase", child.conversion_factor).else_(1.0), 1.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
(table.docstatus == 1)
|
(table.docstatus == 1)
|
||||||
& (child.item_code == item_code)
|
& (child.item_code == item_code)
|
||||||
& (child.warehouse == warehouse)
|
& (child.warehouse == warehouse)
|
||||||
& (table.status.notin(["Completed", "Closed"]))
|
& (table.status.notin(["Completed", "Closed"]))
|
||||||
)
|
)
|
||||||
).run()
|
)
|
||||||
|
|
||||||
|
if completed_production_plans:
|
||||||
|
query = query.where(table.name.notin(completed_production_plans))
|
||||||
|
|
||||||
|
query = query.run()
|
||||||
|
|
||||||
if not query:
|
if not query:
|
||||||
return 0.0
|
return 0.0
|
||||||
@@ -1635,7 +1651,9 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
|
|||||||
reserved_qty_for_production_plan = flt(query[0][0])
|
reserved_qty_for_production_plan = flt(query[0][0])
|
||||||
|
|
||||||
reserved_qty_for_production = flt(
|
reserved_qty_for_production = flt(
|
||||||
get_reserved_qty_for_production(item_code, warehouse, check_production_plan=True)
|
get_reserved_qty_for_production(
|
||||||
|
item_code, warehouse, completed_production_plans, check_production_plan=True
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if reserved_qty_for_production > reserved_qty_for_production_plan:
|
if reserved_qty_for_production > reserved_qty_for_production_plan:
|
||||||
@@ -1644,6 +1662,25 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
|
|||||||
return reserved_qty_for_production_plan - reserved_qty_for_production
|
return reserved_qty_for_production_plan - reserved_qty_for_production
|
||||||
|
|
||||||
|
|
||||||
|
def get_completed_production_plans():
|
||||||
|
table = frappe.qb.DocType("Production Plan")
|
||||||
|
child = frappe.qb.DocType("Production Plan Item")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.inner_join(child)
|
||||||
|
.on(table.name == child.parent)
|
||||||
|
.select(table.name)
|
||||||
|
.where(
|
||||||
|
(table.docstatus == 1)
|
||||||
|
& (table.status.notin(["Completed", "Closed"]))
|
||||||
|
& (child.ordered_qty >= child.planned_qty)
|
||||||
|
)
|
||||||
|
).run(as_dict=True)
|
||||||
|
|
||||||
|
return list(set([d.name for d in query]))
|
||||||
|
|
||||||
|
|
||||||
def get_raw_materials_of_sub_assembly_items(
|
def get_raw_materials_of_sub_assembly_items(
|
||||||
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from frappe.utils import add_to_date, flt, getdate, now_datetime, nowdate
|
|||||||
|
|
||||||
from erpnext.controllers.item_variant import create_variant
|
from erpnext.controllers.item_variant import create_variant
|
||||||
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
from erpnext.manufacturing.doctype.production_plan.production_plan import (
|
||||||
|
get_completed_production_plans,
|
||||||
get_items_for_material_requests,
|
get_items_for_material_requests,
|
||||||
get_sales_orders,
|
get_sales_orders,
|
||||||
get_warehouse_list,
|
get_warehouse_list,
|
||||||
@@ -1092,6 +1093,49 @@ class TestProductionPlan(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(after_qty, before_qty)
|
self.assertEqual(after_qty, before_qty)
|
||||||
|
|
||||||
|
def test_resered_qty_for_production_plan_for_less_rm_qty(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", planned_qty=10)
|
||||||
|
|
||||||
|
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, 10)
|
||||||
|
|
||||||
|
pln.make_work_order()
|
||||||
|
|
||||||
|
plans = []
|
||||||
|
for row in frappe.get_all("Work Order", filters={"production_plan": pln.name}, fields=["name"]):
|
||||||
|
wo_doc = frappe.get_doc("Work Order", row.name)
|
||||||
|
wo_doc.source_warehouse = "_Test Warehouse - _TC"
|
||||||
|
wo_doc.wip_warehouse = "_Test Warehouse 1 - _TC"
|
||||||
|
wo_doc.fg_warehouse = "_Test Warehouse - _TC"
|
||||||
|
for d in wo_doc.required_items:
|
||||||
|
d.source_warehouse = "_Test Warehouse - _TC"
|
||||||
|
d.required_qty -= 5
|
||||||
|
make_stock_entry(
|
||||||
|
item_code=d.item_code,
|
||||||
|
qty=d.required_qty,
|
||||||
|
rate=100,
|
||||||
|
target="_Test Warehouse - _TC",
|
||||||
|
)
|
||||||
|
|
||||||
|
wo_doc.submit()
|
||||||
|
plans.append(pln.name)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
completed_plans = get_completed_production_plans()
|
||||||
|
for plan in plans:
|
||||||
|
self.assertTrue(plan in completed_plans)
|
||||||
|
|
||||||
def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self):
|
def test_resered_qty_for_production_plan_for_material_requests_with_multi_UOM(self):
|
||||||
from erpnext.stock.utils import get_or_make_bin
|
from erpnext.stock.utils import get_or_make_bin
|
||||||
|
|
||||||
|
|||||||
@@ -362,10 +362,10 @@ class WorkOrder(Document):
|
|||||||
else:
|
else:
|
||||||
self.update_work_order_qty_in_so()
|
self.update_work_order_qty_in_so()
|
||||||
|
|
||||||
|
self.update_ordered_qty()
|
||||||
self.update_reserved_qty_for_production()
|
self.update_reserved_qty_for_production()
|
||||||
self.update_completed_qty_in_material_request()
|
self.update_completed_qty_in_material_request()
|
||||||
self.update_planned_qty()
|
self.update_planned_qty()
|
||||||
self.update_ordered_qty()
|
|
||||||
self.create_job_card()
|
self.create_job_card()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
@@ -1491,7 +1491,10 @@ def create_pick_list(source_name, target_doc=None, for_qty=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_reserved_qty_for_production(
|
def get_reserved_qty_for_production(
|
||||||
item_code: str, warehouse: str, check_production_plan: bool = False
|
item_code: str,
|
||||||
|
warehouse: str,
|
||||||
|
completed_production_plans: list = None,
|
||||||
|
check_production_plan: bool = False,
|
||||||
) -> float:
|
) -> 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")
|
||||||
@@ -1524,6 +1527,9 @@ def get_reserved_qty_for_production(
|
|||||||
if check_production_plan:
|
if check_production_plan:
|
||||||
query = query.where(wo.production_plan.isnotnull())
|
query = query.where(wo.production_plan.isnotnull())
|
||||||
|
|
||||||
|
if completed_production_plans:
|
||||||
|
query = query.where(wo.production_plan.notin(completed_production_plans))
|
||||||
|
|
||||||
return query.run()[0][0] or 0.0
|
return query.run()[0][0] or 0.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
"fieldname": "subject",
|
"fieldname": "subject",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"in_global_search": 1,
|
"in_global_search": 1,
|
||||||
"in_list_view": 1,
|
|
||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Subject",
|
"label": "Subject",
|
||||||
"reqd": 1,
|
"reqd": 1,
|
||||||
@@ -140,7 +139,6 @@
|
|||||||
"fieldname": "parent_task",
|
"fieldname": "parent_task",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"ignore_user_permissions": 1,
|
"ignore_user_permissions": 1,
|
||||||
"in_list_view": 1,
|
|
||||||
"label": "Parent Task",
|
"label": "Parent Task",
|
||||||
"options": "Task",
|
"options": "Task",
|
||||||
"search_index": 1
|
"search_index": 1
|
||||||
@@ -398,7 +396,7 @@
|
|||||||
"is_tree": 1,
|
"is_tree": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"max_attachments": 5,
|
"max_attachments": 5,
|
||||||
"modified": "2023-09-06 13:52:05.861175",
|
"modified": "2023-09-28 13:52:05.861175",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Projects",
|
"module": "Projects",
|
||||||
"name": "Task",
|
"name": "Task",
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ frappe.ui.form.on("Customer", {
|
|||||||
// custom buttons
|
// custom buttons
|
||||||
|
|
||||||
frm.add_custom_button(__('Accounts Receivable'), function () {
|
frm.add_custom_button(__('Accounts Receivable'), function () {
|
||||||
frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name});
|
frappe.set_route('query-report', 'Accounts Receivable', { party_type: "Customer", party: frm.doc.name });
|
||||||
}, __('View'));
|
}, __('View'));
|
||||||
|
|
||||||
frm.add_custom_button(__('Accounting Ledger'), function () {
|
frm.add_custom_button(__('Accounting Ledger'), function () {
|
||||||
|
|||||||
@@ -236,6 +236,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
|
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
|
||||||
"fieldname": "discount_and_margin",
|
"fieldname": "discount_and_margin",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Discount and Margin"
|
"label": "Discount and Margin"
|
||||||
@@ -667,7 +668,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-02-06 11:00:07.042364",
|
"modified": "2023-09-27 14:02:12.332407",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Quotation Item",
|
"name": "Quotation Item",
|
||||||
|
|||||||
@@ -463,6 +463,7 @@
|
|||||||
"depends_on": "eval:doc.is_subcontracted",
|
"depends_on": "eval:doc.is_subcontracted",
|
||||||
"fieldname": "supplier_warehouse",
|
"fieldname": "supplier_warehouse",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"ignore_user_permissions": 1,
|
||||||
"label": "Supplier Warehouse",
|
"label": "Supplier Warehouse",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldname": "supplier_warehouse",
|
"oldfieldname": "supplier_warehouse",
|
||||||
@@ -1240,7 +1241,7 @@
|
|||||||
"idx": 261,
|
"idx": 261,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-04 17:24:17.025390",
|
"modified": "2023-10-01 21:00:44.556816",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Purchase Receipt",
|
"name": "Purchase Receipt",
|
||||||
|
|||||||
Reference in New Issue
Block a user