Merge branch 'hotfix' into bugfix/17547-Pricing-Rules-are-broken

This commit is contained in:
Nabin Hait
2019-05-27 19:22:34 +05:30
committed by GitHub
130 changed files with 7581 additions and 6332 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '11.1.28' __version__ = '11.1.33'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -42,15 +42,14 @@ frappe.ui.form.on('Account', {
// show / hide convert buttons // show / hide convert buttons
frm.trigger('add_toolbar_buttons'); frm.trigger('add_toolbar_buttons');
} }
frm.add_custom_button(__('Update Account Name / Number'), function () { if (frm.has_perm('write')) {
frm.trigger("update_account_number"); frm.add_custom_button(__('Update Account Name / Number'), function () {
}); frm.trigger("update_account_number");
} });
frm.add_custom_button(__('Merge Account'), function () {
if(!frm.doc.__islocal) { frm.trigger("merge_account");
frm.add_custom_button(__('Merge Account'), function () { });
frm.trigger("merge_account"); }
});
} }
}, },
account_type: function (frm) { account_type: function (frm) {

View File

@@ -268,7 +268,7 @@ def update_account_number(name, account_name, account_number=None):
new_name = get_account_autoname(account_number, account_name, account.company) new_name = get_account_autoname(account_number, account_name, account.company)
if name != new_name: if name != new_name:
frappe.rename_doc("Account", name, new_name, ignore_permissions=1) frappe.rename_doc("Account", name, new_name, force=1)
return new_name return new_name
@frappe.whitelist() @frappe.whitelist()
@@ -287,7 +287,7 @@ def merge_account(old, new, is_group, root_type, company):
frappe.db.set_value("Account", new, "parent_account", frappe.db.set_value("Account", new, "parent_account",
frappe.db.get_value("Account", old, "parent_account")) frappe.db.get_value("Account", old, "parent_account"))
frappe.rename_doc("Account", old, new, merge=1, ignore_permissions=1) frappe.rename_doc("Account", old, new, merge=1, force=1)
return new return new

View File

@@ -22,7 +22,5 @@ class BankGuarantee(Document):
@frappe.whitelist() @frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname): def get_vouchar_detials(column_list, doctype, docname):
print (column_list, doctype, docname)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0] .format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0]

View File

@@ -121,7 +121,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "Settled", "default": "Pending",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "status", "fieldname": "status",
"fieldtype": "Select", "fieldtype": "Select",
@@ -755,7 +755,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-26 14:32:16.437813", "modified": "2019-05-11 05:27:55.244721",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Transaction", "name": "Bank Transaction",

View File

@@ -36,6 +36,10 @@ class BankTransaction(StatusUpdater):
frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0) frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit))) frappe.db.set_value(self.doctype, self.name, "unallocated_amount", abs(flt(self.credit) - flt(self.debit)))
amount = self.debit or self.credit
if amount == self.allocated_amount:
frappe.db.set_value(self.doctype, self.name, "status", "Reconciled")
self.reload() self.reload()
def clear_linked_payment_entries(self): def clear_linked_payment_entries(self):

View File

@@ -167,7 +167,12 @@ def get_pricing_rule_for_item(args):
if args.transaction_type=="selling": if args.transaction_type=="selling":
if args.customer and not (args.customer_group and args.territory): if args.customer and not (args.customer_group and args.territory):
customer = frappe.get_cached_value("Customer", args.customer, ["customer_group", "territory"])
if args.quotation_to and args.quotation_to != 'Customer':
customer = frappe._dict()
else:
customer = frappe.get_cached_value("Customer", args.customer, ["customer_group", "territory"])
if customer: if customer:
args.customer_group, args.territory = customer args.customer_group, args.territory = customer

View File

@@ -100,6 +100,7 @@ class PurchaseInvoice(BuyingController):
self.validate_fixed_asset() self.validate_fixed_asset()
self.create_remarks() self.create_remarks()
self.set_status() self.set_status()
self.validate_purchase_receipt_if_update_stock()
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference) validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
def validate_release_date(self): def validate_release_date(self):
@@ -284,7 +285,7 @@ class PurchaseInvoice(BuyingController):
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.extend([{ self.status_updater.append({
'source_dt': 'Purchase Invoice Item', 'source_dt': 'Purchase Invoice Item',
'target_dt': 'Purchase Order Item', 'target_dt': 'Purchase Order Item',
'join_field': 'po_detail', 'join_field': 'po_detail',
@@ -292,28 +293,29 @@ class PurchaseInvoice(BuyingController):
'target_parent_dt': 'Purchase Order', 'target_parent_dt': 'Purchase Order',
'target_parent_field': 'per_received', 'target_parent_field': 'per_received',
'target_ref_field': 'qty', 'target_ref_field': 'qty',
'source_field': 'qty', 'source_field': 'received_qty',
'second_source_dt': 'Purchase Receipt Item',
'second_source_field': 'received_qty',
'second_join_field': 'purchase_order_item',
'percent_join_field':'purchase_order', 'percent_join_field':'purchase_order',
# 'percent_join_field': 'prevdoc_docname',
'overflow_type': 'receipt', 'overflow_type': 'receipt',
'extra_cond': """ and exists(select name from `tabPurchase Invoice` 'extra_cond': """ and exists(select name from `tabPurchase Invoice`
where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""" where name=`tabPurchase Invoice Item`.parent and update_stock = 1)"""
}, })
{ if cint(self.is_return):
'source_dt': 'Purchase Invoice Item', self.status_updater.append({
'target_dt': 'Purchase Order Item', 'source_dt': 'Purchase Invoice Item',
'join_field': 'po_detail', 'target_dt': 'Purchase Order Item',
'target_field': 'returned_qty', 'join_field': 'po_detail',
'target_parent_dt': 'Purchase Order', 'target_field': 'returned_qty',
# 'target_parent_field': 'per_received', 'source_field': '-1 * qty',
# 'target_ref_field': 'qty', 'second_source_dt': 'Purchase Receipt Item',
'source_field': '-1 * qty', 'second_source_field': '-1 * qty',
# 'percent_join_field': 'prevdoc_docname', 'second_join_field': 'purchase_order_item',
# 'overflow_type': 'receipt', 'overflow_type': 'receipt',
'extra_cond': """ and exists (select name from `tabPurchase Invoice` 'extra_cond': """ and exists (select name from `tabPurchase Invoice`
where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)""" where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)"""
} })
])
def validate_purchase_receipt_if_update_stock(self): def validate_purchase_receipt_if_update_stock(self):
if self.update_stock: if self.update_stock:
@@ -327,13 +329,13 @@ class PurchaseInvoice(BuyingController):
self.check_prev_docstatus() self.check_prev_docstatus()
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status()
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
self.company, self.base_grand_total) self.company, self.base_grand_total)
if not self.is_return: if not self.is_return:
self.update_against_document_in_jv() self.update_against_document_in_jv()
self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() self.update_billing_status_in_pr()
@@ -763,13 +765,13 @@ class PurchaseInvoice(BuyingController):
self.check_for_closed_status() self.check_for_closed_status()
self.update_status_updater_args() self.update_status_updater_args()
self.update_prevdoc_status()
if not self.is_return: if not self.is_return:
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
self.update_prevdoc_status()
self.update_billing_status_for_zero_amount_refdoc("Purchase Order") self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
self.update_billing_status_in_pr() self.update_billing_status_in_pr()

View File

@@ -6,8 +6,8 @@ frappe.listview_settings['Purchase Invoice'] = {
add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return", "release_date", "on_hold"], "currency", "is_return", "release_date", "on_hold"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) { if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"]
} else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { } else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) {
if(cint(doc.on_hold) && !doc.release_date) { if(cint(doc.on_hold) && !doc.release_date) {
return [__("On Hold"), "darkgrey"]; return [__("On Hold"), "darkgrey"];
@@ -18,9 +18,9 @@ frappe.listview_settings['Purchase Invoice'] = {
} else { } else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"]; return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"];
} }
} else if(flt(doc.outstanding_amount) < 0 && doc.docstatus == 1) { } else if(cint(doc.is_return)) {
return [__("Debit Note Issued"), "darkgrey", "outstanding_amount,<,0"] return [__("Return"), "darkgrey", "is_return,=,Yes"];
}else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) { } else if(flt(doc.outstanding_amount)==0 && doc.docstatus==1) {
return [__("Paid"), "green", "outstanding_amount,=,0"]; return [__("Paid"), "green", "outstanding_amount,=,0"];
} }
} }

View File

@@ -402,9 +402,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.save() pi.save()
pi.submit() pi.submit()
self.assertEqual(pi.payment_schedule[0].payment_amount, 756.15) self.assertEqual(pi.payment_schedule[0].payment_amount, 606.15)
self.assertEqual(pi.payment_schedule[0].due_date, pi.posting_date) self.assertEqual(pi.payment_schedule[0].due_date, pi.posting_date)
self.assertEqual(pi.payment_schedule[1].payment_amount, 756.15) self.assertEqual(pi.payment_schedule[1].payment_amount, 606.15)
self.assertEqual(pi.payment_schedule[1].due_date, add_days(pi.posting_date, 30)) self.assertEqual(pi.payment_schedule[1].due_date, add_days(pi.posting_date, 30))
pi.load_from_db() pi.load_from_db()

View File

@@ -54,8 +54,8 @@ class SalesInvoice(SellingController):
def set_indicator(self): def set_indicator(self):
"""Set indicator for portal""" """Set indicator for portal"""
if cint(self.is_return) == 1: if self.outstanding_amount < 0:
self.indicator_title = _("Return") self.indicator_title = _("Credit Note Issued")
self.indicator_color = "darkgrey" self.indicator_color = "darkgrey"
elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()): elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()):
self.indicator_color = "orange" self.indicator_color = "orange"
@@ -63,8 +63,8 @@ class SalesInvoice(SellingController):
elif self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()): elif self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()):
self.indicator_color = "red" self.indicator_color = "red"
self.indicator_title = _("Overdue") self.indicator_title = _("Overdue")
elif self.outstanding_amount < 0: elif cint(self.is_return) == 1:
self.indicator_title = _("Credit Note Issued") self.indicator_title = _("Return")
self.indicator_color = "darkgrey" self.indicator_color = "darkgrey"
else: else:
self.indicator_color = "green" self.indicator_color = "green"
@@ -256,7 +256,7 @@ class SalesInvoice(SellingController):
def update_status_updater_args(self): def update_status_updater_args(self):
if cint(self.update_stock): if cint(self.update_stock):
self.status_updater.extend([{ self.status_updater.append({
'source_dt':'Sales Invoice Item', 'source_dt':'Sales Invoice Item',
'target_dt':'Sales Order Item', 'target_dt':'Sales Order Item',
'target_parent_dt':'Sales Order', 'target_parent_dt':'Sales Order',
@@ -274,21 +274,20 @@ class SalesInvoice(SellingController):
'overflow_type': 'delivery', 'overflow_type': 'delivery',
'extra_cond': """ and exists(select name from `tabSales Invoice` 'extra_cond': """ and exists(select name from `tabSales Invoice`
where name=`tabSales Invoice Item`.parent and update_stock = 1)""" where name=`tabSales Invoice Item`.parent and update_stock = 1)"""
}, })
{ if cint(self.is_return):
'source_dt': 'Sales Invoice Item', self.status_updater.append({
'target_dt': 'Sales Order Item', 'source_dt': 'Sales Invoice Item',
'join_field': 'so_detail', 'target_dt': 'Sales Order Item',
'target_field': 'returned_qty', 'join_field': 'so_detail',
'target_parent_dt': 'Sales Order', 'target_field': 'returned_qty',
# 'target_parent_field': 'per_delivered', 'target_parent_dt': 'Sales Order',
# 'target_ref_field': 'qty', 'source_field': '-1 * qty',
'source_field': '-1 * qty', 'second_source_dt': 'Delivery Note Item',
# 'percent_join_field': 'sales_order', 'second_source_field': '-1 * qty',
# 'overflow_type': 'delivery', 'second_join_field': 'so_detail',
'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)""" 'extra_cond': """ and exists (select name from `tabSales Invoice` where name=`tabSales Invoice Item`.parent and update_stock=1 and is_return=1)"""
} })
])
def check_credit_limit(self): def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
@@ -504,12 +503,15 @@ class SalesInvoice(SellingController):
def so_dn_required(self): def so_dn_required(self):
"""check in manage account if sales order / delivery note required or not.""" """check in manage account if sales order / delivery note required or not."""
if self.is_return:
return
dic = {'Sales Order':['so_required', 'is_pos'],'Delivery Note':['dn_required', 'update_stock']} dic = {'Sales Order':['so_required', 'is_pos'],'Delivery Note':['dn_required', 'update_stock']}
for i in dic: for i in dic:
if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes': if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes':
for d in self.get('items'): for d in self.get('items'):
if frappe.get_cached_value('Item', d.item_code, 'is_stock_item') == 1 \ is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item')
and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1]): if (d.item_code and is_stock_item == 1\
and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])):
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1) msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
@@ -1167,6 +1169,8 @@ class SalesInvoice(SellingController):
self.set_missing_values(for_validate = True) self.set_missing_values(for_validate = True)
def validate_inter_company_party(doctype, party, company, inter_company_invoice_reference): def validate_inter_company_party(doctype, party, company, inter_company_invoice_reference):
if not party:
return
if doctype == "Sales Invoice": if doctype == "Sales Invoice":
partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer" partytype, ref_partytype, internal = "Customer", "Supplier", "is_internal_customer"
ref_doc = "Purchase Invoice" ref_doc = "Purchase Invoice"

View File

@@ -6,16 +6,16 @@ frappe.listview_settings['Sales Invoice'] = {
add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company", add_fields: ["customer", "customer_name", "base_grand_total", "outstanding_amount", "due_date", "company",
"currency", "is_return"], "currency", "is_return"],
get_indicator: function(doc) { get_indicator: function(doc) {
if(cint(doc.is_return)==1) { if(flt(doc.outstanding_amount) < 0) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0) {
return [__("Paid"), "green", "outstanding_amount,=,0"]
} else if(flt(doc.outstanding_amount) < 0) {
return [__("Credit Note Issued"), "darkgrey", "outstanding_amount,<,0"] return [__("Credit Note Issued"), "darkgrey", "outstanding_amount,<,0"]
}else if (flt(doc.outstanding_amount) > 0 && doc.due_date >= frappe.datetime.get_today()) { } else if (flt(doc.outstanding_amount) > 0 && doc.due_date >= frappe.datetime.get_today()) {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>,Today"] return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>,Today"]
} else if (flt(doc.outstanding_amount) > 0 && doc.due_date < frappe.datetime.get_today()) { } else if (flt(doc.outstanding_amount) > 0 && doc.due_date < frappe.datetime.get_today()) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<=,Today"] return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<=,Today"]
} else if(cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"];
} else if(flt(doc.outstanding_amount)==0) {
return [__("Paid"), "green", "outstanding_amount,=,0"]
} }
}, },
right_column: "grand_total" right_column: "grand_total"

View File

@@ -314,13 +314,11 @@ class Subscription(Document):
self.save() self.save()
@property
def is_postpaid_to_invoice(self): def is_postpaid_to_invoice(self):
return getdate(nowdate()) > getdate(self.current_invoice_end) or \ return getdate(nowdate()) > getdate(self.current_invoice_end) or \
(getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \ (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \
not self.has_outstanding_invoice() not self.has_outstanding_invoice()
@property
def is_prepaid_to_invoice(self): def is_prepaid_to_invoice(self):
if not self.generate_invoice_at_period_start: if not self.generate_invoice_at_period_start:
return False return False
@@ -340,7 +338,7 @@ class Subscription(Document):
2. Change the `Subscription` status to 'Past Due Date' 2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled' 3. Change the `Subscription` status to 'Cancelled'
""" """
if self.is_postpaid_to_invoice or self.is_prepaid_to_invoice: if self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice():
self.generate_invoice() self.generate_invoice()
if self.current_invoice_is_past_due(): if self.current_invoice_is_past_due():
self.status = 'Past Due Date' self.status = 'Past Due Date'

View File

@@ -39,7 +39,9 @@ def add_payment_to_transaction(transaction, payment_entry, gl_entry):
"payment_entry": payment_entry.name, "payment_entry": payment_entry.name,
"allocated_amount": allocated_amount "allocated_amount": allocated_amount
}) })
transaction.save() transaction.save()
transaction.update_allocations()
@frappe.whitelist() @frappe.whitelist()
def get_linked_payments(bank_transaction): def get_linked_payments(bank_transaction):
@@ -56,7 +58,11 @@ def get_linked_payments(bank_transaction):
return check_amount_vs_description(amount_matching, description_matching) return check_amount_vs_description(amount_matching, description_matching)
elif description_matching: elif description_matching:
return sorted(description_matching, key = lambda x: x["posting_date"], reverse=True) description_matching = filter(lambda x: not x.get('clearance_date'), description_matching)
if not description_matching:
return []
return sorted(list(description_matching), key = lambda x: x["posting_date"], reverse=True)
else: else:
return [] return []
@@ -71,7 +77,7 @@ def check_matching_amount(bank_account, company, transaction):
payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date", payment_entries = frappe.get_all("Payment Entry", fields=["'Payment Entry' as doctype", "name", "paid_amount", "payment_type", "reference_no", "reference_date",
"party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)], "party", "party_type", "posting_date", "{0}".format(currency_field)], filters=[["paid_amount", "like", "{0}%".format(amount)],
["docstatus", "=", "1"], ["payment_type", "=", payment_type], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]]) ["docstatus", "=", "1"], ["payment_type", "=", [payment_type, "Internal Transfer"]], ["ifnull(clearance_date, '')", "=", ""], ["{0}".format(account_from_to), "=", "{0}".format(bank_account)]])
if transaction.credit > 0: if transaction.credit > 0:
journal_entries = frappe.db.sql(""" journal_entries = frappe.db.sql("""
@@ -97,7 +103,8 @@ def check_matching_amount(bank_account, company, transaction):
journal_entries = frappe.db.sql(""" journal_entries = frappe.db.sql("""
SELECT SELECT
'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no, 'Journal Entry' as doctype, je.name, je.posting_date, je.cheque_no as reference_no,
je.pay_to_recd_from as party, je.cheque_date as reference_date, jea.credit_in_account_currency as paid_amount jea.account_currency as currency, je.pay_to_recd_from as party, je.cheque_date as reference_date,
jea.credit_in_account_currency as paid_amount
FROM FROM
`tabJournal Entry Account` as jea `tabJournal Entry Account` as jea
JOIN JOIN
@@ -107,12 +114,17 @@ def check_matching_amount(bank_account, company, transaction):
WHERE WHERE
(je.clearance_date is null or je.clearance_date='0000-00-00') (je.clearance_date is null or je.clearance_date='0000-00-00')
AND AND
jea.account = %s jea.account = %(bank_account)s
AND AND
jea.credit_in_account_currency like %s jea.credit_in_account_currency like %(txt)s
AND AND
je.docstatus = 1 je.docstatus = 1
""", (bank_account, amount), as_dict=True) """, {
'bank_account': bank_account,
'txt': '%%%s%%' % amount
}, as_dict=True)
frappe.errprint(journal_entries)
if transaction.credit > 0: if transaction.credit > 0:
sales_invoices = frappe.db.sql(""" sales_invoices = frappe.db.sql("""
@@ -213,9 +225,14 @@ def get_matching_descriptions_data(company, transaction):
company_currency = get_company_currency(company) company_currency = get_company_currency(company)
for key, value in iteritems(links): for key, value in iteritems(links):
if key == "Payment Entry": if key == "Payment Entry":
data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]], fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no", "reference_date", "paid_amount", "paid_to_account_currency as currency"])) data.extend(frappe.get_all("Payment Entry", filters=[["name", "in", value]],
fields=["'Payment Entry' as doctype", "posting_date", "party", "reference_no",
"reference_date", "paid_amount", "paid_to_account_currency as currency", "clearance_date"]))
if key == "Journal Entry": if key == "Journal Entry":
journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]], fields=["name", "'Journal Entry' as doctype", "posting_date", "pay_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date", "total_credit as paid_amount"]) journal_entries = frappe.get_all("Journal Entry", filters=[["name", "in", value]],
fields=["name", "'Journal Entry' as doctype", "posting_date",
"pay_to_recd_from as party", "cheque_no as reference_no", "cheque_date as reference_date",
"total_credit as paid_amount", "clearance_date"])
for journal_entry in journal_entries: for journal_entry in journal_entries:
journal_entry_accounts = frappe.get_all("Journal Entry Account", filters={"parenttype": journal_entry["doctype"], "parent": journal_entry["name"]}, fields=["account_currency"]) journal_entry_accounts = frappe.get_all("Journal Entry Account", filters={"parenttype": journal_entry["doctype"], "parent": journal_entry["name"]}, fields=["account_currency"])
journal_entry["currency"] = journal_entry_accounts[0]["account_currency"] if journal_entry_accounts else company_currency journal_entry["currency"] = journal_entry_accounts[0]["account_currency"] if journal_entry_accounts else company_currency
@@ -236,6 +253,9 @@ def check_amount_vs_description(amount_matching, description_matching):
if description_matching: if description_matching:
for am_match in amount_matching: for am_match in amount_matching:
for des_match in description_matching: for des_match in description_matching:
if des_match.get("clearance_date"):
continue
if am_match["party"] == des_match["party"]: if am_match["party"] == des_match["party"]:
if am_match not in result: if am_match not in result:
result.append(am_match) result.append(am_match)

View File

@@ -1957,6 +1957,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
}], }],
function(values){ function(values){
me.item_batch_no[me.items[0].item_code] = values.batch; me.item_batch_no[me.items[0].item_code] = values.batch;
const item = me.frm.doc.items.find(
({ item_code }) => item_code === me.items[0].item_code
);
if (item) {
item.batch_no = values.batch;
}
}, },
__('Select Batch No')) __('Select Batch No'))
} }

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Payable"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Payable Summary"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -111,10 +111,10 @@
<th style="width: 4%">{%= __("Age (Days)") %}</th> <th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %}
<th style="width: 16%">{%= __("Reference") %}</th> <th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th> <th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %} {% } else { %}
<th style="width: 26%">{%= __("Reference") %}</th> <th style="width: 24%">{%= __("Reference") %}</th>
{% } %} {% } %}
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_pdc_in_print) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th> <th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
@@ -127,7 +127,7 @@
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_pdc_in_print) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 10%">{%= __("Customer LPO No.") %}</th> <th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %} {% } %}
<th style="width: 10%">{%= __("PDC/LC Ref") %}</th> <th style="width: 10%">{%= __("PDC/LC Ref") %}</th>
<th style="width: 10%">{%= __("PDC/LC Amount") %}</th> <th style="width: 10%">{%= __("PDC/LC Amount") %}</th>

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Receivable"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -100,11 +100,14 @@ class ReceivablePayableReport(object):
self.filters["range2"] = "60" self.filters["range2"] = "60"
if not "range3" in self.filters: if not "range3" in self.filters:
self.filters["range3"] = "90" self.filters["range3"] = "90"
if not "range4" in self.filters:
self.filters["range4"] = "120"
for label in ("0-{range1}".format(range1=self.filters["range1"]), for label in ("0-{range1}".format(range1=self.filters["range1"]),
"{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]), "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]), "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
"{range3}-{above}".format(range3=cint(self.filters["range3"])+ 1, above=_("Above"))): "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))):
columns.append({ columns.append({
"label": label, "label": label,
"fieldname":label, "fieldname":label,
@@ -328,18 +331,17 @@ class ReceivablePayableReport(object):
entry_date = gle.posting_date entry_date = gle.posting_date
row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2),
cint(self.filters.range3), self.age_as_on, entry_date, outstanding_amount) cint(self.filters.range3), cint(self.filters.range4), self.age_as_on, entry_date, outstanding_amount)
# issue 6371-Ageing buckets should not have amounts if due date is not reached # issue 6371-Ageing buckets should not have amounts if due date is not reached
if self.filters.ageing_based_on == "Due Date" \ if self.filters.ageing_based_on == "Due Date" \
and getdate(due_date) > getdate(self.filters.report_date): and getdate(due_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0 row[-1]=row[-2]=row[-3]=row[-4]=row[-5]=0
if self.filters.ageing_based_on == "Supplier Invoice Date" \ if self.filters.ageing_based_on == "Supplier Invoice Date" \
and getdate(bill_date) > getdate(self.filters.report_date): and getdate(bill_date) > getdate(self.filters.report_date):
row[-1]=row[-2]=row[-3]=row[-4]=0 row[-1]=row[-2]=row[-3]=row[-4]=row[-5]=0
if self.filters.get(scrub(args.get("party_type"))): if self.filters.get(scrub(args.get("party_type"))):
row.append(gle.account_currency) row.append(gle.account_currency)
@@ -585,13 +587,13 @@ class ReceivablePayableReport(object):
return payment_term_map return payment_term_map
def get_chart_data(self, columns, data): def get_chart_data(self, columns, data):
ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+4] ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+5]
rows = [] rows = []
for d in data: for d in data:
rows.append( rows.append(
{ {
'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+4] 'values': d[self.ageing_col_idx_start : self.ageing_col_idx_start+5]
} }
) )
@@ -610,21 +612,22 @@ def execute(filters=None):
} }
return ReceivablePayableReport(filters).run(args) return ReceivablePayableReport(filters).run(args)
def get_ageing_data(first_range, second_range, third_range, age_as_on, entry_date, outstanding_amount): def get_ageing_data(first_range, second_range, third_range,
# [0-30, 30-60, 60-90, 90-above] fourth_range, age_as_on, entry_date, outstanding_amount):
outstanding_range = [0.0, 0.0, 0.0, 0.0] # [0-30, 30-60, 60-90, 90-120, 120-above]
outstanding_range = [0.0, 0.0, 0.0, 0.0, 0.0]
if not (age_as_on and entry_date): if not (age_as_on and entry_date):
return [0] + outstanding_range return [0] + outstanding_range
age = (getdate(age_as_on) - getdate(entry_date)).days or 0 age = (getdate(age_as_on) - getdate(entry_date)).days or 0
index = None index = None
for i, days in enumerate([first_range, second_range, third_range]): for i, days in enumerate([first_range, second_range, third_range, fourth_range]):
if age <= days: if age <= days:
index = i index = i
break break
if index is None: index = 3 if index is None: index = 4
outstanding_range[index] = outstanding_amount outstanding_range[index] = outstanding_amount
return [age] + outstanding_range return [age] + outstanding_range

View File

@@ -44,6 +44,13 @@ frappe.query_reports["Accounts Receivable Summary"] = {
"default": "90", "default": "90",
"reqd": 1 "reqd": 1
}, },
{
"fieldname":"range4",
"label": __("Ageing Range 4"),
"fieldtype": "Int",
"default": "120",
"reqd": 1
},
{ {
"fieldname":"finance_book", "fieldname":"finance_book",
"label": __("Finance Book"), "label": __("Finance Book"),

View File

@@ -82,8 +82,15 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"width": 160 "width": 160
}, },
{ {
"label": _(str(self.filters.range3) + _("-Above")), "label": _(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldname": scrub(str(self.filters.range3) + _("-Above")), "fieldname": scrub(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range4) + _("-Above")),
"fieldname": scrub(str(self.filters.range4) + _("-Above")),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "currency", "options": "currency",
"width": 160 "width": 160
@@ -152,7 +159,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
row += [ row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt, party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,
party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range5
] ]
if args.get("party_type") == "Customer": if args.get("party_type") == "Customer":
@@ -178,6 +185,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
"range2": 0, "range2": 0,
"range3": 0, "range3": 0,
"range4": 0, "range4": 0,
"range5": 0,
"sales_person": [] "sales_person": []
}) })
) )
@@ -209,7 +217,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
cols += ["bill_no", "bill_date"] cols += ["bill_no", "bill_date"]
cols += ["invoiced_amt", "paid_amt", "credit_amt", cols += ["invoiced_amt", "paid_amt", "credit_amt",
"outstanding_amt", "age", "range1", "range2", "range3", "range4", "currency", "pdc/lc_date", "pdc/lc_ref", "outstanding_amt", "age", "range1", "range2", "range3", "range4", "range5", "currency", "pdc/lc_date", "pdc/lc_ref",
"pdc/lc_amount"] "pdc/lc_amount"]
if args.get("party_type") == "Supplier": if args.get("party_type") == "Supplier":

View File

@@ -321,7 +321,10 @@ def sort_accounts(accounts, is_root=False, key="name"):
"""Sort root types as Asset, Liability, Equity, Income, Expense""" """Sort root types as Asset, Liability, Equity, Income, Expense"""
def compare_accounts(a, b): def compare_accounts(a, b):
if is_root: if re.split('\W+', a[key])[0].isdigit():
# if chart of accounts is numbered, then sort by number
return cmp(a[key], b[key])
elif is_root:
if a.report_type != b.report_type and a.report_type == "Balance Sheet": if a.report_type != b.report_type and a.report_type == "Balance Sheet":
return -1 return -1
if a.root_type != b.root_type and a.root_type == "Asset": if a.root_type != b.root_type and a.root_type == "Asset":
@@ -330,10 +333,6 @@ def sort_accounts(accounts, is_root=False, key="name"):
return -1 return -1
if a.root_type == "Income" and b.root_type == "Expense": if a.root_type == "Income" and b.root_type == "Expense":
return -1 return -1
else:
if re.split('\W+', a[key])[0].isdigit():
# if chart of accounts is numbered, then sort by number
return cmp(a[key], b[key])
return 1 return 1
accounts.sort(key = functools.cmp_to_key(compare_accounts)) accounts.sort(key = functools.cmp_to_key(compare_accounts))

View File

@@ -211,6 +211,11 @@ frappe.query_reports["General Ledger"] = {
"label": __("Currency"), "label": __("Currency"),
"fieldtype": "Select", "fieldtype": "Select",
"options": erpnext.get_presentation_currency_list() "options": erpnext.get_presentation_currency_list()
},
{
"fieldname": "show_opening_entries",
"label": __("Show Opening Entries"),
"fieldtype": "Check"
} }
] ]
} }

View File

@@ -283,7 +283,8 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date) from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
for gle in gl_entries: for gle in gl_entries:
if gle.posting_date < from_date or cstr(gle.is_opening) == "Yes": if (gle.posting_date < from_date or
(cstr(gle.is_opening) == "Yes" and not filters.get("show_opening_entries"))):
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'opening', gle) update_value_in_dict(gle_map[gle.get(group_by)].totals, 'opening', gle)
update_value_in_dict(totals, 'opening', gle) update_value_in_dict(totals, 'opening', gle)

View File

@@ -4,6 +4,13 @@
frappe.query_reports["Inactive Sales Items"] = { frappe.query_reports["Inactive Sales Items"] = {
"filters": [ "filters": [
{
fieldname: "territory",
label: __("Territory"),
fieldtype: "Link",
options: "Territory",
reqd: 1,
},
{ {
fieldname: "item", fieldname: "item",
label: __("Item"), label: __("Item"),

View File

@@ -7,13 +7,11 @@ from frappe.utils import getdate, add_days, today, cint
from frappe import _ from frappe import _
def execute(filters=None): def execute(filters=None):
columns = get_columns() columns = get_columns()
data = get_data(filters) data = get_data(filters)
return columns, data return columns, data
def get_columns(): def get_columns():
columns = [ columns = [
{ {
"fieldname": "territory", "fieldname": "territory",
@@ -30,7 +28,7 @@ def get_columns():
"width": 150 "width": 150
}, },
{ {
"fieldname": "item_name", "fieldname": "item",
"fieldtype": "Link", "fieldtype": "Link",
"options": "Item", "options": "Item",
"label": "Item", "label": "Item",
@@ -74,56 +72,68 @@ def get_columns():
def get_data(filters): def get_data(filters):
data = [] data = []
items = get_items(filters) items = get_items(filters)
territories = get_territories(filters)
sales_invoice_data = get_sales_details(filters) sales_invoice_data = get_sales_details(filters)
for item in items: for territory in territories:
row = { for item in items:
row = {
"territory": territory.name,
"item_group": item.item_group, "item_group": item.item_group,
"item": item.name, "item": item.item_code,
"item_name": item.item_name "item_name": item.item_name
} }
if sales_invoice_data.get(item.name): if sales_invoice_data.get((territory.name,item.item_code)):
item_obj = sales_invoice_data[item.name] item_obj = sales_invoice_data[(territory.name,item.item_code)]
if item_obj.days_since_last_order > cint(filters['days']): if item_obj.days_since_last_order > cint(filters['days']):
row.update({ row.update({
"territory": item_obj.territory, "territory": item_obj.territory,
"customer": item_obj.customer, "customer": item_obj.customer,
"last_order_date": item_obj.last_order_date, "last_order_date": item_obj.last_order_date,
"qty": item_obj.qty, "qty": item_obj.qty,
"days_since_last_order": item_obj.days_since_last_order "days_since_last_order": item_obj.days_since_last_order
}) })
else:
continue
data.append(row) data.append(row)
return data return data
def get_sales_details(filters): def get_sales_details(filters):
data = [] data = []
item_details_map = {} item_details_map = {}
date_field = "s.transaction_date" if filters["based_on"] == "Sales Order" else "s.posting_date" date_field = "s.transaction_date" if filters["based_on"] == "Sales Order" else "s.posting_date"
sales_data = frappe.db.sql(""" sales_data = frappe.db.sql("""
select s.territory, s.customer, si.item_group, si.item_name, si.qty, {date_field} as last_order_date, select s.territory, s.customer, si.item_group, si.item_code, si.qty, {date_field} as last_order_date,
DATEDIFF(CURDATE(), {date_field}) as days_since_last_order DATEDIFF(CURDATE(), {date_field}) as days_since_last_order
from `tab{doctype}` s, `tab{doctype} Item` si from `tab{doctype}` s, `tab{doctype} Item` si
where s.name = si.parent and s.docstatus = 1 where s.name = si.parent and s.docstatus = 1
group by si.name order by days_since_last_order """ #nosec order by days_since_last_order """ #nosec
.format(date_field = date_field, doctype = filters['based_on']), as_dict=1) .format(date_field = date_field, doctype = filters['based_on']), as_dict=1)
for d in sales_data: for d in sales_data:
item_details_map.setdefault(d.item_name, d) item_details_map.setdefault((d.territory,d.item_code), d)
return item_details_map return item_details_map
def get_items(filters): def get_territories(filters):
filter_dict = {}
if filters.get("territory"):
filter_dict.update({'name': filters['territory']})
territories = frappe.get_all("Territory", fields=["name"], filters=filter_dict)
return territories
def get_items(filters):
filters_dict = { filters_dict = {
"disabled": 0, "disabled": 0,
"is_stock_item": 1 "is_stock_item": 1
@@ -139,6 +149,6 @@ def get_items(filters):
"name": filters["item"] "name": filters["item"]
}) })
items = frappe.get_all("Item", fields=["name", "item_group", "item_name"], filters=filters_dict, order_by="name") items = frappe.get_all("Item", fields=["name", "item_group", "item_name", "item_code"], filters=filters_dict, order_by="name")
return items return items

View File

@@ -333,6 +333,9 @@ def reconcile_against_document(args):
doc = frappe.get_doc(d.voucher_type, d.voucher_no) doc = frappe.get_doc(d.voucher_type, d.voucher_no)
doc.make_gl_entries(cancel = 0, adv_adj =1) doc.make_gl_entries(cancel = 0, adv_adj =1)
if d.voucher_type in ('Payment Entry', 'Journal Entry'):
doc.update_expense_claim()
def check_if_advance_entry_modified(args): def check_if_advance_entry_modified(args):
""" """
check if there is already a voucher reference check if there is already a voucher reference
@@ -375,9 +378,9 @@ def check_if_advance_entry_modified(args):
def validate_allocated_amount(args): def validate_allocated_amount(args):
if args.get("allocated_amount") < 0: if args.get("allocated_amount") < 0:
throw(_("Allocated amount can not be negative")) throw(_("Allocated amount cannot be negative"))
elif args.get("allocated_amount") > args.get("unadjusted_amount"): elif args.get("allocated_amount") > args.get("unadjusted_amount"):
throw(_("Allocated amount can not greater than unadjusted amount")) throw(_("Allocated amount cannot be greater than unadjusted amount"))
def update_reference_in_journal_entry(d, jv_obj): def update_reference_in_journal_entry(d, jv_obj):
""" """

View File

@@ -8,7 +8,9 @@ import frappe.defaults
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from frappe.utils import flt, add_days, nowdate, getdate from frappe.utils import flt, add_days, nowdate, getdate
from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.item.test_item import make_item
from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry) from erpnext.buying.doctype.purchase_order.purchase_order \
import (make_purchase_receipt, make_purchase_invoice as make_pi_from_po, make_rm_stock_entry as make_subcontract_transfer_entry)
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as make_pi_from_pr
from erpnext.stock.doctype.material_request.test_material_request import make_material_request from erpnext.stock.doctype.material_request.test_material_request import make_material_request
from erpnext.stock.doctype.material_request.material_request import make_purchase_order from erpnext.stock.doctype.material_request.material_request import make_purchase_order
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -62,7 +64,7 @@ class TestPurchaseOrder(unittest.TestCase):
frappe.db.set_value('Item', '_Test Item', 'tolerance', 50) frappe.db.set_value('Item', '_Test Item', 'tolerance', 50)
pi = make_purchase_invoice(po.name) pi = make_pi_from_po(po.name)
pi.update_stock = 1 pi.update_stock = 1
pi.items[0].qty = 12 pi.items[0].qty = 12
pi.insert() pi.insert()
@@ -89,7 +91,7 @@ class TestPurchaseOrder(unittest.TestCase):
create_pr_against_po(po.name) create_pr_against_po(po.name)
make_purchase_invoice(po.name) make_pi_from_po(po.name)
existing_ordered_qty = get_ordered_qty() existing_ordered_qty = get_ordered_qty()
existing_requested_qty = get_requested_qty() existing_requested_qty = get_requested_qty()
@@ -108,14 +110,85 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(po.get("items")[0].amount, 1400) self.assertEqual(po.get("items")[0].amount, 1400)
self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3) self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
def test_update_qty(self):
po = create_purchase_order()
pr = make_pr_against_po(po.name, 2)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 2)
# Check received_qty after making PI from PR without update_stock checked
pi1 = make_pi_from_pr(pr.name)
pi1.get("items")[0].qty = 2
pi1.insert()
pi1.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 2)
# Check received_qty after making PI from PO with update_stock checked
pi2 = make_pi_from_po(po.name)
pi2.set("update_stock", 1)
pi2.get("items")[0].qty = 3
pi2.insert()
pi2.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
# Check received_qty after making PR from PO
pr = make_pr_against_po(po.name, 1)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
def test_return_against_purchase_order(self):
po = create_purchase_order()
pr = make_pr_against_po(po.name, 6)
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 6)
pi2 = make_pi_from_po(po.name)
pi2.set("update_stock", 1)
pi2.get("items")[0].qty = 3
pi2.insert()
pi2.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 9)
# Make return purchase receipt, purchase invoice and check quantity
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
import make_purchase_receipt as make_purchase_receipt_return
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice \
import make_purchase_invoice as make_purchase_invoice_return
pr1 = make_purchase_receipt_return(is_return=1, return_against=pr.name, qty=-3, do_not_submit=True)
pr1.items[0].purchase_order = po.name
pr1.items[0].purchase_order_item = po.items[0].name
pr1.submit()
pi1= make_purchase_invoice_return(is_return=1, return_against=pi2.name, qty=-1, update_stock=1, do_not_submit=True)
pi1.items[0].purchase_order = po.name
pi1.items[0].po_detail = po.items[0].name
pi1.submit()
po.load_from_db()
self.assertEqual(po.get("items")[0].received_qty, 5)
def test_make_purchase_invoice(self): def test_make_purchase_invoice(self):
po = create_purchase_order(do_not_submit=True) po = create_purchase_order(do_not_submit=True)
self.assertRaises(frappe.ValidationError, make_purchase_invoice, po.name) self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
po.submit() po.submit()
pi = make_purchase_invoice(po.name) pi = make_pi_from_po(po.name)
self.assertEqual(pi.doctype, "Purchase Invoice") self.assertEqual(pi.doctype, "Purchase Invoice")
self.assertEqual(len(pi.get("items", [])), 1) self.assertEqual(len(pi.get("items", [])), 1)
@@ -123,7 +196,7 @@ class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_invoice_with_terms(self): def test_make_purchase_invoice_with_terms(self):
po = create_purchase_order(do_not_save=True) po = create_purchase_order(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_purchase_invoice, po.name) self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
po.update( po.update(
{"payment_terms_template": "_Test Payment Term Template"} {"payment_terms_template": "_Test Payment Term Template"}
@@ -136,7 +209,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(getdate(po.payment_schedule[0].due_date), getdate(po.transaction_date)) self.assertEqual(getdate(po.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(po.payment_schedule[1].payment_amount, 2500.0) self.assertEqual(po.payment_schedule[1].payment_amount, 2500.0)
self.assertEqual(getdate(po.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30)) self.assertEqual(getdate(po.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
pi = make_purchase_invoice(po.name) pi = make_pi_from_po(po.name)
pi.save() pi.save()
self.assertEqual(pi.doctype, "Purchase Invoice") self.assertEqual(pi.doctype, "Purchase Invoice")
@@ -274,7 +347,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertTrue(po.get('payment_schedule')) self.assertTrue(po.get('payment_schedule'))
pi = make_purchase_invoice(po.name) pi = make_pi_from_po(po.name)
self.assertFalse(pi.get('payment_schedule')) self.assertFalse(pi.get('payment_schedule'))
@@ -285,7 +358,7 @@ class TestPurchaseOrder(unittest.TestCase):
po.submit() po.submit()
self.assertTrue(po.get('payment_schedule')) self.assertTrue(po.get('payment_schedule'))
pi = make_purchase_invoice(po.name) pi = make_pi_from_po(po.name)
pi.insert() pi.insert()
self.assertTrue(pi.get('payment_schedule')) self.assertTrue(pi.get('payment_schedule'))
@@ -365,7 +438,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6)
# Make Purchase Invoice # Make Purchase Invoice
pi = make_purchase_invoice(po.name) pi = make_pi_from_po(po.name)
pi.update_stock = 1 pi.update_stock = 1
pi.supplier_warehouse = "_Test Warehouse 1 - _TC" pi.supplier_warehouse = "_Test Warehouse 1 - _TC"
pi.insert() pi.insert()
@@ -469,6 +542,13 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEquals(se_items, supplied_items) self.assertEquals(se_items, supplied_items)
update_backflush_based_on("BOM") update_backflush_based_on("BOM")
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)
pr.get("items")[0].qty = received_qty or 5
pr.insert()
pr.submit()
return pr
def make_subcontracted_item(item_code): def make_subcontracted_item(item_code):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom

View File

@@ -291,6 +291,12 @@ def get_data():
"name": "Customers Without Any Sales Transactions", "name": "Customers Without Any Sales Transactions",
"doctype": "Customer" "doctype": "Customer"
}, },
{
"type": "report",
"is_query_report": True,
"name": "Sales Partners Commission",
"doctype": "Customer"
}
] ]
}, },
{ {

View File

@@ -237,9 +237,9 @@ class AccountsController(TransactionBase):
document_type = "{} Item".format(self.doctype) document_type = "{} Item".format(self.doctype)
parent_dict.update({"document_type": document_type}) parent_dict.update({"document_type": document_type})
# To fix the problems cause by renaming 'customer' to 'party_name- in selling docs # party_name field used for customer in quotation
if not parent_dict.get("customer") and parent_dict.get("party_name"): if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"):
parent_dict.update({"customer": parent_dict.get("party_name")}) parent_dict.update({"customer": parent_dict.get("party_name")})
for item in self.get("items"): for item in self.get("items"):
if item.get("item_code"): if item.get("item_code"):
@@ -348,7 +348,7 @@ class AccountsController(TransactionBase):
'fiscal_year': fiscal_year, 'fiscal_year': fiscal_year,
'voucher_type': self.doctype, 'voucher_type': self.doctype,
'voucher_no': self.name, 'voucher_no': self.name,
'remarks': self.get("remarks"), 'remarks': self.get("remarks") or self.get("remark"),
'debit': 0, 'debit': 0,
'credit': 0, 'credit': 0,
'debit_in_account_currency': 0, 'debit_in_account_currency': 0,
@@ -767,6 +767,9 @@ class AccountsController(TransactionBase):
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount) grand_total = grand_total - flt(self.write_off_amount)
if self.get("total_advance"):
grand_total -= self.get("total_advance")
if not self.get("payment_schedule"): if not self.get("payment_schedule"):
if self.get("payment_terms_template"): if self.get("payment_terms_template"):
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total) data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
@@ -812,6 +815,9 @@ class AccountsController(TransactionBase):
total = flt(total, self.precision("grand_total")) total = flt(total, self.precision("grand_total"))
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total')) grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
if self.get("total_advance"):
grand_total -= self.get("total_advance")
if self.doctype in ("Sales Invoice", "Purchase Invoice"): if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount) grand_total = grand_total - flt(self.write_off_amount)
if total != grand_total: if total != grand_total:

View File

@@ -287,19 +287,15 @@ def copy_attributes_to_variant(item, variant):
variant.set(field.fieldname, item.get(field.fieldname)) variant.set(field.fieldname, item.get(field.fieldname))
variant.variant_of = item.name variant.variant_of = item.name
if 'description' in allow_fields:
variant.has_variants = 0
if not variant.description:
variant.description = ""
if item.variant_based_on=='Item Attribute': if not variant.description:
if variant.attributes: variant.description = ""
attributes_description = ""
for d in variant.attributes:
attributes_description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
if attributes_description not in variant.description: if 'description' not in allow_fields:
variant.description += attributes_description if item.variant_based_on == 'Item Attribute' and not variant.description:
variant.description = "<div><b>" + item.name + "</b></div>"
for d in variant.attributes:
variant.description += "<div><b>" + d.attribute + "</b>: " + cstr(d.attribute_value) + "</div>"
def make_variant_item_code(template_item_code, template_item_name, variant): def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code""" """Uses template's item code and abbreviations to make variant's item code"""

View File

@@ -254,11 +254,13 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
and return_against in (select name from `tabDelivery Note` where per_billed < 100) and return_against in (select name from `tabDelivery Note` where per_billed < 100)
) )
) )
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc %(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
""" % { """ % {
"key": searchfield, "key": searchfield,
"fcond": get_filters_cond(doctype, filters, []), "fcond": get_filters_cond(doctype, filters, []),
"mcond": get_match_cond(doctype), "mcond": get_match_cond(doctype),
"start": start,
"page_len": page_len,
"txt": "%(txt)s" "txt": "%(txt)s"
}, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict) }, {"txt": ("%%%s%%" % txt)}, as_dict=as_dict)

View File

@@ -34,8 +34,8 @@ status_map = {
], ],
"Sales Order": [ "Sales Order": [
["Draft", None], ["Draft", None],
["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1 and self.order_type in ['Sales', 'Shopping Cart']"], ["To Deliver and Bill", "eval:self.per_delivered < 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Bill", "eval:self.per_delivered == 100 or self.order_type == 'Maintenance' and self.per_billed < 100 and self.docstatus == 1"], ["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"], ["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"], ["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
@@ -45,9 +45,9 @@ status_map = {
"Sales Invoice": [ "Sales Invoice": [
["Draft", None], ["Draft", None],
["Submitted", "eval:self.docstatus==1"], ["Submitted", "eval:self.docstatus==1"],
["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
["Return", "eval:self.is_return==1 and self.docstatus==1"], ["Return", "eval:self.is_return==1 and self.docstatus==1"],
["Paid", "eval:self.outstanding_amount<=0 and self.docstatus==1 and self.is_return==0"], ["Credit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1"],
["Credit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1 and self.is_return==0 and get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"], ["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"], ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
@@ -55,9 +55,9 @@ status_map = {
"Purchase Invoice": [ "Purchase Invoice": [
["Draft", None], ["Draft", None],
["Submitted", "eval:self.docstatus==1"], ["Submitted", "eval:self.docstatus==1"],
["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
["Return", "eval:self.is_return==1 and self.docstatus==1"], ["Return", "eval:self.is_return==1 and self.docstatus==1"],
["Paid", "eval:self.outstanding_amount<=0 and self.docstatus==1 and self.is_return==0"], ["Debit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1"],
["Debit Note Issued", "eval:self.outstanding_amount < 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"], ["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"], ["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],

View File

@@ -80,6 +80,7 @@ class StockController(AccountsController):
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": flt(sle.stock_value_difference, 2), "debit": flt(sle.stock_value_difference, 2),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"])) }, warehouse_account[sle.warehouse]["account_currency"]))
# to target warehouse / expense account # to target warehouse / expense account
@@ -89,7 +90,8 @@ class StockController(AccountsController):
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": flt(sle.stock_value_difference, 2), "credit": flt(sle.stock_value_difference, 2),
"project": item_row.get("project") or self.get("project") "project": item_row.get("project") or self.get("project"),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
})) }))
elif sle.warehouse not in warehouse_with_no_account: elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse) warehouse_with_no_account.append(sle.warehouse)
@@ -123,8 +125,17 @@ class StockController(AccountsController):
def get_voucher_details(self, default_expense_account, default_cost_center, sle_map): def get_voucher_details(self, default_expense_account, default_cost_center, sle_map):
if self.doctype == "Stock Reconciliation": if self.doctype == "Stock Reconciliation":
return [frappe._dict({ "name": voucher_detail_no, "expense_account": default_expense_account, reconciliation_purpose = frappe.db.get_value(self.doctype, self.name, "purpose")
"cost_center": default_cost_center }) for voucher_detail_no, sle in sle_map.items()] is_opening = "Yes" if reconciliation_purpose == "Opening Stock" else "No"
details = []
for voucher_detail_no in sle_map:
details.append(frappe._dict({
"name": voucher_detail_no,
"expense_account": default_expense_account,
"cost_center": default_cost_center,
"is_opening": is_opening
}))
return details
else: else:
details = self.get("items") details = self.get("items")

View File

@@ -5,79 +5,79 @@ frappe.provide("erpnext");
cur_frm.email_field = "email_id"; cur_frm.email_field = "email_id";
erpnext.LeadController = frappe.ui.form.Controller.extend({ erpnext.LeadController = frappe.ui.form.Controller.extend({
setup: function() { setup: function () {
this.frm.fields_dict.customer.get_query = function(doc, cdt, cdn) { this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {
return { query: "erpnext.controllers.queries.customer_query" } } return { query: "erpnext.controllers.queries.customer_query" }
}
this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
}, },
onload: function() { onload: function () {
if (cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) {
if(cur_frm.fields_dict.lead_owner.df.options.match(/^User/)) { cur_frm.fields_dict.lead_owner.get_query = function (doc, cdt, cdn) {
cur_frm.fields_dict.lead_owner.get_query = function(doc, cdt, cdn) {
return { query: "frappe.core.doctype.user.user.user_query" } return { query: "frappe.core.doctype.user.user.user_query" }
} }
} }
if(cur_frm.fields_dict.contact_by.df.options.match(/^User/)) { if (cur_frm.fields_dict.contact_by.df.options.match(/^User/)) {
cur_frm.fields_dict.contact_by.get_query = function(doc, cdt, cdn) { cur_frm.fields_dict.contact_by.get_query = function (doc, cdt, cdn) {
return { query: "frappe.core.doctype.user.user.user_query" } } return { query: "frappe.core.doctype.user.user.user_query" }
}
} }
}, },
refresh: function() { refresh: function () {
var doc = this.frm.doc; var doc = this.frm.doc;
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
frappe.dynamic_link = {doc: doc, fieldname: 'name', doctype: 'Lead'} frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
if(!doc.__islocal && doc.__onload && !doc.__onload.is_customer) { if (!doc.__islocal && doc.__onload && !doc.__onload.is_customer) {
this.frm.add_custom_button(__("Customer"), this.create_customer, __("Make")); this.frm.add_custom_button(__("Customer"), this.create_customer, __("Make"));
this.frm.add_custom_button(__("Opportunity"), this.create_opportunity, __("Make")); this.frm.add_custom_button(__("Opportunity"), this.create_opportunity, __("Make"));
this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Make")); this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Make"));
} }
if(!this.frm.doc.__islocal) { if (!this.frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(cur_frm); frappe.contacts.render_address_and_contact(cur_frm);
} else { } else {
frappe.contacts.clear_address_and_contact(cur_frm); frappe.contacts.clear_address_and_contact(cur_frm);
} }
}, },
create_customer: function() { create_customer: function () {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_customer", method: "erpnext.crm.doctype.lead.lead.make_customer",
frm: cur_frm frm: cur_frm
}) })
}, },
create_opportunity: function() { create_opportunity: function () {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_opportunity", method: "erpnext.crm.doctype.lead.lead.make_opportunity",
frm: cur_frm frm: cur_frm
}) })
}, },
make_quotation: function() { make_quotation: function () {
frappe.model.open_mapped_doc({ frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_quotation", method: "erpnext.crm.doctype.lead.lead.make_quotation",
frm: cur_frm frm: cur_frm
}) })
}, },
organization_lead: function() { organization_lead: function () {
if (this.frm.doc.organization_lead == 1) { this.frm.toggle_reqd("lead_name", !this.frm.doc.organization_lead);
this.frm.set_df_property('company_name', 'reqd', 1); this.frm.toggle_reqd("company_name", this.frm.doc.organization_lead);
} else {
this.frm.set_df_property('company_name', 'reqd', 0);
}
}, },
company_name: function() { company_name: function () {
if (this.frm.doc.organization_lead == 1) { if (this.frm.doc.organization_lead == 1) {
this.frm.set_value("lead_name", this.frm.doc.company_name); this.frm.set_value("lead_name", this.frm.doc.company_name);
} }
}, },
contact_date: function() { contact_date: function () {
if (this.frm.doc.contact_date) { if (this.frm.doc.contact_date) {
let d = moment(this.frm.doc.contact_date); let d = moment(this.frm.doc.contact_date);
d.add(1, "hours"); d.add(1, "hours");
@@ -86,4 +86,4 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
} }
}); });
$.extend(cur_frm.cscript, new erpnext.LeadController({frm: cur_frm})); $.extend(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));

View File

@@ -141,7 +141,7 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 1, "search_index": 1,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
@@ -499,7 +499,7 @@
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Lead Owner", "label": "Lead Owner",
"length": 0, "length": 0,
@@ -1389,7 +1389,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-10-02 09:12:23.415379", "modified": "2019-05-10 03:22:57.283628",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Lead", "name": "Lead",

View File

@@ -102,14 +102,18 @@ class Lead(SellingController):
def has_lost_quotation(self): def has_lost_quotation(self):
return frappe.db.get_value("Quotation", { return frappe.db.get_value("Quotation", {
"lead": self.name, "party_name": self.name,
"docstatus": 1, "docstatus": 1,
"status": "Lost" "status": "Lost"
}) })
def set_lead_name(self): def set_lead_name(self):
if not self.lead_name: if not self.lead_name:
frappe.db.set_value("Lead", self.name, "lead_name", self.company_name) # Check for leads being created through data import
if not self.company_name and not self.flags.ignore_mandatory:
frappe.throw(_("A Lead requires either a person's name or an organization's name"))
self.lead_name = self.company_name
@frappe.whitelist() @frappe.whitelist()
def make_customer(source_name, target_doc=None): def make_customer(source_name, target_doc=None):
@@ -146,8 +150,8 @@ def make_opportunity(source_name, target_doc=None):
"doctype": "Opportunity", "doctype": "Opportunity",
"field_map": { "field_map": {
"campaign_name": "campaign", "campaign_name": "campaign",
"doctype": "enquiry_from", "doctype": "opportunity_from",
"name": "lead", "name": "party_name",
"lead_name": "contact_display", "lead_name": "contact_display",
"company_name": "customer_name", "company_name": "customer_name",
"email_id": "contact_email", "email_id": "contact_email",
@@ -163,7 +167,7 @@ def make_quotation(source_name, target_doc=None):
{"Lead": { {"Lead": {
"doctype": "Quotation", "doctype": "Quotation",
"field_map": { "field_map": {
"name": "lead" "name": "party_name"
} }
}}, target_doc) }}, target_doc)
target_doc.quotation_to = "Lead" target_doc.quotation_to = "Lead"
@@ -225,4 +229,4 @@ def make_lead_from_communication(communication, ignore_communication_links=False
lead_name = lead.name lead_name = lead.name
link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links) link_communication_to_document(doc, "Lead", lead_name, ignore_communication_links)
return lead_name return lead_name

View File

@@ -8,6 +8,9 @@ def get_data():
'Quotation': 'party_name', 'Quotation': 'party_name',
'Opportunity': 'party_name' 'Opportunity': 'party_name'
}, },
'dynamic_links': {
'party_name': ['Lead', 'quotation_to']
},
'transactions': [ 'transactions': [
{ {
'items': ['Opportunity', 'Quotation'] 'items': ['Opportunity', 'Quotation']

View File

@@ -20,10 +20,22 @@ frappe.ui.form.on("Opportunity", {
}); });
}, },
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
},
party_name: function(frm) { party_name: function(frm) {
frm.toggle_display("contact_info", frm.doc.party_name);
if (frm.doc.opportunity_from == "Customer") { if (frm.doc.opportunity_from == "Customer") {
frm.trigger('set_contact_link'); frm.trigger('set_contact_link');
erpnext.utils.get_party_details(frm); erpnext.utils.get_party_details(frm);
} else if (frm.doc.opportunity_from == "Lead") {
erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
source_name: frm.doc.party_name,
frm: frm
});
} }
}, },
@@ -82,9 +94,9 @@ frappe.ui.form.on("Opportunity", {
set_contact_link: function(frm) { set_contact_link: function(frm) {
if(frm.doc.opportunity_from == "Customer" && frm.doc.party_name) { if(frm.doc.opportunity_from == "Customer" && frm.doc.party_name) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'customer', doctype: 'Customer'} frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Customer'}
} else if(frm.doc.opportunity_from == "Lead" && frm.doc.party_name) { } else if(frm.doc.opportunity_from == "Lead" && frm.doc.party_name) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'lead', doctype: 'Lead'} frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Lead'}
} }
}, },
@@ -138,12 +150,14 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
}; };
}); });
$.each([["lead", "lead"], me.frm.set_query('contact_person', erpnext.queries['contact_query'])
["customer", "customer"],
["contact_person", "contact_query"]], if (me.frm.doc.opportunity_from == "Lead") {
function(i, opts) { me.frm.set_query('party_name', erpnext.queries['lead']);
me.frm.set_query(opts[0], erpnext.queries[opts[1]]); }
}); else if (me.frm.doc.opportunity_from == "Cuatomer") {
me.frm.set_query('party_name', erpnext.queries['customer']);
}
}, },
create_quotation: function() { create_quotation: function() {
@@ -156,11 +170,6 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
$.extend(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm})); $.extend(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) {
if(doc.opportunity_from == 'Lead' && doc.party_name)
cur_frm.cscript.lead(doc, cdt, cdn);
}
cur_frm.cscript.item_code = function(doc, cdt, cdn) { cur_frm.cscript.item_code = function(doc, cdt, cdn) {
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if (d.item_code) { if (d.item_code) {
@@ -179,15 +188,6 @@ cur_frm.cscript.item_code = function(doc, cdt, cdn) {
} }
} }
cur_frm.cscript.lead = function(doc, cdt, cdn) {
cur_frm.toggle_display("contact_info", doc.party_name);
erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.lead.lead.make_opportunity",
source_name: cur_frm.doc.party_name,
frm: cur_frm
});
}
cur_frm.cscript['Declare Opportunity Lost'] = function() { cur_frm.cscript['Declare Opportunity Lost'] = function() {
var dialog = new frappe.ui.Dialog({ var dialog = new frappe.ui.Dialog({
title: __("Set as Lost"), title: __("Set as Lost"),

View File

@@ -99,7 +99,7 @@
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 1,
"label": "Opportunity From", "label": "Opportunity From",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -136,7 +136,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Customer/Lead", "label": "Party",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"oldfieldname": "customer", "oldfieldname": "customer",
@@ -878,7 +878,7 @@
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "next_contact_by", "collapsible_depends_on": "next_contact_by",
"columns": 0, "columns": 0,
"depends_on": "eval:doc.lead || doc.customer", "depends_on": "eval:doc.party_name",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "contact_info", "fieldname": "contact_info",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -912,7 +912,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.customer || doc.lead", "depends_on": "eval:doc.party_name",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "customer_address", "fieldname": "customer_address",
"fieldtype": "Link", "fieldtype": "Link",
@@ -1083,7 +1083,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.lead || doc.customer", "depends_on": "eval:doc.party_name",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "contact_person", "fieldname": "contact_person",
"fieldtype": "Link", "fieldtype": "Link",
@@ -1150,7 +1150,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.lead || doc.customer", "depends_on": "eval:doc.party_name",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "contact_email", "fieldname": "contact_email",
"fieldtype": "Data", "fieldtype": "Data",
@@ -1183,7 +1183,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.lead || doc.customer", "depends_on": "eval:doc.party_name",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "contact_mobile", "fieldname": "contact_mobile",
"fieldtype": "Small Text", "fieldtype": "Small Text",
@@ -1468,7 +1468,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-25 18:55:43.874656", "modified": "2019-05-17 19:03:32.740910",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Opportunity", "name": "Opportunity",

View File

@@ -17,5 +17,13 @@ frappe.listview_settings['Opportunity'] = {
listview.page.add_menu_item(__("Set as Closed"), function() { listview.page.add_menu_item(__("Set as Closed"), function() {
listview.call_for_selected_items(method, {"status": "Closed"}); listview.call_for_selected_items(method, {"status": "Closed"});
}); });
listview.page.fields_dict.opportunity_from.get_query = function() {
return {
"filters": {
"name": ["in", ["Customer", "Lead"]],
}
};
};
} }
}; };

View File

@@ -35,14 +35,14 @@ def get_data(filters):
for lead in frappe.get_all('Lead', fields = ['name', 'lead_name', 'company_name'], filters=lead_filters): for lead in frappe.get_all('Lead', fields = ['name', 'lead_name', 'company_name'], filters=lead_filters):
data = frappe.db.sql(""" data = frappe.db.sql("""
select select
`tabCommunication`.reference_doctype, `tabCommunication`.reference_name, `tabCommunication`.reference_doctype, `tabCommunication`.reference_name,
`tabCommunication`.content, `tabCommunication`.communication_date `tabCommunication`.content, `tabCommunication`.communication_date
from from
( (
(select name, lead from `tabOpportunity` where lead = %(lead)s) (select name, party_name as lead from `tabOpportunity` where opportunity_from='Lead' and party_name = %(lead)s)
union union
(select name, lead from `tabQuotation` where lead = %(lead)s) (select name, party_name as lead from `tabQuotation` where quotation_to = 'Lead' and party_name = %(lead)s)
union union
(select name, lead from `tabIssue` where lead = %(lead)s and status!='Closed') (select name, lead from `tabIssue` where lead = %(lead)s and status!='Closed')
union union

View File

@@ -107,10 +107,18 @@ def get_series():
def setup_custom_fields(): def setup_custom_fields():
custom_fields = { custom_fields = {
"Customer": [dict(fieldname='shopify_customer_id', label='Shopify Customer Id', "Customer": [
fieldtype='Data', insert_after='series', read_only=1, print_hide=1)], dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
"Address": [dict(fieldname='shopify_address_id', label='Shopify Address Id', fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)], ],
"Supplier": [
dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
],
"Address": [
dict(fieldname='shopify_address_id', label='Shopify Address Id',
fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
],
"Item": [ "Item": [
dict(fieldname='shopify_variant_id', label='Shopify Variant Id', dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1), fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
@@ -119,16 +127,20 @@ def setup_custom_fields():
dict(fieldname='shopify_description', label='Shopify Description', dict(fieldname='shopify_description', label='Shopify Description',
fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1) fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
], ],
"Sales Order": [dict(fieldname='shopify_order_id', label='Shopify Order Id', "Sales Order": [
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)], dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
],
"Delivery Note":[ "Delivery Note":[
dict(fieldname='shopify_order_id', label='Shopify Order Id', dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1), fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id', dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1) fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
], ],
"Sales Invoice": [dict(fieldname='shopify_order_id', label='Shopify Order Id', "Sales Invoice": [
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)] dict(fieldname='shopify_order_id', label='Shopify Order Id',
fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
]
} }
create_custom_fields(custom_fields) create_custom_fields(custom_fields)

View File

@@ -82,7 +82,7 @@ def get_healthcare_services_to_invoice(patient):
'service': service_item, 'rate': practitioner_charge, 'service': service_item, 'rate': practitioner_charge,
'income_account': income_account}) 'income_account': income_account})
lab_tests = frappe.get_list("Lab Test", {'patient': patient.name, 'invoiced': False}) lab_tests = frappe.get_list("Lab Test", {'patient': patient.name, 'invoiced': False, 'docstatus': 1})
if lab_tests: if lab_tests:
for lab_test in lab_tests: for lab_test in lab_tests:
lab_test_obj = frappe.get_doc("Lab Test", lab_test['name']) lab_test_obj = frappe.get_doc("Lab Test", lab_test['name'])

View File

@@ -3,13 +3,8 @@
frappe.ui.form.on('Additional Salary', { frappe.ui.form.on('Additional Salary', {
setup: function(frm) { setup: function(frm) {
frm.set_query("salary_component", function() { frm.add_fetch("salary_component", "deduct_full_tax_on_selected_payroll_date", "deduct_full_tax_on_selected_payroll_date");
return {
filters: {
is_additional_component: true
}
};
});
frm.set_query("employee", function() { frm.set_query("employee", function() {
return { return {
filters: { filters: {

View File

@@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
@@ -21,6 +22,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"fetch_if_empty": 0,
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
@@ -54,6 +56,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "employee", "fieldname": "employee",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@@ -87,6 +90,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "salary_component", "fieldname": "salary_component",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@@ -120,6 +124,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0, "hidden": 0,
@@ -153,6 +158,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "1", "default": "1",
"fetch_if_empty": 0,
"fieldname": "overwrite_salary_structure_amount", "fieldname": "overwrite_salary_structure_amount",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "hidden": 0,
@@ -185,6 +191,40 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_5", "fieldname": "column_break_5",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
@@ -217,6 +257,8 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "", "default": "",
"description": "Date on which this component is applied",
"fetch_if_empty": 0,
"fieldname": "payroll_date", "fieldname": "payroll_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "hidden": 0,
@@ -250,6 +292,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
"fetch_if_empty": 0,
"fieldname": "employee_name", "fieldname": "employee_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@@ -284,6 +327,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "employee.department", "fetch_from": "employee.department",
"fetch_if_empty": 0,
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@@ -317,6 +361,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@@ -350,6 +395,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "salary_slip", "fieldname": "salary_slip",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@@ -384,6 +430,7 @@
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_from": "salary_component.type", "fetch_from": "salary_component.type",
"fetch_if_empty": 0,
"fieldname": "type", "fieldname": "type",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@@ -417,6 +464,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
@@ -453,7 +501,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-21 14:44:22.921926", "modified": "2019-05-09 19:53:37.475839",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Additional Salary", "name": "Additional Salary",

View File

@@ -9,6 +9,12 @@ from frappe import _
from frappe.utils import getdate, date_diff from frappe.utils import getdate, date_diff
class AdditionalSalary(Document): class AdditionalSalary(Document):
def before_insert(self):
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company}):
frappe.throw(_("Additional Salary Component Exists."))
def validate(self): def validate(self):
self.validate_dates() self.validate_dates()
if self.amount < 0: if self.amount < 0:
@@ -35,7 +41,8 @@ class AdditionalSalary(Document):
@frappe.whitelist() @frappe.whitelist()
def get_additional_salary_component(employee, start_date, end_date): def get_additional_salary_component(employee, start_date, end_date):
additional_components = frappe.db.sql(""" additional_components = frappe.db.sql("""
select salary_component, sum(amount) as amount, overwrite_salary_structure_amount from `tabAdditional Salary` select salary_component, sum(amount) as amount, overwrite_salary_structure_amount, deduct_full_tax_on_selected_payroll_date
from `tabAdditional Salary`
where employee=%(employee)s where employee=%(employee)s
and docstatus = 1 and docstatus = 1
and payroll_date between %(from_date)s and %(to_date)s and payroll_date between %(from_date)s and %(to_date)s
@@ -48,16 +55,20 @@ def get_additional_salary_component(employee, start_date, end_date):
}, as_dict=1) }, as_dict=1)
additional_components_list = [] additional_components_list = []
component_fields = ["depends_on_payment_days", "salary_component_abbr", "is_tax_applicable", "variable_based_on_taxable_salary", 'type']
for d in additional_components: for d in additional_components:
component = frappe.get_doc("Salary Component", d.salary_component) struct_row = frappe._dict({'salary_component': d.salary_component})
struct_row = {'salary_component': d.salary_component} component = frappe.get_all("Salary Component", filters={'name': d.salary_component}, fields=component_fields)
for field in ["depends_on_payment_days", "abbr", "is_tax_applicable", "variable_based_on_taxable_salary", "is_additional_component"]: if component:
struct_row[field] = component.get(field) struct_row.update(component[0])
additional_components_list.append({ struct_row['deduct_full_tax_on_selected_payroll_date'] = d.deduct_full_tax_on_selected_payroll_date
struct_row['is_additional_component'] = 1
additional_components_list.append(frappe._dict({
'amount': d.amount, 'amount': d.amount,
'type': component.type, 'type': component[0].type,
'struct_row': struct_row, 'struct_row': struct_row,
'overwrite': d.overwrite_salary_structure_amount 'overwrite': d.overwrite_salary_structure_amount,
}) }))
return additional_components_list return additional_components_list

View File

@@ -3,13 +3,18 @@
frappe.ui.form.on('Employee Benefit Application', { frappe.ui.form.on('Employee Benefit Application', {
setup: function(frm) { setup: function(frm) {
frm.set_query("earning_component", "employee_benefits", function() { if(!frm.doc.employee || !frm.doc.date) {
return { frappe.throw(__("Please select Employee and Date first"));
query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components", } else {
filters: {date: frm.doc.date, employee: frm.doc.employee} frm.set_query("earning_component", "employee_benefits", function() {
}; return {
}); query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components",
filters: {date: frm.doc.date, employee: frm.doc.employee}
};
});
}
}, },
employee: function(frm) { employee: function(frm) {
var method, args; var method, args;
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){

View File

@@ -7,7 +7,7 @@ import frappe
from frappe import _ from frappe import _
from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt from frappe.utils import date_diff, getdate, rounded, add_days, cstr, cint, flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
@@ -131,7 +131,7 @@ def get_max_benefits_remaining(employee, on_date, payroll_period):
salary_component = frappe.get_doc("Salary Component", sal_struct_row.salary_component) salary_component = frappe.get_doc("Salary Component", sal_struct_row.salary_component)
if salary_component.depends_on_payment_days == 1 and salary_component.pay_against_benefit_claim != 1: if salary_component.depends_on_payment_days == 1 and salary_component.pay_against_benefit_claim != 1:
have_depends_on_payment_days = True have_depends_on_payment_days = True
benefit_amount = get_benefit_pro_rata_ratio_amount(sal_struct, salary_component.max_benefit_amount) benefit_amount = get_benefit_amount_based_on_pro_rata(sal_struct, salary_component.max_benefit_amount)
amount_per_day = benefit_amount / payroll_period_days amount_per_day = benefit_amount / payroll_period_days
per_day_amount_total += amount_per_day per_day_amount_total += amount_per_day
@@ -167,16 +167,14 @@ def calculate_lwp(employee, start_date, holidays, working_days):
lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1) lwp = cint(leave[0][1]) and (lwp + 0.5) or (lwp + 1)
return lwp return lwp
def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal_struct, period_length, frequency): def get_benefit_component_amount(employee, start_date, end_date, salary_component, sal_struct, payroll_frequency, payroll_period):
payroll_period, period_factor, actual_payroll_days = get_payroll_period_days(start_date, end_date, employee)
if not payroll_period: if not payroll_period:
frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}.") frappe.msgprint(_("Start and end dates not in a valid Payroll Period, cannot calculate {0}")
.format(struct_row.salary_component)) .format(salary_component))
return False return False
# Considering there is only one application for a year # Considering there is only one application for a year
benefit_application_name = frappe.db.sql(""" benefit_application = frappe.db.sql("""
select name select name
from `tabEmployee Benefit Application` from `tabEmployee Benefit Application`
where where
@@ -185,69 +183,58 @@ def get_benefit_component_amount(employee, start_date, end_date, struct_row, sal
and docstatus = 1 and docstatus = 1
""", { """, {
'employee': employee, 'employee': employee,
'payroll_period': payroll_period 'payroll_period': payroll_period.name
}) })
if frappe.db.get_value("Salary Component", struct_row.salary_component, "depends_on_payment_days") != 1: current_benefit_amount = 0.0
if frequency == "Monthly" and actual_payroll_days in range(360, 370): component_max_benefit, depends_on_payment_days = frappe.db.get_value("Salary Component",
period_length = 1 salary_component, ["max_benefit_amount", "depends_on_payment_days"])
period_factor = 12
if period_factor: if benefit_application:
# If there is application for benefit then fetch the amount from the application. benefit_amount = frappe.db.get_value("Employee Benefit Application Detail",
# else Split the max benefits to the pro-rata components with the ratio of their max_benefit_amount {"parent": benefit_application[0][0], "earning_component": salary_component}, "amount")
if benefit_application_name: elif component_max_benefit:
benefit_application = frappe.get_doc("Employee Benefit Application", benefit_application_name[0][0]) benefit_amount = get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit)
return get_benefit_amount(benefit_application, struct_row, period_factor, period_length)
# TODO: Check if there is benefit claim for employee then pro-rata divide the rest of amount (Late Benefit Application) current_benefit_amount = 0
else: if benefit_amount:
component_max = frappe.db.get_value("Salary Component", struct_row.salary_component, "max_benefit_amount") total_sub_periods = get_period_factor(employee,
if component_max: start_date, end_date, payroll_frequency, payroll_period, depends_on_payment_days)[0]
benefit_amount = get_benefit_pro_rata_ratio_amount(sal_struct, component_max)
return get_amount(period_factor, benefit_amount, period_length)
return False
def get_benefit_pro_rata_ratio_amount(sal_struct, component_max): current_benefit_amount = benefit_amount / total_sub_periods
total_pro_rata_max = 0
return current_benefit_amount
def get_benefit_amount_based_on_pro_rata(sal_struct, component_max_benefit):
max_benefits_total = 0
benefit_amount = 0 benefit_amount = 0
for sal_struct_row in sal_struct.get("earnings"): for d in sal_struct.get("earnings"):
pay_against_benefit_claim, max_benefit_amount = frappe.db.get_value("Salary Component", if d.is_flexible_benefit == 1:
sal_struct_row.salary_component, ["pay_against_benefit_claim", "max_benefit_amount"]) component = frappe.db.get_value("Salary Component", d.salary_component, ["max_benefit_amount", "pay_against_benefit_claim"], as_dict=1)
if sal_struct_row.is_flexible_benefit == 1 and pay_against_benefit_claim != 1: if not component.pay_against_benefit_claim:
total_pro_rata_max += max_benefit_amount max_benefits_total += component.max_benefit_amount
if total_pro_rata_max > 0:
benefit_amount = component_max * sal_struct.max_benefits / total_pro_rata_max if max_benefits_total > 0:
if benefit_amount > component_max: benefit_amount = sal_struct.max_benefits * component.max_benefit_amount / max_benefits_total
benefit_amount = component_max if benefit_amount > component_max_benefit:
benefit_amount = component_max_benefit
return benefit_amount return benefit_amount
def get_benefit_amount(application, struct_row, period_factor, period_length):
amount = 0
for employee_benefit in application.employee_benefits:
if employee_benefit.earning_component == struct_row.salary_component:
amount += get_amount(period_factor, employee_benefit.amount, period_length)
return amount if amount > 0 else False
def get_amount(period_factor, amount, period_length):
amount_per_day = amount / period_factor
total_amount = amount_per_day * period_length
return total_amount
def get_earning_components(doctype, txt, searchfield, start, page_len, filters): def get_earning_components(doctype, txt, searchfield, start, page_len, filters):
if len(filters) < 2: if len(filters) < 2:
return {} return {}
employee = filters['employee']
date = filters['date'] salary_structure = get_assigned_salary_structure(filters['employee'], filters['date'])
salary_structure = get_assigned_salary_structure(employee, date)
if salary_structure: if salary_structure:
query = """select salary_component from `tabSalary Detail` where parent = '{salary_structure}' return frappe.db.sql("""
and is_flexible_benefit = 1 select salary_component
order by name""" from `tabSalary Detail`
where parent = %s and is_flexible_benefit = 1
return frappe.db.sql(query.format(**{ order by name
"salary_structure": salary_structure """, salary_structure)
})) else:
frappe.throw(_("Salary Structure not found for employee {0} and date {1}")
return {} .format(filters['employee'], filters['date']))

View File

@@ -5,9 +5,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits from erpnext.hr.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
from erpnext.hr.utils import get_payroll_period, get_previous_claimed_amount from erpnext.hr.utils import get_previous_claimed_amount
from erpnext.hr.doctype.payroll_period.payroll_period import get_payroll_period
from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
class EmployeeBenefitClaim(Document): class EmployeeBenefitClaim(Document):
@@ -97,31 +99,28 @@ def get_benefit_pro_rata_ratio_amount(employee, on_date, sal_struct):
benefit_amount_total += benefit_amount benefit_amount_total += benefit_amount
return benefit_amount_total return benefit_amount_total
def get_benefit_claim_amount(employee, start_date, end_date, salary_component): def get_benefit_claim_amount(employee, start_date, end_date, salary_component=None):
query = """select claimed_amount from `tabEmployee Benefit Claim` query = """
where employee=%(employee)s select sum(claimed_amount)
and docstatus = 1 and pay_against_benefit_claim = 1 from `tabEmployee Benefit Claim`
where
employee=%(employee)s
and docstatus = 1
and pay_against_benefit_claim = 1
and claim_date between %(start_date)s and %(end_date)s
""" """
if not start_date:
query += "and claim_date <= %(end_date)s"
else:
query += "and (claim_date between %(start_date)s and %(end_date)s)"
if salary_component: if salary_component:
query += "and earning_component = %(earning_component)s" query += " and earning_component = %(earning_component)s"
benefit_claim_details = frappe.db.sql(query, { claimed_amount = flt(frappe.db.sql(query, {
'employee': employee, 'employee': employee,
'start_date': start_date, 'start_date': start_date,
'end_date': end_date, 'end_date': end_date,
'earning_component': salary_component 'earning_component': salary_component
}, as_dict = True) })[0][0])
if benefit_claim_details:
claimed_amount = 0 return claimed_amount
for claim_detail in benefit_claim_details:
claimed_amount += claim_detail.claimed_amount
return claimed_amount
return False
def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payroll_period): def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payroll_period):
pro_rata_amount = 0 pro_rata_amount = 0
@@ -140,11 +139,11 @@ def get_total_benefit_dispensed(employee, sal_struct, sal_slip_start_date, payro
else: else:
pro_rata_amount = get_benefit_pro_rata_ratio_amount(employee, sal_slip_start_date, sal_struct) pro_rata_amount = get_benefit_pro_rata_ratio_amount(employee, sal_slip_start_date, sal_struct)
claimed_amount += get_benefit_claim_amount(employee, payroll_period.start_date, payroll_period.end_date, False) claimed_amount += get_benefit_claim_amount(employee, payroll_period.start_date, payroll_period.end_date)
return claimed_amount + pro_rata_amount return claimed_amount + pro_rata_amount
def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end_date, current_flexi_amount, payroll_period, sal_struct): def get_last_payroll_period_benefits(employee, sal_slip_start_date, sal_slip_end_date, payroll_period, sal_struct):
max_benefits = get_max_benefits(employee, payroll_period.end_date) max_benefits = get_max_benefits(employee, payroll_period.end_date)
if not max_benefits: if not max_benefits:
max_benefits = 0 max_benefits = 0

View File

@@ -446,6 +446,72 @@
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Income From Other Sources",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@@ -458,7 +524,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-25 16:38:05.847925", "modified": "2019-05-11 16:13:50.472670",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Tax Exemption Declaration", "name": "Employee Tax Exemption Declaration",

View File

@@ -25,7 +25,8 @@ class EmployeeTaxExemptionDeclaration(Document):
filters = { filters = {
"employee": self.employee, "employee": self.employee,
"payroll_period": self.payroll_period, "payroll_period": self.payroll_period,
"name": ["!=", self.name] "name": ["!=", self.name],
"docstatus": ["!=", 2]
} }
) )
if duplicate: if duplicate:

View File

@@ -448,6 +448,72 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "other_incomes_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Other Incomes",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "income_from_other_sources",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Income From Other Sources",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -558,7 +624,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-25 17:06:36.569549", "modified": "2019-05-13 12:17:18.045171",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Tax Exemption Proof Submission", "name": "Employee Tax Exemption Proof Submission",

View File

@@ -219,7 +219,8 @@ frappe.ui.form.on("Expense Claim", {
frm.fields_dict["cost_center"].get_query = function() { frm.fields_dict["cost_center"].get_query = function() {
return { return {
filters: { filters: {
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
}; };
}; };
@@ -230,7 +231,9 @@ frappe.ui.form.on("Expense Claim", {
return { return {
filters: { filters: {
"report_type": "Balance Sheet", "report_type": "Balance Sheet",
"account_type": "Payable" "account_type": "Payable",
"company": frm.doc.company,
"is_group": 0
} }
}; };
}; };

View File

@@ -0,0 +1,20 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'reference_name',
'internal_links': {
'Employee Advance': ['advances', 'employee_advance']
},
'transactions': [
{
'label': _('Payment'),
'items': ['Payment Entry', 'Journal Entry']
},
{
'label': _('Reference'),
'items': ['Employee Advance']
},
]
}

View File

@@ -79,6 +79,7 @@ def get_events(start, end, filters=None):
filters.append(['Holiday', 'holiday_date', '>', getdate(start)]) filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
if end: if end:
filters.append(['Holiday', 'holiday_date', '<', getdate(end)]) filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
return frappe.get_list('Holiday List', return frappe.get_list('Holiday List',
fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'], fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description', '`tabHoliday List`.color'],
filters = filters, filters = filters,

View File

@@ -3,8 +3,8 @@
frappe.views.calendar["Holiday List"] = { frappe.views.calendar["Holiday List"] = {
field_map: { field_map: {
"start": "from_date", "start": "holiday_date",
"end": "to_date", "end": "holiday_date",
"id": "name", "id": "name",
"title": "description", "title": "description",
"allDay": "allDay" "allDay": "allDay"

View File

@@ -146,7 +146,7 @@ frappe.ui.form.on('Loan', {
'payment_date': payment.payment_date, 'payment_date': payment.payment_date,
'principal_amount': payment.principal_amount, 'principal_amount': payment.principal_amount,
'interest_amount': payment.interest_amount, 'interest_amount': payment.interest_amount,
'total_payment': payment.total_payment 'total_payment': payment.total_payment
}); });
dialog.fields_dict.payments.grid.refresh(); dialog.fields_dict.payments.grid.refresh();
$(dialog.wrapper.find(".grid-buttons")).hide(); $(dialog.wrapper.find(".grid-buttons")).hide();
@@ -172,18 +172,20 @@ frappe.ui.form.on('Loan', {
}, },
mode_of_payment: function (frm) { mode_of_payment: function (frm) {
frappe.call({ if (frm.doc.mode_of_payment && frm.doc.company) {
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", frappe.call({
args: { method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
"mode_of_payment": frm.doc.mode_of_payment, args: {
"company": frm.doc.company "mode_of_payment": frm.doc.mode_of_payment,
}, "company": frm.doc.company
callback: function (r, rt) { },
if (r.message) { callback: function (r, rt) {
frm.set_value("payment_account", r.message.account); if (r.message) {
frm.set_value("payment_account", r.message.account);
}
} }
} });
}); }
}, },
loan_application: function (frm) { loan_application: function (frm) {

View File

@@ -539,14 +539,15 @@ def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progr
payroll_entry.email_salary_slip(submitted_ss) payroll_entry.email_salary_slip(submitted_ss)
payroll_entry.db_set("salary_slips_submitted", 1) payroll_entry.db_set("salary_slips_submitted", 1)
payroll_entry.notify_update() payroll_entry.notify_update()
if not submitted_ss and not not_submitted_ss: if not submitted_ss and not not_submitted_ss:
frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted")) frappe.msgprint(_("No salary slip found to submit for the above selected criteria OR salary slip already submitted"))
if not_submitted_ss: if not_submitted_ss:
frappe.msgprint(_("Could not submit some Salary Slips")) frappe.msgprint(_("Could not submit some Salary Slips"))
def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters): def get_payroll_entries_for_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" return frappe.db.sql("""
select name from `tabPayroll Entry` select name from `tabPayroll Entry`

View File

@@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import date_diff, getdate, formatdate, cint from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.utils import get_holidays_for_employee from erpnext.hr.utils import get_holidays_for_employee
@@ -48,12 +48,13 @@ class PayrollPeriod(Document):
def get_payroll_period_days(start_date, end_date, employee): def get_payroll_period_days(start_date, end_date, employee):
company = frappe.db.get_value("Employee", employee, "company") company = frappe.db.get_value("Employee", employee, "company")
payroll_period = frappe.db.sql(""" payroll_period = frappe.db.sql("""
select name, start_date, end_date from `tabPayroll Period` select name, start_date, end_date
where company=%(company)s from `tabPayroll Period`
and ( where
(%(start_date)s between start_date and end_date) company=%(company)s
and (%(end_date)s between start_date and end_date) and %(start_date)s between start_date and end_date
)""", { and %(end_date)s between start_date and end_date
""", {
'company': company, 'company': company,
'start_date': start_date, 'start_date': start_date,
'end_date': end_date 'end_date': end_date
@@ -67,3 +68,38 @@ def get_payroll_period_days(start_date, end_date, employee):
working_days -= len(holidays) working_days -= len(holidays)
return payroll_period[0][0], working_days, actual_no_of_days return payroll_period[0][0], working_days, actual_no_of_days
return False, False, False return False, False, False
def get_payroll_period(from_date, to_date, company):
payroll_period = frappe.db.sql("""
select name, start_date, end_date
from `tabPayroll Period`
where start_date<=%s and end_date>= %s and company=%s
""", (from_date, to_date, company), as_dict=1)
return payroll_period[0] if payroll_period else None
def get_period_factor(employee, start_date, end_date, payroll_frequency, payroll_period, depends_on_payment_days=0):
# TODO if both deduct checked update the factor to make tax consistent
period_start, period_end = payroll_period.start_date, payroll_period.end_date
joining_date, relieving_date = frappe.db.get_value("Employee", employee, ["date_of_joining", "relieving_date"])
if getdate(joining_date) > getdate(period_start):
period_start = joining_date
if relieving_date and getdate(relieving_date) < getdate(period_end):
period_end = relieving_date
total_sub_periods, remaining_sub_periods = 0.0, 0.0
if payroll_frequency == "Monthly" and not depends_on_payment_days:
total_sub_periods = month_diff(payroll_period.end_date, payroll_period.start_date)
remaining_sub_periods = month_diff(period_end, start_date)
else:
salary_days = date_diff(end_date, start_date) + 1
days_in_payroll_period = date_diff(payroll_period.end_date, payroll_period.start_date) + 1
total_sub_periods = flt(days_in_payroll_period) / flt(salary_days)
remaining_days_in_payroll_period = date_diff(period_end, start_date) + 1
remaining_sub_periods = flt(remaining_days_in_payroll_period) / flt(salary_days)
return total_sub_periods, remaining_sub_periods

View File

@@ -24,14 +24,6 @@ frappe.ui.form.on('Salary Component', {
is_flexible_benefit: function(frm) { is_flexible_benefit: function(frm) {
if(frm.doc.is_flexible_benefit){ if(frm.doc.is_flexible_benefit){
set_value_for_condition_and_formula(frm); set_value_for_condition_and_formula(frm);
frm.set_value("is_additional_component", 0);
frm.set_value("formula", '');
frm.set_value("amount", 0);
}
},
is_additional_component: function(frm) {
if(frm.doc.is_additional_component){
frm.set_value("is_flexible_benefit", 0);
frm.set_value("formula", ''); frm.set_value("formula", '');
frm.set_value("amount", 0); frm.set_value("amount", 0);
} }

View File

@@ -115,40 +115,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_flexible_benefit != 1",
"fetch_if_empty": 0,
"fieldname": "is_additional_component",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Additional Component",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -285,6 +251,39 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -424,7 +423,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.type==\"Earning\" && doc.is_additional_component != 1 && doc.statistical_component!=1", "depends_on": "eval:doc.type==\"Earning\" && doc.statistical_component!=1",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "flexible_benefits", "fieldname": "flexible_benefits",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@@ -458,7 +457,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.is_additional_component != 1", "depends_on": "",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "is_flexible_benefit", "fieldname": "is_flexible_benefit",
"fieldtype": "Check", "fieldtype": "Check",
@@ -1035,7 +1034,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-16 19:08:55.323567", "modified": "2019-05-13 12:55:55.424370",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Component", "name": "Salary Component",

View File

@@ -35,7 +35,6 @@
"salary_component": "Leave Encashment", "salary_component": "Leave Encashment",
"type": "Earning", "type": "Earning",
"is_payable": 1, "is_payable": 1,
"is_tax_applicable": 1, "is_tax_applicable": 1
"is_additional_component": 1
} }
] ]

View File

@@ -19,7 +19,5 @@ def create_salary_component(component_name, **args):
"salary_component": component_name, "salary_component": component_name,
"type": args.get("type") or "Earning", "type": args.get("type") or "Earning",
"is_payable": args.get("is_payable") or 1, "is_payable": args.get("is_payable") or 1,
"is_tax_applicable": args.get("is_tax_applicable") or 1, "is_tax_applicable": args.get("is_tax_applicable") or 1
"is_additional_component": args.get("is_additional_component") or 1
}).insert() }).insert()

View File

@@ -218,41 +218,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_from": "salary_component.is_additional_component",
"fetch_if_empty": 0,
"fieldname": "is_additional_component",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Additional Component",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -323,6 +288,39 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "deduct_full_tax_on_selected_payroll_date",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Deduct Full Tax on Selected Payroll Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -566,6 +564,41 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fetch_from": "",
"fetch_if_empty": 0,
"fieldname": "additional_amount",
"fieldtype": "Currency",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Additional Amount",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -713,7 +746,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-04-16 19:09:31.726597", "modified": "2019-05-11 17:33:08.508653",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Detail", "name": "Salary Detail",

View File

@@ -11,7 +11,7 @@ frappe.ui.form.on("Salary Slip", {
{fieldname: 'salary_component', columns: 6}, {fieldname: 'salary_component', columns: 6},
{fieldname: 'amount', columns: 4} {fieldname: 'amount', columns: 4}
]; ];
}) });
frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){ frm.fields_dict["timesheets"].grid.get_field("time_sheet").get_query = function(){
return { return {
@@ -19,31 +19,39 @@ frappe.ui.form.on("Salary Slip", {
employee: frm.doc.employee employee: frm.doc.employee
} }
} }
} };
frm.set_query("salary_component", "earnings", function() { frm.set_query("salary_component", "earnings", function() {
return { return {
filters: { filters: {
type: "earning" type: "earning"
} }
} }
}) });
frm.set_query("salary_component", "deductions", function() { frm.set_query("salary_component", "deductions", function() {
return { return {
filters: { filters: {
type: "deduction" type: "deduction"
} }
} }
}) });
frm.set_query("employee", function() {
return{
query: "erpnext.controllers.queries.employee_query"
}
});
}, },
start_date: function(frm, dt, dn){ start_date: function(frm){
if(frm.doc.start_date){ if(frm.doc.start_date){
frm.trigger("set_end_date"); frm.trigger("set_end_date");
} }
}, },
end_date: function(frm, dt, dn) { end_date: function(frm) {
get_emp_and_leave_details(frm.doc, dt, dn); frm.events.get_emp_and_leave_details(frm);
}, },
set_end_date: function(frm){ set_end_date: function(frm){
@@ -70,43 +78,54 @@ frappe.ui.form.on("Salary Slip", {
refresh: function(frm) { refresh: function(frm) {
frm.trigger("toggle_fields") frm.trigger("toggle_fields")
frm.trigger("toggle_reqd_fields")
var salary_detail_fields = ["formula", "abbr", "statistical_component", "is_tax_applicable", var salary_detail_fields = ["formula", "abbr", "statistical_component", "variable_based_on_taxable_salary"];
"is_flexible_benefit", "variable_based_on_taxable_salary", "is_additional_component"]
cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false); cur_frm.fields_dict['earnings'].grid.set_column_disp(salary_detail_fields,false);
cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false); cur_frm.fields_dict['deductions'].grid.set_column_disp(salary_detail_fields,false);
}, },
salary_slip_based_on_timesheet: function(frm, dt, dn) { salary_slip_based_on_timesheet: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
get_emp_and_leave_details(frm.doc, dt, dn); frm.events.get_emp_and_leave_details(frm);
}, },
payroll_frequency: function(frm, dt, dn) { payroll_frequency: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
frm.set_value('end_date', ''); frm.set_value('end_date', '');
}, },
employee: function(frm, dt, dn) { employee: function(frm) {
get_emp_and_leave_details(frm.doc, dt, dn); frm.events.get_emp_and_leave_details(frm);
},
leave_without_pay: function(frm){
if (frm.doc.employee && frm.doc.start_date && frm.doc.end_date) {
return frappe.call({
method: 'process_salary_based_on_leave',
doc: frm.doc,
args: {"lwp": frm.doc.leave_without_pay},
callback: function(r, rt) {
frm.refresh();
}
});
}
}, },
toggle_fields: function(frm) { toggle_fields: function(frm) {
frm.toggle_display(['hourly_wages', 'timesheets'], frm.toggle_display(['hourly_wages', 'timesheets'], cint(frm.doc.salary_slip_based_on_timesheet)===1);
cint(frm.doc.salary_slip_based_on_timesheet)==1);
frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'], frm.toggle_display(['payment_days', 'total_working_days', 'leave_without_pay'],
frm.doc.payroll_frequency!=""); frm.doc.payroll_frequency!="");
}
})
frappe.ui.form.on('Salary Detail', {
earnings_remove: function(frm, dt, dn) {
calculate_all(frm.doc, dt, dn);
}, },
deductions_remove: function(frm, dt, dn) {
calculate_all(frm.doc, dt, dn); get_emp_and_leave_details: function(frm) {
return frappe.call({
method: 'get_emp_and_leave_details',
doc: frm.doc,
callback: function(r, rt) {
frm.refresh();
}
});
} }
}) })
@@ -119,133 +138,29 @@ frappe.ui.form.on('Salary Slip Timesheet', {
} }
}); });
// Get leave details
//---------------------------------------------------------------------
var get_emp_and_leave_details = function(doc, dt, dn) {
return frappe.call({
method: 'get_emp_and_leave_details',
doc: locals[dt][dn],
callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
}
});
}
cur_frm.cscript.leave_without_pay = function(doc,dt,dn){
if (doc.employee && doc.start_date && doc.end_date) {
return $c_obj(doc, 'get_leave_details', {"lwp": doc.leave_without_pay}, function(r, rt) {
var doc = locals[dt][dn];
cur_frm.refresh();
calculate_all(doc, dt, dn);
});
}
}
var calculate_all = function(doc, dt, dn) {
calculate_earning_total(doc, dt, dn);
calculate_ded_total(doc, dt, dn);
calculate_net_pay(doc, dt, dn);
}
cur_frm.cscript.amount = function(doc,dt,dn){
var child = locals[dt][dn];
if(!doc.salary_structure){
frappe.model.set_value(dt,dn, "default_amount", child.amount)
}
calculate_all(doc, dt, dn);
}
cur_frm.cscript.depends_on_payment_days = function(doc,dt,dn){
calculate_earning_total(doc, dt, dn, true);
calculate_ded_total(doc, dt, dn, true);
calculate_net_pay(doc, dt, dn);
refresh_many(['amount','gross_pay', 'rounded_total', 'net_pay', 'loan_repayment']);
};
// Calculate earning total
// ------------------------------------------------------------------------
var calculate_earning_total = function(doc, dt, dn, reset_amount) {
var tbl = doc.earnings || [];
var total_earn = 0;
for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_payment_days) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days) /
cint(doc.total_working_days)*100)/100;
} else if(reset_amount && tbl[i].default_amount) {
tbl[i].amount = tbl[i].default_amount;
}
if(!tbl[i].do_not_include_in_total) {
total_earn += flt(tbl[i].amount);
}
}
doc.gross_pay = total_earn;
refresh_many(['earnings', 'amount','gross_pay']);
}
// Calculate deduction total
// ------------------------------------------------------------------------
var calculate_ded_total = function(doc, dt, dn, reset_amount) {
var tbl = doc.deductions || [];
var total_ded = 0;
for(var i = 0; i < tbl.length; i++){
if(cint(tbl[i].depends_on_payment_days) == 1) {
tbl[i].amount = Math.round(tbl[i].default_amount)*(flt(doc.payment_days)/cint(doc.total_working_days)*100)/100;
} else if(reset_amount && tbl[i].default_amount) {
tbl[i].amount = tbl[i].default_amount;
}
if(!tbl[i].do_not_include_in_total) {
total_ded += flt(tbl[i].amount);
}
}
doc.total_deduction = total_ded;
refresh_many(['deductions', 'total_deduction']);
}
var calculate_net_pay = function(doc, dt, dn) {
doc.net_pay = flt(doc.gross_pay) - flt(doc.total_deduction);
doc.rounded_total = Math.round(doc.net_pay);
refresh_many(['net_pay', 'rounded_total']);
}
cur_frm.cscript.validate = function(doc, dt, dn) {
calculate_all(doc, dt, dn);
}
cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) {
return{
query: "erpnext.controllers.queries.employee_query"
}
}
// calculate total working hours, earnings based on hourly wages and totals // calculate total working hours, earnings based on hourly wages and totals
// ------------------------------------------------------------------------
var total_work_hours = function(frm, dt, dn) { var total_work_hours = function(frm, dt, dn) {
frm.set_value('total_working_hours', 0); var total_working_hours = 0.0;
$.each(frm.doc["timesheets"] || [], function(i, timesheet) { $.each(frm.doc["timesheets"] || [], function(i, timesheet) {
frm.doc.total_working_hours += timesheet.working_hours; total_working_hours += timesheet.working_hours;
}); });
frm.refresh_field('total_working_hours'); frm.set_value('total_working_hours', total_working_hours);
var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate; var wages_amount = frm.doc.total_working_hours * frm.doc.hour_rate;
frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => { frappe.db.get_value('Salary Structure', {'name': frm.doc.salary_structure}, 'salary_component', (r) => {
frm.set_value('gross_pay', 0); var gross_pay = 0.0;
$.each(frm.doc["earnings"], function(i, earning) { $.each(frm.doc["earnings"], function(i, earning) {
if (earning.salary_component == r.salary_component) { if (earning.salary_component == r.salary_component) {
earning.amount = wages_amount; earning.amount = wages_amount;
frm.refresh_fields('earnings'); frm.refresh_fields('earnings');
} }
frm.doc.gross_pay += earning.amount; gross_pay += earning.amount;
}); });
frm.set_value('gross_pay', gross_pay);
frm.refresh_field('gross_pay'); frm.doc.net_pay = flt(frm.doc.gross_pay) - flt(frm.doc.total_deduction);
calculate_net_pay(frm.doc, dt, dn); frm.doc.rounded_total = Math.round(frm.doc.net_pay);
refresh_many(['net_pay', 'rounded_total']);
}); });
} }

View File

@@ -2,7 +2,7 @@
"allow_copy": 0, "allow_copy": 0,
"allow_events_in_timeline": 0, "allow_events_in_timeline": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"beta": 0, "beta": 0,
"creation": "2013-01-10 16:34:15", "creation": "2013-01-10 16:34:15",
@@ -1042,73 +1042,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_01",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Salary Slip",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@@ -1950,6 +1883,41 @@
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"oldfieldname": "amended_from",
"oldfieldtype": "Data",
"options": "Salary Slip",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@@ -1963,7 +1931,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-05-08 20:16:21.549386", "modified": "2019-05-13 13:10:14.524119",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Salary Slip", "name": "Salary Slip",

File diff suppressed because it is too large Load Diff

View File

@@ -45,12 +45,12 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.total_working_days, no_of_days[0]) self.assertEqual(ss.total_working_days, no_of_days[0])
self.assertEqual(ss.payment_days, no_of_days[0]) self.assertEqual(ss.payment_days, no_of_days[0])
self.assertEqual(ss.earnings[0].amount, 25000) self.assertEqual(ss.earnings[0].amount, 50000)
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.deductions[0].amount, 5000) self.assertEqual(ss.deductions[0].amount, 5000)
self.assertEqual(ss.deductions[1].amount, 5000) self.assertEqual(ss.deductions[1].amount, 5000)
self.assertEqual(ss.gross_pay, 40500) self.assertEqual(ss.gross_pay, 78000)
self.assertEqual(ss.net_pay, 29918) self.assertEqual(ss.net_pay, 67418.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()
@@ -64,13 +64,13 @@ class TestSalarySlip(unittest.TestCase):
self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1]) self.assertEqual(ss.total_working_days, no_of_days[0] - no_of_days[1])
self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1]) self.assertEqual(ss.payment_days, no_of_days[0] - no_of_days[1])
self.assertEqual(ss.earnings[0].amount, 25000) self.assertEqual(ss.earnings[0].amount, 50000)
self.assertEqual(ss.earnings[0].default_amount, 25000) self.assertEqual(ss.earnings[0].default_amount, 50000)
self.assertEqual(ss.earnings[1].amount, 3000) self.assertEqual(ss.earnings[1].amount, 3000)
self.assertEqual(ss.deductions[0].amount, 5000) self.assertEqual(ss.deductions[0].amount, 5000)
self.assertEqual(ss.deductions[1].amount, 5000) self.assertEqual(ss.deductions[1].amount, 5000)
self.assertEqual(ss.gross_pay, 40500) self.assertEqual(ss.gross_pay, 78000)
self.assertEqual(ss.net_pay, 29918) self.assertEqual(ss.net_pay, 67418.0)
def test_payment_days(self): def test_payment_days(self):
no_of_days = self.get_no_of_days() no_of_days = self.get_no_of_days()
@@ -211,7 +211,7 @@ class TestSalarySlip(unittest.TestCase):
tax_paid = get_tax_paid_in_period(employee) tax_paid = get_tax_paid_in_period(employee)
# total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200 # total taxable income 586000, 250000 @ 5%, 86000 @ 20% ie. 12500 + 17200
annual_tax = 29700 annual_tax = 113567.79
try: try:
self.assertEqual(tax_paid, annual_tax) self.assertEqual(tax_paid, annual_tax)
except AssertionError: except AssertionError:
@@ -250,7 +250,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 416000, 166000 @ 5% ie. 8300 # total taxable income 416000, 166000 @ 5% ie. 8300
try: try:
self.assertEqual(tax_paid, 8300) self.assertEqual(tax_paid, 88607.79)
except AssertionError: except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise raise
@@ -265,7 +265,7 @@ class TestSalarySlip(unittest.TestCase):
# total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200 # total taxable income 566000, 250000 @ 5%, 66000 @ 20%, 12500 + 13200
tax_paid = get_tax_paid_in_period(employee) tax_paid = get_tax_paid_in_period(employee)
try: try:
self.assertEqual(tax_paid, 25700) self.assertEqual(tax_paid, 121211.48)
except AssertionError: except AssertionError:
print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n") print("\nSalary Slip - Tax calculation failed on following case\n", data, "\n")
raise raise
@@ -365,7 +365,7 @@ def make_earning_salary_component(setup=False, test_tax=False):
"salary_component": 'Basic Salary', "salary_component": 'Basic Salary',
"abbr":'BS', "abbr":'BS',
"condition": 'base > 10000', "condition": 'base > 10000',
"formula": 'base*.5', "formula": 'base',
"type": "Earning", "type": "Earning",
"amount_based_on_formula": 1 "amount_based_on_formula": 1
}, },
@@ -386,7 +386,6 @@ def make_earning_salary_component(setup=False, test_tax=False):
{ {
"salary_component": "Leave Encashment", "salary_component": "Leave Encashment",
"abbr": 'LE', "abbr": 'LE',
"is_additional_component": 1,
"type": "Earning" "type": "Earning"
} }
] ]
@@ -398,7 +397,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"is_flexible_benefit": 1, "is_flexible_benefit": 1,
"type": "Earning", "type": "Earning",
"pay_against_benefit_claim": 1, "pay_against_benefit_claim": 1,
"max_benefit_amount": 100000 "max_benefit_amount": 100000,
"depends_on_payment_days": 0
}, },
{ {
"salary_component": "Medical Allowance", "salary_component": "Medical Allowance",
@@ -409,9 +409,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"max_benefit_amount": 15000 "max_benefit_amount": 15000
}, },
{ {
"salary_component": "Perfomance Bonus", "salary_component": "Performance Bonus",
"abbr": 'B', "abbr": 'B',
"is_additional_component": 1,
"type": "Earning" "type": "Earning"
} }
]) ])
@@ -442,7 +441,8 @@ def make_deduction_salary_component(setup=False, test_tax=False):
"abbr":'T', "abbr":'T',
"formula": 'base*.1', "formula": 'base*.1',
"type": "Deduction", "type": "Deduction",
"amount_based_on_formula": 1 "amount_based_on_formula": 1,
"depends_on_payment_days": 0
} }
] ]
if not test_tax: if not test_tax:
@@ -512,21 +512,23 @@ def create_tax_slab(payroll_period):
{ {
"from_amount": 250000, "from_amount": 250000,
"to_amount": 500000, "to_amount": 500000,
"percent_deduction": 5 "percent_deduction": 5.2,
"condition": "annual_taxable_earning > 500000"
}, },
{ {
"from_amount": 500000, "from_amount": 500001,
"to_amount": 1000000, "to_amount": 1000000,
"percent_deduction": 20 "percent_deduction": 20.8
}, },
{ {
"from_amount": 1000000, "from_amount": 1000001,
"percent_deduction": 30 "percent_deduction": 31.2
} }
] ]
payroll_period.taxable_salary_slabs = [] payroll_period.taxable_salary_slabs = []
for item in data: for item in data:
payroll_period.append("taxable_salary_slabs", item) payroll_period.append("taxable_salary_slabs", item)
payroll_period.standard_tax_exemption_amount = 52500
payroll_period.save() payroll_period.save()
def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True): def create_salary_slips_for_payroll_period(employee, salary_structure, payroll_period, deduct_random=True):
@@ -559,7 +561,7 @@ def create_additional_salary(employee, payroll_period, amount):
"doctype": "Additional Salary", "doctype": "Additional Salary",
"employee": employee, "employee": employee,
"company": erpnext.get_default_company(), "company": erpnext.get_default_company(),
"salary_component": "Perfomance Bonus", "salary_component": "Performance Bonus",
"payroll_date": salary_date, "payroll_date": salary_date,
"amount": amount, "amount": amount,
"type": "Earning" "type": "Earning"

View File

@@ -147,7 +147,8 @@ frappe.ui.form.on('Salary Structure', {
source_name: frm.doc.name, source_name: frm.doc.name,
employee: values.employee, employee: values.employee,
as_print: 1, as_print: 1,
print_format: print_format print_format: print_format,
for_preview: 1
}, },
callback: function(r) { callback: function(r) {
var new_window = window.open(); var new_window = window.open();

View File

@@ -137,7 +137,7 @@ def get_existing_assignments(employees, salary_structure,from_date):
return salary_structures_assignments return salary_structures_assignments
@frappe.whitelist() @frappe.whitelist()
def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None): def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None, for_preview=0):
def postprocess(source, target): def postprocess(source, target):
if employee: if employee:
employee_details = frappe.db.get_value("Employee", employee, employee_details = frappe.db.get_value("Employee", employee,
@@ -147,7 +147,7 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
target.branch = employee_details.branch target.branch = employee_details.branch
target.designation = employee_details.designation target.designation = employee_details.designation
target.department = employee_details.department target.department = employee_details.department
target.run_method('process_salary_structure') target.run_method('process_salary_structure', for_preview=for_preview)
doc = get_mapped_doc("Salary Structure", source_name, { doc = get_mapped_doc("Salary Structure", source_name, {
"Salary Structure": { "Salary Structure": {

View File

@@ -39,18 +39,19 @@ class TestSalaryStructure(unittest.TestCase):
holiday_list.save() holiday_list.save()
def test_amount_totals(self): def test_amount_totals(self):
frappe.db.set_value("HR Settings", None, "include_holidays_in_total_working_days", 0)
sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"}) sal_slip = frappe.get_value("Salary Slip", {"employee_name":"test_employee_2@salary.com"})
if not sal_slip: if not sal_slip:
sal_slip = make_employee_salary_slip("test_employee_2@salary.com", "Monthly", "Salary Structure Sample") sal_slip = make_employee_salary_slip("test_employee_2@salary.com", "Monthly", "Salary Structure Sample")
self.assertEqual(sal_slip.get("salary_structure"), 'Salary Structure Sample') self.assertEqual(sal_slip.get("salary_structure"), 'Salary Structure Sample')
self.assertEqual(sal_slip.get("earnings")[0].amount, 25000) self.assertEqual(sal_slip.get("earnings")[0].amount, 50000)
self.assertEqual(sal_slip.get("earnings")[1].amount, 3000) self.assertEqual(sal_slip.get("earnings")[1].amount, 3000)
self.assertEqual(sal_slip.get("earnings")[2].amount, 12500) self.assertEqual(sal_slip.get("earnings")[2].amount, 25000)
self.assertEqual(sal_slip.get("gross_pay"), 40500) self.assertEqual(sal_slip.get("gross_pay"), 78000)
self.assertEqual(sal_slip.get("deductions")[0].amount, 5000) self.assertEqual(sal_slip.get("deductions")[0].amount, 5000)
self.assertEqual(sal_slip.get("deductions")[1].amount, 5000) self.assertEqual(sal_slip.get("deductions")[1].amount, 5000)
self.assertEqual(sal_slip.get("total_deduction"), 10000) self.assertEqual(sal_slip.get("total_deduction"), 10000)
self.assertEqual(sal_slip.get("net_pay"), 30500) self.assertEqual(sal_slip.get("net_pay"), 68000)
def test_whitespaces_in_formula_conditions_fields(self): def test_whitespaces_in_formula_conditions_fields(self):
salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", dont_submit=True) salary_structure = make_salary_structure("Salary Structure Sample", "Monthly", dont_submit=True)

View File

@@ -5,9 +5,19 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _
from frappe.utils import time_diff_in_seconds
from erpnext.hr.doctype.employee.employee import get_employee_emails from erpnext.hr.doctype.employee.employee import get_employee_emails
class TrainingEvent(Document): class TrainingEvent(Document):
def validate(self): def validate(self):
self.set_employee_emails()
self.validate_period()
def set_employee_emails(self):
self.employee_emails = ', '.join(get_employee_emails([d.employee self.employee_emails = ', '.join(get_employee_emails([d.employee
for d in self.employees])) for d in self.employees]))
def validate_period(self):
if time_diff_in_seconds(self.end_time, self.start_time) <= 0:
frappe.throw(_('End time cannot be before start time'))

View File

@@ -15,11 +15,11 @@ class TrainingFeedback(Document):
def on_submit(self): def on_submit(self):
training_event = frappe.get_doc("Training Event", self.training_event) training_event = frappe.get_doc("Training Event", self.training_event)
status = None event_status = None
for e in training_event.employees: for e in training_event.employees:
if e.employee == self.employee: if e.employee == self.employee:
status = 'Feedback Submitted' event_status = 'Feedback Submitted'
break break
if status: if event_status:
frappe.db.set_value("Training Event", self.training_event, "status", status) frappe.db.set_value("Training Event", self.training_event, "event_status", event_status)

View File

@@ -35,6 +35,8 @@ def execute(filters=None):
for d in ded_types: for d in ded_types:
row.append(ss_ded_map.get(ss.name, {}).get(d)) row.append(ss_ded_map.get(ss.name, {}).get(d))
row.append(ss.total_loan_repayment)
row += [ss.total_deduction, ss.net_pay] row += [ss.total_deduction, ss.net_pay]
data.append(row) data.append(row)
@@ -67,7 +69,7 @@ def get_columns(salary_slips):
columns = columns + [(e + ":Currency:120") for e in salary_components[_("Earning")]] + \ columns = columns + [(e + ":Currency:120") for e in salary_components[_("Earning")]] + \
[_("Gross Pay") + ":Currency:120"] + [(d + ":Currency:120") for d in salary_components[_("Deduction")]] + \ [_("Gross Pay") + ":Currency:120"] + [(d + ":Currency:120") for d in salary_components[_("Deduction")]] + \
[_("Total Deduction") + ":Currency:120", _("Net Pay") + ":Currency:120"] [_("Loan Repayment") + ":Currency:120", _("Total Deduction") + ":Currency:120", _("Net Pay") + ":Currency:120"]
return columns, salary_components[_("Earning")], salary_components[_("Deduction")] return columns, salary_components[_("Earning")], salary_components[_("Deduction")]

View File

@@ -125,9 +125,9 @@ def get_employee_fields_label():
fields = [] fields = []
for df in frappe.get_meta("Employee").get("fields"): for df in frappe.get_meta("Employee").get("fields"):
if df.fieldname in ["salutation", "user_id", "employee_number", "employment_type", if df.fieldname in ["salutation", "user_id", "employee_number", "employment_type",
"holiday_list", "branch", "department", "designation", "grade", "holiday_list", "branch", "department", "designation", "grade",
"notice_number_of_days", "reports_to", "leave_policy", "company_email"]: "notice_number_of_days", "reports_to", "leave_policy", "company_email"]:
fields.append({"value": df.fieldname, "label": df.label}) fields.append({"value": df.fieldname, "label": df.label})
return fields return fields
@frappe.whitelist() @frappe.whitelist()
@@ -262,12 +262,6 @@ def get_leave_period(from_date, to_date, company):
if leave_period: if leave_period:
return leave_period return leave_period
def get_payroll_period(from_date, to_date, company):
payroll_period = frappe.db.sql("""select name, start_date, end_date from
`tabPayroll Period`
where start_date<=%s and end_date>= %s and company=%s""", (from_date, to_date, company), as_dict=1)
return payroll_period[0] if payroll_period else None
def allocate_earned_leaves(): def allocate_earned_leaves():
'''Allocate earned leaves to Employees''' '''Allocate earned leaves to Employees'''
e_leave_types = frappe.get_all("Leave Type", e_leave_types = frappe.get_all("Leave Type",

View File

@@ -31,5 +31,9 @@ frappe.ui.form.on('Blanket Order', {
}); });
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
},
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
} }
}); });

View File

@@ -205,7 +205,12 @@ var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
'item_code': d.item_code, 'item_code': d.item_code,
'bom_no': d.bom_no != null ? d.bom_no: '', 'bom_no': d.bom_no != null ? d.bom_no: '',
"scrap_items": scrap_items, "scrap_items": scrap_items,
'qty': d.qty 'qty': d.qty,
"stock_qty": d.stock_qty,
"include_item_in_manufacturing": d.include_item_in_manufacturing,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor
}, },
callback: function(r) { callback: function(r) {
d = locals[cdt][cdn]; d = locals[cdt][cdn];

View File

@@ -170,13 +170,14 @@ class BOM(WebsiteGenerator):
rate = self.get_valuation_rate(arg) rate = self.get_valuation_rate(arg)
elif arg: elif arg:
if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom: if arg.get('bom_no') and self.set_rate_of_sub_assembly_item_based_on_bom:
rate = self.get_bom_unitcost(arg['bom_no']) rate = self.get_bom_unitcost(arg['bom_no']) * (arg.get("conversion_factor") or 1)
else: else:
if self.rm_cost_as_per == 'Valuation Rate': if self.rm_cost_as_per == 'Valuation Rate':
rate = self.get_valuation_rate(arg) rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
elif self.rm_cost_as_per == 'Last Purchase Rate': elif self.rm_cost_as_per == 'Last Purchase Rate':
rate = arg.get('last_purchase_rate') \ rate = (arg.get('last_purchase_rate') \
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate") or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \
* (arg.get("conversion_factor") or 1)
elif self.rm_cost_as_per == "Price List": elif self.rm_cost_as_per == "Price List":
if not self.buying_price_list: if not self.buying_price_list:
frappe.throw(_("Please select Price List")) frappe.throw(_("Please select Price List"))
@@ -189,7 +190,7 @@ class BOM(WebsiteGenerator):
"transaction_type": "buying", "transaction_type": "buying",
"company": self.company, "company": self.company,
"currency": self.currency, "currency": self.currency,
"conversion_rate": self.conversion_rate or 1, "conversion_rate": 1, # Passed conversion rate as 1 purposefully, as conversion rate is applied at the end of the function
"conversion_factor": arg.get("conversion_factor") or 1, "conversion_factor": arg.get("conversion_factor") or 1,
"plc_conversion_rate": 1, "plc_conversion_rate": 1,
"ignore_party": True "ignore_party": True
@@ -207,7 +208,7 @@ class BOM(WebsiteGenerator):
frappe.msgprint(_("{0} not found for item {1}") frappe.msgprint(_("{0} not found for item {1}")
.format(self.rm_cost_as_per, arg["item_code"]), alert=True) .format(self.rm_cost_as_per, arg["item_code"]), alert=True)
return flt(rate) return flt(rate) / (self.conversion_rate or 1)
def update_cost(self, update_parent=True, from_child_bom=False, save=True): def update_cost(self, update_parent=True, from_child_bom=False, save=True):
if self.docstatus == 2: if self.docstatus == 2:

View File

@@ -9,6 +9,7 @@ from frappe.utils import cstr
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
from six import string_types
test_records = frappe.get_test_records('BOM') test_records = frappe.get_test_records('BOM')
@@ -63,16 +64,8 @@ class TestBOM(unittest.TestCase):
and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""") and item_code='_Test Item 2' and docstatus=1 and parenttype='BOM'""")
rm_rate = rm_rate[0][0] if rm_rate else 0 rm_rate = rm_rate[0][0] if rm_rate else 0
# update valuation rate of item '_Test Item 2' # Reset item valuation rate
warehouse_list = frappe.db.sql_list("""select warehouse from `tabBin` reset_item_valuation_rate(item_code='_Test Item 2', qty=200, rate=rm_rate + 10)
where item_code='_Test Item 2' and actual_qty > 0""")
if not warehouse_list:
warehouse_list.append("_Test Warehouse - _TC")
for warehouse in warehouse_list:
create_stock_reconciliation(item_code="_Test Item 2", warehouse=warehouse,
qty=200, rate=rm_rate + 10)
# update cost of all BOMs based on latest valuation rate # update cost of all BOMs based on latest valuation rate
update_cost() update_cost()
@@ -96,7 +89,7 @@ class TestBOM(unittest.TestCase):
self.assertEqual(bom.base_raw_material_cost, 480000) self.assertEqual(bom.base_raw_material_cost, 480000)
self.assertEqual(bom.base_total_cost, 486000) self.assertEqual(bom.base_total_cost, 486000)
def test_bom_cost_multi_uom_multi_currency(self): def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependant", 1) frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependant", 1)
for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)): for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s", frappe.db.sql("delete from `tabItem Price` where price_list='_Test Price List' and item_code=%s",
@@ -131,5 +124,35 @@ class TestBOM(unittest.TestCase):
self.assertEqual(bom.base_raw_material_cost, 27000) self.assertEqual(bom.base_raw_material_cost, 27000)
self.assertEqual(bom.base_total_cost, 33000) self.assertEqual(bom.base_total_cost, 33000)
def test_bom_cost_multi_uom_based_on_valuation_rate(self):
bom = frappe.copy_doc(test_records[2])
bom.set_rate_of_sub_assembly_item_based_on_bom = 0
bom.rm_cost_as_per = "Valuation Rate"
bom.items[0].uom = "_Test UOM 1"
bom.items[0].conversion_factor = 6
bom.insert()
reset_item_valuation_rate(item_code='_Test Item', qty=200, rate=200)
bom.update_cost()
self.assertEqual(bom.items[0].rate, 20)
def get_default_bom(item_code="_Test FG Item 2"): def get_default_bom(item_code="_Test FG Item 2"):
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1}) return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
def reset_item_valuation_rate(item_code, warehouse_list=None, qty=None, rate=None):
if warehouse_list and isinstance(warehouse_list, string_types):
warehouse_list = [warehouse_list]
if not warehouse_list:
warehouse_list = frappe.db.sql_list("""
select warehouse from `tabBin`
where item_code=%s and actual_qty > 0
""", item_code)
if not warehouse_list:
warehouse_list.append("_Test Warehouse - _TC")
for warehouse in warehouse_list:
create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate)

View File

@@ -18,21 +18,22 @@ class JobCard(Document):
self.total_completed_qty = 0.0 self.total_completed_qty = 0.0
self.total_time_in_mins = 0.0 self.total_time_in_mins = 0.0
for d in self.get('time_logs'): if self.get('time_logs'):
if get_datetime(d.from_time) > get_datetime(d.to_time): for d in self.get('time_logs'):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx)) if get_datetime(d.from_time) > get_datetime(d.to_time):
frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
data = self.get_overlap_for(d) data = self.get_overlap_for(d)
if data: if data:
frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}") frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}")
.format(d.idx, self.name, data.name)) .format(d.idx, self.name, data.name))
if d.from_time and d.to_time: if d.from_time and d.to_time:
d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60
self.total_time_in_mins += d.time_in_mins self.total_time_in_mins += d.time_in_mins
if d.completed_qty: if d.completed_qty:
self.total_completed_qty += d.completed_qty self.total_completed_qty += d.completed_qty
def get_overlap_for(self, args): def get_overlap_for(self, args):
existing = frappe.db.sql("""select jc.name as name from existing = frappe.db.sql("""select jc.name as name from
@@ -112,8 +113,10 @@ class JobCard(Document):
for_quantity += doc.total_completed_qty for_quantity += doc.total_completed_qty
time_in_mins += doc.total_time_in_mins time_in_mins += doc.total_time_in_mins
for time_log in doc.time_logs: for time_log in doc.time_logs:
from_time_list.append(time_log.from_time) if time_log.from_time:
to_time_list.append(time_log.to_time) from_time_list.append(time_log.from_time)
if time_log.to_time:
to_time_list.append(time_log.to_time)
if for_quantity: if for_quantity:
wo = frappe.get_doc('Work Order', self.work_order) wo = frappe.get_doc('Work Order', self.work_order)

View File

@@ -598,4 +598,5 @@ erpnext.patches.v11_1.set_salary_details_submittable
erpnext.patches.v11_1.rename_depends_on_lwp erpnext.patches.v11_1.rename_depends_on_lwp
erpnext.patches.v11_1.set_missing_title_for_quotation erpnext.patches.v11_1.set_missing_title_for_quotation
execute:frappe.delete_doc("Report", "Inactive Items") execute:frappe.delete_doc("Report", "Inactive Items")
erpnext.patches.v11_1.delete_scheduling_tool erpnext.patches.v11_1.delete_scheduling_tool
erpnext.patches.v11_1.update_bank_transaction_status

View File

@@ -70,7 +70,7 @@ def update_instructors(comp_dict):
emp_details = frappe.get_all("Employee", fields=["name", "company"]) emp_details = frappe.get_all("Employee", fields=["name", "company"])
for employee in emp_details: for employee in emp_details:
records = comp_dict[employee.company] records = comp_dict[employee.company] if employee.company else []
for department in records: for department in records:
when_then.append(''' when_then.append('''

View File

@@ -8,6 +8,17 @@ def execute():
return return
frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Declaration") frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Declaration")
frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Proof Submission") frappe.reload_doc("hr", "doctype", "Employee Tax Exemption Proof Submission")
frappe.reload_doc("hr", "doctype", "Employee Grade")
frappe.reload_doc("hr", "doctype", "Leave Policy")
frappe.reload_doc("accounts", "doctype", "Bank Account")
frappe.reload_doc("accounts", "doctype", "Tax Withholding Category")
frappe.reload_doc("accounts", "doctype", "Allowed To Transact With")
frappe.reload_doc("accounts", "doctype", "Finance Book")
frappe.reload_doc("accounts", "doctype", "Loyalty Program")
frappe.reload_doc("stock", "doctype", "Item Barcode")
make_custom_fields() make_custom_fields()
frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges") frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges")

View File

@@ -6,5 +6,10 @@ import frappe
def execute(): def execute():
stock_settings = frappe.get_doc('Stock Settings') stock_settings = frappe.get_doc('Stock Settings')
if stock_settings.default_warehouse and not frappe.db.exists("Warehouse", stock_settings.default_warehouse):
stock_settings.default_warehouse = None
if stock_settings.stock_uom and not frappe.db.exists("UOM", stock_settings.stock_uom):
stock_settings.stock_uom = None
stock_settings.flags.ignore_mandatory = True
stock_settings.action_if_quality_inspection_is_not_submitted = "Stop" stock_settings.action_if_quality_inspection_is_not_submitted = "Stop"
stock_settings.save() stock_settings.save()

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doc("accounts", "doctype", "bank_transaction")
frappe.db.sql(""" UPDATE `tabBank Transaction`
SET status = 'Reconciled'
WHERE
status = 'Settled' and (debit = allocated_amount or credit = allocated_amount)
and ifnull(allocated_amount, 0) > 0
""")

View File

@@ -7,8 +7,11 @@ def execute():
if not company: if not company:
return return
frappe.reload_doc('hr', 'doctype', 'payroll_period')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration') frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission') frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration_category')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission_detail')
for doctype in ["Sales Invoice", "Delivery Note", "Purchase Invoice"]: for doctype in ["Sales Invoice", "Delivery Note", "Purchase Invoice"]:
frappe.db.sql("""delete from `tabCustom Field` where dt = %s frappe.db.sql("""delete from `tabCustom Field` where dt = %s

View File

@@ -64,7 +64,7 @@ class Project(Document):
'name': ("not in", self.deleted_task_list) 'name': ("not in", self.deleted_task_list)
}) })
return frappe.get_all("Task", "*", filters, order_by="exp_start_date asc") return frappe.get_all("Task", "*", filters, order_by="exp_start_date asc, status asc")
def validate(self): def validate(self):
self.validate_project_name() self.validate_project_name()
@@ -442,16 +442,17 @@ def daily_reminder():
projects = get_projects_for_collect_progress("Daily", fields) projects = get_projects_for_collect_progress("Daily", fields)
for project in projects: for project in projects:
if not check_project_update_exists(project.name, project.get("daily_time_to_send")): if allow_to_make_project_update(project.name, project.get("daily_time_to_send"), "Daily"):
send_project_update_email_to_users(project.name) send_project_update_email_to_users(project.name)
def twice_daily_reminder(): def twice_daily_reminder():
fields = ["first_email", "second_email"] fields = ["first_email", "second_email"]
projects = get_projects_for_collect_progress("Twice Daily", fields) projects = get_projects_for_collect_progress("Twice Daily", fields)
fields.remove("name")
for project in projects: for project in projects:
for d in fields: for d in fields:
if not check_project_update_exists(project.name, project.get(d)): if allow_to_make_project_update(project.name, project.get(d), "Twicely"):
send_project_update_email_to_users(project.name) send_project_update_email_to_users(project.name)
def weekly_reminder(): def weekly_reminder():
@@ -463,14 +464,19 @@ def weekly_reminder():
if current_day != project.day_to_send: if current_day != project.day_to_send:
continue continue
if not check_project_update_exists(project.name, project.get("weekly_time_to_send")): if allow_to_make_project_update(project.name, project.get("weekly_time_to_send"), "Weekly"):
send_project_update_email_to_users(project.name) send_project_update_email_to_users(project.name)
def check_project_update_exists(project, time): def allow_to_make_project_update(project, time, frequency):
data = frappe.db.sql(""" SELECT name from `tabProject Update` data = frappe.db.sql(""" SELECT name from `tabProject Update`
WHERE project = %s and date = %s and time >= %s """, (project, today(), time)) WHERE project = %s and date = %s """, (project, today()))
return True if data and data[0][0] else False # len(data) > 1 condition is checked for twicely frequency
if data and (frequency in ['Daily', 'Weekly'] or len(data) > 1):
return False
if get_time(nowtime()) >= get_time(time):
return True
def get_projects_for_collect_progress(frequency, fields): def get_projects_for_collect_progress(frequency, fields):
fields.extend(["name"]) fields.extend(["name"])

View File

@@ -260,6 +260,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) { if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
this.setup_item_selector(); this.setup_item_selector();
this.frm.get_field("items").grid.set_multiple_add("item_code", "qty");
} }
}, },
@@ -429,6 +430,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
set_warehouse: me.frm.doc.set_warehouse, set_warehouse: me.frm.doc.set_warehouse,
warehouse: item.warehouse, warehouse: item.warehouse,
customer: me.frm.doc.customer || me.frm.doc.party_name, customer: me.frm.doc.customer || me.frm.doc.party_name,
quotation_to: me.frm.doc.quotation_to,
supplier: me.frm.doc.supplier, supplier: me.frm.doc.supplier,
currency: me.frm.doc.currency, currency: me.frm.doc.currency,
update_stock: update_stock, update_stock: update_stock,
@@ -1119,6 +1121,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return { return {
"items": this._get_item_list(item), "items": this._get_item_list(item),
"customer": me.frm.doc.customer || me.frm.doc.party_name, "customer": me.frm.doc.customer || me.frm.doc.party_name,
"quotation_to": me.frm.doc.quotation_to,
"customer_group": me.frm.doc.customer_group, "customer_group": me.frm.doc.customer_group,
"territory": me.frm.doc.territory, "territory": me.frm.doc.territory,
"supplier": me.frm.doc.supplier, "supplier": me.frm.doc.supplier,

View File

@@ -154,11 +154,9 @@ def calculate_annual_eligible_hra_exemption(doc):
hra_component = frappe.get_cached_value('Company', doc.company, "hra_component") hra_component = frappe.get_cached_value('Company', doc.company, "hra_component")
if not (basic_component and hra_component): if not (basic_component and hra_component):
frappe.throw(_("Please mention Basic and HRA component in Company")) frappe.throw(_("Please mention Basic and HRA component in Company"))
annual_exemption, monthly_exemption, hra_amount = 0, 0, 0 annual_exemption, monthly_exemption, hra_amount = 0, 0, 0
if hra_component and basic_component: if hra_component and basic_component:
assignment = get_salary_assignment(doc.employee, nowdate()) assignment = get_salary_assignment(doc.employee, nowdate())
if assignment: if assignment:
hra_component_exists = frappe.db.exists("Salary Detail", { hra_component_exists = frappe.db.exists("Salary Detail", {
"parent": assignment.salary_structure, "parent": assignment.salary_structure,
@@ -166,18 +164,19 @@ def calculate_annual_eligible_hra_exemption(doc):
"parentfield": "earnings", "parentfield": "earnings",
"parenttype": "Salary Structure" "parenttype": "Salary Structure"
}) })
if hra_component_exists: if hra_component_exists:
basic_amount, hra_amount = get_component_amt_from_salary_slip(doc.employee, basic_amount, hra_amount = get_component_amt_from_salary_slip(doc.employee,
assignment.salary_structure, basic_component, hra_component) assignment.salary_structure, basic_component, hra_component)
if hra_amount: if hra_amount:
if doc.monthly_house_rent: if doc.monthly_house_rent:
annual_exemption = calculate_hra_exemption(assignment.salary_structure, annual_exemption = calculate_hra_exemption(assignment.salary_structure,
basic_amount, hra_amount, doc.monthly_house_rent, basic_amount, hra_amount, doc.monthly_house_rent, doc.rented_in_metro_city)
doc.rented_in_metro_city)
if annual_exemption > 0: if annual_exemption > 0:
monthly_exemption = annual_exemption / 12 monthly_exemption = annual_exemption / 12
else: else:
annual_exemption = 0 annual_exemption = 0
elif doc.docstatus == 1: elif doc.docstatus == 1:
frappe.throw(_("Salary Structure must be submitted before submission of Tax Ememption Declaration")) frappe.throw(_("Salary Structure must be submitted before submission of Tax Ememption Declaration"))
@@ -188,7 +187,7 @@ def calculate_annual_eligible_hra_exemption(doc):
}) })
def get_component_amt_from_salary_slip(employee, salary_structure, basic_component, hra_component): def get_component_amt_from_salary_slip(employee, salary_structure, basic_component, hra_component):
salary_slip = make_salary_slip(salary_structure, employee=employee) salary_slip = make_salary_slip(salary_structure, employee=employee, for_preview=1)
basic_amt, hra_amt = 0, 0 basic_amt, hra_amt = 0, 0
for earning in salary_slip.earnings: for earning in salary_slip.earnings:
if earning.salary_component == basic_component: if earning.salary_component == basic_component:

View File

@@ -75,12 +75,12 @@ def prepare_invoice(invoice, progressive_number):
invoice.tax_data = tax_data invoice.tax_data = tax_data
#Check if stamp duty (Bollo) of 2 EUR exists. #Check if stamp duty (Bollo) of 2 EUR exists.
stamp_duty_charge_row = next((tax for tax in invoice.taxes if tax.charge_type == _("Actual") and tax.tax_amount == 2.0 ), None) stamp_duty_charge_row = next((tax for tax in invoice.taxes if tax.charge_type == "Actual" and tax.tax_amount == 2.0 ), None)
if stamp_duty_charge_row: if stamp_duty_charge_row:
invoice.stamp_duty = stamp_duty_charge_row.tax_amount invoice.stamp_duty = stamp_duty_charge_row.tax_amount
for item in invoice.e_invoice_items: for item in invoice.e_invoice_items:
if item.tax_rate == 0.0 and item.tax_amount == 0.0: if item.tax_rate == 0.0 and item.tax_amount == 0.0 and tax_data.get("0.0"):
item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"] item.tax_exemption_reason = tax_data["0.0"]["tax_exemption_reason"]
customer_po_data = {} customer_po_data = {}
@@ -222,7 +222,7 @@ def sales_invoice_validate(doc):
#Validate customer details #Validate customer details
customer = frappe.get_doc("Customer", doc.customer) customer = frappe.get_doc("Customer", doc.customer)
if customer.customer_type == _("Individual"): if customer.customer_type == "Individual":
doc.customer_fiscal_code = customer.fiscal_code doc.customer_fiscal_code = customer.fiscal_code
if not doc.customer_fiscal_code: if not doc.customer_fiscal_code:
frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing")) frappe.throw(_("Please set Fiscal Code for the customer '%s'" % doc.customer), title=_("E-Invoicing Information Missing"))

View File

@@ -80,7 +80,10 @@ def get_gl_entries(filters):
jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle, jnl.cheque_no as JnlRef, jnl.posting_date as JnlPostDate, jnl.title as JnlTitle,
pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle, pay.name as PayName, pay.posting_date as PayPostDate, pay.title as PayTitle,
cus.customer_name, cus.name as cusName, cus.customer_name, cus.name as cusName,
sup.supplier_name, sup.name as supName sup.supplier_name, sup.name as supName,
emp.employee_name, emp.name as empName,
stu.title as student_name, stu.name as stuName,
member_name, mem.name as memName
from `tabGL Entry` gl from `tabGL Entry` gl
left join `tabSales Invoice` inv on gl.voucher_no = inv.name left join `tabSales Invoice` inv on gl.voucher_no = inv.name
@@ -89,6 +92,9 @@ def get_gl_entries(filters):
left join `tabPayment Entry` pay on gl.voucher_no = pay.name left join `tabPayment Entry` pay on gl.voucher_no = pay.name
left join `tabCustomer` cus on gl.party = cus.name left join `tabCustomer` cus on gl.party = cus.name
left join `tabSupplier` sup on gl.party = sup.name left join `tabSupplier` sup on gl.party = sup.name
left join `tabEmployee` emp on gl.party = emp.name
left join `tabStudent` stu on gl.party = stu.name
left join `tabMember` mem on gl.party = mem.name
where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s where gl.company=%(company)s and gl.fiscal_year=%(fiscal_year)s
{group_by_condition} {group_by_condition}
order by GlPostDate, voucher_no"""\ order by GlPostDate, voucher_no"""\
@@ -128,6 +134,18 @@ def get_result_as_list(data, filters):
CompAuxNum = d.get("supName") CompAuxNum = d.get("supName")
CompAuxLib = d.get("supplier_name") CompAuxLib = d.get("supplier_name")
elif d.get("party_type") == "Employee":
CompAuxNum = d.get("empName")
CompAuxLib = d.get("employee_name")
elif d.get("party_type") == "Student":
CompAuxNum = d.get("stuName")
CompAuxLib = d.get("student_name")
elif d.get("party_type") == "Member":
CompAuxNum = d.get("memName")
CompAuxLib = d.get("member_name")
else: else:
CompAuxNum = "" CompAuxNum = ""
CompAuxLib = "" CompAuxLib = ""

View File

@@ -61,7 +61,7 @@ class Gstr1Report(object):
for inv, items_based_on_rate in self.items_based_on_tax_rate.items(): for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
invoice_details = self.invoices.get(inv) invoice_details = self.invoices.get(inv)
for rate, items in items_based_on_rate.items(): for rate, items in items_based_on_rate.items():
row = self.get_row_data_for_invoice(inv, invoice_details, rate, items) row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
if self.filters.get("type_of_business") == "CDNR": if self.filters.get("type_of_business") == "CDNR":
row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
@@ -118,7 +118,7 @@ class Gstr1Report(object):
for item_code, net_amount in self.invoice_items.get(invoice).items() if item_code in items]) for item_code, net_amount in self.invoice_items.get(invoice).items() if item_code in items])
row += [tax_rate or 0, taxable_value] row += [tax_rate or 0, taxable_value]
return row return row, taxable_value
def get_invoice_data(self): def get_invoice_data(self):
self.invoices = frappe._dict() self.invoices = frappe._dict()
@@ -149,7 +149,8 @@ class Gstr1Report(object):
if self.filters.get("type_of_business") == "B2B": if self.filters.get("type_of_business") == "B2B":
conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1 conditions += """ and ifnull(invoice_type, '') != 'Export' and is_return != 1
and customer in ('{0}')""".format("', '".join([frappe.db.escape(c.name) for c in customers])) and customer in ('{0}') and (customer_gstin IS NOT NULL OR customer_gstin NOT IN ('', 'NA'))""".\
format("', '".join([frappe.db.escape(c.name) for c in customers]))
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')

View File

@@ -10,6 +10,9 @@ def get_data():
'Quotation': 'party_name', 'Quotation': 'party_name',
'Opportunity': 'party_name' 'Opportunity': 'party_name'
}, },
'dynamic_links': {
'party_name': ['Customer', 'quotation_to']
},
'transactions': [ 'transactions': [
{ {
'label': _('Pre Sales'), 'label': _('Pre Sales'),

View File

@@ -47,6 +47,10 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
erpnext.utils.get_party_details(this.frm, null, null, function() { erpnext.utils.get_party_details(this.frm, null, null, function() {
me.apply_price_list(); me.apply_price_list();
}); });
if(me.frm.doc.quotation_to=="Lead" && me.frm.doc.party_name) {
me.frm.trigger("get_lead_details");
}
}, },
refresh: function(doc, dt, dn) { refresh: function(doc, dt, dn) {
this._super(doc, dt, dn); this._super(doc, dt, dn);
@@ -87,10 +91,10 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
this.frm.add_custom_button(__('Opportunity'), this.frm.add_custom_button(__('Opportunity'),
function() { function() {
var setters = {}; var setters = {};
if(me.frm.doc.customer) { if(me.frm.doc.quotation_to == "Customer" && me.frm.doc.party_name) {
setters.customer = me.frm.doc.customer || undefined; setters.customer = me.frm.doc.party_name || undefined;
} else if (me.frm.doc.lead) { } else if (me.frm.doc.quotation_to == "Lead" && me.frm.doc.party_name) {
setters.lead = me.frm.doc.lead || undefined; setters.lead = me.frm.doc.party_name || undefined;
} }
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation", method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation",
@@ -162,16 +166,16 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
} }
}, },
lead: function() { get_lead_details: function() {
var me = this; var me = this;
if(!this.frm.doc.lead) { if(!this.frm.doc.quotation_to === "Lead") {
return; return;
} }
frappe.call({ frappe.call({
method: "erpnext.crm.doctype.lead.lead.get_lead_details", method: "erpnext.crm.doctype.lead.lead.get_lead_details",
args: { args: {
'lead': this.frm.doc.lead, 'lead': this.frm.doc.party_name,
'posting_date': this.frm.doc.transaction_date, 'posting_date': this.frm.doc.transaction_date,
'company': this.frm.doc.company, 'company': this.frm.doc.company,
}, },

View File

@@ -133,7 +133,7 @@
"in_filter": 0, "in_filter": 0,
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 1,
"label": "Quotation To", "label": "Quotation To",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -169,7 +169,7 @@
"in_filter": 0, "in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 1,
"label": "Party", "label": "Party",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@@ -3224,7 +3224,7 @@
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"menu_index": 0, "menu_index": 0,
"modified": "2019-05-07 14:29:22.565474", "modified": "2019-05-11 19:26:50.735628",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@@ -1,6 +1,17 @@
frappe.listview_settings['Quotation'] = { frappe.listview_settings['Quotation'] = {
add_fields: ["customer_name", "base_grand_total", "status", add_fields: ["customer_name", "base_grand_total", "status",
"company", "currency", 'valid_till'], "company", "currency", 'valid_till'],
onload: function(listview) {
listview.page.fields_dict.quotation_to.get_query = function() {
return {
"filters": {
"name": ["in", ["Customer", "Lead"]],
}
};
};
},
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.status==="Submitted") { if(doc.status==="Submitted") {
if (doc.valid_till && doc.valid_till < frappe.datetime.nowdate()) { if (doc.valid_till && doc.valid_till < frappe.datetime.nowdate()) {

View File

@@ -77,10 +77,6 @@ frappe.ui.form.on("Sales Order", {
if(!d.delivery_date) d.delivery_date = frm.doc.delivery_date; if(!d.delivery_date) d.delivery_date = frm.doc.delivery_date;
}); });
refresh_field("items"); refresh_field("items");
},
onload_post_render: function(frm) {
frm.get_field("items").grid.set_multiple_add("item_code", "qty");
} }
}); });
@@ -144,12 +140,15 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
} }
// delivery note // delivery note
if(flt(doc.per_delivered, 6) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) { if(flt(doc.per_delivered, 6) < 100 && allow_delivery) {
this.frm.add_custom_button(__('Delivery'), this.frm.add_custom_button(__('Delivery'),
function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make")); function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make"));
this.frm.add_custom_button(__('Work Order'),
function() { me.make_work_order() }, __("Make"));
if(["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1){
this.frm.add_custom_button(__('Work Order'),
function() { me.make_work_order() }, __("Make"));
}
this.frm.page.set_inner_btn_group_as_primary(__("Make")); this.frm.page.set_inner_btn_group_as_primary(__("Make"));
} }

View File

@@ -31,17 +31,25 @@ frappe.listview_settings['Sales Order'] = {
"per_delivered,<,100|per_billed,=,100|status,!=,Closed"]; "per_delivered,<,100|per_billed,=,100|status,!=,Closed"];
} }
} else if ((doc.order_type === "Maintenance" || flt(doc.per_delivered, 6) == 100) } else if ((flt(doc.per_delivered, 6) == 100)
&& flt(doc.grand_total) !== 0 && flt(doc.per_billed, 6) < 100 && doc.status !== "Closed") { && flt(doc.grand_total) !== 0 && flt(doc.per_billed, 6) < 100 && doc.status !== "Closed") {
// to bill // to bill
return [__("To Bill"), "orange", "per_delivered,=,100|per_billed,<,100|status,!=,Closed"]; return [__("To Bill"), "orange", "per_delivered,=,100|per_billed,<,100|status,!=,Closed"];
} else if ((doc.order_type === "Maintenance" || flt(doc.per_delivered, 6) == 100) } else if ((flt(doc.per_delivered, 6) === 100)
&& (flt(doc.grand_total) === 0 || flt(doc.per_billed, 6) == 100) && doc.status !== "Closed") { && (flt(doc.grand_total) === 0 || flt(doc.per_billed, 6) == 100) && doc.status !== "Closed") {
return [__("Completed"), "green", "per_delivered,=,100|per_billed,=,100|status,!=,Closed"]; return [__("Completed"), "green", "per_delivered,=,100|per_billed,=,100|status,!=,Closed"];
}else if (doc.order_type === "Maintenance" && flt(doc.per_delivered, 6) < 100 && doc.status !== "Closed"){
if(flt(doc.per_billed, 6) < 100 ){
return [__("To Deliver and Bill"), "orange", "per_delivered,=,100|per_billed,<,100|status,!=,Closed"];
}else if(flt(doc.per_billed, 6) == 100){
return [__("To Deliver"), "orange", "per_delivered,=,100|per_billed,=,100|status,!=,Closed"];
}
} }
}, },
onload: function(listview) { onload: function(listview) {
var method = "erpnext.selling.doctype.sales_order.sales_order.close_or_unclose_sales_orders"; var method = "erpnext.selling.doctype.sales_order.sales_order.close_or_unclose_sales_orders";

Some files were not shown because too many files have changed in this diff Show More