Merge branch 'develop' of https://github.com/frappe/erpnext into party

This commit is contained in:
Saqib Ansari 2022-03-10 13:51:36 +05:30
commit 9dc9758522
41 changed files with 443 additions and 193 deletions

View File

@ -84,20 +84,12 @@ class POSInvoiceMergeLog(Document):
sales_invoice.set_posting_time = 1 sales_invoice.set_posting_time = 1
sales_invoice.posting_date = getdate(self.posting_date) sales_invoice.posting_date = getdate(self.posting_date)
sales_invoice.save() sales_invoice.save()
self.write_off_fractional_amount(sales_invoice, data)
sales_invoice.submit() sales_invoice.submit()
self.consolidated_invoice = sales_invoice.name self.consolidated_invoice = sales_invoice.name
return sales_invoice.name return sales_invoice.name
def write_off_fractional_amount(self, invoice, data):
pos_invoice_grand_total = sum(d.grand_total for d in data)
if abs(pos_invoice_grand_total - invoice.grand_total) < 1:
invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total)
invoice.save()
def process_merging_into_credit_note(self, data): def process_merging_into_credit_note(self, data):
credit_note = self.get_new_sales_invoice() credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1 credit_note.is_return = 1
@ -110,7 +102,6 @@ class POSInvoiceMergeLog(Document):
# TODO: return could be against multiple sales invoice which could also have been consolidated? # TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice # credit_note.return_against = self.consolidated_invoice
credit_note.save() credit_note.save()
self.write_off_fractional_amount(credit_note, data)
credit_note.submit() credit_note.submit()
self.consolidated_credit_note = credit_note.name self.consolidated_credit_note = credit_note.name

View File

@ -5,6 +5,7 @@ import json
import unittest import unittest
import frappe import frappe
from frappe.tests.utils import change_settings
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
@ -280,3 +281,100 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`") frappe.db.sql("delete from `tabPOS Invoice`")
@change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3})
def test_consolidation_round_off_error_3(self):
frappe.db.sql("delete from `tabPOS Invoice`")
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile()
item_rates = [69, 59, 29]
for i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
inv.append("items", {
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": -1,
"rate": rate,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
})
inv.append("taxes", {
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 15,
"included_in_print_rate": 1
})
inv.payments = []
inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157
})
inv.paid_amount = -157
inv.save()
inv.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.status, 'Return')
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_rounding_adjustment(self):
'''
Test if the rounding adjustment is calculated correctly
'''
frappe.db.sql("delete from `tabPOS Invoice`")
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile()
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70
})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60
})
inv2.insert()
inv2.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")

View File

@ -64,10 +64,10 @@
<td></td> <td></td>
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or "&nbsp;" }}</b></td> <td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or "&nbsp;" }}</b></td>
<td style="text-align: right"> <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>
<td style="text-align: right"> <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> </td>
{% endif %} {% endif %}
<td style="text-align: right"> <td style="text-align: right">

View File

@ -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){ if(frm.doc.__islocal){
frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1)); frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
frm.set_value('to_date', frappe.datetime.get_today()); frm.set_value('to_date', frappe.datetime.get_today());

View File

@ -2,7 +2,7 @@
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2022-01-25 10:29:57.771398",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
@ -651,7 +651,6 @@
"hide_seconds": 1, "hide_seconds": 1,
"label": "Ignore Pricing Rule", "label": "Ignore Pricing Rule",
"no_copy": 1, "no_copy": 1,
"permlevel": 0,
"print_hide": 1 "print_hide": 1
}, },
{ {
@ -1974,9 +1973,10 @@
}, },
{ {
"default": "0", "default": "0",
"description": "Issue a debit note with 0 qty against an existing Sales Invoice",
"fieldname": "is_debit_note", "fieldname": "is_debit_note",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Debit Note" "label": "Is Rate Adjustment Entry (Debit Note)"
}, },
{ {
"default": "0", "default": "0",
@ -2038,7 +2038,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2021-12-23 20:19:38.667508", "modified": "2022-03-08 16:08:53.517903",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",
@ -2089,6 +2089,7 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"timeline_field": "customer", "timeline_field": "customer",
"title_field": "title", "title_field": "title",
"track_changes": 1, "track_changes": 1,

View File

@ -263,6 +263,9 @@ class SalesInvoice(SellingController):
self.process_common_party_accounting() self.process_common_party_accounting()
def validate_pos_return(self): def validate_pos_return(self):
if self.is_consolidated:
# pos return is already validated in pos invoice
return
if self.is_pos and self.is_return: if self.is_pos and self.is_return:
total_amount_in_payments = 0 total_amount_in_payments = 0

View File

@ -1,3 +1,6 @@
from frappe import _
def get_data(): def get_data():
return { return {
'non_standard_fieldnames': { 'non_standard_fieldnames': {
@ -5,7 +8,7 @@ def get_data():
}, },
'transactions': [ 'transactions': [
{ {
'label': ['Movement'], 'label': _('Movement'),
'items': ['Asset Movement'] 'items': ['Asset Movement']
} }
] ]

View File

@ -23,7 +23,7 @@ def post_depreciation_entries(date=None):
frappe.db.commit() frappe.db.commit()
def get_depreciable_assets(date): 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 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 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') and a.status in ('Submitted', 'Partially Depreciated')

View File

@ -48,7 +48,7 @@ frappe.ui.form.on('Asset Maintenance', {
<div class='col-sm-3 small'> <div class='col-sm-3 small'>
<a onclick="frappe.set_route('List', 'Asset Maintenance Log', <a onclick="frappe.set_route('List', 'Asset Maintenance Log',
{'asset_name': '${d.asset_name}','maintenance_status': '${d.maintenance_status}' });"> {'asset_name': '${d.asset_name}','maintenance_status': '${d.maintenance_status}' });">
${d.maintenance_status} <span class="badge">${d.count}</span> ${__(d.maintenance_status)} <span class="badge">${d.count}</span>
</a> </a>
</div> </div>
</div>`).appendTo(rows); </div>`).appendTo(rows);

View File

@ -68,6 +68,28 @@ frappe.ui.form.on('Asset Repair', {
}); });
frappe.ui.form.on('Asset Repair Consumed Item', { frappe.ui.form.on('Asset Repair Consumed Item', {
item_code: function(frm, cdt, cdn) {
var item = locals[cdt][cdn];
let item_args = {
'item_code': item.item_code,
'warehouse': frm.doc.warehouse,
'qty': item.consumed_quantity,
'serial_no': item.serial_no,
'company': frm.doc.company
};
frappe.call({
method: 'erpnext.stock.utils.get_incoming_rate',
args: {
args: item_args
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message);
}
});
},
consumed_quantity: function(frm, cdt, cdn) { consumed_quantity: function(frm, cdt, cdn) {
var row = locals[cdt][cdn]; var row = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate); frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);

View File

@ -13,12 +13,10 @@
], ],
"fields": [ "fields": [
{ {
"fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate", "fieldname": "valuation_rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Valuation Rate", "label": "Valuation Rate"
"read_only": 1
}, },
{ {
"fieldname": "consumed_quantity", "fieldname": "consumed_quantity",
@ -49,7 +47,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-11-11 18:23:00.492483", "modified": "2022-02-08 17:37:20.028290",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Repair Consumed Item", "name": "Asset Repair Consumed Item",

View File

@ -3,15 +3,11 @@
frappe.ui.form.on('Bulk Transaction Log', { frappe.ui.form.on('Bulk Transaction Log', {
before_load: function(frm) {
query(frm);
},
refresh: function(frm) { refresh: function(frm) {
frm.disable_save(); frm.disable_save();
frm.add_custom_button(__('Retry Failed Transactions'), ()=>{ frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
frappe.confirm(__("Retry Failing Transactions ?"), ()=>{ frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
query(frm); query(frm, 1);
} }
); );
}); });
@ -25,8 +21,8 @@ function query(frm) {
log_date: frm.doc.log_date log_date: frm.doc.log_date
} }
}).then((r) => { }).then((r) => {
if (r.message) { if (r.message === "No Failed Records") {
frm.remove_custom_button("Retry Failed Transactions"); frappe.show_alert(__(r.message), 5);
} else { } else {
frappe.show_alert(__("Retrying Failed Transactions"), 5); frappe.show_alert(__("Retrying Failed Transactions"), 5);
} }

View File

@ -15,6 +15,8 @@ class BulkTransactionLog(Document):
@frappe.whitelist() @frappe.whitelist()
def retry_failing_transaction(log_date=None): def retry_failing_transaction(log_date=None):
if not log_date:
log_date = str(date.today())
btp = frappe.qb.DocType("Bulk Transaction Log Detail") btp = frappe.qb.DocType("Bulk Transaction Log Detail")
data = ( data = (
frappe.qb.from_(btp) frappe.qb.from_(btp)
@ -25,9 +27,7 @@ def retry_failing_transaction(log_date=None):
.where(btp.date == log_date) .where(btp.date == log_date)
).run(as_dict=True) ).run(as_dict=True)
if data: if data :
if not log_date:
log_date = str(date.today())
if len(data) > 10: if len(data) > 10:
frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date) frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
else: else:

View File

@ -277,7 +277,8 @@ class calculate_taxes_and_totals(object):
shipping_rule.apply(self.doc) shipping_rule.apply(self.doc)
def calculate_taxes(self): def calculate_taxes(self):
if not self.doc.get('is_consolidated'): rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
if not rounding_adjustment_computed:
self.doc.rounding_adjustment = 0 self.doc.rounding_adjustment = 0
# maintain actual tax rate based on idx # maintain actual tax rate based on idx
@ -333,7 +334,7 @@ class calculate_taxes_and_totals(object):
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
and self.doc.discount_amount \ and self.doc.discount_amount \
and self.doc.apply_discount_on == "Grand Total" \ and self.doc.apply_discount_on == "Grand Total" \
and not self.doc.get('is_consolidated'): and not rounding_adjustment_computed:
self.doc.rounding_adjustment = flt(self.doc.grand_total self.doc.rounding_adjustment = flt(self.doc.grand_total
- flt(self.doc.discount_amount) - tax.total, - flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment")) self.doc.precision("rounding_adjustment"))
@ -472,20 +473,22 @@ class calculate_taxes_and_totals(object):
self.doc.total_net_weight += d.total_weight self.doc.total_net_weight += d.total_weight
def set_rounded_total(self): def set_rounded_total(self):
if not self.doc.get('is_consolidated'): if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
if self.doc.meta.get_field("rounded_total"): return
if self.doc.is_rounded_total_disabled():
self.doc.rounded_total = self.doc.base_rounded_total = 0
return
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, if self.doc.meta.get_field("rounded_total"):
self.doc.currency, self.doc.precision("rounded_total")) if self.doc.is_rounded_total_disabled():
self.doc.rounded_total = self.doc.base_rounded_total = 0
return
#if print_in_rate is set, we would have already calculated rounding adjustment self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total"))
self.doc.precision("rounding_adjustment"))
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) #if print_in_rate is set, we would have already calculated rounding adjustment
self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
self.doc.precision("rounding_adjustment"))
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
def _cleanup(self): def _cleanup(self):
if not self.doc.get('is_consolidated'): if not self.doc.get('is_consolidated'):

View File

@ -48,7 +48,6 @@ def get_product_filter_data(query_args=None):
sub_categories = [] sub_categories = []
if item_group: if item_group:
field_filters['item_group'] = item_group
sub_categories = get_child_groups_for_website(item_group, immediate=True) sub_categories = get_child_groups_for_website(item_group, immediate=True)
engine = ProductQuery() engine = ProductQuery()

View File

@ -14,6 +14,8 @@ class ProductFiltersBuilder:
self.item_group = item_group self.item_group = item_group
def get_field_filters(self): 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: if not self.item_group and not self.doc.enable_field_filters:
return return
@ -25,18 +27,26 @@ class ProductFiltersBuilder:
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)] fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
for df in fields: 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) link_doctype_values = self.get_filtered_link_doctype_records(df)
if df.fieldtype == "Link": if df.fieldtype == "Link":
if self.item_group: if self.item_group:
item_or_filters.extend([ include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants")
["item_group", "=", self.item_group], if include_child:
["Website Item Group", "item_group", "=", self.item_group] # consider website item groups 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 # Get link field values attached to published items
item_filters['published_in_website'] = 1
item_values = frappe.get_all( item_values = frappe.get_all(
"Item", "Item",
fields=[df.fieldname], fields=[df.fieldname],

View File

@ -46,10 +46,10 @@ class ProductQuery:
self.filter_with_discount = bool(fields.get("discount")) self.filter_with_discount = bool(fields.get("discount"))
result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0 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: if fields:
self.build_fields_filters(fields) self.build_fields_filters(fields)
if item_group:
self.build_item_group_filters(item_group)
if search_term: if search_term:
self.build_search_filters(search_term) self.build_search_filters(search_term)
if self.settings.hide_variants: if self.settings.hide_variants:
@ -61,8 +61,6 @@ class ProductQuery:
else: else:
result, count = self.query_items(start=start) 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 # sort combined results by ranking
result = sorted(result, key=lambda x: x.get("ranking"), reverse=True) 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 # `=` will be faster than `IN` for most cases
self.filters.append([field, "=", values]) 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): def build_search_filters(self, search_term):
"""Query search term in specified fields """Query search term in specified fields
@ -190,19 +207,6 @@ class ProductQuery:
for field in search_fields: for field in search_fields:
self.or_filters.append([field, "like", search]) 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): def add_display_details(self, result, discount_list, cart_items):
"""Add price and availability details in result.""" """Add price and availability details in result."""
for item in result: for item in result:
@ -278,16 +282,6 @@ class ProductQuery:
return [] 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): def filter_results_by_discount(self, fields, result):
if fields and fields.get("discount"): if fields and fields.get("discount"):
discount_percent = frappe.utils.flt(fields["discount"][0]) discount_percent = frappe.utils.flt(fields["discount"][0])

View File

@ -13,8 +13,7 @@ test_dependencies = ["Item", "Item Group"]
class TestItemGroupProductDataEngine(unittest.TestCase): class TestItemGroupProductDataEngine(unittest.TestCase):
"Test Products & Sub-Category Querying for Product Listing on Item Group Page." "Test Products & Sub-Category Querying for Product Listing on Item Group Page."
@classmethod def setUp(self):
def setUpClass(cls):
item_codes = [ item_codes = [
("Test Mobile A", "_Test Item Group B"), ("Test Mobile A", "_Test Item Group B"),
("Test Mobile B", "_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}): if not frappe.db.exists("Website Item", {"item_code": item_code}):
create_regular_web_item(item_code, item_args=item_args) create_regular_web_item(item_code, item_args=item_args)
@classmethod frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
def tearDownClass(cls): frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
def test_product_listing_in_item_group(self): def test_product_listing_in_item_group(self):
@ -87,7 +88,6 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
def test_item_group_with_sub_groups(self): def test_item_group_with_sub_groups(self):
"Test Valid Sub Item Groups in Item Group Page." "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) frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
result = get_product_filter_data(query_args={ result = get_product_filter_data(query_args={
@ -115,3 +115,44 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
# check if child group is fetched if shown in website # 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 - 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)

View File

@ -1,10 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe.utils import add_days, get_first_day, getdate, nowdate from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, get_first_day, getdate, now_datetime, nowdate
from erpnext.hr.doctype.attendance.attendance import ( from erpnext.hr.doctype.attendance.attendance import (
get_month_map, get_month_map,
@ -16,7 +15,7 @@ from erpnext.hr.doctype.leave_application.test_leave_application import get_firs
test_records = frappe.get_test_records('Attendance') test_records = frappe.get_test_records('Attendance')
class TestAttendance(unittest.TestCase): class TestAttendance(FrappeTestCase):
def test_mark_absent(self): def test_mark_absent(self):
employee = make_employee("test_mark_absent@example.com") employee = make_employee("test_mark_absent@example.com")
date = nowdate() date = nowdate()
@ -74,12 +73,14 @@ class TestAttendance(unittest.TestCase):
self.assertNotIn(first_sunday, unmarked_days) self.assertNotIn(first_sunday, unmarked_days)
def test_unmarked_days_as_per_joining_and_relieving_dates(self): def test_unmarked_days_as_per_joining_and_relieving_dates(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()
doj = add_days(first_day, 1) doj = add_days(first_day, 1)
relieving_date = add_days(first_day, 5) relieving_date = add_days(first_day, 5)
employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj, employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj,
date_of_relieving=relieving_date) relieving_date=relieving_date)
frappe.db.delete('Attendance', {'employee': employee}) frappe.db.delete('Attendance', {'employee': employee})
attendance_date = add_days(first_day, 2) attendance_date = add_days(first_day, 2)

View File

@ -4,6 +4,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.query_builder.functions import Max, Min, Sum
from frappe.utils import ( from frappe.utils import (
add_days, add_days,
cint, cint,
@ -494,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 number_of_days = date_diff(to_date, from_date) + .5
else: else:
number_of_days = date_diff(to_date, from_date) + 1 number_of_days = date_diff(to_date, from_date) + 1
else: else:
number_of_days = date_diff(to_date, from_date) + 1 number_of_days = date_diff(to_date, from_date) + 1
@ -567,28 +567,39 @@ def get_leave_balance_on(employee, leave_type, date, to_date=None, consider_all_
return get_remaining_leaves(allocation, leaves_taken, date, expiry) return get_remaining_leaves(allocation, leaves_taken, date, expiry)
def get_leave_allocation_records(employee, date, leave_type=None): def get_leave_allocation_records(employee, date, leave_type=None):
''' returns the total allocated leaves and carry forwarded leaves based on ledger entries ''' """Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
Ledger = frappe.qb.DocType("Leave Ledger Entry")
conditions = ("and leave_type='%s'" % leave_type) if leave_type else "" cf_leave_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
allocation_details = frappe.db.sql(""" sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves")
SELECT
SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves, new_leaves_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves, sum_new_leaves = Sum(new_leaves_case).as_("new_leaves")
MIN(from_date) as from_date,
MAX(to_date) as to_date, query = (
leave_type frappe.qb.from_(Ledger)
FROM `tabLeave Ledger Entry` .select(
WHERE sum_cf_leaves,
from_date <= %(date)s sum_new_leaves,
AND to_date >= %(date)s Min(Ledger.from_date).as_("from_date"),
AND docstatus=1 Max(Ledger.to_date).as_("to_date"),
AND transaction_type="Leave Allocation" Ledger.leave_type
AND employee=%(employee)s ).where(
AND is_expired=0 (Ledger.from_date <= date)
AND is_lwp=0 & (Ledger.to_date >= date)
{0} & (Ledger.docstatus == 1)
GROUP BY employee, leave_type & (Ledger.transaction_type == "Leave Allocation")
""".format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec & (Ledger.employee == employee)
& (Ledger.is_expired == 0)
& (Ledger.is_lwp == 0)
)
)
if leave_type:
query = query.where((Ledger.leave_type == leave_type))
query = query.groupby(Ledger.employee, Ledger.leave_type)
allocation_details = query.run(as_dict=True)
allocated_leaves = frappe._dict() allocated_leaves = frappe._dict()
for d in allocation_details: for d in allocation_details:

View File

@ -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)

View File

@ -5,10 +5,13 @@ def execute():
frappe.reload_doc('accounts', 'doctype', 'bank', force=1) 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'): 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(""" try:
UPDATE `tabBank` b, `tabBank Account` ba frappe.db.sql("""
SET b.swift_number = ba.swift_number WHERE b.name = ba.bank 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', 'bank_account')
frappe.reload_doc('accounts', 'doctype', 'payment_request') frappe.reload_doc('accounts', 'doctype', 'payment_request')

View File

@ -1,14 +1,12 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
def execute(filters=None): def execute(filters=None):
columns, data = [], []
data = get_data(filters) data = get_data(filters)
columns = get_columns() columns = get_columns()
charts = get_chart_data(data) charts = get_chart_data(data)

View File

@ -1,6 +1,5 @@
import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, getdate from frappe.utils import add_days, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
@ -12,7 +11,7 @@ from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_
from erpnext.projects.report.project_profitability.project_profitability import execute from erpnext.projects.report.project_profitability.project_profitability import execute
class TestProjectProfitability(unittest.TestCase): class TestProjectProfitability(FrappeTestCase):
def setUp(self): def setUp(self):
frappe.db.sql('delete from `tabTimesheet`') frappe.db.sql('delete from `tabTimesheet`')
emp = make_employee('test_employee_9@salary.com', company='_Test Company') emp = make_employee('test_employee_9@salary.com', company='_Test Company')
@ -67,6 +66,3 @@ class TestProjectProfitability(unittest.TestCase):
fractional_cost = self.salary_slip.base_gross_pay * utilization fractional_cost = self.salary_slip.base_gross_pay * utilization
self.assertEqual(fractional_cost, row.fractional_cost) self.assertEqual(fractional_cost, row.fractional_cost)
def tearDown(self):
frappe.db.rollback()

View File

@ -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 */ /* against number or, if empty, party against number */
%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)', %(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
/* disable automatic VAT deduction */ '' as 'BU-Schlüssel',
'40' as 'BU-Schlüssel',
gl.posting_date as 'Belegdatum', gl.posting_date as 'Belegdatum',
gl.voucher_no as 'Belegfeld 1', gl.voucher_no as 'Belegfeld 1',

View File

@ -30,7 +30,6 @@ def execute(filters=None):
if region != 'United States': if region != 'United States':
return [], [] return [], []
data = []
columns = get_columns() columns = get_columns()
conditions = "" conditions = ""
if filters.supplier_group: if filters.supplier_group:

View File

@ -118,8 +118,7 @@ def make_customer():
"customer_type": "Company", "customer_type": "Company",
}) })
customer.insert() customer.insert()
else:
customer = frappe.get_doc("Customer", "_Test UAE Customer")
def make_supplier(): def make_supplier():
if not frappe.db.exists("Supplier", "_Test UAE Supplier"): if not frappe.db.exists("Supplier", "_Test UAE Supplier"):

View File

@ -218,7 +218,9 @@ class Customer(TransactionBase):
else: else:
company_record.append(limit.company) company_record.append(limit.company)
outstanding_amt = get_customer_outstanding(self.name, limit.company) outstanding_amt = get_customer_outstanding(
self.name, limit.company, ignore_outstanding_sales_order=limit.bypass_credit_limit_check
)
if flt(limit.credit_limit) < outstanding_amt: if flt(limit.credit_limit) < outstanding_amt:
frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt)) frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt))

View File

@ -156,24 +156,24 @@ def get_data(filters):
customer_record = customer_details.get(record.customer) customer_record = customer_details.get(record.customer)
item_record = item_details.get(record.item_code) item_record = item_details.get(record.item_code)
row = { row = {
"item_code": record.item_code, "item_code": record.get('item_code'),
"item_name": item_record.item_name, "item_name": item_record.get('item_name'),
"item_group": item_record.item_group, "item_group": item_record.get('item_group'),
"description": record.description, "description": record.get('description'),
"quantity": record.qty, "quantity": record.get('qty'),
"uom": record.uom, "uom": record.get('uom'),
"rate": record.base_rate, "rate": record.get('base_rate'),
"amount": record.base_amount, "amount": record.get('base_amount'),
"sales_order": record.name, "sales_order": record.get('name'),
"transaction_date": record.transaction_date, "transaction_date": record.get('transaction_date'),
"customer": record.customer, "customer": record.get('customer'),
"customer_name": customer_record.customer_name, "customer_name": customer_record.get('customer_name'),
"customer_group": customer_record.customer_group, "customer_group": customer_record.get('customer_group'),
"territory": record.territory, "territory": record.get('territory'),
"project": record.project, "project": record.get('project'),
"delivered_quantity": flt(record.delivered_qty), "delivered_quantity": flt(record.get('delivered_qty')),
"billed_amount": flt(record.billed_amt), "billed_amount": flt(record.get('billed_amt')),
"company": record.company "company": record.get('company')
} }
data.append(row) data.append(row)

View File

@ -14,6 +14,16 @@ frappe.ui.form.on("Item Group", {
] ]
} }
} }
frm.fields_dict['item_group_defaults'].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
'report_type': 'Profit and Loss',
'company': row.company,
"is_group": 0
}
};
}
frm.fields_dict["item_group_defaults"].grid.get_field("expense_account").get_query = function(doc, cdt, cdn) { frm.fields_dict["item_group_defaults"].grid.get_field("expense_account").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
return { return {

View File

@ -20,12 +20,14 @@
"sec_break_taxes", "sec_break_taxes",
"taxes", "taxes",
"sb9", "sb9",
"show_in_website",
"route", "route",
"weightage",
"slideshow",
"website_title", "website_title",
"description", "description",
"show_in_website",
"include_descendants",
"column_break_16",
"weightage",
"slideshow",
"website_specifications", "website_specifications",
"website_filters_section", "website_filters_section",
"filter_fields", "filter_fields",
@ -111,7 +113,7 @@
}, },
{ {
"default": "0", "default": "0",
"description": "Check this if you want to show in website", "description": "Make Item Group visible in website",
"fieldname": "show_in_website", "fieldname": "show_in_website",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show in Website" "label": "Show in Website"
@ -124,6 +126,7 @@
"unique": 1 "unique": 1
}, },
{ {
"depends_on": "show_in_website",
"fieldname": "weightage", "fieldname": "weightage",
"fieldtype": "Int", "fieldtype": "Int",
"label": "Weightage" "label": "Weightage"
@ -186,6 +189,8 @@
"report_hide": 1 "report_hide": 1
}, },
{ {
"collapsible": 1,
"depends_on": "show_in_website",
"fieldname": "website_filters_section", "fieldname": "website_filters_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Website Filters" "label": "Website Filters"
@ -203,9 +208,22 @@
"options": "Website Attribute" "options": "Website Attribute"
}, },
{ {
"depends_on": "show_in_website",
"fieldname": "website_title", "fieldname": "website_title",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Title" "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", "icon": "fa fa-sitemap",
@ -214,11 +232,12 @@
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"max_attachments": 3, "max_attachments": 3,
"modified": "2021-02-18 13:40:30.049650", "modified": "2022-03-09 12:27:11.055782",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Item Group", "name": "Item Group",
"name_case": "Title Case", "name_case": "Title Case",
"naming_rule": "By fieldname",
"nsm_parent_field": "parent_item_group", "nsm_parent_field": "parent_item_group",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
@ -285,5 +304,6 @@
"search_fields": "parent_item_group", "search_fields": "parent_item_group",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

@ -111,7 +111,7 @@ class ItemGroup(NestedSet, WebsiteGenerator):
from erpnext.stock.doctype.item.item import validate_item_default_company_links from erpnext.stock.doctype.item.item import validate_item_default_company_links
validate_item_default_company_links(self.item_group_defaults) 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.""" """Returns child item groups *excluding* passed group."""
item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1) item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
filters = { filters = {
@ -123,6 +123,12 @@ def get_child_groups_for_website(item_group_name, immediate=False):
if immediate: if immediate:
filters["parent_item_group"] = item_group_name filters["parent_item_group"] = item_group_name
if include_self:
filters.update({
"lft": [">=", item_group.lft],
"rgt": ["<=", item_group.rgt]
})
return frappe.get_all( return frappe.get_all(
"Item Group", "Item Group",
filters=filters, filters=filters,

View File

@ -165,21 +165,21 @@ frappe.ui.form.on("Item", {
frm.set_value('has_batch_no', 0); frm.set_value('has_batch_no', 0);
frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset); frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
frm.call({ frappe.call({
method: "set_asset_naming_series", method: "erpnext.stock.doctype.item.item.get_asset_naming_series",
doc: frm.doc, callback: function(r) {
callback: function() {
frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); 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'); frm.trigger('auto_create_assets');
}, },
set_asset_naming_series: function(frm) { set_asset_naming_series: function(frm, asset_naming_series) {
if (frm.doc.__onload && frm.doc.__onload.asset_naming_series) { if ((frm.doc.__onload && frm.doc.__onload.asset_naming_series) || asset_naming_series) {
frm.set_df_property("asset_naming_series", "options", frm.doc.__onload.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);
} }
}, },

View File

@ -50,15 +50,7 @@ class DataValidationError(frappe.ValidationError):
class Item(Document): class Item(Document):
def onload(self): def onload(self):
self.set_onload('stock_exists', self.stock_ledger_created()) self.set_onload('stock_exists', self.stock_ledger_created())
self.set_asset_naming_series() self.set_onload('asset_naming_series', get_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)
def autoname(self): def autoname(self):
if frappe.db.get_default("item_naming_by") == "Naming Series": if frappe.db.get_default("item_naming_by") == "Naming Series":
@ -999,7 +991,7 @@ def get_uom_conv_factor(uom, stock_uom):
if uom == stock_uom: if uom == stock_uom:
return 1.0 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) exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1)
if exact_match: if exact_match:
@ -1011,9 +1003,9 @@ def get_uom_conv_factor(uom, stock_uom):
# This attempts to try and get conversion from intermediate UOM. # This attempts to try and get conversion from intermediate UOM.
# case: # case:
# g -> mg = 1000 # g -> mg = 1000
# g -> kg = 0.001 # g -> kg = 0.001
# therefore kg -> mg = 1000 / 0.001 = 1,000,000 # therefore kg -> mg = 1000 / 0.001 = 1,000,000
intermediate_match = frappe.db.sql(""" intermediate_match = frappe.db.sql("""
select (first.value / second.value) as value select (first.value / second.value) as value
from `tabUOM Conversion Factor` first from `tabUOM Conversion Factor` first
@ -1072,3 +1064,11 @@ def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> Non
frappe.bold(item_default.company), frappe.bold(item_default.company),
frappe.bold(frappe.unscrub(field)) frappe.bold(frappe.unscrub(field))
), title=_("Invalid Item Defaults")) ), 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()

View File

@ -272,9 +272,9 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
{warehouse_condition} {warehouse_condition}
GROUP BY GROUP BY
`warehouse`, sle.`warehouse`,
`batch_no`, sle.`batch_no`,
`item_code` sle.`item_code`
HAVING `qty` > 0 HAVING `qty` > 0
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation` ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
""".format(warehouse_condition=warehouse_condition), { #nosec """.format(warehouse_condition=warehouse_condition), { #nosec

View File

@ -367,7 +367,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not out[d[1]]: if not out[d[1]]:
out[d[1]] = frappe.get_cached_value('Company', args.company, d[2]) if d[2] else None out[d[1]] = frappe.get_cached_value('Company', args.company, d[2]) if d[2] else None
for fieldname in ("item_name", "item_group", "barcodes", "brand", "stock_uom"): for fieldname in ("item_name", "item_group", "brand", "stock_uom"):
out[fieldname] = item.get(fieldname) out[fieldname] = item.get(fieldname)
if args.get("manufacturer"): if args.get("manufacturer"):

View File

@ -18,7 +18,6 @@ def execute(filters=None):
is_reposting_item_valuation_in_progress() is_reposting_item_valuation_in_progress()
if not filters: filters = {} if not filters: filters = {}
from_date = filters.get('from_date')
to_date = filters.get('to_date') to_date = filters.get('to_date')
if filters.get("company"): if filters.get("company"):

View File

@ -829,7 +829,7 @@ class update_entries_after(object):
if msg_list: if msg_list:
message = "\n\n".join(msg_list) message = "\n\n".join(msg_list)
if self.verbose: if self.verbose:
frappe.throw(message, NegativeStockError, title='Insufficient Stock') frappe.throw(message, NegativeStockError, title=_('Insufficient Stock'))
else: else:
raise NegativeStockError(message) raise NegativeStockError(message)
@ -1157,7 +1157,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
neg_sle[0]["posting_date"], neg_sle[0]["posting_time"], neg_sle[0]["posting_date"], neg_sle[0]["posting_time"],
frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"])) frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"]))
frappe.throw(message, NegativeStockError, title='Insufficient Stock') frappe.throw(message, NegativeStockError, title=_('Insufficient Stock'))
if not args.batch_no: if not args.batch_no:
@ -1171,7 +1171,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
frappe.get_desk_link('Warehouse', args.warehouse), frappe.get_desk_link('Warehouse', args.warehouse),
neg_batch_sle[0]["posting_date"], neg_batch_sle[0]["posting_time"], neg_batch_sle[0]["posting_date"], neg_batch_sle[0]["posting_time"],
frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"])) frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]))
frappe.throw(message, NegativeStockError, title="Insufficient Stock for Batch") frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
def get_future_sle_with_negative_qty(args): def get_future_sle_with_negative_qty(args):

View File

@ -39,16 +39,13 @@
frappe.call(opts).then(res => { frappe.call(opts).then(res => {
let success_dialog = new frappe.ui.Dialog({ let success_dialog = new frappe.ui.Dialog({
title: __('Success'), title: __('Success'),
primary_action_label: __('View Program Content'), primary_action_label: __('OK'),
primary_action: function() { primary_action: function() {
window.location.reload(); window.location.reload();
},
secondary_action: function() {
window.location.reload();
} }
}) })
success_dialog.show(); 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> </script>