Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
144594b3e1 | ||
|
|
64b416b4dd | ||
|
|
4e83d0baa6 | ||
|
|
5eb5bf7102 | ||
|
|
51d9d0a454 | ||
|
|
344c339484 | ||
|
|
bef9dd79e7 | ||
|
|
815696964a | ||
|
|
f0ae02e921 | ||
|
|
f50b4d80f1 | ||
|
|
7ad795bec5 | ||
|
|
662d9cb5aa | ||
|
|
8ac718d98c | ||
|
|
3810b02023 | ||
|
|
25b37737a2 | ||
|
|
0e39e5e868 | ||
|
|
fedde7fe3b | ||
|
|
551c96e1e5 | ||
|
|
78c34d71e2 | ||
|
|
453249d868 | ||
|
|
35ec125b34 | ||
|
|
2f3ed23a9d | ||
|
|
6597c74d6c | ||
|
|
e33fb3b242 | ||
|
|
d844a2b990 | ||
|
|
3ba2b9ed2e | ||
|
|
cca2fcec54 | ||
|
|
80230fec3e | ||
|
|
7021e3adb1 | ||
|
|
69c0d81c55 | ||
|
|
28dfc13dc6 | ||
|
|
5f381cd520 | ||
|
|
8571e6c5d2 | ||
|
|
e4ce6fa195 | ||
|
|
ef3d352a17 | ||
|
|
fc611cf86b | ||
|
|
a5489ee2ac |
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.23.0"
|
||||
__version__ = "14.23.2"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
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) {
|
||||
frappe.model.set_default_values(frm.doc);
|
||||
|
||||
@@ -19,18 +34,6 @@ frappe.ui.form.on("Journal Entry Template", {
|
||||
|
||||
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) {
|
||||
var add_accounts = function(doc, r) {
|
||||
|
||||
@@ -654,6 +654,28 @@ class PaymentEntry(AccountsController):
|
||||
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):
|
||||
if self.payment_type == "Internal Transfer":
|
||||
return
|
||||
@@ -662,9 +684,7 @@ class PaymentEntry(AccountsController):
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount:
|
||||
total_allocated_amount += flt(d.allocated_amount)
|
||||
base_total_allocated_amount += flt(
|
||||
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
|
||||
)
|
||||
base_total_allocated_amount += self.calculate_base_allocated_amount_for_reference(d)
|
||||
|
||||
self.total_allocated_amount = abs(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(
|
||||
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("paid_amount")
|
||||
)
|
||||
allocated_amount_in_company_currency = self.calculate_base_allocated_amount_for_reference(d)
|
||||
|
||||
gle.update(
|
||||
{
|
||||
@@ -1715,6 +1733,13 @@ def get_payment_entry(
|
||||
# bank or cash
|
||||
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(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
|
||||
)
|
||||
@@ -1931,19 +1956,27 @@ def set_paid_amount_and_received_amount(
|
||||
paid_amount = received_amount = 0
|
||||
if party_account_currency == bank.account_currency:
|
||||
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:
|
||||
received_amount = abs(outstanding_amount)
|
||||
if bank_amount:
|
||||
paid_amount = bank_amount
|
||||
company_currency = frappe.get_cached_value("Company", doc.get("company"), "default_currency")
|
||||
if payment_type == "Receive":
|
||||
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:
|
||||
# if party account currency and bank currency is different then populate paid amount as well
|
||||
paid_amount = received_amount * doc.get("conversion_rate", 1)
|
||||
received_amount = abs(outstanding_amount)
|
||||
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
|
||||
|
||||
|
||||
@@ -51,6 +51,38 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid")
|
||||
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):
|
||||
supplier = frappe.get_doc("Supplier", "_Test Supplier")
|
||||
supplier.on_hold = 1
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import today
|
||||
from frappe.utils import add_months, today
|
||||
|
||||
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
|
||||
@@ -176,6 +176,23 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(pcv_gle, expected_gle)
|
||||
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 = add_months(today(), 13)
|
||||
repost_doc.save()
|
||||
|
||||
def make_period_closing_voucher(self, submit=True):
|
||||
surplus_account = create_account()
|
||||
|
||||
@@ -15,7 +15,12 @@
|
||||
</div>
|
||||
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
||||
<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;">
|
||||
{{ _("Date: ") }}
|
||||
<b>{{ frappe.format(filters.from_date, 'Date')}}
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
"terms_and_conditions",
|
||||
"section_break_1",
|
||||
"enable_auto_email",
|
||||
"column_break_ocfq",
|
||||
"sender",
|
||||
"section_break_18",
|
||||
"frequency",
|
||||
"filter_duration",
|
||||
@@ -284,10 +286,32 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Terms and Conditions",
|
||||
"options": "Terms and Conditions"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "include_break",
|
||||
"fieldtype": "Check",
|
||||
"label": "Page Break After Each SoA"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_net_values_in_party_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Net Values in Party Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "sender",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sender",
|
||||
"options": "Email Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ocfq",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-09-06 21:00:45.732505",
|
||||
"modified": "2023-04-26 12:46:43.645455",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -327,7 +327,7 @@ def send_emails(document_name, from_scheduler=False):
|
||||
queue="short",
|
||||
method=frappe.sendmail,
|
||||
recipients=recipients,
|
||||
sender=frappe.session.user,
|
||||
sender=doc.sender or frappe.session.user,
|
||||
cc=cc,
|
||||
subject=subject,
|
||||
message=message,
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_email",
|
||||
"fieldtype": "Read Only",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Email"
|
||||
},
|
||||
@@ -41,7 +41,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-13 00:12:34.508086",
|
||||
"modified": "2023-04-26 13:02:41.964499",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts Customer",
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
"column_break8",
|
||||
"grand_total",
|
||||
"rounding_adjustment",
|
||||
"use_company_roundoff_cost_center",
|
||||
"rounded_total",
|
||||
"in_words",
|
||||
"total_advance",
|
||||
@@ -1559,13 +1560,19 @@
|
||||
"fieldname": "only_include_allocated_payments",
|
||||
"fieldtype": "Check",
|
||||
"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",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-03 22:57:14.074982",
|
||||
"modified": "2023-04-28 12:57:50.832598",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -978,7 +978,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def make_precision_loss_gl_entry(self, gl_entries):
|
||||
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(
|
||||
@@ -992,7 +992,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"account": round_off_account,
|
||||
"against": self.supplier,
|
||||
"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"),
|
||||
}
|
||||
)
|
||||
@@ -1386,7 +1388,7 @@ class PurchaseInvoice(BuyingController):
|
||||
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(
|
||||
self.company, "Purchase Invoice", self.name
|
||||
self.company, "Purchase Invoice", self.name, self.use_company_roundoff_cost_center
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
@@ -1396,7 +1398,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"against": self.supplier,
|
||||
"debit_in_account_currency": self.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,
|
||||
)
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"column_break5",
|
||||
"grand_total",
|
||||
"rounding_adjustment",
|
||||
"use_company_roundoff_cost_center",
|
||||
"rounded_total",
|
||||
"in_words",
|
||||
"total_advance",
|
||||
@@ -2134,6 +2135,12 @@
|
||||
"fieldname": "only_include_allocated_payments",
|
||||
"fieldtype": "Check",
|
||||
"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",
|
||||
@@ -2146,7 +2153,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-04-03 22:55:14.206473",
|
||||
"modified": "2023-04-28 14:15:59.901154",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -1450,7 +1450,7 @@ class SalesInvoice(SellingController):
|
||||
and not self.is_internal_transfer()
|
||||
):
|
||||
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(
|
||||
@@ -1462,7 +1462,9 @@ class SalesInvoice(SellingController):
|
||||
self.rounding_adjustment, self.precision("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,
|
||||
)
|
||||
|
||||
@@ -472,7 +472,9 @@ def update_accounting_dimensions(round_off_gle):
|
||||
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(
|
||||
"Company", company, ["round_off_account", "round_off_cost_center"]
|
||||
) or [None, None]
|
||||
@@ -480,7 +482,7 @@ def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
|
||||
meta = frappe.get_meta(voucher_type)
|
||||
|
||||
# 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")
|
||||
if parent_cost_center:
|
||||
round_off_cost_center = parent_cost_center
|
||||
|
||||
@@ -492,11 +492,22 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
additional_conditions.append("cost_center in %(cost_center)s")
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
filters.get("company_fb")
|
||||
):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(company_fb)s) OR finance_book IS NULL)")
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
if filters.get("finance_book"):
|
||||
additional_conditions.append("(finance_book in (%(finance_book)s) OR finance_book IS NULL)")
|
||||
else:
|
||||
additional_conditions.append("(finance_book IS NULL)")
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
|
||||
@@ -176,7 +176,8 @@ frappe.query_reports["General Ledger"] = {
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check"
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_cancelled_entries",
|
||||
|
||||
@@ -244,13 +244,23 @@ def get_conditions(filters):
|
||||
if filters.get("project"):
|
||||
conditions.append("project in %(project)s")
|
||||
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("include_default_book_entries"):
|
||||
conditions.append(
|
||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
if filters.get("include_default_book_entries"):
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
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:
|
||||
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 IS NULL)")
|
||||
|
||||
if not filters.get("show_cancelled_entries"):
|
||||
conditions.append("is_cancelled = 0")
|
||||
|
||||
@@ -59,7 +59,7 @@ frappe.query_reports["Gross Profit"] = {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
|
||||
column._options = "Sales Invoice";
|
||||
} else {
|
||||
column._options = "Item";
|
||||
column._options = "";
|
||||
}
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ def get_columns(group_wise_columns, filters):
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "warehouse",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
"qty": {"label": _("Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 80},
|
||||
@@ -305,7 +305,8 @@ def get_columns(group_wise_columns, filters):
|
||||
"sales_person": {
|
||||
"label": _("Sales Person"),
|
||||
"fieldname": "sales_person",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Person",
|
||||
"width": 100,
|
||||
},
|
||||
"allocated_amount": {
|
||||
@@ -326,14 +327,14 @@ def get_columns(group_wise_columns, filters):
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "customer",
|
||||
"options": "Customer Group",
|
||||
"width": 100,
|
||||
},
|
||||
"territory": {
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "territory",
|
||||
"options": "Territory",
|
||||
"width": 100,
|
||||
},
|
||||
"monthly": {
|
||||
|
||||
@@ -157,12 +157,23 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
if filters.project:
|
||||
additional_conditions += " and project = %(project)s"
|
||||
|
||||
company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
additional_conditions += (
|
||||
" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
if filters.get("finance_book"):
|
||||
if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in (%(finance_book)s) OR finance_book IS NULL)"
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in (%(company_fb)s) OR finance_book IS NULL)"
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
if filters.get("finance_book"):
|
||||
additional_conditions += " AND (finance_book in (%(finance_book)s) OR finance_book IS NULL)"
|
||||
else:
|
||||
additional_conditions += " AND (finance_book IS NULL)"
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
@@ -174,7 +185,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
"year_start_date": filters.year_start_date,
|
||||
"project": filters.project,
|
||||
"finance_book": filters.finance_book,
|
||||
"company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"),
|
||||
"company_fb": company_fb,
|
||||
}
|
||||
|
||||
if accounting_dimensions:
|
||||
|
||||
@@ -116,7 +116,9 @@ class AssetValueAdjustment(Document):
|
||||
if d.depreciation_method in ("Straight Line", "Manual"):
|
||||
end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx)
|
||||
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
|
||||
else:
|
||||
no_of_depreciations = len(
|
||||
|
||||
@@ -329,9 +329,10 @@ class StockController(AccountsController):
|
||||
"""Create batches if required. Called before submit"""
|
||||
for d in self.items:
|
||||
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"]
|
||||
)
|
||||
|
||||
if has_batch_no and create_new_batch:
|
||||
d.batch_no = (
|
||||
frappe.get_doc(
|
||||
@@ -414,7 +415,7 @@ class StockController(AccountsController):
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"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"
|
||||
),
|
||||
"incoming_rate": 0,
|
||||
@@ -609,7 +610,7 @@ class StockController(AccountsController):
|
||||
def validate_customer_provided_item(self):
|
||||
for d in self.get("items"):
|
||||
# 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
|
||||
|
||||
def set_rate_of_stock_uom(self):
|
||||
@@ -722,7 +723,7 @@ class StockController(AccountsController):
|
||||
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
return message
|
||||
|
||||
def repost_future_sle_and_gle(self):
|
||||
def repost_future_sle_and_gle(self, force=False):
|
||||
args = frappe._dict(
|
||||
{
|
||||
"posting_date": self.posting_date,
|
||||
@@ -733,7 +734,7 @@ class StockController(AccountsController):
|
||||
}
|
||||
)
|
||||
|
||||
if future_sle_exists(args) or repost_required_for_queue(self):
|
||||
if force or future_sle_exists(args) or repost_required_for_queue(self):
|
||||
item_based_reposting = cint(
|
||||
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
|
||||
)
|
||||
@@ -894,9 +895,6 @@ def future_sle_exists(args, sl_entries=None):
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
return len(data)
|
||||
@@ -919,7 +917,7 @@ def validate_future_sle_not_exists(args, key, sl_entries=None):
|
||||
|
||||
def get_cached_data(args, key):
|
||||
if key not in frappe.local.future_sle:
|
||||
return False
|
||||
frappe.local.future_sle[key] = frappe._dict({})
|
||||
|
||||
if args.get("item_code"):
|
||||
item_key = (args.get("item_code"), args.get("warehouse"))
|
||||
|
||||
@@ -87,6 +87,12 @@ class JobCard(Document):
|
||||
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",
|
||||
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
|
||||
|
||||
if job_card_qty and ((job_card_qty - completed_qty) > wo_qty):
|
||||
msg = f"""Job Card quantity cannot be greater than
|
||||
Work Order quantity for the operation {self.operation}"""
|
||||
form_link = get_link_to_form("Manufacturing Settings", "Manufacturing Settings")
|
||||
|
||||
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"))
|
||||
|
||||
def set_sub_operations(self):
|
||||
|
||||
@@ -1649,6 +1649,14 @@ class TestWorkOrder(FrappeTestCase):
|
||||
job_card2 = frappe.copy_doc(job_card_doc)
|
||||
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():
|
||||
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
|
||||
|
||||
@@ -69,7 +69,7 @@ def get_columns(filters):
|
||||
"label": _("Id"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Work Order",
|
||||
"options": "Quality Inspection",
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},
|
||||
|
||||
@@ -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 = source.get("qty")
|
||||
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)
|
||||
|
||||
args = target.as_dict().copy()
|
||||
@@ -581,7 +581,7 @@ def make_material_request(source_name, target_doc=None):
|
||||
"doctype": "Material Request Item",
|
||||
"field_map": {"name": "sales_order_item", "parent": "sales_order"},
|
||||
"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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1878,6 +1878,37 @@ class TestSalesOrder(FrappeTestCase):
|
||||
self.assertEqual(pe.references[1].reference_name, so.name)
|
||||
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 automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
|
||||
@@ -118,7 +118,7 @@ class DeliveryNote(SellingController):
|
||||
|
||||
def so_required(self):
|
||||
"""check in manage account if sales order required or not"""
|
||||
if frappe.db.get_value("Selling Settings", None, "so_required") == "Yes":
|
||||
if frappe.db.get_single_value("Selling Settings", "so_required") == "Yes":
|
||||
for d in self.get("items"):
|
||||
if not d.against_sales_order:
|
||||
frappe.throw(_("Sales Order required for Item {0}").format(d.item_code))
|
||||
@@ -205,7 +205,7 @@ class DeliveryNote(SellingController):
|
||||
super(DeliveryNote, self).validate_warehouse()
|
||||
|
||||
for d in self.get_item_list():
|
||||
if not d["warehouse"] and frappe.db.get_value("Item", d["item_code"], "is_stock_item") == 1:
|
||||
if not d["warehouse"] and frappe.get_cached_value("Item", d["item_code"], "is_stock_item") == 1:
|
||||
frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"]))
|
||||
|
||||
def update_current_stock(self):
|
||||
|
||||
@@ -629,7 +629,8 @@
|
||||
"no_copy": 1,
|
||||
"options": "Sales Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_sales_invoice",
|
||||
@@ -662,7 +663,8 @@
|
||||
"label": "Against Sales Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "installed_qty",
|
||||
@@ -854,7 +856,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-06 09:28:29.182053",
|
||||
"modified": "2023-05-01 21:05:14.175640",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
||||
@@ -6,7 +6,7 @@ from frappe import _
|
||||
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType, Interval
|
||||
from frappe.query_builder.functions import Now
|
||||
from frappe.query_builder.functions import Max, Now
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
|
||||
from frappe.utils.user import get_users_with_role
|
||||
from rq.timeouts import JobTimeoutException
|
||||
@@ -36,11 +36,38 @@ class RepostItemValuation(Document):
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_period_closing_voucher()
|
||||
self.set_status(write=False)
|
||||
self.reset_field_values()
|
||||
self.set_company()
|
||||
self.validate_accounts_freeze()
|
||||
|
||||
def validate_period_closing_voucher(self):
|
||||
year_end_date = self.get_max_year_end_date(self.company)
|
||||
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
|
||||
msg = f"Due to period closing, you cannot repost item valuation before {year_end_date}"
|
||||
frappe.throw(_(msg))
|
||||
|
||||
@staticmethod
|
||||
def get_max_year_end_date(company):
|
||||
data = frappe.get_all(
|
||||
"Period Closing Voucher", fields=["fiscal_year"], filters={"docstatus": 1, "company": company}
|
||||
)
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
fiscal_years = [d.fiscal_year for d in data]
|
||||
table = frappe.qb.DocType("Fiscal Year")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(Max(table.year_end_date))
|
||||
.where((table.name.isin(fiscal_years)) & (table.disabled == 0))
|
||||
).run()
|
||||
|
||||
return query[0][0] if query else None
|
||||
|
||||
def validate_accounts_freeze(self):
|
||||
acc_settings = frappe.db.get_value(
|
||||
"Accounts Settings",
|
||||
|
||||
@@ -1508,11 +1508,15 @@ def get_so_reservation_for_item(args):
|
||||
elif args.get("against_sales_invoice"):
|
||||
sales_order = frappe.db.get_all(
|
||||
"Sales Invoice Item",
|
||||
filters={"parent": args.get("against_sales_invoice"), "item_code": args.get("item_code")},
|
||||
filters={
|
||||
"parent": args.get("against_sales_invoice"),
|
||||
"item_code": args.get("item_code"),
|
||||
"docstatus": 1,
|
||||
},
|
||||
fields="sales_order",
|
||||
)
|
||||
if sales_order and sales_order[0]:
|
||||
if get_reserved_qty_for_so(sales_order[0][0], args.get("item_code")):
|
||||
if get_reserved_qty_for_so(sales_order[0].sales_order, args.get("item_code")):
|
||||
reserved_so = sales_order[0]
|
||||
elif args.get("sales_order"):
|
||||
if get_reserved_qty_for_so(args.get("sales_order"), args.get("item_code")):
|
||||
|
||||
@@ -1392,7 +1392,7 @@ def regenerate_sle_for_batch_stock_reco(detail):
|
||||
if not frappe.db.exists(
|
||||
"Repost Item Valuation", {"voucher_no": doc.name, "status": "Queued", "docstatus": "1"}
|
||||
):
|
||||
doc.repost_future_sle_and_gle()
|
||||
doc.repost_future_sle_and_gle(force=True)
|
||||
|
||||
|
||||
def get_stock_reco_qty_shift(args):
|
||||
|
||||
Reference in New Issue
Block a user