Skip to content

Commit

Permalink
Merge pull request #31414 from frappe/version-13-hotfix
Browse files Browse the repository at this point in the history
chore: weekly version-13 release
  • Loading branch information
ankush committed Jun 21, 2022
2 parents 943d83b + e43e442 commit d6d2215
Show file tree
Hide file tree
Showing 22 changed files with 350 additions and 106 deletions.
13 changes: 9 additions & 4 deletions erpnext/accounts/doctype/pricing_rule/pricing_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@ def validate(self):
self.margin_rate_or_amount = 0.0

def validate_duplicate_apply_on(self):
field = apply_on_dict.get(self.apply_on)
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field) if field]
if len(values) != len(set(values)):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))
if self.apply_on != "Transaction":
apply_on_table = apply_on_dict.get(self.apply_on)
if not apply_on_table:
return

apply_on_field = frappe.scrub(self.apply_on)
values = [d.get(apply_on_field) for d in self.get(apply_on_table) if d.get(apply_on_field)]
if len(values) != len(set(values)):
frappe.throw(_("Duplicate {0} found in the table").format(self.apply_on))

def validate_mandatory(self):
for apply_on, field in apply_on_dict.items():
Expand Down
11 changes: 0 additions & 11 deletions erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,6 @@ def set_missing_values(self, for_validate=False):

super(PurchaseInvoice, self).set_missing_values(for_validate)

def check_conversion_rate(self):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
throw(_("Please enter default currency in Company Master"))
if (
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
or not self.conversion_rate
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
):
throw(_("Conversion rate cannot be 0 or 1"))

def validate_credit_to_acc(self):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
Expand Down
20 changes: 20 additions & 0 deletions erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,26 @@ def test_provisional_accounting_entry(self):
company.enable_provisional_accounting_for_non_stock_items = 0
company.save()

def test_item_less_defaults(self):

pi = frappe.new_doc("Purchase Invoice")
pi.supplier = "_Test Supplier"
pi.company = "_Test Company"
pi.append(
"items",
{
"item_name": "Opening item",
"qty": 1,
"uom": "Tonne",
"stock_uom": "Kg",
"rate": 1000,
"expense_account": "Stock Received But Not Billed - _TC",
},
)

pi.save()
self.assertEqual(pi.items[0].conversion_factor, 1000)


def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql(
Expand Down
1 change: 1 addition & 0 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def validate(self):
self.set_income_account_for_fixed_assets()
self.validate_item_cost_centers()
self.validate_income_account()
self.check_conversion_rate()

validate_inter_company_party(
self.doctype, self.customer, self.company, self.inter_company_invoice_reference
Expand Down
11 changes: 11 additions & 0 deletions erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -1612,6 +1612,17 @@ def test_multi_currency_gle(self):

self.assertTrue(gle)

def test_invoice_exchange_rate(self):
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=1,
do_not_save=1,
)

self.assertRaises(frappe.ValidationError, si.save)

def test_invalid_currency(self):
# Customer currency = USD

Expand Down
2 changes: 1 addition & 1 deletion erpnext/accounts/test/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def test_stock_voucher_sorting(self):
stock_entry = {"item": item, "to_warehouse": "_Test Warehouse - _TC", "qty": 1, "rate": 10}

se1 = make_stock_entry(posting_date="2022-01-01", **stock_entry)
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)
se3 = make_stock_entry(posting_date="2022-03-01", **stock_entry)
se2 = make_stock_entry(posting_date="2022-02-01", **stock_entry)

for doc in (se1, se2, se3):
vouchers.append((doc.doctype, doc.name))
Expand Down
84 changes: 60 additions & 24 deletions erpnext/accounts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@


from json import loads
from typing import List, Tuple
from typing import TYPE_CHECKING, List, Optional, Tuple

import frappe
import frappe.defaults
from frappe import _, throw
from frappe.model.meta import get_field_precision
from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
from frappe.utils import (
cint,
create_batch,
cstr,
flt,
formatdate,
get_number_format_info,
getdate,
now,
nowdate,
)
from six import string_types

import erpnext
Expand All @@ -19,6 +29,9 @@
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.utils import get_stock_value_on

if TYPE_CHECKING:
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import RepostItemValuation


class FiscalYearError(frappe.ValidationError):
pass
Expand All @@ -28,6 +41,9 @@ class PaymentEntryUnlinkError(frappe.ValidationError):
pass


GL_REPOSTING_CHUNK = 100


@frappe.whitelist()
def get_fiscal_year(
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
Expand Down Expand Up @@ -1122,38 +1138,55 @@ def update_gl_entries_after(


def repost_gle_for_stock_vouchers(
stock_vouchers, posting_date, company=None, warehouse_account=None
stock_vouchers: List[Tuple[str, str]],
posting_date: str,
company: Optional[str] = None,
warehouse_account=None,
repost_doc: Optional["RepostItemValuation"] = None,
):
if not stock_vouchers:
return

def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql(
"""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""",
(voucher_type, voucher_no),
)

stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)

if not warehouse_account:
warehouse_account = get_warehouse_account_map(company)

precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit")) or 2

gle = get_voucherwise_gl_entries(stock_vouchers, posting_date)
for voucher_type, voucher_no in stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_cached_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(
existing_gle, expected_gle, precision
):
stock_vouchers = sort_stock_vouchers_by_posting_date(stock_vouchers)
if repost_doc and repost_doc.gl_reposting_index:
# Restore progress
stock_vouchers = stock_vouchers[cint(repost_doc.gl_reposting_index) :]

for stock_vouchers_chunk in create_batch(stock_vouchers, GL_REPOSTING_CHUNK):
gle = get_voucherwise_gl_entries(stock_vouchers_chunk, posting_date)
for voucher_type, voucher_no in stock_vouchers_chunk:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(
existing_gle, expected_gle, precision
):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(gl_entries=expected_gle, from_repost=True)
else:
_delete_gl_entries(voucher_type, voucher_no)

if not frappe.flags.in_test:
frappe.db.commit()

if repost_doc:
repost_doc.db_set(
"gl_reposting_index", cint(repost_doc.gl_reposting_index) + len(stock_vouchers_chunk)
)


def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql(
"""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""",
(voucher_type, voucher_no),
)


def sort_stock_vouchers_by_posting_date(
Expand All @@ -1167,6 +1200,9 @@ def sort_stock_vouchers_by_posting_date(
.select(sle.voucher_type, sle.voucher_no, sle.posting_date, sle.posting_time, sle.creation)
.where((sle.is_cancelled == 0) & (sle.voucher_no.isin(voucher_nos)))
.groupby(sle.voucher_type, sle.voucher_no)
.orderby(sle.posting_date)
.orderby(sle.posting_time)
.orderby(sle.creation)
).run(as_dict=True)
sorted_vouchers = [(sle.voucher_type, sle.voucher_no) for sle in sles]

Expand Down
21 changes: 21 additions & 0 deletions erpnext/controllers/accounts_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from erpnext.controllers.sales_and_purchase_return import validate_return
from erpnext.exceptions import InvalidCurrency
from erpnext.setup.utils import get_exchange_rate
from erpnext.stock.doctype.item.item import get_uom_conv_factor
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
from erpnext.stock.get_item_details import (
_get_item_tax_template,
Expand Down Expand Up @@ -549,6 +550,15 @@ def set_missing_item_details(self, for_validate=False):
if ret.get("pricing_rules"):
self.apply_pricing_rule_on_items(item, ret)
self.set_pricing_rule_details(item, ret)
else:
# Transactions line item without item code

uom = item.get("uom")
stock_uom = item.get("stock_uom")
if bool(uom) != bool(stock_uom): # xor
item.stock_uom = item.uom = uom or stock_uom

item.conversion_factor = get_uom_conv_factor(item.get("uom"), item.get("stock_uom"))

if self.doctype == "Purchase Invoice":
self.set_expense_account(for_validate)
Expand Down Expand Up @@ -1836,6 +1846,17 @@ def create_advance_and_reconcile(self, party_link):
jv.save()
jv.submit()

def check_conversion_rate(self):
default_currency = erpnext.get_company_currency(self.company)
if not default_currency:
throw(_("Please enter default currency in Company Master"))
if (
(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
or not self.conversion_rate
or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
):
throw(_("Conversion rate cannot be 0 or 1"))


@frappe.whitelist()
def get_tax_rate(account_head):
Expand Down
2 changes: 1 addition & 1 deletion erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def get_gl_entries(
"against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision),
"debit": -1 * flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"actions": [],
"autoname": "autoincrement",
"autoname": "hash",
"creation": "2022-05-31 17:34:39.825537",
"doctype": "DocType",
"engine": "InnoDB",
Expand Down Expand Up @@ -42,14 +42,13 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-06-06 14:50:35.161062",
"modified": "2022-06-20 15:10:15.826571",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM Update Batch",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
"sort_order": "DESC"
}
1 change: 0 additions & 1 deletion erpnext/public/js/controllers/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
is_pos: cint(me.frm.doc.is_pos),
is_return: cint(me.frm.doc.is_return),
is_subcontracted: me.frm.doc.is_subcontracted,
transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
doctype: me.frm.doc.doctype,
name: me.frm.doc.name,
Expand Down
4 changes: 4 additions & 0 deletions erpnext/regional/india/e_invoice/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ def validate_eligibility(doc):
return False

invalid_company = not frappe.db.get_value("E Invoice User", {"company": doc.get("company")})
invalid_company_gstin = not frappe.db.get_value(
"E Invoice User", {"gstin": doc.get("company_gstin")}
)
invalid_supply_type = doc.get("gst_category") not in [
"Registered Regular",
"Registered Composition",
Expand All @@ -72,6 +75,7 @@ def validate_eligibility(doc):

if (
invalid_company
or invalid_company_gstin
or invalid_supply_type
or company_transaction
or no_taxes_applied
Expand Down
6 changes: 6 additions & 0 deletions erpnext/selling/doctype/customer/test_customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@ def create_internal_customer(
if not allowed_to_interact_with:
allowed_to_interact_with = represents_company

exisiting_representative = frappe.db.get_value(
"Customer", {"represents_company": represents_company}
)
if exisiting_representative:
return exisiting_representative

if not frappe.db.exists("Customer", customer_name):
customer = frappe.get_doc(
{
Expand Down
6 changes: 3 additions & 3 deletions erpnext/selling/doctype/quotation/quotation.json
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@
"show_seconds": 1
},
{
"depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
"depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
"fieldname": "col_break98",
"fieldtype": "Column Break",
"show_days": 1,
Expand All @@ -357,7 +357,7 @@
"show_seconds": 1
},
{
"depends_on": "eval:doc.quotaion_to=='Customer' && doc.party_name",
"depends_on": "eval:doc.quotation_to=='Customer' && doc.party_name",
"fieldname": "customer_group",
"fieldtype": "Link",
"hidden": 1,
Expand Down Expand Up @@ -1174,7 +1174,7 @@
"idx": 82,
"is_submittable": 1,
"links": [],
"modified": "2022-06-11 20:35:32.635804",
"modified": "2022-06-15 20:35:32.635804",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation",
Expand Down
2 changes: 1 addition & 1 deletion erpnext/selling/doctype/quotation/quotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def update_opportunity_status(self, status, opportunity=None):

@frappe.whitelist()
def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
if not self.has_sales_order():
if not (self.is_fully_ordered() or self.is_partially_ordered()):
get_lost_reasons = frappe.get_list("Quotation Lost Reason", fields=["name"])
lost_reasons_lst = [reason.get("name") for reason in get_lost_reasons]
frappe.db.set(self, "status", "Lost")
Expand Down
Loading

0 comments on commit d6d2215

Please sign in to comment.