Merge branch 'develop' into item-group-filters-listing

This commit is contained in:
Marica 2022-03-09 15:33:04 +05:30 committed by GitHub
commit 7555e1548b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 478 additions and 146 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

@ -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,5 @@
from frappe import _
def get_data(): def get_data():
return { return {
'non_standard_fieldnames': { 'non_standard_fieldnames': {
@ -5,7 +7,7 @@ def get_data():
}, },
'transactions': [ 'transactions': [
{ {
'label': ['Movement'], 'label': _('Movement'),
'items': ['Asset Movement'] 'items': ['Asset Movement']
} }
] ]

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

@ -113,17 +113,24 @@ class calculate_taxes_and_totals(object):
for item in self.doc.get("items"): for item in self.doc.get("items"):
self.doc.round_floats_in(item) self.doc.round_floats_in(item)
if not item.rate:
item.rate = item.price_list_rate
if item.discount_percentage == 100: if item.discount_percentage == 100:
item.rate = 0.0 item.rate = 0.0
elif item.price_list_rate: elif item.price_list_rate:
if not item.rate or (item.pricing_rules and item.discount_percentage > 0): if item.pricing_rules or abs(item.discount_percentage) > 0:
item.rate = flt(item.price_list_rate * item.rate = flt(item.price_list_rate *
(1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
elif item.discount_amount and item.pricing_rules: if abs(item.discount_percentage) > 0:
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
elif item.discount_amount or item.pricing_rules:
item.rate = item.price_list_rate - item.discount_amount item.rate = item.price_list_rate - item.discount_amount
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']: if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item) item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0: if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")) item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
@ -270,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
@ -326,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"))
@ -465,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

@ -174,16 +174,22 @@ def get_month_map():
def get_unmarked_days(employee, month, exclude_holidays=0): def get_unmarked_days(employee, month, exclude_holidays=0):
import calendar import calendar
month_map = get_month_map() month_map = get_month_map()
today = get_datetime() today = get_datetime()
dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(1, calendar.monthrange(today.year, month_map[month])[1] + 1)] joining_date, relieving_date = frappe.get_cached_value("Employee", employee, ["date_of_joining", "relieving_date"])
start_day = 1
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
length = len(dates_of_month) if joining_date and joining_date.month == month_map[month]:
month_start, month_end = dates_of_month[0], dates_of_month[length-1] start_day = joining_date.day
if relieving_date and relieving_date.month == month_map[month]:
end_day = relieving_date.day + 1
records = frappe.get_all("Attendance", fields = ['attendance_date', 'employee'] , filters = [ dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(start_day, end_day)]
month_start, month_end = dates_of_month[0], dates_of_month[-1]
records = frappe.get_all("Attendance", fields=['attendance_date', 'employee'], filters=[
["attendance_date", ">=", month_start], ["attendance_date", ">=", month_start],
["attendance_date", "<=", month_end], ["attendance_date", "<=", month_end],
["employee", "=", employee], ["employee", "=", employee],
@ -200,7 +206,7 @@ def get_unmarked_days(employee, month, exclude_holidays=0):
for date in dates_of_month: for date in dates_of_month:
date_time = get_datetime(date) date_time = get_datetime(date)
if today.day == date_time.day and today.month == date_time.month: if today.day <= date_time.day and today.month <= date_time.month:
break break
if date_time not in marked_days: if date_time not in marked_days:
unmarked_days.append(date) unmarked_days.append(date)

View File

@ -1,20 +1,108 @@
# 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 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 (
get_month_map,
get_unmarked_days,
mark_attendance,
)
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
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):
from erpnext.hr.doctype.employee.test_employee import make_employee
employee = make_employee("test_mark_absent@example.com") employee = make_employee("test_mark_absent@example.com")
date = nowdate() date = nowdate()
frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date}) frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date})
from erpnext.hr.doctype.attendance.attendance import mark_attendance
attendance = mark_attendance(employee, date, 'Absent') attendance = mark_attendance(employee, date, 'Absent')
fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'}) fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'})
self.assertEqual(attendance, fetch_attendance) self.assertEqual(attendance, fetch_attendance)
def test_unmarked_days(self):
first_day = get_first_day(getdate())
employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
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)
first_sunday = get_first_sunday(holiday_list)
mark_attendance(employee, first_day, 'Present')
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
unmarked_days = [getdate(date) for date in unmarked_days]
# attendance already marked for the day
self.assertNotIn(first_day, unmarked_days)
# attendance unmarked
self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
# holiday considered in unmarked days
self.assertIn(first_sunday, unmarked_days)
def test_unmarked_days_excluding_holidays(self):
first_day = get_first_day(getdate())
employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
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)
first_sunday = get_first_sunday(holiday_list)
mark_attendance(employee, first_day, 'Present')
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
unmarked_days = [getdate(date) for date in unmarked_days]
# attendance already marked for the day
self.assertNotIn(first_day, unmarked_days)
# attendance unmarked
self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
# holidays not considered in unmarked days
self.assertNotIn(first_sunday, unmarked_days)
def test_unmarked_days_as_per_joining_and_relieving_dates(self):
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)
relieving_date = add_days(first_day, 5)
employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj,
relieving_date=relieving_date)
frappe.db.delete('Attendance', {'employee': employee})
attendance_date = add_days(first_day, 2)
mark_attendance(employee, attendance_date, 'Present')
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
unmarked_days = [getdate(date) for date in unmarked_days]
# attendance already marked for the day
self.assertNotIn(attendance_date, unmarked_days)
# date before doj not in unmarked days
self.assertNotIn(add_days(doj, -1), unmarked_days)
# date after relieving not in unmarked days
self.assertNotIn(add_days(relieving_date, 1), unmarked_days)
def tearDown(self):
frappe.db.rollback()
def get_month_name(date):
month_number = date.month
for month, number in get_month_map().items():
if number == month_number:
return month

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,
@ -567,28 +568,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

@ -792,4 +792,4 @@ def get_first_sunday(holiday_list):
order by holiday_date order by holiday_date
""", (holiday_list, month_start_date, month_end_date))[0][0] """, (holiday_list, month_start_date, month_end_date))[0][0]
return first_sunday return first_sunday

View File

@ -307,28 +307,59 @@ class SalarySlip(TransactionBase):
if payroll_based_on == "Attendance": if payroll_based_on == "Attendance":
self.payment_days -= flt(absent) self.payment_days -= flt(absent)
unmarked_days = self.get_unmarked_days()
consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present" consider_unmarked_attendance_as = frappe.db.get_value("Payroll Settings", None, "consider_unmarked_attendance_as") or "Present"
if payroll_based_on == "Attendance" and consider_unmarked_attendance_as =="Absent": if payroll_based_on == "Attendance" and consider_unmarked_attendance_as =="Absent":
unmarked_days = self.get_unmarked_days(include_holidays_in_total_working_days)
self.absent_days += unmarked_days #will be treated as absent self.absent_days += unmarked_days #will be treated as absent
self.payment_days -= unmarked_days self.payment_days -= unmarked_days
if include_holidays_in_total_working_days:
for holiday in holidays:
if not frappe.db.exists("Attendance", {"employee": self.employee, "attendance_date": holiday, "docstatus": 1 }):
self.payment_days += 1
else: else:
self.payment_days = 0 self.payment_days = 0
def get_unmarked_days(self): def get_unmarked_days(self, include_holidays_in_total_working_days):
marked_days = frappe.get_all("Attendance", filters = { unmarked_days = self.total_working_days
"attendance_date": ["between", [self.start_date, self.end_date]], joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
"employee": self.employee, ["date_of_joining", "relieving_date"])
"docstatus": 1 start_date = self.start_date
}, fields = ["COUNT(*) as marked_days"])[0].marked_days end_date = self.end_date
return self.total_working_days - marked_days if joining_date and (getdate(self.start_date) < joining_date <= getdate(self.end_date)):
start_date = joining_date
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(unmarked_days,
include_holidays_in_total_working_days, self.start_date, joining_date)
if relieving_date and (getdate(self.start_date) <= relieving_date < getdate(self.end_date)):
end_date = relieving_date
unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving(unmarked_days,
include_holidays_in_total_working_days, relieving_date, self.end_date)
# exclude days for which attendance has been marked
unmarked_days -= frappe.get_all("Attendance", filters = {
"attendance_date": ["between", [start_date, end_date]],
"employee": self.employee,
"docstatus": 1
}, fields = ["COUNT(*) as marked_days"])[0].marked_days
return unmarked_days
def get_unmarked_days_based_on_doj_or_relieving(self, unmarked_days,
include_holidays_in_total_working_days, start_date, end_date):
"""
Exclude days before DOJ or after
Relieving Date from unmarked days
"""
from erpnext.hr.doctype.employee.employee import is_holiday
if include_holidays_in_total_working_days:
unmarked_days -= date_diff(end_date, start_date)
else:
# exclude only if not holidays
for days in range(date_diff(end_date, start_date)):
date = add_days(end_date, -days)
if not is_holiday(self.employee, date):
unmarked_days -= 1
return unmarked_days
def get_payment_days(self, joining_date, relieving_date, include_holidays_in_total_working_days): def get_payment_days(self, joining_date, relieving_date, include_holidays_in_total_working_days):
if not joining_date: if not joining_date:

View File

@ -7,10 +7,12 @@ import unittest
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.tests.utils import change_settings
from frappe.utils import ( from frappe.utils import (
add_days, add_days,
add_months, add_months,
cstr, cstr,
date_diff,
flt, flt,
get_first_day, get_first_day,
get_last_day, get_last_day,
@ -21,6 +23,7 @@ from frappe.utils.make_random import get_random
import erpnext import erpnext
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from erpnext.hr.doctype.attendance.attendance import mark_attendance
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
@ -37,17 +40,17 @@ class TestSalarySlip(unittest.TestCase):
setup_test() setup_test()
def tearDown(self): def tearDown(self):
frappe.db.rollback()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0) frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
frappe.set_user("Administrator") frappe.set_user("Administrator")
@change_settings("Payroll Settings", {
"payroll_based_on": "Attendance",
"daily_wages_fraction_for_half_day": 0.75
})
def test_payment_days_based_on_attendance(self): def test_payment_days_based_on_attendance(self):
from erpnext.hr.doctype.attendance.attendance import mark_attendance
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
# Payroll based on attendance
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
frappe.db.set_value("Payroll Settings", None, "daily_wages_fraction_for_half_day", 0.75)
emp_id = make_employee("test_payment_days_based_on_attendance@salary.com") emp_id = make_employee("test_payment_days_based_on_attendance@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"}) frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
@ -85,14 +88,78 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.gross_pay, gross_pay) self.assertEqual(ss.gross_pay, gross_pay)
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") @change_settings("Payroll Settings", {
"payroll_based_on": "Attendance",
"consider_unmarked_attendance_as": "Absent",
"include_holidays_in_total_working_days": True
})
def test_payment_days_for_mid_joinee_including_holidays(self):
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
no_of_days = self.get_no_of_days()
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
frappe.db.set_value("Employee", new_emp_id, {
"date_of_joining": joining_date,
"relieving_date": relieving_date,
"status": "Left"
})
holidays = 0
for days in range(date_diff(relieving_date, joining_date) + 1):
date = add_days(joining_date, days)
if not is_holiday("Salary Slip Test Holiday List", date):
mark_attendance(new_emp_id, date, 'Present', ignore_validate=True)
else:
holidays += 1
new_ss = make_employee_salary_slip("test_payment_days_based_on_joining_date@salary.com", "Monthly", "Test Payment Based On Attendence")
self.assertEqual(new_ss.total_working_days, no_of_days[0])
self.assertEqual(new_ss.payment_days, no_of_days[0] - holidays - 8)
@change_settings("Payroll Settings", {
"payroll_based_on": "Attendance",
"consider_unmarked_attendance_as": "Absent",
"include_holidays_in_total_working_days": False
})
def test_payment_days_for_mid_joinee_excluding_holidays(self):
from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
no_of_days = self.get_no_of_days()
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
new_emp_id = make_employee("test_payment_days_based_on_joining_date@salary.com")
joining_date, relieving_date = add_days(month_start_date, 3), add_days(month_end_date, -5)
frappe.db.set_value("Employee", new_emp_id, {
"date_of_joining": joining_date,
"relieving_date": relieving_date,
"status": "Left"
})
holidays = 0
for days in range(date_diff(relieving_date, joining_date) + 1):
date = add_days(joining_date, days)
if not is_holiday("Salary Slip Test Holiday List", date):
mark_attendance(new_emp_id, date, 'Present', ignore_validate=True)
else:
holidays += 1
new_ss = make_employee_salary_slip("test_payment_days_based_on_joining_date@salary.com", "Monthly", "Test Payment Based On Attendence")
self.assertEqual(new_ss.total_working_days, no_of_days[0] - no_of_days[1])
self.assertEqual(new_ss.payment_days, no_of_days[0] - holidays - 8)
@change_settings("Payroll Settings", {
"payroll_based_on": "Leave"
})
def test_payment_days_based_on_leave_application(self): def test_payment_days_based_on_leave_application(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
# Payroll based on attendance
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com") emp_id = make_employee("test_payment_days_based_on_leave_application@salary.com")
frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"}) frappe.db.set_value("Employee", emp_id, {"relieving_date": None, "status": "Active"})
@ -133,8 +200,9 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4) self.assertEqual(ss.payment_days, days_in_month - no_of_holidays - 4)
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") @change_settings("Payroll Settings", {
"payroll_based_on": "Attendance"
})
def test_payment_days_in_salary_slip_based_on_timesheet(self): def test_payment_days_in_salary_slip_based_on_timesheet(self):
from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.hr.doctype.attendance.attendance import mark_attendance
from erpnext.projects.doctype.timesheet.test_timesheet import ( from erpnext.projects.doctype.timesheet.test_timesheet import (
@ -145,9 +213,6 @@ class TestSalarySlip(unittest.TestCase):
make_salary_slip as make_salary_slip_for_timesheet, make_salary_slip as make_salary_slip_for_timesheet,
) )
# Payroll based on attendance
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company", holiday_list="Salary Slip Test Holiday List") emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company", holiday_list="Salary Slip Test Holiday List")
frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"}) frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
@ -185,17 +250,15 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2)) self.assertEqual(salary_slip.gross_pay, flt(gross_pay, 2))
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave") @change_settings("Payroll Settings", {
"payroll_based_on": "Attendance"
})
def test_component_amount_dependent_on_another_payment_days_based_component(self): def test_component_amount_dependent_on_another_payment_days_based_component(self):
from erpnext.hr.doctype.attendance.attendance import mark_attendance from erpnext.hr.doctype.attendance.attendance import mark_attendance
from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment, create_salary_structure_assignment,
) )
# Payroll based on attendance
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
salary_structure = make_salary_structure_for_payment_days_based_component_dependency() salary_structure = make_salary_structure_for_payment_days_based_component_dependency()
employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company") employee = make_employee("test_payment_days_based_component@salary.com", company="_Test Company")
@ -238,11 +301,12 @@ class TestSalarySlip(unittest.TestCase):
expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision) expected_amount = flt((flt(ss.gross_pay) - payment_days_based_comp_amount) * 0.12, precision)
self.assertEqual(actual_amount, expected_amount) self.assertEqual(actual_amount, expected_amount)
frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Leave")
@change_settings("Payroll Settings", {
"include_holidays_in_total_working_days": 1
})
def test_salary_slip_with_holidays_included(self): def test_salary_slip_with_holidays_included(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
make_employee("test_salary_slip_with_holidays_included@salary.com") make_employee("test_salary_slip_with_holidays_included@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee", frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "relieving_date", None) {"employee_name":"test_salary_slip_with_holidays_included@salary.com"}, "name"), "relieving_date", None)
@ -256,9 +320,11 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.gross_pay, 78000) self.assertEqual(ss.gross_pay, 78000)
@change_settings("Payroll Settings", {
"include_holidays_in_total_working_days": 0
})
def test_salary_slip_with_holidays_excluded(self): def test_salary_slip_with_holidays_excluded(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 0)
make_employee("test_salary_slip_with_holidays_excluded@salary.com") make_employee("test_salary_slip_with_holidays_excluded@salary.com")
frappe.db.set_value("Employee", frappe.get_value("Employee", frappe.db.set_value("Employee", frappe.get_value("Employee",
{"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "relieving_date", None) {"employee_name":"test_salary_slip_with_holidays_excluded@salary.com"}, "name"), "relieving_date", None)
@ -273,14 +339,15 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.gross_pay, 78000) self.assertEqual(ss.gross_pay, 78000)
@change_settings("Payroll Settings", {
"include_holidays_in_total_working_days": 1
})
def test_payment_days(self): def test_payment_days(self):
from erpnext.payroll.doctype.salary_structure.test_salary_structure import ( from erpnext.payroll.doctype.salary_structure.test_salary_structure import (
create_salary_structure_assignment, create_salary_structure_assignment,
) )
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
# Holidays not included in working days
frappe.db.set_value("Payroll Settings", None, "include_holidays_in_total_working_days", 1)
# set joinng date in the same month # set joinng date in the same month
employee = make_employee("test_payment_days@salary.com") employee = make_employee("test_payment_days@salary.com")
@ -338,11 +405,12 @@ class TestSalarySlip(unittest.TestCase):
frappe.set_user("test_employee_salary_slip_read_permission@salary.com") frappe.set_user("test_employee_salary_slip_read_permission@salary.com")
self.assertTrue(salary_slip_test_employee.has_permission("read")) self.assertTrue(salary_slip_test_employee.has_permission("read"))
@change_settings("Payroll Settings", {
"email_salary_slip_to_employee": 1
})
def test_email_salary_slip(self): def test_email_salary_slip(self):
frappe.db.sql("delete from `tabEmail Queue`") frappe.db.sql("delete from `tabEmail Queue`")
frappe.db.set_value("Payroll Settings", None, "email_salary_slip_to_employee", 1)
make_employee("test_email_salary_slip@salary.com") make_employee("test_email_salary_slip@salary.com")
ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email") ss = make_employee_salary_slip("test_email_salary_slip@salary.com", "Monthly", "Test Salary Slip Email")
ss.company = "_Test Company" ss.company = "_Test Company"

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

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

@ -40,7 +40,6 @@ frappe.ui.form.on('Quotation', {
erpnext.selling.QuotationController = class QuotationController extends erpnext.selling.SellingController { erpnext.selling.QuotationController = class QuotationController extends erpnext.selling.SellingController {
onload(doc, dt, dn) { onload(doc, dt, dn) {
var me = this;
super.onload(doc, dt, dn); super.onload(doc, dt, dn);
} }
party_name() { party_name() {

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

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

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