Merge branch 'develop' into sales_purchase_retrun_optimization
This commit is contained in:
commit
e4e38ad60b
@ -64,10 +64,10 @@
|
||||
<td></td>
|
||||
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
|
||||
{{ row.get('account', '') and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
|
||||
{{ row.get('account', '') and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
|
@ -51,6 +51,13 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
}
|
||||
}
|
||||
});
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
'company': frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
if(frm.doc.__islocal){
|
||||
frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
|
||||
frm.set_value('to_date', frappe.datetime.get_today());
|
||||
|
@ -9,7 +9,6 @@
|
||||
"customer_section",
|
||||
"title",
|
||||
"naming_series",
|
||||
"tax_invoice_number",
|
||||
"customer",
|
||||
"customer_name",
|
||||
"tax_id",
|
||||
@ -2027,12 +2026,6 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_invoice_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tax Invoice Number",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@ -2045,7 +2038,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2022-03-07 16:08:53.517903",
|
||||
"modified": "2022-03-08 16:08:53.517903",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -1255,14 +1255,14 @@ class SalesInvoice(SellingController):
|
||||
def update_billing_status_in_dn(self, update_modified=True):
|
||||
updated_delivery_notes = []
|
||||
for d in self.get("items"):
|
||||
if d.so_detail:
|
||||
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
|
||||
elif d.dn_detail:
|
||||
if d.dn_detail:
|
||||
billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
|
||||
where dn_detail=%s and docstatus=1""", d.dn_detail)
|
||||
billed_amt = billed_amt and billed_amt[0][0] or 0
|
||||
frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified)
|
||||
updated_delivery_notes.append(d.delivery_note)
|
||||
elif d.so_detail:
|
||||
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
|
||||
|
||||
for dn in set(updated_delivery_notes):
|
||||
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
|
||||
|
@ -71,8 +71,7 @@ class ShippingRule(Document):
|
||||
if doc.currency != doc.company_currency:
|
||||
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
|
||||
|
||||
if shipping_amount:
|
||||
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
|
||||
self.add_shipping_rule_to_tax_table(doc, shipping_amount)
|
||||
|
||||
def get_shipping_amount_from_rules(self, value):
|
||||
for condition in self.get("conditions"):
|
||||
|
@ -151,7 +151,7 @@ def set_contact_details(party_details, party, party_type):
|
||||
|
||||
def set_other_values(party_details, party, party_type):
|
||||
# copy
|
||||
if party_type=="Customer":
|
||||
if party_type == "Customer":
|
||||
to_copy = ["customer_name", "customer_group", "territory", "language"]
|
||||
else:
|
||||
to_copy = ["supplier_name", "supplier_group", "language"]
|
||||
@ -170,12 +170,8 @@ def get_default_price_list(party):
|
||||
return party.default_price_list
|
||||
|
||||
if party.doctype == "Customer":
|
||||
price_list = frappe.get_cached_value("Customer Group",
|
||||
party.customer_group, "default_price_list")
|
||||
if price_list:
|
||||
return price_list
|
||||
return frappe.db.get_value("Customer Group", party.customer_group, "default_price_list")
|
||||
|
||||
return None
|
||||
|
||||
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
|
||||
# price list
|
||||
|
16
erpnext/accounts/test_party.py
Normal file
16
erpnext/accounts/test_party.py
Normal file
@ -0,0 +1,16 @@
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.accounts.party import get_default_price_list
|
||||
|
||||
|
||||
class PartyTestCase(FrappeTestCase):
|
||||
def test_get_default_price_list_should_return_none_for_invalid_group(self):
|
||||
customer = frappe.get_doc({
|
||||
'doctype': 'Customer',
|
||||
'customer_name': 'test customer',
|
||||
}).insert(ignore_permissions=True, ignore_mandatory=True)
|
||||
customer.customer_group = None
|
||||
customer.save()
|
||||
price_list = get_default_price_list(customer)
|
||||
assert price_list is None
|
@ -1,5 +1,6 @@
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'non_standard_fieldnames': {
|
||||
|
@ -23,7 +23,7 @@ def post_depreciation_entries(date=None):
|
||||
frappe.db.commit()
|
||||
|
||||
def get_depreciable_assets(date):
|
||||
return frappe.db.sql_list("""select a.name
|
||||
return frappe.db.sql_list("""select distinct a.name
|
||||
from tabAsset a, `tabDepreciation Schedule` ds
|
||||
where a.name = ds.parent and a.docstatus=1 and ds.schedule_date<=%s and a.calculate_depreciation = 1
|
||||
and a.status in ('Submitted', 'Partially Depreciated')
|
||||
|
@ -17,8 +17,8 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
|
||||
def setUp(self) -> None:
|
||||
create_item("Test MR Report Item")
|
||||
self.setup_material_request() # to order and receive
|
||||
self.setup_material_request(order=True) # to receive (ordered)
|
||||
self.setup_material_request(order=True, receive=True) # complete (ordered & received)
|
||||
self.setup_material_request(order=True, days=1) # to receive (ordered)
|
||||
self.setup_material_request(order=True, receive=True, days=2) # complete (ordered & received)
|
||||
|
||||
self.filters = frappe._dict(
|
||||
company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
|
||||
@ -32,9 +32,8 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
|
||||
data = get_data(self.filters)
|
||||
self.assertEqual(len(data), 2) # MRs today should be fetched
|
||||
|
||||
self.filters.from_date = add_days(today(), 1)
|
||||
data = get_data(self.filters)
|
||||
self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is tomorrow
|
||||
data = get_data(self.filters.update({"from_date": add_days(today(), 10)}))
|
||||
self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is in future
|
||||
|
||||
def test_ordered_received_material_requests(self):
|
||||
data = get_data(self.filters)
|
||||
@ -44,19 +43,19 @@ class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
|
||||
self.assertEqual(data[0].ordered_qty, 0.0)
|
||||
self.assertEqual(data[1].ordered_qty, 57.0)
|
||||
|
||||
def setup_material_request(self, order=False, receive=False):
|
||||
def setup_material_request(self, order=False, receive=False, days=0):
|
||||
po = None
|
||||
test_records = frappe.get_test_records('Material Request')
|
||||
|
||||
mr = frappe.copy_doc(test_records[0])
|
||||
mr.transaction_date = today()
|
||||
mr.schedule_date = add_days(today(), 1)
|
||||
mr.transaction_date = add_days(today(), days)
|
||||
mr.schedule_date = add_days(mr.transaction_date, 1)
|
||||
for row in mr.items:
|
||||
row.item_code = "Test MR Report Item"
|
||||
row.item_name = "Test MR Report Item"
|
||||
row.description = "Test MR Report Item"
|
||||
row.uom = "Nos"
|
||||
row.schedule_date = add_days(today(), 1)
|
||||
row.schedule_date = mr.schedule_date
|
||||
mr.submit()
|
||||
|
||||
if order or receive:
|
||||
|
@ -37,6 +37,8 @@ class calculate_taxes_and_totals(object):
|
||||
self.set_discount_amount()
|
||||
self.apply_discount_amount()
|
||||
|
||||
self.calculate_shipping_charges()
|
||||
|
||||
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
self.calculate_total_advance()
|
||||
|
||||
@ -50,7 +52,6 @@ class calculate_taxes_and_totals(object):
|
||||
self.initialize_taxes()
|
||||
self.determine_exclusive_rate()
|
||||
self.calculate_net_total()
|
||||
self.calculate_shipping_charges()
|
||||
self.calculate_taxes()
|
||||
self.manipulate_grand_total_for_inclusive_tax()
|
||||
self.calculate_totals()
|
||||
@ -276,6 +277,8 @@ class calculate_taxes_and_totals(object):
|
||||
shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
|
||||
shipping_rule.apply(self.doc)
|
||||
|
||||
self._calculate()
|
||||
|
||||
def calculate_taxes(self):
|
||||
rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
|
||||
if not rounding_adjustment_computed:
|
||||
|
@ -214,6 +214,7 @@ class Lead(SellingController):
|
||||
})
|
||||
|
||||
contact.insert(ignore_permissions=True)
|
||||
contact.reload() # load changes by hooks on contact
|
||||
|
||||
return contact
|
||||
|
||||
|
@ -48,7 +48,6 @@ def get_product_filter_data(query_args=None):
|
||||
|
||||
sub_categories = []
|
||||
if item_group:
|
||||
field_filters['item_group'] = item_group
|
||||
sub_categories = get_child_groups_for_website(item_group, immediate=True)
|
||||
|
||||
engine = ProductQuery()
|
||||
|
@ -14,6 +14,8 @@ class ProductFiltersBuilder:
|
||||
self.item_group = item_group
|
||||
|
||||
def get_field_filters(self):
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
|
||||
|
||||
if not self.item_group and not self.doc.enable_field_filters:
|
||||
return
|
||||
|
||||
@ -25,18 +27,26 @@ class ProductFiltersBuilder:
|
||||
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
|
||||
|
||||
for df in fields:
|
||||
item_filters, item_or_filters = {}, []
|
||||
item_filters, item_or_filters = {"published_in_website": 1}, []
|
||||
link_doctype_values = self.get_filtered_link_doctype_records(df)
|
||||
|
||||
if df.fieldtype == "Link":
|
||||
if self.item_group:
|
||||
item_or_filters.extend([
|
||||
["item_group", "=", self.item_group],
|
||||
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
|
||||
])
|
||||
include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants")
|
||||
if include_child:
|
||||
include_groups = get_child_groups_for_website(self.item_group, include_self=True)
|
||||
include_groups = [x.name for x in include_groups]
|
||||
item_or_filters.extend([
|
||||
["item_group", "in", include_groups],
|
||||
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
|
||||
])
|
||||
else:
|
||||
item_or_filters.extend([
|
||||
["item_group", "=", self.item_group],
|
||||
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
|
||||
])
|
||||
|
||||
# Get link field values attached to published items
|
||||
item_filters['published_in_website'] = 1
|
||||
item_values = frappe.get_all(
|
||||
"Item",
|
||||
fields=[df.fieldname],
|
||||
|
@ -46,10 +46,10 @@ class ProductQuery:
|
||||
self.filter_with_discount = bool(fields.get("discount"))
|
||||
result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
|
||||
|
||||
website_item_groups = self.get_website_item_group_results(item_group, website_item_groups)
|
||||
|
||||
if fields:
|
||||
self.build_fields_filters(fields)
|
||||
if item_group:
|
||||
self.build_item_group_filters(item_group)
|
||||
if search_term:
|
||||
self.build_search_filters(search_term)
|
||||
if self.settings.hide_variants:
|
||||
@ -61,8 +61,6 @@ class ProductQuery:
|
||||
else:
|
||||
result, count = self.query_items(start=start)
|
||||
|
||||
result = self.combine_web_item_group_results(item_group, result, website_item_groups)
|
||||
|
||||
# sort combined results by ranking
|
||||
result = sorted(result, key=lambda x: x.get("ranking"), reverse=True)
|
||||
|
||||
@ -167,6 +165,25 @@ class ProductQuery:
|
||||
# `=` will be faster than `IN` for most cases
|
||||
self.filters.append([field, "=", values])
|
||||
|
||||
def build_item_group_filters(self, item_group):
|
||||
"Add filters for Item group page and include Website Item Groups."
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
|
||||
item_group_filters = []
|
||||
|
||||
item_group_filters.append(["Website Item", "item_group", "=", item_group])
|
||||
# Consider Website Item Groups
|
||||
item_group_filters.append(["Website Item Group", "item_group", "=", item_group])
|
||||
|
||||
if frappe.db.get_value("Item Group", item_group, "include_descendants"):
|
||||
# include child item group's items as well
|
||||
# eg. Group Node A, will show items of child 1 and child 2 as well
|
||||
# on it's web page
|
||||
include_groups = get_child_groups_for_website(item_group, include_self=True)
|
||||
include_groups = [x.name for x in include_groups]
|
||||
item_group_filters.append(["Website Item", "item_group", "in", include_groups])
|
||||
|
||||
self.or_filters.extend(item_group_filters)
|
||||
|
||||
def build_search_filters(self, search_term):
|
||||
"""Query search term in specified fields
|
||||
|
||||
@ -190,19 +207,6 @@ class ProductQuery:
|
||||
for field in search_fields:
|
||||
self.or_filters.append([field, "like", search])
|
||||
|
||||
def get_website_item_group_results(self, item_group, website_item_groups):
|
||||
"""Get Web Items for Item Group Page via Website Item Groups."""
|
||||
if item_group:
|
||||
website_item_groups = frappe.db.get_all(
|
||||
"Website Item",
|
||||
fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
|
||||
filters=[
|
||||
["Website Item Group", "item_group", "=", item_group],
|
||||
["published", "=", 1]
|
||||
]
|
||||
)
|
||||
return website_item_groups
|
||||
|
||||
def add_display_details(self, result, discount_list, cart_items):
|
||||
"""Add price and availability details in result."""
|
||||
for item in result:
|
||||
@ -278,16 +282,6 @@ class ProductQuery:
|
||||
|
||||
return []
|
||||
|
||||
def combine_web_item_group_results(self, item_group, result, website_item_groups):
|
||||
"""Combine results with context of website item groups into item results."""
|
||||
if item_group and website_item_groups:
|
||||
items_list = {row.name for row in result}
|
||||
for row in website_item_groups:
|
||||
if row.wig_parent not in items_list:
|
||||
result.append(row)
|
||||
|
||||
return result
|
||||
|
||||
def filter_results_by_discount(self, fields, result):
|
||||
if fields and fields.get("discount"):
|
||||
discount_percent = frappe.utils.flt(fields["discount"][0])
|
||||
|
@ -13,8 +13,7 @@ test_dependencies = ["Item", "Item Group"]
|
||||
class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
"Test Products & Sub-Category Querying for Product Listing on Item Group Page."
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
def setUp(self):
|
||||
item_codes = [
|
||||
("Test Mobile A", "_Test Item Group B"),
|
||||
("Test Mobile B", "_Test Item Group B"),
|
||||
@ -28,8 +27,10 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
if not frappe.db.exists("Website Item", {"item_code": item_code}):
|
||||
create_regular_web_item(item_code, item_args=item_args)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_product_listing_in_item_group(self):
|
||||
@ -87,7 +88,6 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
|
||||
def test_item_group_with_sub_groups(self):
|
||||
"Test Valid Sub Item Groups in Item Group Page."
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
@ -114,4 +114,45 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
|
||||
|
||||
# check if child group is fetched if shown in website
|
||||
self.assertIn("_Test Item Group B - 1", child_groups)
|
||||
self.assertIn("_Test Item Group B - 2", child_groups)
|
||||
self.assertIn("_Test Item Group B - 2", child_groups)
|
||||
|
||||
def test_item_group_page_with_descendants_included(self):
|
||||
"""
|
||||
Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3).
|
||||
> _Test Item Group B [Level 1]
|
||||
> _Test Item Group B - 1 [Level 2]
|
||||
> _Test Item Group B - 1 - 1 [Level 3]
|
||||
"""
|
||||
frappe.get_doc({ # create Level 3 nested child group
|
||||
"doctype": "Item Group",
|
||||
"is_group": 1,
|
||||
"item_group_name": "_Test Item Group B - 1 - 1",
|
||||
"parent_item_group": "_Test Item Group B - 1"
|
||||
}).insert()
|
||||
|
||||
create_regular_web_item( # create an item belonging to level 3 item group
|
||||
"Test Mobile F",
|
||||
item_args={"item_group": "_Test Item Group B - 1 - 1"}
|
||||
)
|
||||
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1)
|
||||
|
||||
# enable 'include descendants' in Level 1
|
||||
frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1)
|
||||
|
||||
result = get_product_filter_data(query_args={
|
||||
"field_filters": {},
|
||||
"attribute_filters": {},
|
||||
"start": 0,
|
||||
"item_group": "_Test Item Group B"
|
||||
})
|
||||
|
||||
items = result.get("items")
|
||||
item_codes = [item.get("item_code") for item in items]
|
||||
|
||||
# check if all sub groups' items are pulled
|
||||
self.assertEqual(len(items), 6)
|
||||
self.assertIn("Test Mobile A", item_codes)
|
||||
self.assertIn("Test Mobile C", item_codes)
|
||||
self.assertIn("Test Mobile E", item_codes)
|
||||
self.assertIn("Test Mobile F", item_codes)
|
@ -120,7 +120,11 @@ def place_order():
|
||||
def request_for_quotation():
|
||||
quotation = _get_cart_quotation()
|
||||
quotation.flags.ignore_permissions = True
|
||||
quotation.submit()
|
||||
|
||||
if get_shopping_cart_settings().save_quotations_as_draft:
|
||||
quotation.save()
|
||||
else:
|
||||
quotation.submit()
|
||||
return quotation.name
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -6,7 +6,7 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_months, nowdate
|
||||
from frappe.utils import add_months, cint, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
|
||||
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
|
||||
@ -14,22 +14,17 @@ from erpnext.e_commerce.shopping_cart.cart import (
|
||||
_get_cart_quotation,
|
||||
get_cart_quotation,
|
||||
get_party,
|
||||
request_for_quotation,
|
||||
update_cart,
|
||||
)
|
||||
from erpnext.tests.utils import create_test_contact_and_address
|
||||
|
||||
# test_dependencies = ['Payment Terms Template']
|
||||
|
||||
class TestShoppingCart(unittest.TestCase):
|
||||
"""
|
||||
Note:
|
||||
Shopping Cart == Quotation
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
create_test_contact_and_address()
|
||||
@ -45,6 +40,10 @@ class TestShoppingCart(unittest.TestCase):
|
||||
frappe.set_user("Administrator")
|
||||
self.disable_shopping_cart()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def test_get_cart_new_user(self):
|
||||
self.login_as_new_user()
|
||||
|
||||
@ -179,6 +178,28 @@ class TestShoppingCart(unittest.TestCase):
|
||||
# test if items are rendered without error
|
||||
frappe.render_template("templates/includes/cart/cart_items.html", cart)
|
||||
|
||||
@change_settings("E Commerce Settings",{
|
||||
"save_quotations_as_draft": 1
|
||||
})
|
||||
def test_cart_without_checkout_and_draft_quotation(self):
|
||||
"Test impact of 'save_quotations_as_draft' checkbox."
|
||||
frappe.local.shopping_cart_settings = None
|
||||
|
||||
# add item to cart
|
||||
update_cart("_Test Item", 1)
|
||||
quote_name = request_for_quotation() # Request for Quote
|
||||
quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
|
||||
|
||||
self.assertEqual(quote_doctstatus, 0)
|
||||
|
||||
frappe.db.set_value("E Commerce Settings", None, "save_quotations_as_draft", 0)
|
||||
frappe.local.shopping_cart_settings = None
|
||||
update_cart("_Test Item", 1)
|
||||
quote_name = request_for_quotation() # Request for Quote
|
||||
quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
|
||||
|
||||
self.assertEqual(quote_doctstatus, 1)
|
||||
|
||||
def create_tax_rule(self):
|
||||
tax_rule = frappe.get_test_records("Tax Rule")[0]
|
||||
try:
|
||||
|
@ -25,7 +25,9 @@ class TestAttendance(FrappeTestCase):
|
||||
self.assertEqual(attendance, fetch_attendance)
|
||||
|
||||
def test_unmarked_days(self):
|
||||
first_day = get_first_day(getdate())
|
||||
now = now_datetime()
|
||||
previous_month = now.month - 1
|
||||
first_day = now.replace(day=1).replace(month=previous_month).date()
|
||||
|
||||
employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
|
||||
frappe.db.delete('Attendance', {'employee': employee})
|
||||
@ -34,7 +36,7 @@ class TestAttendance(FrappeTestCase):
|
||||
holiday_list = make_holiday_list()
|
||||
frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
|
||||
|
||||
first_sunday = get_first_sunday(holiday_list)
|
||||
first_sunday = get_first_sunday(holiday_list, for_date=first_day)
|
||||
mark_attendance(employee, first_day, 'Present')
|
||||
month_name = get_month_name(first_day)
|
||||
|
||||
@ -49,7 +51,9 @@ class TestAttendance(FrappeTestCase):
|
||||
self.assertIn(first_sunday, unmarked_days)
|
||||
|
||||
def test_unmarked_days_excluding_holidays(self):
|
||||
first_day = get_first_day(getdate())
|
||||
now = now_datetime()
|
||||
previous_month = now.month - 1
|
||||
first_day = now.replace(day=1).replace(month=previous_month).date()
|
||||
|
||||
employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
|
||||
frappe.db.delete('Attendance', {'employee': employee})
|
||||
@ -58,7 +62,7 @@ class TestAttendance(FrappeTestCase):
|
||||
holiday_list = make_holiday_list()
|
||||
frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
|
||||
|
||||
first_sunday = get_first_sunday(holiday_list)
|
||||
first_sunday = get_first_sunday(holiday_list, for_date=first_day)
|
||||
mark_attendance(employee, first_day, 'Present')
|
||||
month_name = get_month_name(first_day)
|
||||
|
||||
@ -83,6 +87,10 @@ class TestAttendance(FrappeTestCase):
|
||||
relieving_date=relieving_date)
|
||||
frappe.db.delete('Attendance', {'employee': employee})
|
||||
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
|
||||
holiday_list = make_holiday_list()
|
||||
frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
|
||||
|
||||
attendance_date = add_days(first_day, 2)
|
||||
mark_attendance(employee, attendance_date, 'Present')
|
||||
month_name = get_month_name(first_day)
|
||||
|
@ -495,7 +495,6 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day
|
||||
number_of_days = date_diff(to_date, from_date) + .5
|
||||
else:
|
||||
number_of_days = date_diff(to_date, from_date) + 1
|
||||
|
||||
else:
|
||||
number_of_days = date_diff(to_date, from_date) + 1
|
||||
|
||||
|
@ -133,7 +133,9 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
holiday_list = make_holiday_list()
|
||||
employee = get_employee()
|
||||
frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
|
||||
original_holiday_list = employee.holiday_list
|
||||
frappe.db.set_value("Employee", employee.name, "holiday_list", holiday_list)
|
||||
|
||||
first_sunday = get_first_sunday(holiday_list)
|
||||
|
||||
leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
|
||||
@ -143,6 +145,8 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
leave_application.cancel()
|
||||
|
||||
frappe.db.set_value("Employee", employee.name, "holiday_list", original_holiday_list)
|
||||
|
||||
def test_attendance_update_for_exclude_holidays(self):
|
||||
# Case 2: leave type with 'Include holidays within leaves as leaves' disabled
|
||||
frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
|
||||
@ -157,7 +161,8 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
|
||||
holiday_list = make_holiday_list()
|
||||
employee = get_employee()
|
||||
frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
|
||||
original_holiday_list = employee.holiday_list
|
||||
frappe.db.set_value("Employee", employee.name, "holiday_list", holiday_list)
|
||||
first_sunday = get_first_sunday(holiday_list)
|
||||
|
||||
# already marked attendance on a holiday should be deleted in this case
|
||||
@ -177,7 +182,7 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
attendance.flags.ignore_validate = True
|
||||
attendance.save()
|
||||
|
||||
leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
|
||||
leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name, employee.company)
|
||||
leave_application.reload()
|
||||
# holiday should be excluded while marking attendance
|
||||
self.assertEqual(leave_application.total_leave_days, 3)
|
||||
@ -189,6 +194,8 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
# attendance on non-holiday updated
|
||||
self.assertEqual(frappe.db.get_value("Attendance", attendance.name, "status"), "On Leave")
|
||||
|
||||
frappe.db.set_value("Employee", employee.name, "holiday_list", original_holiday_list)
|
||||
|
||||
def test_block_list(self):
|
||||
self._clear_roles()
|
||||
|
||||
@ -327,7 +334,8 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
employee = get_employee()
|
||||
|
||||
default_holiday_list = make_holiday_list()
|
||||
frappe.db.set_value("Company", employee.company, "default_holiday_list", default_holiday_list)
|
||||
original_holiday_list = employee.holiday_list
|
||||
frappe.db.set_value("Employee", employee.name, "holiday_list", default_holiday_list)
|
||||
first_sunday = get_first_sunday(default_holiday_list)
|
||||
|
||||
optional_leave_date = add_days(first_sunday, 1)
|
||||
@ -378,6 +386,8 @@ class TestLeaveApplication(unittest.TestCase):
|
||||
# check leave balance is reduced
|
||||
self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9)
|
||||
|
||||
frappe.db.set_value("Employee", employee.name, "holiday_list", original_holiday_list)
|
||||
|
||||
def test_leaves_allowed(self):
|
||||
employee = get_employee()
|
||||
leave_period = get_leave_period()
|
||||
@ -782,9 +792,10 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
|
||||
allocate_leave.submit()
|
||||
|
||||
|
||||
def get_first_sunday(holiday_list):
|
||||
month_start_date = get_first_day(nowdate())
|
||||
month_end_date = get_last_day(nowdate())
|
||||
def get_first_sunday(holiday_list, for_date=None):
|
||||
date = for_date or getdate()
|
||||
month_start_date = get_first_day(date)
|
||||
month_end_date = get_last_day(date)
|
||||
first_sunday = frappe.db.sql("""
|
||||
select holiday_date from `tabHoliday`
|
||||
where parent = %s
|
||||
|
@ -0,0 +1,44 @@
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import now_datetime
|
||||
|
||||
from erpnext.hr.doctype.attendance.attendance import mark_attendance
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.report.monthly_attendance_sheet.monthly_attendance_sheet import execute
|
||||
|
||||
|
||||
class TestMonthlyAttendanceSheet(FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.employee = make_employee("test_employee@example.com")
|
||||
frappe.db.delete('Attendance', {'employee': self.employee})
|
||||
|
||||
def test_monthly_attendance_sheet_report(self):
|
||||
now = now_datetime()
|
||||
previous_month = now.month - 1
|
||||
previous_month_first = now.replace(day=1).replace(month=previous_month).date()
|
||||
|
||||
company = frappe.db.get_value('Employee', self.employee, 'company')
|
||||
|
||||
# mark different attendance status on first 3 days of previous month
|
||||
mark_attendance(self.employee, previous_month_first, 'Absent')
|
||||
mark_attendance(self.employee, previous_month_first + relativedelta(days=1), 'Present')
|
||||
mark_attendance(self.employee, previous_month_first + relativedelta(days=2), 'On Leave')
|
||||
|
||||
filters = frappe._dict({
|
||||
'month': previous_month,
|
||||
'year': now.year,
|
||||
'company': company,
|
||||
})
|
||||
report = execute(filters=filters)
|
||||
employees = report[1][0]
|
||||
datasets = report[3]['data']['datasets']
|
||||
absent = datasets[0]['values']
|
||||
present = datasets[1]['values']
|
||||
leaves = datasets[2]['values']
|
||||
|
||||
# ensure correct attendance is reflect on the report
|
||||
self.assertIn(self.employee, employees)
|
||||
self.assertEqual(absent[0], 1)
|
||||
self.assertEqual(present[1], 1)
|
||||
self.assertEqual(leaves[2], 1)
|
@ -2,6 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import functools
|
||||
import re
|
||||
from collections import deque
|
||||
from operator import itemgetter
|
||||
from typing import List
|
||||
@ -103,25 +104,33 @@ class BOM(WebsiteGenerator):
|
||||
)
|
||||
|
||||
def autoname(self):
|
||||
names = frappe.db.sql_list("""select name from `tabBOM` where item=%s""", self.item)
|
||||
# ignore amended documents while calculating current index
|
||||
existing_boms = frappe.get_all(
|
||||
"BOM",
|
||||
filters={"item": self.item, "amended_from": ["is", "not set"]},
|
||||
pluck="name"
|
||||
)
|
||||
|
||||
if names:
|
||||
# name can be BOM/ITEM/001, BOM/ITEM/001-1, BOM-ITEM-001, BOM-ITEM-001-1
|
||||
|
||||
# split by item
|
||||
names = [name.split(self.item, 1) for name in names]
|
||||
names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)]
|
||||
|
||||
# split by (-) if cancelled
|
||||
if names:
|
||||
names = [cint(name.split('-')[-1]) for name in names]
|
||||
idx = max(names) + 1
|
||||
else:
|
||||
idx = 1
|
||||
if existing_boms:
|
||||
index = self.get_next_version_index(existing_boms)
|
||||
else:
|
||||
idx = 1
|
||||
index = 1
|
||||
|
||||
prefix = self.doctype
|
||||
suffix = "%.3i" % index # convert index to string (1 -> "001")
|
||||
bom_name = f"{prefix}-{self.item}-{suffix}"
|
||||
|
||||
if len(bom_name) <= 140:
|
||||
name = bom_name
|
||||
else:
|
||||
# since max characters for name is 140, remove enough characters from the
|
||||
# item name to fit the prefix, suffix and the separators
|
||||
truncated_length = 140 - (len(prefix) + len(suffix) + 2)
|
||||
truncated_item_name = self.item[:truncated_length]
|
||||
# if a partial word is found after truncate, remove the extra characters
|
||||
truncated_item_name = truncated_item_name.rsplit(" ", 1)[0]
|
||||
name = f"{prefix}-{truncated_item_name}-{suffix}"
|
||||
|
||||
name = 'BOM-' + self.item + ('-%.3i' % idx)
|
||||
if frappe.db.exists("BOM", name):
|
||||
conflicting_bom = frappe.get_doc("BOM", name)
|
||||
|
||||
@ -134,6 +143,26 @@ class BOM(WebsiteGenerator):
|
||||
|
||||
self.name = name
|
||||
|
||||
@staticmethod
|
||||
def get_next_version_index(existing_boms: List[str]) -> int:
|
||||
# split by "/" and "-"
|
||||
delimiters = ["/", "-"]
|
||||
pattern = "|".join(map(re.escape, delimiters))
|
||||
bom_parts = [re.split(pattern, bom_name) for bom_name in existing_boms]
|
||||
|
||||
# filter out BOMs that do not follow the following formats: BOM/ITEM/001, BOM-ITEM-001
|
||||
valid_bom_parts = list(filter(lambda x: len(x) > 1 and x[-1], bom_parts))
|
||||
|
||||
# extract the current index from the BOM parts
|
||||
if valid_bom_parts:
|
||||
# handle cancelled and submitted documents
|
||||
indexes = [cint(part[-1]) for part in valid_bom_parts]
|
||||
index = max(indexes) + 1
|
||||
else:
|
||||
index = 1
|
||||
|
||||
return index
|
||||
|
||||
def validate(self):
|
||||
self.route = frappe.scrub(self.name).replace('_', '-')
|
||||
|
||||
@ -192,12 +221,13 @@ class BOM(WebsiteGenerator):
|
||||
if self.routing:
|
||||
self.set("operations", [])
|
||||
fields = ["sequence_id", "operation", "workstation", "description",
|
||||
"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate"]
|
||||
"time_in_mins", "batch_size", "operating_cost", "idx", "hour_rate",
|
||||
"set_cost_based_on_bom_qty", "fixed_time"]
|
||||
|
||||
for row in frappe.get_all("BOM Operation", fields = fields,
|
||||
filters = {'parenttype': 'Routing', 'parent': self.routing}, order_by="sequence_id, idx"):
|
||||
child = self.append('operations', row)
|
||||
child.hour_rate = flt(row.hour_rate / self.conversion_rate, 2)
|
||||
child.hour_rate = flt(row.hour_rate / self.conversion_rate, child.precision("hour_rate"))
|
||||
|
||||
def set_bom_material_details(self):
|
||||
for item in self.get("items"):
|
||||
|
@ -432,6 +432,69 @@ class TestBOM(FrappeTestCase):
|
||||
self.assertEqual(bom.transfer_material_against, "Work Order")
|
||||
bom.delete()
|
||||
|
||||
def test_bom_name_length(self):
|
||||
""" test >140 char names"""
|
||||
bom_tree = {
|
||||
"x" * 140 : {
|
||||
" ".join(["abc"] * 35): {}
|
||||
}
|
||||
}
|
||||
create_nested_bom(bom_tree, prefix="")
|
||||
|
||||
def test_version_index(self):
|
||||
|
||||
bom = frappe.new_doc("BOM")
|
||||
|
||||
version_index_test_cases = [
|
||||
(1, []),
|
||||
(1, ["BOM#XYZ"]),
|
||||
(2, ["BOM/ITEM/001"]),
|
||||
(2, ["BOM-ITEM-001"]),
|
||||
(3, ["BOM-ITEM-001", "BOM-ITEM-002"]),
|
||||
(4, ["BOM-ITEM-001", "BOM-ITEM-002", "BOM-ITEM-003"]),
|
||||
]
|
||||
|
||||
for expected_index, existing_boms in version_index_test_cases:
|
||||
with self.subTest():
|
||||
self.assertEqual(expected_index, bom.get_next_version_index(existing_boms),
|
||||
msg=f"Incorrect index for {existing_boms}")
|
||||
|
||||
def test_bom_versioning(self):
|
||||
bom_tree = {
|
||||
frappe.generate_hash(length=10) : {
|
||||
frappe.generate_hash(length=10): {}
|
||||
}
|
||||
}
|
||||
bom = create_nested_bom(bom_tree, prefix="")
|
||||
self.assertEqual(int(bom.name.split("-")[-1]), 1)
|
||||
original_bom_name = bom.name
|
||||
|
||||
bom.cancel()
|
||||
bom.reload()
|
||||
self.assertEqual(bom.name, original_bom_name)
|
||||
|
||||
# create a new amendment
|
||||
amendment = frappe.copy_doc(bom)
|
||||
amendment.docstatus = 0
|
||||
amendment.amended_from = bom.name
|
||||
|
||||
amendment.save()
|
||||
amendment.submit()
|
||||
amendment.reload()
|
||||
|
||||
self.assertNotEqual(amendment.name, bom.name)
|
||||
# `origname-001-1` version
|
||||
self.assertEqual(int(amendment.name.split("-")[-1]), 1)
|
||||
self.assertEqual(int(amendment.name.split("-")[-2]), 1)
|
||||
|
||||
# create a new version
|
||||
version = frappe.copy_doc(amendment)
|
||||
version.docstatus = 0
|
||||
version.amended_from = None
|
||||
version.save()
|
||||
self.assertNotEqual(amendment.name, version.name)
|
||||
self.assertEqual(int(version.name.split("-")[-1]), 2)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
@ -66,7 +66,8 @@
|
||||
"label": "Hour Rate",
|
||||
"oldfieldname": "hour_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency"
|
||||
"options": "currency",
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"description": "In minutes",
|
||||
@ -186,7 +187,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-12-15 03:00:00.473173",
|
||||
"modified": "2022-03-10 06:19:08.462027",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM Operation",
|
||||
|
@ -5,10 +5,13 @@ def execute():
|
||||
frappe.reload_doc('accounts', 'doctype', 'bank', force=1)
|
||||
|
||||
if frappe.db.table_exists('Bank') and frappe.db.table_exists('Bank Account') and frappe.db.has_column('Bank Account', 'swift_number'):
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabBank` b, `tabBank Account` ba
|
||||
SET b.swift_number = ba.swift_number WHERE b.name = ba.bank
|
||||
""")
|
||||
try:
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabBank` b, `tabBank Account` ba
|
||||
SET b.swift_number = ba.swift_number WHERE b.name = ba.bank
|
||||
""")
|
||||
except Exception as e:
|
||||
frappe.log_error(e, title="Patch Migration Failed")
|
||||
|
||||
frappe.reload_doc('accounts', 'doctype', 'bank_account')
|
||||
frappe.reload_doc('accounts', 'doctype', 'payment_request')
|
||||
|
@ -1,14 +1,12 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = [], []
|
||||
data = get_data(filters)
|
||||
columns = get_columns()
|
||||
charts = get_chart_data(data)
|
||||
|
@ -39,6 +39,8 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this._calculate_taxes_and_totals();
|
||||
this.calculate_discount_amount();
|
||||
|
||||
this.calculate_shipping_charges();
|
||||
|
||||
// Advance calculation applicable to Sales /Purchase Invoice
|
||||
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
|
||||
@ -81,7 +83,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.initialize_taxes();
|
||||
this.determine_exclusive_rate();
|
||||
this.calculate_net_total();
|
||||
this.calculate_shipping_charges();
|
||||
this.calculate_taxes();
|
||||
this.manipulate_grand_total_for_inclusive_tax();
|
||||
this.calculate_totals();
|
||||
@ -275,6 +276,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
|
||||
if (frappe.meta.get_docfield(this.frm.doc.doctype, "shipping_rule", this.frm.doc.name)) {
|
||||
this.shipping_rule();
|
||||
this._calculate_taxes_and_totals();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,8 +343,7 @@ def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1):
|
||||
/* against number or, if empty, party against number */
|
||||
%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
|
||||
|
||||
/* disable automatic VAT deduction */
|
||||
'40' as 'BU-Schlüssel',
|
||||
'' as 'BU-Schlüssel',
|
||||
|
||||
gl.posting_date as 'Belegdatum',
|
||||
gl.voucher_no as 'Belegfeld 1',
|
||||
|
@ -30,7 +30,6 @@ def execute(filters=None):
|
||||
if region != 'United States':
|
||||
return [], []
|
||||
|
||||
data = []
|
||||
columns = get_columns()
|
||||
conditions = ""
|
||||
if filters.supplier_group:
|
||||
|
@ -118,8 +118,7 @@ def make_customer():
|
||||
"customer_type": "Company",
|
||||
})
|
||||
customer.insert()
|
||||
else:
|
||||
customer = frappe.get_doc("Customer", "_Test UAE Customer")
|
||||
|
||||
|
||||
def make_supplier():
|
||||
if not frappe.db.exists("Supplier", "_Test UAE Supplier"):
|
||||
|
@ -1477,6 +1477,28 @@ class TestSalesOrder(FrappeTestCase):
|
||||
self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
|
||||
self.assertEqual(mr.status, "Manufactured")
|
||||
|
||||
def test_sales_order_with_shipping_rule(self):
|
||||
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
|
||||
shipping_rule = create_shipping_rule(shipping_rule_type = "Selling", shipping_rule_name = "Shipping Rule - Sales Invoice Test")
|
||||
sales_order = make_sales_order(do_not_save=True)
|
||||
sales_order.shipping_rule = shipping_rule.name
|
||||
|
||||
sales_order.items[0].qty = 1
|
||||
sales_order.save()
|
||||
self.assertEqual(sales_order.taxes[0].tax_amount, 50)
|
||||
|
||||
sales_order.items[0].qty = 2
|
||||
sales_order.save()
|
||||
self.assertEqual(sales_order.taxes[0].tax_amount, 100)
|
||||
|
||||
sales_order.items[0].qty = 3
|
||||
sales_order.save()
|
||||
self.assertEqual(sales_order.taxes[0].tax_amount, 200)
|
||||
|
||||
sales_order.items[0].qty = 21
|
||||
sales_order.save()
|
||||
self.assertEqual(sales_order.taxes[0].tax_amount, 0)
|
||||
|
||||
def automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
accounts_settings.automatically_fetch_payment_terms = enable
|
||||
|
@ -20,12 +20,14 @@
|
||||
"sec_break_taxes",
|
||||
"taxes",
|
||||
"sb9",
|
||||
"show_in_website",
|
||||
"route",
|
||||
"weightage",
|
||||
"slideshow",
|
||||
"website_title",
|
||||
"description",
|
||||
"show_in_website",
|
||||
"include_descendants",
|
||||
"column_break_16",
|
||||
"weightage",
|
||||
"slideshow",
|
||||
"website_specifications",
|
||||
"website_filters_section",
|
||||
"filter_fields",
|
||||
@ -111,7 +113,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Check this if you want to show in website",
|
||||
"description": "Make Item Group visible in website",
|
||||
"fieldname": "show_in_website",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show in Website"
|
||||
@ -124,6 +126,7 @@
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "show_in_website",
|
||||
"fieldname": "weightage",
|
||||
"fieldtype": "Int",
|
||||
"label": "Weightage"
|
||||
@ -186,6 +189,8 @@
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "show_in_website",
|
||||
"fieldname": "website_filters_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Website Filters"
|
||||
@ -203,9 +208,22 @@
|
||||
"options": "Website Attribute"
|
||||
},
|
||||
{
|
||||
"depends_on": "show_in_website",
|
||||
"fieldname": "website_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "show_in_website",
|
||||
"description": "Include Website Items belonging to child Item Groups",
|
||||
"fieldname": "include_descendants",
|
||||
"fieldtype": "Check",
|
||||
"label": "Include Descendants"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
@ -214,11 +232,12 @@
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"max_attachments": 3,
|
||||
"modified": "2021-02-18 13:40:30.049650",
|
||||
"modified": "2022-03-09 12:27:11.055782",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Item Group",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By fieldname",
|
||||
"nsm_parent_field": "parent_item_group",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
@ -285,5 +304,6 @@
|
||||
"search_fields": "parent_item_group",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
@ -111,7 +111,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
|
||||
from erpnext.stock.doctype.item.item import validate_item_default_company_links
|
||||
validate_item_default_company_links(self.item_group_defaults)
|
||||
|
||||
def get_child_groups_for_website(item_group_name, immediate=False):
|
||||
def get_child_groups_for_website(item_group_name, immediate=False, include_self=False):
|
||||
"""Returns child item groups *excluding* passed group."""
|
||||
item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
|
||||
filters = {
|
||||
@ -123,6 +123,12 @@ def get_child_groups_for_website(item_group_name, immediate=False):
|
||||
if immediate:
|
||||
filters["parent_item_group"] = item_group_name
|
||||
|
||||
if include_self:
|
||||
filters.update({
|
||||
"lft": [">=", item_group.lft],
|
||||
"rgt": ["<=", item_group.rgt]
|
||||
})
|
||||
|
||||
return frappe.get_all(
|
||||
"Item Group",
|
||||
filters=filters,
|
||||
|
@ -342,25 +342,21 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
# Billed against Sales Order directly
|
||||
si = frappe.qb.DocType("Sales Invoice").as_("si")
|
||||
si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
|
||||
sum_amount = Sum(si_item.amount).as_("amount")
|
||||
|
||||
billed_against_so = frappe.qb.from_(si).from_(si_item).select(sum_amount).where(
|
||||
(si_item.parent == si.name) &
|
||||
billed_against_so = frappe.qb.from_(si_item).select(sum_amount).where(
|
||||
(si_item.so_detail == so_detail) &
|
||||
((si_item.dn_detail.isnull()) | (si_item.dn_detail == '')) &
|
||||
(si_item.docstatus == 1) &
|
||||
(si.update_stock == 0)
|
||||
(si_item.docstatus == 1)
|
||||
).run()
|
||||
billed_against_so = billed_against_so and billed_against_so[0][0] or 0
|
||||
|
||||
# Get all Delivery Note Item rows against the Sales Order Item row
|
||||
|
||||
dn = frappe.qb.DocType("Delivery Note").as_("dn")
|
||||
dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item")
|
||||
|
||||
dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent, dn_item.stock_qty, dn_item.returned_qty).where(
|
||||
dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent).where(
|
||||
(dn.name == dn_item.parent) &
|
||||
(dn_item.so_detail == so_detail) &
|
||||
(dn.docstatus == 1) &
|
||||
@ -385,11 +381,7 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
|
||||
|
||||
# Distribute billed amount directly against SO between DNs based on FIFO
|
||||
if billed_against_so and billed_amt_agianst_dn < dnd.amount:
|
||||
if dnd.returned_qty:
|
||||
pending_to_bill = flt(dnd.amount) * (dnd.stock_qty - dnd.returned_qty) / dnd.stock_qty
|
||||
else:
|
||||
pending_to_bill = flt(dnd.amount)
|
||||
pending_to_bill -= billed_amt_agianst_dn
|
||||
pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
|
||||
if pending_to_bill <= billed_against_so:
|
||||
billed_amt_agianst_dn += pending_to_bill
|
||||
billed_against_so -= pending_to_bill
|
||||
|
@ -165,21 +165,21 @@ frappe.ui.form.on("Item", {
|
||||
frm.set_value('has_batch_no', 0);
|
||||
frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
|
||||
|
||||
frm.call({
|
||||
method: "set_asset_naming_series",
|
||||
doc: frm.doc,
|
||||
callback: function() {
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.item.item.get_asset_naming_series",
|
||||
callback: function(r) {
|
||||
frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1);
|
||||
frm.trigger("set_asset_naming_series");
|
||||
frm.events.set_asset_naming_series(frm, r.message);
|
||||
}
|
||||
});
|
||||
|
||||
frm.trigger('auto_create_assets');
|
||||
},
|
||||
|
||||
set_asset_naming_series: function(frm) {
|
||||
if (frm.doc.__onload && frm.doc.__onload.asset_naming_series) {
|
||||
frm.set_df_property("asset_naming_series", "options", frm.doc.__onload.asset_naming_series);
|
||||
set_asset_naming_series: function(frm, asset_naming_series) {
|
||||
if ((frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series) {
|
||||
let naming_series = (frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series;
|
||||
frm.set_df_property("asset_naming_series", "options", naming_series);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -50,15 +50,7 @@ class DataValidationError(frappe.ValidationError):
|
||||
class Item(Document):
|
||||
def onload(self):
|
||||
self.set_onload('stock_exists', self.stock_ledger_created())
|
||||
self.set_asset_naming_series()
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_asset_naming_series(self):
|
||||
if not hasattr(self, '_asset_naming_series'):
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
|
||||
self._asset_naming_series = get_asset_naming_series()
|
||||
|
||||
self.set_onload('asset_naming_series', self._asset_naming_series)
|
||||
self.set_onload('asset_naming_series', get_asset_naming_series())
|
||||
|
||||
def autoname(self):
|
||||
if frappe.db.get_default("item_naming_by") == "Naming Series":
|
||||
@ -400,6 +392,7 @@ class Item(Document):
|
||||
self.validate_properties_before_merge(new_name)
|
||||
self.validate_duplicate_product_bundles_before_merge(old_name, new_name)
|
||||
self.validate_duplicate_website_item_before_merge(old_name, new_name)
|
||||
self.delete_old_bins(old_name)
|
||||
|
||||
def after_rename(self, old_name, new_name, merge):
|
||||
if merge:
|
||||
@ -428,6 +421,9 @@ class Item(Document):
|
||||
frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
|
||||
json.dumps(item_wise_tax_detail), update_modified=False)
|
||||
|
||||
def delete_old_bins(self, old_name):
|
||||
frappe.db.delete("Bin", {"item_code": old_name})
|
||||
|
||||
def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
|
||||
records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
|
||||
FROM `tabStock Reconciliation Item`
|
||||
@ -508,11 +504,11 @@ class Item(Document):
|
||||
existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
|
||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
|
||||
|
||||
repost_stock_for_warehouses = frappe.db.sql_list("""select distinct warehouse
|
||||
from tabBin where item_code=%s""", new_name)
|
||||
repost_stock_for_warehouses = frappe.get_all("Stock Ledger Entry",
|
||||
"warehouse", filters={"item_code": new_name}, pluck="warehouse", distinct=True)
|
||||
|
||||
# Delete all existing bins to avoid duplicate bins for the same item and warehouse
|
||||
frappe.db.sql("delete from `tabBin` where item_code=%s", new_name)
|
||||
frappe.db.delete("Bin", {"item_code": new_name})
|
||||
|
||||
for warehouse in repost_stock_for_warehouses:
|
||||
repost_stock(new_name, warehouse)
|
||||
@ -999,7 +995,7 @@ def get_uom_conv_factor(uom, stock_uom):
|
||||
if uom == stock_uom:
|
||||
return 1.0
|
||||
|
||||
from_uom, to_uom = uom, stock_uom # renaming for readability
|
||||
from_uom, to_uom = uom, stock_uom # renaming for readability
|
||||
|
||||
exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1)
|
||||
if exact_match:
|
||||
@ -1011,9 +1007,9 @@ def get_uom_conv_factor(uom, stock_uom):
|
||||
|
||||
# This attempts to try and get conversion from intermediate UOM.
|
||||
# case:
|
||||
# g -> mg = 1000
|
||||
# g -> kg = 0.001
|
||||
# therefore kg -> mg = 1000 / 0.001 = 1,000,000
|
||||
# g -> mg = 1000
|
||||
# g -> kg = 0.001
|
||||
# therefore kg -> mg = 1000 / 0.001 = 1,000,000
|
||||
intermediate_match = frappe.db.sql("""
|
||||
select (first.value / second.value) as value
|
||||
from `tabUOM Conversion Factor` first
|
||||
@ -1072,3 +1068,11 @@ def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> Non
|
||||
frappe.bold(item_default.company),
|
||||
frappe.bold(frappe.unscrub(field))
|
||||
), title=_("Invalid Item Defaults"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_naming_series():
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
|
||||
|
||||
return get_asset_naming_series()
|
||||
|
||||
|
@ -371,23 +371,24 @@ class TestItem(FrappeTestCase):
|
||||
variant.save()
|
||||
|
||||
def test_item_merging(self):
|
||||
create_item("Test Item for Merging 1")
|
||||
create_item("Test Item for Merging 2")
|
||||
old = create_item(frappe.generate_hash(length=20)).name
|
||||
new = create_item(frappe.generate_hash(length=20)).name
|
||||
|
||||
make_stock_entry(item_code="Test Item for Merging 1", target="_Test Warehouse - _TC",
|
||||
make_stock_entry(item_code=old, target="_Test Warehouse - _TC",
|
||||
qty=1, rate=100)
|
||||
make_stock_entry(item_code="Test Item for Merging 2", target="_Test Warehouse 1 - _TC",
|
||||
make_stock_entry(item_code=old, target="_Test Warehouse 1 - _TC",
|
||||
qty=1, rate=100)
|
||||
make_stock_entry(item_code=new, target="_Test Warehouse 1 - _TC",
|
||||
qty=1, rate=100)
|
||||
|
||||
frappe.rename_doc("Item", "Test Item for Merging 1", "Test Item for Merging 2", merge=True)
|
||||
frappe.rename_doc("Item", old, new, merge=True)
|
||||
|
||||
self.assertFalse(frappe.db.exists("Item", "Test Item for Merging 1"))
|
||||
self.assertFalse(frappe.db.exists("Item", old))
|
||||
|
||||
self.assertTrue(frappe.db.get_value("Bin",
|
||||
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse - _TC"}))
|
||||
|
||||
{"item_code": new, "warehouse": "_Test Warehouse - _TC"}))
|
||||
self.assertTrue(frappe.db.get_value("Bin",
|
||||
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
|
||||
{"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"}))
|
||||
|
||||
def test_item_merging_with_product_bundle(self):
|
||||
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||
|
@ -214,6 +214,7 @@ frappe.ui.form.on('Material Request', {
|
||||
material_request_type: frm.doc.material_request_type,
|
||||
plc_conversion_rate: 1,
|
||||
rate: item.rate,
|
||||
uom: item.uom,
|
||||
conversion_factor: item.conversion_factor
|
||||
},
|
||||
overwrite_warehouse: overwrite_warehouse
|
||||
@ -392,6 +393,7 @@ frappe.ui.form.on("Material Request Item", {
|
||||
item_code: function(frm, doctype, name) {
|
||||
const item = locals[doctype][name];
|
||||
item.rate = 0;
|
||||
item.uom = '';
|
||||
set_schedule_date(frm);
|
||||
frm.events.get_item_data(frm, item, true);
|
||||
},
|
||||
|
@ -769,8 +769,7 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
update_purchase_receipt_status,
|
||||
)
|
||||
|
||||
pr = make_purchase_receipt(do_not_submit=True)
|
||||
pr.submit()
|
||||
pr = make_purchase_receipt()
|
||||
|
||||
update_purchase_receipt_status(pr.name, "Closed")
|
||||
self.assertEqual(
|
||||
|
@ -118,7 +118,8 @@ def repost(doc):
|
||||
doc.set_status('Failed')
|
||||
raise
|
||||
finally:
|
||||
frappe.db.commit()
|
||||
if not frappe.flags.in_test:
|
||||
frappe.db.commit()
|
||||
|
||||
def repost_sl_entries(doc):
|
||||
if doc.based_on == 'Transaction':
|
||||
|
@ -18,7 +18,6 @@ def execute(filters=None):
|
||||
is_reposting_item_valuation_in_progress()
|
||||
if not filters: filters = {}
|
||||
|
||||
from_date = filters.get('from_date')
|
||||
to_date = filters.get('to_date')
|
||||
|
||||
if filters.get("company"):
|
||||
|
@ -838,11 +838,13 @@ class update_entries_after(object):
|
||||
for warehouse, data in self.data.items():
|
||||
bin_name = get_or_make_bin(self.item_code, warehouse)
|
||||
|
||||
frappe.db.set_value('Bin', bin_name, {
|
||||
"valuation_rate": data.valuation_rate,
|
||||
updated_values = {
|
||||
"actual_qty": data.qty_after_transaction,
|
||||
"stock_value": data.stock_value
|
||||
})
|
||||
}
|
||||
if data.valuation_rate is not None:
|
||||
updated_values["valuation_rate"] = data.valuation_rate
|
||||
frappe.db.set_value('Bin', bin_name, updated_values)
|
||||
|
||||
|
||||
def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
|
||||
|
0
erpnext/templates/pages/non_profit/__init__.py
Normal file
0
erpnext/templates/pages/non_profit/__init__.py
Normal file
@ -39,16 +39,13 @@
|
||||
frappe.call(opts).then(res => {
|
||||
let success_dialog = new frappe.ui.Dialog({
|
||||
title: __('Success'),
|
||||
primary_action_label: __('View Program Content'),
|
||||
primary_action_label: __('OK'),
|
||||
primary_action: function() {
|
||||
window.location.reload();
|
||||
},
|
||||
secondary_action: function() {
|
||||
window.location.reload();
|
||||
}
|
||||
})
|
||||
success_dialog.show();
|
||||
success_dialog.set_message(__('You have successfully enrolled for the program '));
|
||||
success_dialog.set_message(__('You have successfully enrolled for the program.'));
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user