Merge branch 'v12-pre-release' into version-12

This commit is contained in:
Sahil Khan
2020-01-16 14:30:35 +05:30
114 changed files with 3973 additions and 8492 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__ = '12.3.1' __version__ = '12.4.0'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -41,8 +41,8 @@ class AccountingPeriod(Document):
def get_doctypes_for_closing(self): def get_doctypes_for_closing(self):
docs_for_closing = [] docs_for_closing = []
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation", doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
"Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"] "Bank Reconciliation", "Asset", "Stock Entry"]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes: for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype) docs_for_closing.append(closed_doctype)

View File

@@ -29,7 +29,6 @@ class GLEntry(Document):
self.validate_and_set_fiscal_year() self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center() self.pl_must_have_cost_center()
self.validate_cost_center() self.validate_cost_center()
self.validate_dimensions_for_pl_and_bs()
if not self.flags.from_repost: if not self.flags.from_repost:
self.check_pl_account() self.check_pl_account()
@@ -39,6 +38,7 @@ class GLEntry(Document):
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False): def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost: if not from_repost:
self.validate_account_details(adv_adj) self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
check_freezing_date(self.posting_date, adv_adj) check_freezing_date(self.posting_date, adv_adj)
validate_frozen_account(self.account, adv_adj) validate_frozen_account(self.account, adv_adj)

View File

@@ -190,7 +190,6 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
if(jvd.reference_type==="Employee Advance") { if(jvd.reference_type==="Employee Advance") {
return { return {
filters: { filters: {
'status': ['=', 'Unpaid'],
'docstatus': 1 'docstatus': 1
} }
}; };

View File

@@ -9,7 +9,6 @@ from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.utils import get_balance_on, get_account_currency from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.hr.doctype.loan.loan import update_disbursement_status, update_total_amount_paid
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
from six import string_types, iteritems from six import string_types, iteritems
@@ -606,8 +605,8 @@ class JournalEntry(AccountsController):
for d in self.accounts: for d in self.accounts:
if d.reference_type=="Loan" and flt(d.debit) > 0: if d.reference_type=="Loan" and flt(d.debit) > 0:
doc = frappe.get_doc("Loan", d.reference_name) doc = frappe.get_doc("Loan", d.reference_name)
update_disbursement_status(doc) doc.update_total_amount_paid()
update_total_amount_paid(doc) doc.set_status()
def validate_expense_claim(self): def validate_expense_claim(self):
for d in self.accounts: for d in self.accounts:
@@ -968,7 +967,7 @@ def get_exchange_rate(posting_date, account=None, account_currency=None, company
# The date used to retreive the exchange rate here is the date passed # The date used to retreive the exchange rate here is the date passed
# in as an argument to this function. # in as an argument to this function.
elif (not exchange_rate or exchange_rate==1) and account_currency and posting_date: elif (not exchange_rate or flt(exchange_rate)==1) and account_currency and posting_date:
exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date) exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
else: else:
exchange_rate = 1 exchange_rate = 1

View File

@@ -90,7 +90,6 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"default": "Customer",
"fieldname": "party_type", "fieldname": "party_type",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
@@ -272,7 +271,7 @@
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2019-10-02 12:23:21.693443", "modified": "2020-01-13 12:41:33.968025",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Journal Entry Account", "name": "Journal Entry Account",

View File

@@ -652,14 +652,16 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
) { ) {
if(total_positive_outstanding > total_negative_outstanding) if(total_positive_outstanding > total_negative_outstanding)
frm.set_value("paid_amount", if (!frm.doc.paid_amount)
total_positive_outstanding - total_negative_outstanding); frm.set_value("paid_amount",
total_positive_outstanding - total_negative_outstanding);
} else if ( } else if (
total_negative_outstanding && total_negative_outstanding &&
total_positive_outstanding < total_negative_outstanding total_positive_outstanding < total_negative_outstanding
) { ) {
frm.set_value("received_amount", if (!frm.doc.received_amount)
total_negative_outstanding - total_positive_outstanding); frm.set_value("received_amount",
total_negative_outstanding - total_positive_outstanding);
} }
} }

View File

@@ -911,7 +911,10 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
else: else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company) party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account) if dt not in ("Sales Invoice", "Purchase Invoice"):
party_account_currency = get_account_currency(party_account)
else:
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
# payment type # payment type
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \ if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees") and doc.outstanding_amount > 0)) \

View File

@@ -23,6 +23,8 @@ class PaymentReconciliation(Document):
if self.party_type in ["Customer", "Supplier"]: if self.party_type in ["Customer", "Supplier"]:
dr_or_cr_notes = self.get_dr_or_cr_notes() dr_or_cr_notes = self.get_dr_or_cr_notes()
else:
dr_or_cr_notes = []
self.add_payment_entries(payment_entries + journal_entries + dr_or_cr_notes) self.add_payment_entries(payment_entries + journal_entries + dr_or_cr_notes)

View File

@@ -2,6 +2,16 @@ cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account
cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway") cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway_account", "message", "message") cur_frm.add_fetch("payment_gateway_account", "message", "message")
frappe.ui.form.on("Payment Request", {
setup: function(frm) {
frm.set_query("party_type", function() {
return {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
};
});
}
})
frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
if (frm.doc.reference_doctype) { if (frm.doc.reference_doctype) {
frappe.call({ frappe.call({

View File

@@ -34,6 +34,9 @@ class PricingRule(Document):
def validate_duplicate_apply_on(self): def validate_duplicate_apply_on(self):
field = apply_on_dict.get(self.apply_on) field = apply_on_dict.get(self.apply_on)
if not field:
return False
values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field)] values = [d.get(frappe.scrub(self.apply_on)) for d in self.get(field)]
if len(values) != len(set(values)): if len(values) != len(set(values)):
@@ -419,4 +422,4 @@ def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
return frappe.get_all('UOM Conversion Detail', return frappe.get_all('UOM Conversion Detail',
filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))}, filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))},
fields = ["distinct uom"], as_list=1) fields = ["distinct uom"], as_list=1)

View File

@@ -495,7 +495,7 @@ def get_pricing_rule_items(pr_doc):
if pr_doc.apply_rule_on_other: if pr_doc.apply_rule_on_other:
apply_on = frappe.scrub(pr_doc.apply_rule_on_other) apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
apply_on_data.append(pr_doc.get(apply_on)) apply_on_data.append(pr_doc.get("other_" + apply_on))
return list(set(apply_on_data)) return list(set(apply_on_data))

View File

@@ -382,21 +382,11 @@ cur_frm.fields_dict['items'].grid.get_field("item_code").get_query = function(do
cur_frm.fields_dict['credit_to'].get_query = function(doc) { cur_frm.fields_dict['credit_to'].get_query = function(doc) {
// filter on Account // filter on Account
if (doc.supplier) { return {
return { filters: {
filters: { 'account_type': 'Payable',
'account_type': 'Payable', 'is_group': 0,
'is_group': 0, 'company': doc.company
'company': doc.company
}
}
} else {
return {
filters: {
'report_type': 'Balance Sheet',
'is_group': 0,
'company': doc.company
}
} }
} }
} }

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-21 16:16:39", "creation": "2013-05-21 16:16:39",
@@ -417,6 +418,7 @@
"fieldname": "contact_email", "fieldname": "contact_email",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Contact Email", "label": "Contact Email",
"options": "Email",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@@ -705,7 +707,7 @@
}, },
{ {
"fieldname": "other_charges_calculation", "fieldname": "other_charges_calculation",
"fieldtype": "Text", "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation", "label": "Taxes and Charges Calculation",
"no_copy": 1, "no_copy": 1,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
@@ -1287,7 +1289,8 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-09-17 22:31:42.666601", "links": [],
"modified": "2019-12-30 19:13:49.610538",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -556,22 +556,11 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
} }
cur_frm.set_query("debit_to", function(doc) { cur_frm.set_query("debit_to", function(doc) {
// filter on Account return {
if (doc.customer) { filters: {
return { 'account_type': 'Receivable',
filters: { 'is_group': 0,
'account_type': 'Receivable', 'company': doc.company
'is_group': 0,
'company': doc.company
}
}
} else {
return {
filters: {
'report_type': 'Balance Sheet',
'is_group': 0,
'company': doc.company
}
} }
} }
}); });

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
@@ -774,7 +775,7 @@
}, },
{ {
"fieldname": "other_charges_calculation", "fieldname": "other_charges_calculation",
"fieldtype": "Text", "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation", "label": "Taxes and Charges Calculation",
"no_copy": 1, "no_copy": 1,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
@@ -1567,7 +1568,8 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-10-05 21:39:49.235990", "links": [],
"modified": "2019-12-30 19:15:59.580414",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
import unittest, copy, time import unittest, copy, time
from frappe.utils import nowdate, flt, getdate, cint from frappe.utils import nowdate, flt, getdate, cint, add_days
from frappe.model.dynamic_links import get_dynamic_link_map from frappe.model.dynamic_links import get_dynamic_link_map
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
@@ -1847,6 +1847,26 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
def test_item_tax_validity(self):
item = frappe.get_doc("Item", "_Test Item 2")
if item.taxes:
item.taxes = []
item.save()
item.append("taxes", {
"item_tax_template": "_Test Item Tax Template 1",
"valid_from": add_days(nowdate(), 1)
})
item.save()
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1"
self.assertRaises(frappe.ValidationError, sales_invoice.save)
item.taxes = []
item.save()
def create_sales_invoice(**args): def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")

View File

@@ -188,7 +188,7 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-11-07 13:31:17.999744", "modified": "2019-12-20 14:48:01.990600",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Share Transfer", "name": "Share Transfer",
@@ -196,6 +196,7 @@
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@@ -221,6 +222,7 @@
"write": 1 "write": 1
}, },
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@@ -230,6 +232,7 @@
"report": 1, "report": 1,
"role": "Accounts Manager", "role": "Accounts Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],

View File

@@ -338,6 +338,16 @@ class Subscription(Document):
# Check invoice dates and make sure it doesn't have outstanding invoices # Check invoice dates and make sure it doesn't have outstanding invoices
return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice() return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice()
def is_current_invoice_paid(self):
if self.is_new_subscription():
return False
last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice)
if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid':
return True
return False
def process_for_active(self): def process_for_active(self):
""" """
@@ -348,7 +358,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 not self.is_current_invoice_paid() and (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

@@ -1,134 +1,134 @@
{ {
"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": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"autoname": "field:title", "autoname": "field:title",
"beta": 0, "beta": 0,
"creation": "2018-11-22 23:38:39.668804", "creation": "2018-11-22 23:38:39.668804",
"custom": 0, "custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "", "document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "title", "fieldname": "title",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Title", "label": "Title",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 1 "unique": 1
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-11-22 23:38:39.668804", "modified": "2020-01-15 17:14:28.951793",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Tax Category", "name": "Tax Category",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts Manager", "role": "Accounts Manager",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "amend": 0,
"cancel": 0, "cancel": 0,
"create": 0, "create": 0,
"delete": 0, "delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User", "role": "Accounts User",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 0 "write": 0
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@@ -49,7 +49,7 @@
{% endfor %} {% endfor %}
<tr> <tr>
<td class="right" colspan="3" ><strong>Total (debit) </strong></td> <td class="right" colspan="3" ><strong>Total (debit) </strong></td>
<td class="left" >{{ gl | sum(attribute='debit') }}</td> <td class="left" >{{ frappe.format((gl | sum(attribute="debit")), {fieldtype: "Currency"}) }}</td>
</tr> </tr>
<tr> <tr>
<td class="top-bottom" colspan="5"><strong>Credit</strong></td> <td class="top-bottom" colspan="5"><strong>Credit</strong></td>
@@ -69,7 +69,7 @@
{% endfor %} {% endfor %}
<tr> <tr>
<td class="right" colspan="3"><strong>Total (credit) </strong></td> <td class="right" colspan="3"><strong>Total (credit) </strong></td>
<td class="left" >{{ gl | sum(attribute='credit') }}</td> <td class="left" >{{ frappe.format((gl | sum(attribute="credit")), {fieldtype: "Currency"}) }}</td>
</tr> </tr>
</table> </table>
<div> <div>

View File

@@ -171,7 +171,7 @@ class ReceivablePayableReport(object):
row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision) row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision)
row.invoice_grand_total = row.invoiced row.invoice_grand_total = row.invoiced
if abs(row.outstanding) > 0.1/10 ** self.currency_precision: if abs(row.outstanding) > 1.0/10 ** self.currency_precision:
# non-zero oustanding, we must consider this row # non-zero oustanding, we must consider this row
if self.is_invoice(row) and self.filters.based_on_payment_terms: if self.is_invoice(row) and self.filters.based_on_payment_terms:
@@ -285,7 +285,7 @@ class ReceivablePayableReport(object):
def set_party_details(self, row): def set_party_details(self, row):
# customer / supplier name # customer / supplier name
party_details = self.get_party_details(row.party) party_details = self.get_party_details(row.party) or {}
row.update(party_details) row.update(party_details)
if self.filters.get(scrub(self.filters.party_type)): if self.filters.get(scrub(self.filters.party_type)):
row.currency = row.account_currency row.currency = row.account_currency

View File

@@ -46,13 +46,24 @@ frappe.query_reports["Budget Variance Report"] = {
fieldtype: "Select", fieldtype: "Select",
options: ["Cost Center", "Project"], options: ["Cost Center", "Project"],
default: "Cost Center", default: "Cost Center",
reqd: 1 reqd: 1,
on_change: function() {
frappe.query_report.set_filter_value("budget_against_filter", []);
frappe.query_report.refresh();
}
}, },
{ {
fieldname: "cost_center", fieldname:"budget_against_filter",
label: __("Cost Center"), label: __('Dimension Filter'),
fieldtype: "Link", fieldtype: "MultiSelectList",
options: "Cost Center" get_data: function(txt) {
if (!frappe.query_report.filters) return;
let budget_against = frappe.query_report.get_filter_value('budget_against');
if (!budget_against) return;
return frappe.db.get_link_options(budget_against, txt);
}
}, },
{ {
fieldname:"show_cumulative", fieldname:"show_cumulative",

View File

@@ -12,22 +12,22 @@ from six import iteritems
from pprint import pprint from pprint import pprint
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
validate_filters(filters)
columns = get_columns(filters) columns = get_columns(filters)
if filters.get("cost_center"): if filters.get("budget_against_filter"):
cost_centers = [filters.get("cost_center")] dimensions = filters.get("budget_against_filter")
else: else:
cost_centers = get_cost_centers(filters) dimensions = get_cost_centers(filters)
period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"]) period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
cam_map = get_cost_center_account_month_map(filters) cam_map = get_dimension_account_month_map(filters)
data = [] data = []
for cost_center in cost_centers: for dimension in dimensions:
cost_center_items = cam_map.get(cost_center) dimension_items = cam_map.get(dimension)
if cost_center_items: if dimension_items:
for account, monthwise_data in iteritems(cost_center_items): for account, monthwise_data in iteritems(dimension_items):
row = [cost_center, account] row = [dimension, account]
totals = [0, 0, 0] totals = [0, 0, 0]
for year in get_fiscal_years(filters): for year in get_fiscal_years(filters):
last_total = 0 last_total = 0
@@ -55,10 +55,6 @@ def execute(filters=None):
return columns, data return columns, data
def validate_filters(filters):
if filters.get("budget_against") != "Cost Center" and filters.get("cost_center"):
frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center"))
def get_columns(filters): def get_columns(filters):
columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"] columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
@@ -98,11 +94,12 @@ def get_cost_centers(filters):
else: else:
return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec
#Get cost center & target details #Get dimension & target details
def get_cost_center_target_details(filters): def get_dimension_target_details(filters):
cond = "" cond = ""
if filters.get("cost_center"): if filters.get("budget_against_filter"):
cond += " and b.cost_center=%s" % frappe.db.escape(filters.get("cost_center")) cond += " and b.{budget_against} in (%s)".format(budget_against = \
frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter')))
return frappe.db.sql(""" return frappe.db.sql("""
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
@@ -110,8 +107,8 @@ def get_cost_center_target_details(filters):
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s
and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year
""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond), """.format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
(filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True) tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')),
as_dict=True)
#Get target distribution details of accounts of cost center #Get target distribution details of accounts of cost center
@@ -153,14 +150,14 @@ def get_actual_details(name, filters):
return cc_actual_details return cc_actual_details
def get_cost_center_account_month_map(filters): def get_dimension_account_month_map(filters):
import datetime import datetime
cost_center_target_details = get_cost_center_target_details(filters) dimension_target_details = get_dimension_target_details(filters)
tdd = get_target_distribution_details(filters) tdd = get_target_distribution_details(filters)
cam_map = {} cam_map = {}
for ccd in cost_center_target_details: for ccd in dimension_target_details:
actual_details = get_actual_details(ccd.budget_against, filters) actual_details = get_actual_details(ccd.budget_against, filters)
for month_id in range(1, 13): for month_id in range(1, 13):

View File

@@ -33,7 +33,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for(var i=0, l=data.length-1; i<l; i++) { %} {% for(var i=0, l=data.length; i<l; i++) { %}
<tr> <tr>
{% if(data[i].posting_date) { %} {% if(data[i].posting_date) { %}
<td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td> <td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td>

View File

@@ -38,32 +38,46 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", []))) cost_center = list(set(invoice_cc_wh_map.get(inv.name, {}).get("cost_center", [])))
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", []))) warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
row = [ row = {
inv.name, inv.posting_date, inv.customer, inv.customer_name 'invoice': inv.name,
] 'posting_date': inv.posting_date,
'customer': inv.customer,
'customer_name': inv.customer_name
}
if additional_query_columns: if additional_query_columns:
for col in additional_query_columns: for col in additional_query_columns:
row.append(inv.get(col)) row.update({
col: inv.get(col)
})
row.update({
'customer_group': inv.get("customer_group"),
'territory': inv.get("territory"),
'tax_id': inv.get("tax_id"),
'receivable_account': inv.debit_to,
'mode_of_payment': ", ".join(mode_of_payments.get(inv.name, [])),
'project': inv.project,
'owner': inv.owner,
'remarks': inv.remarks,
'sales_order': ", ".join(sales_order),
'delivery_note': ", ".join(delivery_note),
'cost_center': ", ".join(cost_center),
'warehouse': ", ".join(warehouse),
'currency': company_currency
})
row +=[
inv.get("customer_group"),
inv.get("territory"),
inv.get("tax_id"),
inv.debit_to, ", ".join(mode_of_payments.get(inv.name, [])),
inv.project, inv.owner, inv.remarks,
", ".join(sales_order), ", ".join(delivery_note),", ".join(cost_center),
", ".join(warehouse), company_currency
]
# map income values # map income values
base_net_total = 0 base_net_total = 0
for income_acc in income_accounts: for income_acc in income_accounts:
income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc)) income_amount = flt(invoice_income_map.get(inv.name, {}).get(income_acc))
base_net_total += income_amount base_net_total += income_amount
row.append(income_amount) row.update({
frappe.scrub(income_acc): income_amount
})
# net total # net total
row.append(base_net_total or inv.base_net_total) row.update({'net_total': base_net_total or inv.base_net_total})
# tax account # tax account
total_tax = 0 total_tax = 0
@@ -72,10 +86,18 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2 tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision) tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision)
total_tax += tax_amount total_tax += tax_amount
row.append(tax_amount) row.update({
frappe.scrub(tax_acc): tax_amount
})
# total tax, grand total, outstanding amount & rounded total # total tax, grand total, outstanding amount & rounded total
row += [total_tax, inv.base_grand_total, inv.base_rounded_total, inv.outstanding_amount]
row.update({
'tax_total': total_tax,
'grand_total': inv.base_grand_total,
'rounded_total': inv.base_rounded_total,
'outstanding_amount': inv.outstanding_amount
})
data.append(row) data.append(row)
@@ -84,19 +106,118 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
def get_columns(invoice_list, additional_table_columns): def get_columns(invoice_list, additional_table_columns):
"""return columns based on filters""" """return columns based on filters"""
columns = [ columns = [
_("Invoice") + ":Link/Sales Invoice:120", _("Posting Date") + ":Date:80", {
_("Customer") + ":Link/Customer:120", _("Customer Name") + "::120" 'label': _("Invoice"),
'fieldname': 'invoice',
'fieldtype': 'Link',
'options': 'Sales Invoice',
'width': 120
},
{
'label': _("Posting Date"),
'fieldname': 'posting_date',
'fieldtype': 'Date',
'width': 80
},
{
'label': _("Customer"),
'fieldname': 'customer',
'fieldtype': 'Link',
'options': 'Customer',
'width': 120
},
{
'label': _("Customer Name"),
'fieldname': 'customer_name',
'fieldtype': 'Data',
'width': 120
},
] ]
if additional_table_columns: if additional_table_columns:
columns += additional_table_columns columns += additional_table_columns
columns +=[ columns +=[
_("Customer Group") + ":Link/Customer Group:120", _("Territory") + ":Link/Territory:80", {
_("Tax Id") + "::80", _("Receivable Account") + ":Link/Account:120", _("Mode of Payment") + "::120", 'label': _("Custmer Group"),
_("Project") +":Link/Project:80", _("Owner") + "::150", _("Remarks") + "::150", 'fieldname': 'customer_group',
_("Sales Order") + ":Link/Sales Order:100", _("Delivery Note") + ":Link/Delivery Note:100", 'fieldtype': 'Link',
_("Cost Center") + ":Link/Cost Center:100", _("Warehouse") + ":Link/Warehouse:100", 'options': 'Customer Group',
'width': 120
},
{
'label': _("Territory"),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 80
},
{
'label': _("Tax Id"),
'fieldname': 'tax_id',
'fieldtype': 'Data',
'width': 120
},
{
'label': _("Receivable Account"),
'fieldname': 'receivable_account',
'fieldtype': 'Link',
'options': 'Account',
'width': 80
},
{
'label': _("Mode Of Payment"),
'fieldname': 'mode_of_payment',
'fieldtype': 'Data',
'width': 120
},
{
'label': _("Project"),
'fieldname': 'project',
'fieldtype': 'Link',
'options': 'project',
'width': 80
},
{
'label': _("Owner"),
'fieldname': 'owner',
'fieldtype': 'Data',
'width': 150
},
{
'label': _("Remarks"),
'fieldname': 'remarks',
'fieldtype': 'Data',
'width': 150
},
{
'label': _("Sales Order"),
'fieldname': 'sales_order',
'fieldtype': 'Link',
'options': 'Sales Order',
'width': 100
},
{
'label': _("Delivery Note"),
'fieldname': 'delivery_note',
'fieldtype': 'Link',
'options': 'Delivery Note',
'width': 100
},
{
'label': _("Cost Center"),
'fieldname': 'cost_center',
'fieldtype': 'Link',
'options': 'Cost Center',
'width': 100
},
{
'label': _("Warehouse"),
'fieldname': 'warehouse',
'fieldtype': 'Link',
'options': 'Warehouse',
'width': 100
},
{ {
"fieldname": "currency", "fieldname": "currency",
"label": _("Currency"), "label": _("Currency"),
@@ -105,7 +226,10 @@ def get_columns(invoice_list, additional_table_columns):
} }
] ]
income_accounts = tax_accounts = income_columns = tax_columns = [] income_accounts = []
tax_accounts = []
income_columns = []
tax_columns = []
if invoice_list: if invoice_list:
income_accounts = frappe.db.sql_list("""select distinct income_account income_accounts = frappe.db.sql_list("""select distinct income_account
@@ -119,14 +243,65 @@ def get_columns(invoice_list, additional_table_columns):
and parent in (%s) order by account_head""" % and parent in (%s) order by account_head""" %
', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list])) ', '.join(['%s']*len(invoice_list)), tuple([inv.name for inv in invoice_list]))
income_columns = [(account + ":Currency/currency:120") for account in income_accounts] for account in income_accounts:
income_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": 'currency',
"width": 120
})
for account in tax_accounts: for account in tax_accounts:
if account not in income_accounts: if account not in income_accounts:
tax_columns.append(account + ":Currency/currency:120") tax_columns.append({
"label": account,
"fieldname": frappe.scrub(account),
"fieldtype": "Currency",
"options": 'currency',
"width": 120
})
columns = columns + income_columns + [_("Net Total") + ":Currency/currency:120"] + tax_columns + \ net_total_column = [{
[_("Total Tax") + ":Currency/currency:120", _("Grand Total") + ":Currency/currency:120", "label": _("Net Total"),
_("Rounded Total") + ":Currency/currency:120", _("Outstanding Amount") + ":Currency/currency:120"] "fieldname": "net_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
}]
total_columns = [
{
"label": _("Tax Total"),
"fieldname": "tax_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
},
{
"label": _("Grand Total"),
"fieldname": "grand_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
},
{
"label": _("Rounded Total"),
"fieldname": "rounded_total",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
},
{
"label": _("Outstanding Amount"),
"fieldname": "outstanding_amount",
"fieldtype": "Currency",
"options": 'currency',
"width": 120
}
]
columns = columns + income_columns + net_total_column + tax_columns + total_columns
return columns, income_accounts, tax_accounts return columns, income_accounts, tax_accounts

View File

@@ -65,6 +65,21 @@ frappe.query_reports["Trial Balance for Party"] = {
return party_type; return party_type;
} }
}, },
{
"fieldname": "account",
"label": __("Account"),
"fieldtype": "Link",
"options": "Account",
"get_query": function() {
var company = frappe.query_report.get_filter_value('company');
return {
"doctype": "Account",
"filters": {
"company": company,
}
}
}
},
{ {
"fieldname": "show_zero_values", "fieldname": "show_zero_values",
"label": __("Show zero values"), "label": __("Show zero values"),

View File

@@ -20,7 +20,7 @@ def execute(filters=None):
def get_data(filters, show_party_name): def get_data(filters, show_party_name):
if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'): if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'):
party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type'))) party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
if filters.get('party_type') == 'Student': elif filters.get('party_type') == 'Student':
party_name_field = 'first_name' party_name_field = 'first_name'
elif filters.get('party_type') == 'Shareholder': elif filters.get('party_type') == 'Shareholder':
party_name_field = 'title' party_name_field = 'title'
@@ -96,13 +96,19 @@ def get_data(filters, show_party_name):
return data return data
def get_opening_balances(filters): def get_opening_balances(filters):
account_filter = ''
if filters.get('account'):
account_filter = "and account = %s" % (frappe.db.escape(filters.get('account')))
gle = frappe.db.sql(""" gle = frappe.db.sql("""
select party, sum(debit) as opening_debit, sum(credit) as opening_credit select party, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry` from `tabGL Entry`
where company=%(company)s where company=%(company)s
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes') and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
group by party""", { {account_filter}
group by party""".format(account_filter=account_filter), {
"company": filters.company, "company": filters.company,
"from_date": filters.from_date, "from_date": filters.from_date,
"party_type": filters.party_type "party_type": filters.party_type
@@ -116,6 +122,11 @@ def get_opening_balances(filters):
return opening return opening
def get_balances_within_period(filters): def get_balances_within_period(filters):
account_filter = ''
if filters.get('account'):
account_filter = "and account = %s" % (frappe.db.escape(filters.get('account')))
gle = frappe.db.sql(""" gle = frappe.db.sql("""
select party, sum(debit) as debit, sum(credit) as credit select party, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry` from `tabGL Entry`
@@ -123,7 +134,8 @@ def get_balances_within_period(filters):
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and posting_date >= %(from_date)s and posting_date <= %(to_date)s and posting_date >= %(from_date)s and posting_date <= %(to_date)s
and ifnull(is_opening, 'No') = 'No' and ifnull(is_opening, 'No') = 'No'
group by party""", { {account_filter}
group by party""".format(account_filter=account_filter), {
"company": filters.company, "company": filters.company,
"from_date": filters.from_date, "from_date": filters.from_date,
"to_date": filters.to_date, "to_date": filters.to_date,

View File

@@ -891,3 +891,9 @@ def get_allow_cost_center_in_entry_of_bs_account():
def generator(): def generator():
return cint(frappe.db.get_value('Accounts Settings', None, 'allow_cost_center_in_entry_of_bs_account')) return cint(frappe.db.get_value('Accounts Settings', None, 'allow_cost_center_in_entry_of_bs_account'))
return frappe.local_cache("get_allow_cost_center_in_entry_of_bs_account", (), generator, regenerate_if_none=True) return frappe.local_cache("get_allow_cost_center_in_entry_of_bs_account", (), generator, regenerate_if_none=True)
def get_stock_accounts(company):
return frappe.get_all("Account", filters = {
"account_type": "Stock",
"company": company
})

View File

@@ -144,6 +144,10 @@ frappe.ui.form.on('Asset', {
frm.set_df_property('purchase_invoice', 'read_only', 1); frm.set_df_property('purchase_invoice', 'read_only', 1);
frm.set_df_property('purchase_receipt', 'read_only', 1); frm.set_df_property('purchase_receipt', 'read_only', 1);
} }
else if (frm.doc.is_existing_asset) {
frm.toggle_reqd('purchase_receipt', 0);
frm.toggle_reqd('purchase_invoice', 0);
}
else if (frm.doc.purchase_receipt) { else if (frm.doc.purchase_receipt) {
// if purchase receipt link is set then set PI disabled // if purchase receipt link is set then set PI disabled
frm.toggle_reqd('purchase_invoice', 0); frm.toggle_reqd('purchase_invoice', 0);
@@ -256,6 +260,7 @@ frappe.ui.form.on('Asset', {
}, },
is_existing_asset: function(frm) { is_existing_asset: function(frm) {
frm.trigger("toggle_reference_doc");
// frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation));
}, },
@@ -333,25 +338,12 @@ frappe.ui.form.on('Asset', {
}) })
}, },
purchase_receipt: function(frm) { purchase_receipt: (frm) => {
frm.trigger('toggle_reference_doc'); frm.trigger('toggle_reference_doc');
if (frm.doc.purchase_receipt) { if (frm.doc.purchase_receipt) {
if (frm.doc.item_code) { if (frm.doc.item_code) {
frappe.db.get_doc('Purchase Receipt', frm.doc.purchase_receipt).then(pr_doc => { frappe.db.get_doc('Purchase Receipt', frm.doc.purchase_receipt).then(pr_doc => {
frm.set_value('company', pr_doc.company); frm.events.set_values_from_purchase_doc(frm, 'Purchase Receipt', pr_doc)
frm.set_value('purchase_date', pr_doc.posting_date);
const item = pr_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
frm.set_value('purchase_receipt', '');
frappe.msgprint({
title: __('Invalid Purchase Receipt'),
message: __("The selected Purchase Receipt doesn't contains selected Asset Item."),
indicator: 'red'
});
}
frm.set_value('gross_purchase_amount', item.base_net_rate);
frm.set_value('location', item.asset_location);
}); });
} else { } else {
frm.set_value('purchase_receipt', ''); frm.set_value('purchase_receipt', '');
@@ -363,24 +355,12 @@ frappe.ui.form.on('Asset', {
} }
}, },
purchase_invoice: function(frm) { purchase_invoice: (frm) => {
frm.trigger('toggle_reference_doc'); frm.trigger('toggle_reference_doc');
if (frm.doc.purchase_invoice) { if (frm.doc.purchase_invoice) {
if (frm.doc.item_code) { if (frm.doc.item_code) {
frappe.db.get_doc('Purchase Invoice', frm.doc.purchase_invoice).then(pi_doc => { frappe.db.get_doc('Purchase Invoice', frm.doc.purchase_invoice).then(pi_doc => {
frm.set_value('company', pi_doc.company); frm.events.set_values_from_purchase_doc(frm, 'Purchase Invoice', pi_doc)
frm.set_value('purchase_date', pi_doc.posting_date);
const item = pi_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
frm.set_value('purchase_invoice', '');
frappe.msgprint({
title: __('Invalid Purchase Invoice'),
message: __("The selected Purchase Invoice doesn't contains selected Asset Item."),
indicator: 'red'
});
}
frm.set_value('gross_purchase_amount', item.base_net_rate);
frm.set_value('location', item.asset_location);
}); });
} else { } else {
frm.set_value('purchase_invoice', ''); frm.set_value('purchase_invoice', '');
@@ -392,6 +372,24 @@ frappe.ui.form.on('Asset', {
} }
}, },
set_values_from_purchase_doc: function(frm, doctype, purchase_doc) {
frm.set_value('company', purchase_doc.company);
frm.set_value('purchase_date', purchase_doc.posting_date);
const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
if (!item) {
doctype_field = frappe.scrub(doctype)
frm.set_value(doctype_field, '');
frappe.msgprint({
title: __(`Invalid ${doctype}`),
message: __(`The selected ${doctype} doesn't contains selected Asset Item.`),
indicator: 'red'
});
}
frm.set_value('gross_purchase_amount', item.base_net_rate + item.item_tax_amount);
frm.set_value('purchase_receipt_amount', item.base_net_rate + item.item_tax_amount);
frm.set_value('location', item.asset_location);
},
set_depreciation_rate: function(frm, row) { set_depreciation_rate: function(frm, row) {
if (row.total_number_of_depreciations && row.frequency_of_depreciation if (row.total_number_of_depreciations && row.frequency_of_depreciation
&& row.expected_value_after_useful_life) { && row.expected_value_after_useful_life) {

View File

@@ -132,9 +132,10 @@ class Asset(AccountsController):
if len(movements) > 1: if len(movements) > 1:
frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \ frappe.throw(_('Asset has multiple Asset Movement Entries which has to be \
cancelled manually to cancel this asset.')) cancelled manually to cancel this asset.'))
movement = frappe.get_doc('Asset Movement', movements[0].get('name')) if movements:
movement.flags.ignore_validate = True movement = frappe.get_doc('Asset Movement', movements[0].get('name'))
movement.cancel() movement.flags.ignore_validate = True
movement.cancel()
def make_asset_movement(self): def make_asset_movement(self):
reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice' reference_doctype = 'Purchase Receipt' if self.purchase_receipt else 'Purchase Invoice'

View File

@@ -110,7 +110,7 @@ class AssetMovement(Document):
ORDER BY ORDER BY
asm.transaction_date asc asm.transaction_date asc
""", (d.asset, self.company, 'Receipt'), as_dict=1) """, (d.asset, self.company, 'Receipt'), as_dict=1)
if auto_gen_movement_entry[0].get('name') == self.name: if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name:
frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \ frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
auto generated for Asset {1}').format(self.name, d.asset)) auto generated for Asset {1}').format(self.name, d.asset))

View File

@@ -122,7 +122,7 @@ def get_data(filters):
filters=conditions, filters=conditions,
fields=["name", "asset_name", "department", "cost_center", "purchase_receipt", fields=["name", "asset_name", "department", "cost_center", "purchase_receipt",
"asset_category", "purchase_date", "gross_purchase_amount", "location", "asset_category", "purchase_date", "gross_purchase_amount", "location",
"available_for_use_date", "status", "purchase_invoice"]) "available_for_use_date", "status", "purchase_invoice", "opening_accumulated_depreciation"])
for asset in assets_record: for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \ asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \

View File

@@ -134,7 +134,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if (doc.status != "On Hold") { if (doc.status != "On Hold") {
if(flt(doc.per_received, 2) < 100 && allow_receipt) { if(flt(doc.per_received, 2) < 100 && allow_receipt) {
cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create')); cur_frm.add_custom_button(__('Receipt'), this.make_purchase_receipt, __('Create'));
if(doc.is_subcontracted==="Yes") { if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'), cur_frm.add_custom_button(__('Material to Supplier'),
function() { me.make_stock_entry(); }, __("Transfer")); function() { me.make_stock_entry(); }, __("Transfer"));
} }
@@ -191,6 +191,10 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
set_schedule_date(this.frm); set_schedule_date(this.frm);
}, },
has_unsupplied_items: function() {
return this.frm.doc['supplied_items'].some(item => item.required_qty != item.supplied_qty)
},
make_stock_entry: function() { make_stock_entry: function() {
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }); var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
var me = this; var me = this;
@@ -267,7 +271,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
if (me.frm.doc['supplied_items']) { if (me.frm.doc['supplied_items']) {
me.frm.doc['supplied_items'].forEach((item, index) => { me.frm.doc['supplied_items'].forEach((item, index) => {
if (item.rm_item_code && item.main_item_code) { if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) {
me.raw_material_data.push ({ me.raw_material_data.push ({
'name':item.name, 'name':item.name,
'item_code': item.main_item_code, 'item_code': item.main_item_code,

View File

@@ -56,6 +56,7 @@
"section_break_48", "section_break_48",
"pricing_rules", "pricing_rules",
"raw_material_details", "raw_material_details",
"set_reserve_warehouse",
"supplied_items", "supplied_items",
"sb_last_purchase", "sb_last_purchase",
"total_qty", "total_qty",
@@ -340,6 +341,7 @@
"fieldname": "contact_email", "fieldname": "contact_email",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Contact Email", "label": "Contact Email",
"options": "Email",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@@ -607,7 +609,7 @@
}, },
{ {
"fieldname": "other_charges_calculation", "fieldname": "other_charges_calculation",
"fieldtype": "Text", "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation", "label": "Taxes and Charges Calculation",
"no_copy": 1, "no_copy": 1,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
@@ -1039,12 +1041,19 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Tax Category", "label": "Tax Category",
"options": "Tax Category" "options": "Tax Category"
},
{
"depends_on": "supplied_items",
"fieldname": "set_reserve_warehouse",
"fieldtype": "Link",
"label": "Set Reserve Warehouse",
"options": "Warehouse"
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-07-11 18:25:49.509343", "modified": "2020-01-14 18:54:39.694448",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@@ -116,6 +116,73 @@ 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_add_new_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
po.save()
po.submit()
pr = make_pr_against_po(po.name, 2)
po.load_from_db()
first_item_of_po = po.get("items")[0]
trans_item = json.dumps([
{
'item_code': first_item_of_po.item_code,
'rate': first_item_of_po.rate,
'qty': first_item_of_po.qty,
'docname': first_item_of_po.name
},
{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}
])
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
self.assertEquals(len(po.get('items')), 2)
self.assertEqual(po.status, 'To Receive and Bill')
def test_remove_item_in_update_child_qty_rate(self):
po = create_purchase_order(do_not_save=1)
po.items[0].qty = 4
po.save()
po.submit()
pr = make_pr_against_po(po.name, 2)
po.reload()
first_item_of_po = po.get("items")[0]
# add an item
trans_item = json.dumps([
{
'item_code': first_item_of_po.item_code,
'rate': first_item_of_po.rate,
'qty': first_item_of_po.qty,
'docname': first_item_of_po.name
},
{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7}])
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
# check if can remove received item
trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
first_item_of_po = po.get("items")[0]
trans_item = json.dumps([
{
'item_code': first_item_of_po.item_code,
'rate': first_item_of_po.rate,
'qty': first_item_of_po.qty,
'docname': first_item_of_po.name
}
])
update_child_qty_rate('Purchase Order', trans_item, po.name)
po.reload()
self.assertEquals(len(po.get('items')), 1)
self.assertEqual(po.status, 'To Receive and Bill')
def test_update_qty(self): def test_update_qty(self):
po = create_purchase_order() po = create_purchase_order()

View File

@@ -138,7 +138,7 @@ def refresh_scorecards():
# Check to see if any new scorecard periods are created # Check to see if any new scorecard periods are created
if make_all_scorecards(sc.name) > 0: if make_all_scorecards(sc.name) > 0:
# Save the scorecard to update the score and standings # Save the scorecard to update the score and standings
sc.save() frappe.get_doc('Supplier Scorecard', sc.name).save()
@frappe.whitelist() @frappe.whitelist()

View File

@@ -0,0 +1,89 @@
# Version 12.4.0 Release Note
### Accounts
- Validity of item tax. [#20135](https://github.com/frappe/erpnext/pull/20135)
- Dynamic filters for dimensions in the budget variance report. [#19973](https://github.com/frappe/erpnext/pull/19973)
- Purchase Receipt and Purchase Invoice is not mandatory for an existing asset. [19980](https://github.com/frappe/erpnext/pull/19980)
- Allowed multiple Landed Cost Vouchers against a Purchase Receipt / Purchase Invoice. [#20058](https://github.com/frappe/erpnext/pull/20058)
- Rounding adjustment while both inclusive tax and additional discount amount are applied. [#20078](https://github.com/frappe/erpnext/pull/20078)
- Fixed an error while doing payment reconciliation for party type Employee. [#20088](https://github.com/frappe/erpnext/pull/20088)
- Show Closing row in General Ledger print. [#20161](https://github.com/frappe/erpnext/pull/20161)
- New report - Stock and Account Balance Comparison. [#20226](https://github.com/frappe/erpnext/pull/20226)
- Paid amount should not be over-written on clicking "Get Outstanding Invoices" button in Payment Entry. [#20050](https://github.com/frappe/erpnext/pull/20050)
- Currency symbol in Sales / Purchase Register report
- Currency symbol in "Bank and Cash Payment Voucher" print format.
### Human Resource
- Fixed leave allocation on the compensatory leave request submission. [#19961](https://github.com/frappe/erpnext/pull/19961)
- Create Payment Entry against Employee Advance to return any unclaimed amount and update returned amount in Employee Advance. [#19955](https://github.com/frappe/erpnext/pull/19955)
- Set Party against loan accounts in an accrual journal entry for salary. [#20022](https://github.com/frappe/erpnext/pull/20022)
- Editable loan repayment schedule after submission [#20122](https://github.com/frappe/erpnext/pull/20112)
- Update the paid amount and status of a loan after processing salary slip against it. [#20023](https://github.com/frappe/erpnext/pull/20023)
- Settings to disable rounded total in salary slip via HR Settings. [#20150](https://github.com/frappe/erpnext/pull/20150)
- Submit Salary button was not showing after creating salary slip in payroll entry. [#19753](https://github.com/frappe/erpnext/pull/19753)
- Payment Entry against payroll entry should deduct loan amount (if there are any loan deductions in salary slip). [#20194](https://github.com/frappe/erpnext/pull/20194)
- Show only relevant "Job Offer" in Employee Onboarding based on Job Applicant
- Added dashboard in Employee Advance
### Manufacturing
- Fixed backflushed qty for partial receipt against a subcontracted purchase order. [#20026](https://github.com/frappe/erpnext/pull/20026)
- Added "Set Reserve Warehouse" field in sub-contracted Purchase Order. [19992](https://github.com/frappe/erpnext/pull/19992)
- Only shows if the supplied items table is not empty
- On entering a warehouse in the field, it sets / overwrites reserve warehouse in Supplied Raw Materials table.
- In Backflush Stock Entry against Work Order, additional cost for service items (defined in BOM) should come proportionately based on finished goods qty. [#20105](https://github.com/frappe/erpnext/pull/20105)
- Hide transfer button in a subcontracted PO if full qty is already transferred. [#20155](https://github.com/frappe/erpnext/pull/20155)
- Set correct valuation rate of finished goods item in case of multiple material consumptions. [#20165](https://github.com/frappe/erpnext/pull/20165)
- Job Card creation from Work Order dashboard
### Stock
- Fixed ambiguous column name in the Batch query. Test by searching in any Batch link field.
- Fixed incorrect reorder level in Stock balance report
- Validate Batch for serialized items
- Get the outgoing rate of serial no from SLE if serial no already transferred to another company. [#20171](https://github.com/frappe/erpnext/pull/20171)
- Deliver Note creation from Sales Order dashboard. [#20199](https://github.com/frappe/erpnext/pull/20199)
### Others
- Addition and deletion of items in submitted Sales Order / Purchase Order. [#19911](https://github.com/frappe/erpnext/pull/19911)
- Get item price based on price list considering minimum qty. [#20206](https://github.com/frappe/erpnext/pull/20206)
- Product Bundle item should not appear in dialog on click of "Create Material Request" button. [#20216](https://github.com/frappe/erpnext/pull/20216)
- Delete linked communications on the deletion of company transactions. [#19928](https://github.com/frappe/erpnext/pull/19928)

View File

@@ -336,6 +336,24 @@ def get_data():
"is_query_report": True, "is_query_report": True,
"name": "Item Variant Details", "name": "Item Variant Details",
"doctype": "Item" "doctype": "Item"
},
{
"type": "report",
"is_query_report": True,
"name": "Subcontracted Raw Materials To Be Transferred",
"doctype": "Purchase Order"
},
{
"type": "report",
"is_query_report": True,
"name": "Subcontracted Item To Be Received",
"doctype": "Purchase Order"
},
{
"type": "report",
"is_query_report": True,
"name": "Stock and Account Value Comparison",
"doctype": "Stock Ledger Entry"
} }
] ]
}, },

View File

@@ -1155,6 +1155,30 @@ def set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docna
child_item.base_amount = 1 # Initiallize value will update in parent validation child_item.base_amount = 1 # Initiallize value will update in parent validation
return child_item return child_item
def check_and_delete_children(parent, data):
deleted_children = []
updated_item_names = [d.get("docname") for d in data]
for item in parent.items:
if item.name not in updated_item_names:
deleted_children.append(item)
for d in deleted_children:
if parent.doctype == "Sales Order":
if flt(d.delivered_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
if flt(d.work_order_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code))
if flt(d.ordered_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code))
if parent.doctype == "Purchase Order" and flt(d.received_qty):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
if flt(d.billed_amt):
frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
d.cancel()
d.delete()
@frappe.whitelist() @frappe.whitelist()
def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"): def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
@@ -1163,6 +1187,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation']
parent = frappe.get_doc(parent_doctype, parent_doctype_name) parent = frappe.get_doc(parent_doctype, parent_doctype_name)
check_and_delete_children(parent, data)
for d in data: for d in data:
new_child_flag = False new_child_flag = False
if not d.get("docname"): if not d.get("docname"):

View File

@@ -4,9 +4,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.desk.reportview import get_match_cond, get_filters_cond from frappe.desk.reportview import get_match_cond, get_filters_cond
from frappe.utils import nowdate from frappe.utils import nowdate, getdate
from collections import defaultdict from collections import defaultdict
from erpnext.stock.get_item_details import _get_item_tax_template
# searches for active employees # searches for active employees
def employee_query(doctype, txt, searchfield, start, page_len, filters): def employee_query(doctype, txt, searchfield, start, page_len, filters):
@@ -311,6 +311,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
and sle.item_code = %(item_code)s and sle.item_code = %(item_code)s
and sle.warehouse = %(warehouse)s and sle.warehouse = %(warehouse)s
and (sle.batch_no like %(txt)s and (sle.batch_no like %(txt)s
or batch.expiry_date like %(txt)s
or batch.manufacturing_date like %(txt)s) or batch.manufacturing_date like %(txt)s)
and batch.docstatus < 2 and batch.docstatus < 2
{cond} {cond}
@@ -329,6 +330,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
where batch.disabled = 0 where batch.disabled = 0
and item = %(item_code)s and item = %(item_code)s
and (name like %(txt)s and (name like %(txt)s
or expiry_date like %(txt)s
or manufacturing_date like %(txt)s) or manufacturing_date like %(txt)s)
and docstatus < 2 and docstatus < 2
{0} {0}
@@ -484,7 +486,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
@frappe.whitelist() @frappe.whitelist()
def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters): def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
query = """ query = """
select pr.name select pr.name
from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem from `tabPurchase Receipt` pr, `tabPurchase Receipt Item` pritem
where pr.docstatus = 1 and pritem.parent = pr.name where pr.docstatus = 1 and pritem.parent = pr.name
and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt))) and pr.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
@@ -497,7 +499,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
@frappe.whitelist() @frappe.whitelist()
def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters): def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
query = """ query = """
select pi.name select pi.name
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` piitem
where pi.docstatus = 1 and piitem.parent = pi.name where pi.docstatus = 1 and piitem.parent = pi.name
and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt))) and pi.name like {txt}""".format(txt = frappe.db.escape('%{0}%'.format(txt)))
@@ -506,3 +508,27 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code'))) query += " and piitem.item_code = {item_code}".format(item_code = frappe.db.escape(filters.get('item_code')))
return frappe.db.sql(query, filters) return frappe.db.sql(query, filters)
@frappe.whitelist()
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
item_group = filters.get('item_group')
taxes = item_doc.taxes or []
while item_group:
item_group_doc = frappe.get_cached_doc('Item Group', item_group)
taxes += item_group_doc.taxes or []
item_group = item_group_doc.parent_item_group
if not taxes:
return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
else:
args = {
'item_code': filters.get('item_code'),
'posting_date': filters.get('valid_from'),
'tax_category': filters.get('tax_category')
}
taxes = _get_item_tax_template(args, taxes, for_validate=True)
return [(d,) for d in set(taxes)]

View File

@@ -20,6 +20,7 @@ class StockController(AccountsController):
def validate(self): def validate(self):
super(StockController, self).validate() super(StockController, self).validate()
self.validate_inspection() self.validate_inspection()
self.validate_serialized_batch()
def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
if self.docstatus == 2: if self.docstatus == 2:
@@ -42,6 +43,18 @@ class StockController(AccountsController):
gl_entries = self.get_asset_gl_entry(gl_entries) gl_entries = self.get_asset_gl_entry(gl_entries)
make_gl_entries(gl_entries, from_repost=from_repost) make_gl_entries(gl_entries, from_repost=from_repost)
def validate_serialized_batch(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for d in self.get("items"):
if not (hasattr(d, 'serial_no') and d.serial_no and d.batch_no): continue
serial_nos = get_serial_nos(d.serial_no)
for serial_no_data in frappe.get_all("Serial No",
filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
if serial_no_data.batch_no != d.batch_no:
frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
.format(d.idx, serial_no_data.name, d.batch_no))
def get_gl_entries(self, warehouse_account=None, default_expense_account=None, def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None): default_cost_center=None):
@@ -54,6 +67,7 @@ class StockController(AccountsController):
gl_list = [] gl_list = []
warehouse_with_no_account = [] warehouse_with_no_account = []
precision = frappe.get_precision("GL Entry", "debit_in_account_currency")
for item_row in voucher_details: for item_row in voucher_details:
sle_list = sle_map.get(item_row.name) sle_list = sle_map.get(item_row.name)
if sle_list: if sle_list:
@@ -79,7 +93,7 @@ class StockController(AccountsController):
"against": item_row.expense_account, "against": item_row.expense_account,
"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, precision),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) }, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
@@ -89,7 +103,7 @@ class StockController(AccountsController):
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"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, precision),
"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" "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No"
}, item=item_row)) }, item=item_row))

View File

@@ -8,6 +8,7 @@ from frappe import _, scrub
from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from erpnext.controllers.accounts_controller import validate_conversion_rate, \ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax validate_taxes_and_charges, validate_inclusive_tax
from erpnext.stock.get_item_details import _get_item_tax_template
class calculate_taxes_and_totals(object): class calculate_taxes_and_totals(object):
def __init__(self, doc): def __init__(self, doc):
@@ -34,6 +35,7 @@ class calculate_taxes_and_totals(object):
def _calculate(self): def _calculate(self):
self.validate_conversion_rate() self.validate_conversion_rate()
self.calculate_item_values() self.calculate_item_values()
self.validate_item_tax_template()
self.initialize_taxes() self.initialize_taxes()
self.determine_exclusive_rate() self.determine_exclusive_rate()
self.calculate_net_total() self.calculate_net_total()
@@ -43,6 +45,38 @@ class calculate_taxes_and_totals(object):
self._cleanup() self._cleanup()
self.calculate_total_net_weight() self.calculate_total_net_weight()
def validate_item_tax_template(self):
for item in self.doc.get('items'):
if item.item_code and item.get('item_tax_template'):
item_doc = frappe.get_cached_doc("Item", item.item_code)
args = {
'tax_category': self.doc.get('tax_category'),
'posting_date': self.doc.get('posting_date'),
'bill_date': self.doc.get('bill_date'),
'transaction_date': self.doc.get('transaction_date')
}
item_group = item_doc.item_group
item_group_taxes = []
while item_group:
item_group_doc = frappe.get_cached_doc('Item Group', item_group)
item_group_taxes += item_group_doc.taxes or []
item_group = item_group_doc.parent_item_group
item_taxes = item_doc.taxes or []
if not item_group_taxes and (not item_taxes):
# No validation if no taxes in item or item group
continue
taxes = _get_item_tax_template(args, item_taxes + item_group_taxes, for_validate=True)
if item.item_tax_template not in taxes:
frappe.throw(_("Row {0}: Invalid Item Tax Template for item {1}").format(
item.idx, frappe.bold(item.item_code)
))
def validate_conversion_rate(self): def validate_conversion_rate(self):
# validate conversion rate # validate conversion rate
company_currency = erpnext.get_company_currency(self.doc.company) company_currency = erpnext.get_company_currency(self.doc.company)
@@ -312,11 +346,19 @@ class calculate_taxes_and_totals(object):
last_tax = self.doc.get("taxes")[-1] last_tax = self.doc.get("taxes")[-1]
non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount) non_inclusive_tax_amount = sum([flt(d.tax_amount_after_discount_amount)
for d in self.doc.get("taxes") if not d.included_in_print_rate]) for d in self.doc.get("taxes") if not d.included_in_print_rate])
diff = self.doc.total + non_inclusive_tax_amount \ diff = self.doc.total + non_inclusive_tax_amount \
- flt(last_tax.total, last_tax.precision("total")) - flt(last_tax.total, last_tax.precision("total"))
# If discount amount applied, deduct the discount amount
# because self.doc.total is always without discount, but last_tax.total is after discount
if self.discount_amount_applied and self.doc.discount_amount:
diff -= flt(self.doc.discount_amount)
diff = flt(diff, self.doc.precision("rounding_adjustment"))
if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")): if diff and abs(diff) <= (5.0 / 10**last_tax.precision("tax_amount")):
self.doc.rounding_adjustment = flt(flt(self.doc.rounding_adjustment) + self.doc.rounding_adjustment = diff
flt(diff), self.doc.precision("rounding_adjustment"))
def calculate_totals(self): def calculate_totals(self):
self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \ self.doc.grand_total = flt(self.doc.get("taxes")[-1].total) + flt(self.doc.rounding_adjustment) \

View File

@@ -11,7 +11,7 @@ from datetime import timedelta
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import get_url from frappe.utils import get_url, getdate
from frappe.utils.verified_command import verify_request, get_signed_params from frappe.utils.verified_command import verify_request, get_signed_params
@@ -117,7 +117,7 @@ class Appointment(Document):
if self._assign: if self._assign:
return return
available_agents = _get_agents_sorted_by_asc_workload( available_agents = _get_agents_sorted_by_asc_workload(
self.scheduled_time.date()) getdate(self.scheduled_time))
for agent in available_agents: for agent in available_agents:
if(_check_agent_availability(agent, self.scheduled_time)): if(_check_agent_availability(agent, self.scheduled_time)):
agent = agent[0] agent = agent[0]
@@ -189,7 +189,7 @@ def _get_agents_sorted_by_asc_workload(date):
assigned_to = frappe.parse_json(appointment._assign) assigned_to = frappe.parse_json(appointment._assign)
if not assigned_to: if not assigned_to:
continue continue
if (assigned_to[0] in agent_list) and appointment.scheduled_time.date() == date: if (assigned_to[0] in agent_list) and getdate(appointment.scheduled_time) == date:
appointment_counter[assigned_to[0]] += 1 appointment_counter[assigned_to[0]] += 1
sorted_agent_list = appointment_counter.most_common() sorted_agent_list = appointment_counter.most_common()
sorted_agent_list.reverse() sorted_agent_list.reverse()

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2018-10-25 10:02:48.656165", "creation": "2018-10-25 10:02:48.656165",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@@ -23,48 +24,41 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:doc.enabled==1", "depends_on": "enabled",
"fieldname": "automatic_sync", "fieldname": "automatic_sync",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Synchronize all accounts every hour" "label": "Synchronize all accounts every hour"
}, },
{ {
"depends_on": "eval:doc.enabled==1",
"fieldname": "plaid_client_id", "fieldname": "plaid_client_id",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Plaid Client ID", "label": "Plaid Client ID"
"reqd": 1
}, },
{ {
"depends_on": "eval:doc.enabled==1",
"fieldname": "plaid_secret", "fieldname": "plaid_secret",
"fieldtype": "Password", "fieldtype": "Password",
"in_list_view": 1, "in_list_view": 1,
"label": "Plaid Secret", "label": "Plaid Secret"
"reqd": 1
}, },
{ {
"depends_on": "eval:doc.enabled==1",
"fieldname": "plaid_public_key", "fieldname": "plaid_public_key",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Plaid Public Key", "label": "Plaid Public Key"
"reqd": 1
}, },
{ {
"depends_on": "eval:doc.enabled==1",
"fieldname": "plaid_env", "fieldname": "plaid_env",
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"label": "Plaid Environment", "label": "Plaid Environment"
"reqd": 1
}, },
{ {
"fieldname": "column_break_2", "fieldname": "column_break_2",
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "enabled",
"fieldname": "section_break_4", "fieldname": "section_break_4",
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
@@ -74,7 +68,8 @@
} }
], ],
"issingle": 1, "issingle": 1,
"modified": "2019-08-13 17:00:06.939422", "links": [],
"modified": "2020-01-05 10:00:22.137832",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "ERPNext Integrations", "module": "ERPNext Integrations",
"name": "Plaid Settings", "name": "Plaid Settings",

View File

@@ -34,7 +34,7 @@ frappe.ui.form.on('Employee Advance', {
} }
else if ( else if (
frm.doc.docstatus === 1 frm.doc.docstatus === 1
&& flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) && flt(frm.doc.claimed_amount) < flt(frm.doc.paid_amount) - flt(frm.doc.return_amount)
&& frappe.model.can_create("Expense Claim") && frappe.model.can_create("Expense Claim")
) { ) {
frm.add_custom_button( frm.add_custom_button(
@@ -45,6 +45,15 @@ frappe.ui.form.on('Employee Advance', {
__('Create') __('Create')
); );
} }
if (frm.doc.docstatus === 1
&& (flt(frm.doc.claimed_amount) + flt(frm.doc.return_amount) < flt(frm.doc.paid_amount))
&& frappe.model.can_create("Journal Entry")) {
frm.add_custom_button(__("Return"), function() {
frm.trigger('make_return_entry');
}, __('Create'));
}
}, },
make_payment_entry: function(frm) { make_payment_entry: function(frm) {
@@ -83,6 +92,24 @@ frappe.ui.form.on('Employee Advance', {
}); });
}, },
make_return_entry: function(frm) {
frappe.call({
method: 'erpnext.hr.doctype.employee_advance.employee_advance.make_return_entry',
args: {
'employee': frm.doc.employee,
'company': frm.doc.company,
'employee_advance_name': frm.doc.name,
'return_amount': flt(frm.doc.paid_amount - frm.doc.claimed_amount),
'advance_account': frm.doc.advance_account,
'mode_of_payment': frm.doc.mode_of_payment
},
callback: function(r) {
const doclist = frappe.model.sync(r.message);
frappe.set_route('Form', doclist[0].doctype, doclist[0].name);
}
});
},
employee: function (frm) { employee: function (frm) {
if (frm.doc.employee) { if (frm.doc.employee) {
return frappe.call({ return frappe.call({

View File

@@ -1,737 +1,213 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "allow_import": 1,
"allow_guest_to_view": 0, "autoname": "naming_series:",
"allow_import": 1, "creation": "2017-10-09 14:26:29.612365",
"allow_rename": 0, "doctype": "DocType",
"autoname": "naming_series:", "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2017-10-09 14:26:29.612365", "field_order": [
"custom": 0, "naming_series",
"docstatus": 0, "employee",
"doctype": "DocType", "employee_name",
"document_type": "", "column_break_4",
"editable_grid": 1, "posting_date",
"engine": "InnoDB", "department",
"section_break_8",
"purpose",
"column_break_11",
"advance_amount",
"paid_amount",
"due_advance_amount",
"claimed_amount",
"return_amount",
"section_break_7",
"status",
"company",
"amended_from",
"column_break_18",
"advance_account",
"mode_of_payment"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "naming_series",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "label": "Series",
"bold": 0, "options": "HR-EAD-.YYYY.-"
"collapsible": 0, },
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"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": "Series",
"length": 0,
"no_copy": 0,
"options": "HR-EAD-.YYYY.-",
"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, "fieldname": "employee",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Employee",
"collapsible": 0, "options": "Employee",
"columns": 0, "reqd": 1
"fieldname": "employee", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.employee_name",
"allow_in_quick_entry": 0, "fieldname": "employee_name",
"allow_on_submit": 0, "fieldtype": "Read Only",
"bold": 0, "label": "Employee Name"
"collapsible": 0, },
"columns": 0,
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Read Only",
"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": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "",
"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, "fieldname": "column_break_4",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"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, "default": "Today",
"allow_in_quick_entry": 0, "fieldname": "posting_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Posting Date",
"columns": 0, "reqd": 1
"default": "Today", },
"fieldname": "posting_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Posting Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.department",
"allow_in_quick_entry": 0, "fieldname": "department",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "label": "Department",
"collapsible": 0, "options": "Department",
"columns": 0, "read_only": 1
"fetch_from": "employee.department", },
"fieldname": "department",
"fieldtype": "Link",
"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": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fieldname": "section_break_8",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_8",
"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,
"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, "fieldname": "purpose",
"allow_in_quick_entry": 0, "fieldtype": "Small Text",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Purpose",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fieldname": "purpose",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Purpose",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_11",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_11",
"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, "fieldname": "advance_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Advance Amount",
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "reqd": 1
"fieldname": "advance_amount", },
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Advance Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "paid_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Paid Amount",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "read_only": 1
"fieldname": "paid_amount", },
"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": "Paid Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "depends_on": "eval:cur_frm.doc.employee",
"allow_in_quick_entry": 0, "fieldname": "due_advance_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "label": "Due Advance Amount",
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "read_only": 1
"depends_on": "eval:cur_frm.doc.employee", },
"fieldname": "due_advance_amount",
"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": "Due Advance Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fieldname": "claimed_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Claimed Amount",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "read_only": 1
"fieldname": "claimed_amount", },
"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": "Claimed Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fieldname": "section_break_7",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"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,
"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, "fieldname": "status",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "label": "Status",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled",
"columns": 0, "read_only": 1
"fieldname": "status", },
"fieldtype": "Select",
"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": "Status",
"length": 0,
"no_copy": 1,
"options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fieldname": "company",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Company",
"bold": 0, "options": "Company",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fieldname": "company",
"fieldtype": "Link",
"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": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "amended_from",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Amended From",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Employee Advance",
"columns": 0, "print_hide": 1,
"fieldname": "amended_from", "read_only": 1
"fieldtype": "Link", },
"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": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Employee Advance",
"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
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_18",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_18",
"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, "fieldname": "advance_account",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "ignore_user_permissions": 1,
"bold": 0, "label": "Advance Account",
"collapsible": 0, "options": "Account",
"columns": 0, "reqd": 1
"fieldname": "advance_account", },
"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": "Advance Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "mode_of_payment",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Mode of Payment",
"bold": 0, "options": "Mode of Payment"
"collapsible": 0, },
"columns": 0, {
"fieldname": "mode_of_payment", "fieldname": "return_amount",
"fieldtype": "Link", "fieldtype": "Currency",
"hidden": 0, "label": "Returned Amount",
"ignore_user_permissions": 0, "options": "Company:company:default_currency",
"ignore_xss_filter": 0, "read_only": 1
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"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, "is_submittable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2019-12-15 19:04:07.044505",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "HR",
"in_create": 0, "name": "Employee Advance",
"is_submittable": 1, "owner": "Administrator",
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-01-30 11:28:15.529649",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 0, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Employee",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Employee",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "Expense Approver",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "Expense Approver",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "search_fields": "employee,employee_name",
"read_only": 0, "sort_field": "modified",
"read_only_onload": 0, "sort_order": "DESC",
"search_fields": "employee,employee_name", "track_changes": 1
"show_name_in_global_search": 0, }
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -7,6 +7,7 @@ import frappe, erpnext
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import flt, nowdate from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
class EmployeeAdvanceOverPayment(frappe.ValidationError): class EmployeeAdvanceOverPayment(frappe.ValidationError):
pass pass
@@ -53,11 +54,25 @@ class EmployeeAdvance(Document):
and party = %s and party = %s
""", (self.name, self.employee), as_dict=1)[0].paid_amount """, (self.name, self.employee), as_dict=1)[0].paid_amount
return_amount = frappe.db.sql("""
select name, ifnull(sum(credit_in_account_currency), 0) as return_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and voucher_type != 'Expense Claim'
and against_voucher = %s
and party_type = 'Employee'
and party = %s
""", (self.name, self.employee), as_dict=1)[0].return_amount
if flt(paid_amount) > self.advance_amount: if flt(paid_amount) > self.advance_amount:
frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"), frappe.throw(_("Row {0}# Paid Amount cannot be greater than requested advance amount"),
EmployeeAdvanceOverPayment) EmployeeAdvanceOverPayment)
if flt(return_amount) > self.paid_amount - self.claimed_amount:
frappe.throw(_("Return amount cannot be greater unclaimed amount"))
self.db_set("paid_amount", paid_amount) self.db_set("paid_amount", paid_amount)
self.db_set("return_amount", return_amount)
self.set_status() self.set_status()
frappe.db.set_value("Employee Advance", self.name , "status", self.status) frappe.db.set_value("Employee Advance", self.name , "status", self.status)
@@ -88,8 +103,6 @@ def get_due_advance_amount(employee, posting_date):
@frappe.whitelist() @frappe.whitelist()
def make_bank_entry(dt, dn): def make_bank_entry(dt, dn):
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
doc = frappe.get_doc(dt, dn) doc = frappe.get_doc(dt, dn)
payment_account = get_default_bank_cash_account(doc.company, account_type="Cash", payment_account = get_default_bank_cash_account(doc.company, account_type="Cash",
mode_of_payment=doc.mode_of_payment) mode_of_payment=doc.mode_of_payment)
@@ -118,3 +131,34 @@ def make_bank_entry(dt, dn):
}) })
return je.as_dict() return je.as_dict()
@frappe.whitelist()
def make_return_entry(employee, company, employee_advance_name,
return_amount, advance_account, mode_of_payment=None):
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
je = frappe.new_doc('Journal Entry')
je.posting_date = nowdate()
je.voucher_type = 'Bank Entry'
je.company = company
je.remark = 'Return against Employee Advance: ' + employee_advance_name
je.append('accounts', {
'account': advance_account,
'credit_in_account_currency': return_amount,
'reference_type': 'Employee Advance',
'reference_name': employee_advance_name,
'party_type': 'Employee',
'party': employee,
'is_advance': 'Yes'
})
je.append("accounts", {
"account": return_account.account,
"debit_in_account_currency": return_amount,
"account_currency": return_account.account_currency,
"account_type": return_account.account_type
})
return je.as_dict()

View File

@@ -0,0 +1,19 @@
from __future__ import unicode_literals
from frappe import _
def get_data():
return {
'fieldname': 'employee_advance',
'non_standard_fieldnames': {
'Payment Entry': 'reference_name',
'Journal Entry': 'reference_name'
},
'transactions': [
{
'items': ['Expense Claim']
},
{
'items': ['Payment Entry', 'Journal Entry']
}
]
}

View File

@@ -7,6 +7,14 @@ frappe.ui.form.on('Employee Onboarding', {
frm.add_fetch("employee_onboarding_template", "department", "department"); frm.add_fetch("employee_onboarding_template", "department", "department");
frm.add_fetch("employee_onboarding_template", "designation", "designation"); frm.add_fetch("employee_onboarding_template", "designation", "designation");
frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade"); frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade");
frm.set_query('job_offer', function () {
return {
filters: {
'job_applicant': frm.doc.job_applicant
}
};
});
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -213,13 +213,14 @@ frappe.ui.form.on("Expense Claim", {
}, },
update_employee_advance_claimed_amount: function(frm) { update_employee_advance_claimed_amount: function(frm) {
console.log("update_employee_advance_claimed_amount")
let amount_to_be_allocated = frm.doc.grand_total; let amount_to_be_allocated = frm.doc.grand_total;
$.each(frm.doc.advances || [], function(i, advance){ $.each(frm.doc.advances || [], function(i, advance){
if (amount_to_be_allocated >= advance.unclaimed_amount){ if (amount_to_be_allocated >= advance.unclaimed_amount){
frm.doc.advances[i].allocated_amount = frm.doc.advances[i].unclaimed_amount; advance.allocated_amount = frm.doc.advances[i].unclaimed_amount;
amount_to_be_allocated -= advance.allocated_amount; amount_to_be_allocated -= advance.allocated_amount;
} else{ } else{
frm.doc.advances[i].allocated_amount = amount_to_be_allocated; advance.allocated_amount = amount_to_be_allocated;
amount_to_be_allocated = 0; amount_to_be_allocated = 0;
} }
frm.refresh_field("advances"); frm.refresh_field("advances");
@@ -295,6 +296,7 @@ frappe.ui.form.on("Expense Claim", {
doc: frm.doc, doc: frm.doc,
callback: () => { callback: () => {
refresh_field("taxes"); refresh_field("taxes");
frm.trigger("update_employee_advance_claimed_amount");
} }
}); });
} }
@@ -331,16 +333,12 @@ frappe.ui.form.on("Expense Claim", {
frappe.ui.form.on("Expense Claim Detail", { frappe.ui.form.on("Expense Claim Detail", {
amount: function(frm, cdt, cdn) { amount: function(frm, cdt, cdn) {
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
var doc = frm.doc;
frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.amount); frappe.model.set_value(cdt, cdn, 'sanctioned_amount', child.amount);
cur_frm.cscript.calculate_total(doc,cdt,cdn);
}, },
sanctioned_amount: function(frm, cdt, cdn) { sanctioned_amount: function(frm, cdt, cdn) {
var doc = frm.doc; cur_frm.cscript.calculate_total(frm.doc, cdt, cdn);
cur_frm.cscript.calculate_total(doc,cdt,cdn);
frm.trigger("get_taxes"); frm.trigger("get_taxes");
frm.trigger("calculate_grand_total");
} }
}); });

View File

@@ -243,6 +243,7 @@ class ExpenseClaim(AccountsController):
precision = self.precision("total_advance_amount") precision = self.precision("total_advance_amount")
if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision): if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total claimed amount")) frappe.throw(_("Total advance amount cannot be greater than total claimed amount"))
if self.total_sanctioned_amount \ if self.total_sanctioned_amount \
and flt(self.total_advance_amount, precision) > flt(self.total_sanctioned_amount, precision): and flt(self.total_advance_amount, precision) > flt(self.total_sanctioned_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount")) frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
@@ -321,7 +322,7 @@ def get_expense_claim_account(expense_claim_type, company):
@frappe.whitelist() @frappe.whitelist()
def get_advances(employee, advance_id=None): def get_advances(employee, advance_id=None):
if not advance_id: if not advance_id:
condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount'.format(frappe.db.escape(employee)) condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount'.format(frappe.db.escape(employee))
else: else:
condition = 'name={0}'.format(frappe.db.escape(advance_id)) condition = 'name={0}'.format(frappe.db.escape(advance_id))

View File

@@ -1,238 +1,93 @@
{ {
"allow_copy": 0, "creation": "2017-10-09 16:53:26.410762",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "document_type": "Document",
"allow_rename": 0, "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2017-10-09 16:53:26.410762", "field_order": [
"custom": 0, "employee_advance",
"docstatus": 0, "posting_date",
"doctype": "DocType", "advance_paid",
"document_type": "Document", "unclaimed_amount",
"editable_grid": 1, "allocated_amount",
"engine": "InnoDB", "advance_account"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "employee_advance",
"bold": 0, "fieldtype": "Link",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Employee Advance",
"fieldname": "employee_advance", "no_copy": 1,
"fieldtype": "Link", "oldfieldname": "journal_voucher",
"hidden": 0, "oldfieldtype": "Link",
"ignore_user_permissions": 0, "options": "Employee Advance",
"ignore_xss_filter": 0, "print_width": "250px",
"in_filter": 0, "reqd": 1,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee Advance",
"length": 0,
"no_copy": 1,
"oldfieldname": "journal_voucher",
"oldfieldtype": "Link",
"options": "Employee Advance",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "250px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "250px" "width": "250px"
}, },
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "posting_date",
"bold": 0, "fieldtype": "Date",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Posting Date",
"fieldname": "posting_date", "read_only": 1
"fieldtype": "Date", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Posting Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "advance_paid",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Advance Paid",
"fieldname": "advance_paid", "options": "Company:company:default_currency",
"fieldtype": "Currency", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Advance Paid",
"length": 0,
"no_copy": 0,
"options": "Company:company.default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "unclaimed_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Unclaimed amount",
"fieldname": "unclaimed_amount", "no_copy": 1,
"fieldtype": "Currency", "oldfieldname": "advance_amount",
"hidden": 0, "oldfieldtype": "Currency",
"ignore_user_permissions": 0, "options": "Company:company:default_currency",
"ignore_xss_filter": 0, "print_width": "120px",
"in_filter": 0, "read_only": 1,
"in_global_search": 0, "reqd": 1,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Unclaimed amount",
"length": 0,
"no_copy": 1,
"oldfieldname": "advance_amount",
"oldfieldtype": "Currency",
"options": "Company:company.default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "120px" "width": "120px"
}, },
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_on_submit": 0, "fieldname": "allocated_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Allocated amount",
"fieldname": "allocated_amount", "no_copy": 1,
"fieldtype": "Currency", "oldfieldname": "allocated_amount",
"hidden": 0, "oldfieldtype": "Currency",
"ignore_user_permissions": 0, "options": "Company:company:default_currency",
"ignore_xss_filter": 0, "print_width": "120px",
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allocated amount",
"length": 0,
"no_copy": 1,
"oldfieldname": "allocated_amount",
"oldfieldtype": "Currency",
"options": "Company:company.default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "120px" "width": "120px"
}, },
{ {
"allow_bulk_edit": 0, "fieldname": "advance_account",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "hidden": 1,
"collapsible": 0, "label": "Advance Account",
"columns": 0, "options": "Account"
"fieldname": "advance_account",
"fieldtype": "Link",
"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": "Advance Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"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,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "modified": "2020-01-14 17:49:17.968373",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "HR",
"image_view": 0, "name": "Expense Claim Advance",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0, "permissions": [],
"issingle": 0, "quick_entry": 1,
"istable": 1, "sort_field": "modified",
"max_attachments": 0, "sort_order": "DESC"
"modified": "2017-10-09 19:59:48.818139",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim Advance",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
} }

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2013-08-02 13:45:23", "creation": "2013-08-02 13:45:23",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
@@ -13,6 +14,7 @@
"expense_approver_mandatory_in_expense_claim", "expense_approver_mandatory_in_expense_claim",
"payroll_settings", "payroll_settings",
"include_holidays_in_total_working_days", "include_holidays_in_total_working_days",
"disable_rounded_total",
"max_working_hours_against_timesheet", "max_working_hours_against_timesheet",
"column_break_11", "column_break_11",
"email_salary_slip_to_employee", "email_salary_slip_to_employee",
@@ -160,12 +162,20 @@
"fieldname": "auto_leave_encashment", "fieldname": "auto_leave_encashment",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Auto Leave Encashment" "label": "Auto Leave Encashment"
},
{
"default": "0",
"description": "If checked, hides and disables Rounded Total field in Salary Slips",
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
} }
], ],
"icon": "fa fa-cog", "icon": "fa fa-cog",
"idx": 1, "idx": 1,
"issingle": 1, "issingle": 1,
"modified": "2019-08-05 13:07:17.993968", "links": [],
"modified": "2019-12-31 14:28:32.004121",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "HR Settings", "name": "HR Settings",

View File

@@ -7,6 +7,8 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
class HRSettings(Document): class HRSettings(Document):
def validate(self): def validate(self):
@@ -22,3 +24,12 @@ class HRSettings(Document):
if self.email_salary_slip_to_employee and self.encrypt_salary_slips_in_emails: if self.email_salary_slip_to_employee and self.encrypt_salary_slips_in_emails:
if not self.password_policy: if not self.password_policy:
frappe.throw(_("Password policy for Salary Slips is not set")) frappe.throw(_("Password policy for Salary Slips is not set"))
def on_update(self):
self.toggle_rounded_total()
frappe.clear_cache()
def toggle_rounded_total(self):
self.disable_rounded_total = cint(self.disable_rounded_total)
make_property_setter("Salary Slip", "rounded_total", "hidden", self.disable_rounded_total, "Check")
make_property_setter("Salary Slip", "rounded_total", "print_hide", self.disable_rounded_total, "Check")

View File

@@ -351,7 +351,7 @@ class LeaveApplication(Document):
pass pass
def create_leave_ledger_entry(self, submit=True): def create_leave_ledger_entry(self, submit=True):
if self.status != 'Approved': if self.status != 'Approved' and submit:
return return
expiry_date = get_allocation_expiry(self.employee, self.leave_type, expiry_date = get_allocation_expiry(self.employee, self.leave_type,

View File

@@ -235,8 +235,8 @@ class TestLeaveApplication(unittest.TestCase):
frappe.get_doc(dict( frappe.get_doc(dict(
doctype = 'Holiday List', doctype = 'Holiday List',
holiday_list_name = holiday_list, holiday_list_name = holiday_list,
from_date = date(date.today().year, 1, 1), from_date = add_months(today, -6),
to_date = date(date.today().year, 12, 31), to_date = add_months(today, 6),
holidays = [ holidays = [
dict(holiday_date = today, description = 'Test') dict(holiday_date = today, description = 'Test')
] ]
@@ -597,8 +597,8 @@ def get_leave_period():
return frappe.get_doc(dict( return frappe.get_doc(dict(
name = 'Test Leave Period', name = 'Test Leave Period',
doctype = 'Leave Period', doctype = 'Leave Period',
from_date = "{0}-12-01".format(now_datetime().year - 1), from_date = add_months(nowdate(), -6),
to_date = "{0}-12-31".format(now_datetime().year), to_date = add_months(nowdate(), 6),
company = "_Test Company", company = "_Test Company",
is_active = 1 is_active = 1
)).insert() )).insert()

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ class Loan(AccountsController):
self.make_repayment_schedule() self.make_repayment_schedule()
self.set_repayment_period() self.set_repayment_period()
self.calculate_totals() self.calculate_totals()
self.set_status(from_validate=True)
def set_missing_fields(self): def set_missing_fields(self):
if not self.company: if not self.company:
@@ -95,43 +96,57 @@ class Loan(AccountsController):
self.total_payment = 0 self.total_payment = 0
self.total_interest_payable = 0 self.total_interest_payable = 0
self.total_amount_paid = 0 self.total_amount_paid = 0
for data in self.repayment_schedule: for schedule in self.repayment_schedule:
self.total_payment += data.total_payment self.total_payment += schedule.total_payment
self.total_interest_payable +=data.interest_amount self.total_interest_payable +=schedule.interest_amount
if data.paid: if schedule.paid:
self.total_amount_paid += data.total_payment self.total_amount_paid += schedule.total_payment
def update_total_amount_paid(doc): def update_total_amount_paid(self):
total_amount_paid = 0 total_amount_paid = 0
for data in doc.repayment_schedule: for schedule in self.repayment_schedule:
if data.paid: if schedule.paid:
total_amount_paid += data.total_payment total_amount_paid += schedule.total_payment
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid) frappe.db.set_value("Loan", self.name, "total_amount_paid", total_amount_paid)
def update_disbursement_status(doc): def set_status(self, from_validate=False):
disbursement = frappe.db.sql(""" disbursement = self.get_disbursement_entry()
select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount disbursement_date = None
from `tabGL Entry`
where account = %s and against_voucher_type = 'Loan' and against_voucher = %s
""", (doc.payment_account, doc.name), as_dict=1)[0]
disbursement_date = None self.status = "Draft"
if not disbursement or disbursement.disbursed_amount == 0:
status = "Sanctioned"
elif disbursement.disbursed_amount == doc.loan_amount:
disbursement_date = disbursement.posting_date
status = "Disbursed"
elif disbursement.disbursed_amount > doc.loan_amount:
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")): if (not disbursement or disbursement.disbursed_amount == 0) and self.docstatus == 1:
self.status = "Sanctioned"
if disbursement:
self.validate_disbursed_amount_and_loan_amount(disbursement.disbursed_amount)
if disbursement.disbursed_amount == self.loan_amount and disbursement.disbursed_amount != 0:
self.status = "Disbursed"
disbursement_date = disbursement.posting_date
self.validate_disbursement_date(disbursement_date, self.status)
if self.total_amount_paid == self.total_payment:
self.status = "Repaid/Closed"
if not from_validate:
frappe.db.set_value("Loan", self.name, "status", self.status)
if disbursement_date:
frappe.db.set_value("Loan", self.name, "disbursement_date", disbursement_date)
def validate_disbursement_date(self, disbursement_date, loan_status):
if loan_status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", self.name, "repayment_start_date")):
frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date")) frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date"))
frappe.db.sql("""
update `tabLoan` def validate_disbursed_amount_and_loan_amount(self, disbursed_amount):
set status = %s, disbursement_date = %s if disbursed_amount > self.loan_amount:
where name = %s frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(self.loan_amount))
""", (status, disbursement_date, doc.name))
def get_disbursement_entry(self):
return frappe.db.sql("""
select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
from `tabGL Entry`
where account = %s and against_voucher_type = 'Loan' and against_voucher = %s
""", (self.payment_account, self.name), as_dict=1)[0]
def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods): def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
if repayment_method == "Repay Over Number of Periods" and not repayment_periods: if repayment_method == "Repay Over Number of Periods" and not repayment_periods:

View File

@@ -31,7 +31,11 @@ frappe.ui.form.on('Payroll Entry', {
} }
if ((frm.doc.employees || []).length) { if ((frm.doc.employees || []).length) {
frm.page.set_primary_action(__('Create Salary Slips'), () => { frm.page.set_primary_action(__('Create Salary Slips'), () => {
frm.save('Submit'); frm.save('Submit').then(()=>{
frm.page.clear_primary_action();
frm.refresh();
frm.events.refresh(frm);
});
}); });
} }
} }

View File

@@ -163,7 +163,7 @@ class PayrollEntry(Document):
""" """
cond = self.get_filter_condition() cond = self.get_filter_condition()
return frappe.db.sql(""" select eld.loan_account, eld.loan, return frappe.db.sql(""" select eld.loan_account, eld.loan,
eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment,t1.employee
from from
`tabSalary Slip` t1, `tabSalary Slip Loan` eld `tabSalary Slip` t1, `tabSalary Slip Loan` eld
where where
@@ -246,6 +246,7 @@ class PayrollEntry(Document):
accounts.append({ accounts.append({
"account": acc, "account": acc,
"debit_in_account_currency": flt(amount, precision), "debit_in_account_currency": flt(amount, precision),
"party_type": '',
"cost_center": self.cost_center, "cost_center": self.cost_center,
"project": self.project "project": self.project
}) })
@@ -257,6 +258,7 @@ class PayrollEntry(Document):
"account": acc, "account": acc,
"credit_in_account_currency": flt(amount, precision), "credit_in_account_currency": flt(amount, precision),
"cost_center": self.cost_center, "cost_center": self.cost_center,
"party_type": '',
"project": self.project "project": self.project
}) })
@@ -264,7 +266,9 @@ class PayrollEntry(Document):
for data in loan_details: for data in loan_details:
accounts.append({ accounts.append({
"account": data.loan_account, "account": data.loan_account,
"credit_in_account_currency": data.principal_amount "credit_in_account_currency": data.principal_amount,
"party_type": "Employee",
"party": data.employee
}) })
if data.interest_amount and not data.interest_income_account: if data.interest_amount and not data.interest_income_account:
@@ -275,14 +279,17 @@ class PayrollEntry(Document):
"account": data.interest_income_account, "account": data.interest_income_account,
"credit_in_account_currency": data.interest_amount, "credit_in_account_currency": data.interest_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
"project": self.project "project": self.project,
"party_type": "Employee",
"party": data.employee
}) })
payable_amount -= flt(data.total_payment, precision) payable_amount -= flt(data.total_payment, precision)
# Payable amount # Payable amount
accounts.append({ accounts.append({
"account": default_payroll_payable_account, "account": default_payroll_payable_account,
"credit_in_account_currency": flt(payable_amount, precision) "credit_in_account_currency": flt(payable_amount, precision),
"party_type": '',
}) })
journal_entry.set("accounts", accounts) journal_entry.set("accounts", accounts)
@@ -322,6 +329,10 @@ class PayrollEntry(Document):
statistical_component = frappe.db.get_value("Salary Component", sal_detail.salary_component, 'statistical_component') statistical_component = frappe.db.get_value("Salary Component", sal_detail.salary_component, 'statistical_component')
if statistical_component != 1: if statistical_component != 1:
salary_slip_total -= sal_detail.amount salary_slip_total -= sal_detail.amount
#loan deduction from bank entry during payroll
salary_slip_total -= salary_slip.total_loan_repayment
if salary_slip_total > 0: if salary_slip_total > 0:
self.create_journal_entry(salary_slip_total, "salary") self.create_journal_entry(salary_slip_total, "salary")
@@ -546,7 +557,6 @@ def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progr
count += 1 count += 1
if publish_progress: if publish_progress:
frappe.publish_progress(count*100/len(salary_slips), title = _("Submitting Salary Slips...")) frappe.publish_progress(count*100/len(salary_slips), title = _("Submitting Salary Slips..."))
if submitted_ss: if submitted_ss:
payroll_entry.make_accrual_jv_entry() payroll_entry.make_accrual_jv_entry()
frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}") frappe.msgprint(_("Salary Slip submitted for period from {0} to {1}")

View File

@@ -1,231 +1,82 @@
{ {
"allow_copy": 0, "creation": "2016-12-20 15:32:25.078334",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2016-12-20 15:32:25.078334", "payment_date",
"custom": 0, "principal_amount",
"docstatus": 0, "interest_amount",
"doctype": "DocType", "total_payment",
"document_type": "", "balance_loan_amount",
"editable_grid": 1, "paid"
"engine": "InnoDB", ],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "allow_on_submit": 1,
"allow_on_submit": 0, "columns": 2,
"bold": 0, "fieldname": "payment_date",
"collapsible": 0, "fieldtype": "Date",
"columns": 2, "in_list_view": 1,
"fieldname": "payment_date", "label": "Payment Date"
"fieldtype": "Date", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment 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, "columns": 2,
"allow_on_submit": 0, "fieldname": "principal_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Principal Amount",
"fieldname": "principal_amount", "no_copy": 1,
"fieldtype": "Currency", "options": "Company:company:default_currency",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Principal Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "columns": 2,
"allow_on_submit": 0, "fieldname": "interest_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Interest Amount",
"fieldname": "interest_amount", "no_copy": 1,
"fieldtype": "Currency", "options": "Company:company:default_currency",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Interest Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "columns": 2,
"allow_on_submit": 0, "fieldname": "total_payment",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Total Payment",
"fieldname": "total_payment", "options": "Company:company:default_currency",
"fieldtype": "Currency", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Payment",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "columns": 2,
"allow_on_submit": 0, "fieldname": "balance_loan_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 2, "label": "Balance Loan Amount",
"fieldname": "balance_loan_amount", "no_copy": 1,
"fieldtype": "Currency", "options": "Company:company:default_currency",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Balance Loan Amount",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "default": "0",
"allow_on_submit": 0, "fieldname": "paid",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Paid",
"fieldname": "paid", "read_only": 1
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Paid",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "istable": 1,
"hide_heading": 0, "modified": "2019-10-29 11:45:10.694557",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "HR",
"image_view": 0, "name": "Repayment Schedule",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0, "permissions": [],
"issingle": 0, "quick_entry": 1,
"istable": 1, "sort_field": "modified",
"max_attachments": 0, "sort_order": "DESC",
"modified": "2018-03-30 17:37:31.834792", "track_changes": 1
"modified_by": "Administrator",
"module": "HR",
"name": "Repayment Schedule",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

@@ -50,7 +50,8 @@ class SalarySlip(TransactionBase):
self.calculate_net_pay() self.calculate_net_pay()
company_currency = erpnext.get_company_currency(self.company) company_currency = erpnext.get_company_currency(self.company)
self.total_in_words = money_in_words(self.rounded_total, company_currency) total = self.net_pay if self.is_rounding_total_disabled() else self.rounded_total
self.total_in_words = money_in_words(total, company_currency)
if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"): if frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet"):
max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet") max_working_hours = frappe.db.get_single_value("HR Settings", "max_working_hours_against_timesheet")
@@ -62,6 +63,7 @@ class SalarySlip(TransactionBase):
if self.net_pay < 0: if self.net_pay < 0:
frappe.throw(_("Net Pay cannot be less than 0")) frappe.throw(_("Net Pay cannot be less than 0"))
else: else:
self.update_loans()
self.set_status() self.set_status()
self.update_status(self.name) self.update_status(self.name)
self.update_salary_slip_in_additional_salary() self.update_salary_slip_in_additional_salary()
@@ -69,6 +71,7 @@ class SalarySlip(TransactionBase):
self.email_salary_slip() self.email_salary_slip()
def on_cancel(self): def on_cancel(self):
self.update_loans()
self.set_status() self.set_status()
self.update_status() self.update_status()
self.update_salary_slip_in_additional_salary() self.update_salary_slip_in_additional_salary()
@@ -90,6 +93,9 @@ class SalarySlip(TransactionBase):
if date_diff(self.end_date, self.start_date) < 0: if date_diff(self.end_date, self.start_date) < 0:
frappe.throw(_("To date cannot be before From date")) frappe.throw(_("To date cannot be before From date"))
def is_rounding_total_disabled(self):
return cint(frappe.db.get_single_value("HR Settings", "disable_rounded_total"))
def check_existing(self): def check_existing(self):
if not self.salary_slip_based_on_timesheet: if not self.salary_slip_based_on_timesheet:
ret_exist = frappe.db.sql("""select name from `tabSalary Slip` ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
@@ -764,7 +770,8 @@ class SalarySlip(TransactionBase):
self.total_principal_amount += loan.principal_amount self.total_principal_amount += loan.principal_amount
def get_loan_details(self): def get_loan_details(self):
return frappe.db.sql("""select rps.principal_amount, rps.interest_amount, l.name, return frappe.db.sql("""select rps.principal_amount,
rps.name as repayment_name, rps.interest_amount, l.name,
rps.total_payment, l.loan_account, l.interest_income_account rps.total_payment, l.loan_account, l.interest_income_account
from from
`tabRepayment Schedule` as rps, `tabLoan` as l `tabRepayment Schedule` as rps, `tabLoan` as l
@@ -815,6 +822,17 @@ class SalarySlip(TransactionBase):
timesheet.set_status() timesheet.set_status()
timesheet.save() timesheet.save()
def update_loans(self):
for loan in self.get_loan_details():
doc = frappe.get_doc("Loan", loan.name)
#setting repayment schedule and updating total amount to pay
repayment_status = 1 if doc.docstatus == 1 else 0
frappe.db.set_value("Repayment Schedule", loan.repayment_name, "paid", repayment_status)
doc.reload()
doc.update_total_amount_paid()
doc.set_status()
def set_status(self, status=None): def set_status(self, status=None):
'''Get and update status''' '''Get and update status'''
if not status: if not status:

File diff suppressed because it is too large Load Diff

View File

@@ -1,524 +1,170 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "allow_import": 1,
"allow_import": 1, "autoname": "HR-SSA-.YY.-.MM.-.#####",
"allow_rename": 0, "creation": "2018-04-13 16:38:41.769237",
"autoname": "HR-SSA-.YY.-.MM.-.#####", "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2018-04-13 16:38:41.769237", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "employee",
"doctype": "DocType", "employee_name",
"document_type": "", "department",
"editable_grid": 1, "designation",
"engine": "InnoDB", "column_break_6",
"salary_structure",
"from_date",
"company",
"section_break_7",
"base",
"column_break_9",
"variable",
"amended_from"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "employee",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "Employee",
"columns": 0, "options": "Employee",
"fieldname": "employee", "reqd": 1,
"fieldtype": "Link", "search_index": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "employee.employee_name",
"allow_in_quick_entry": 0, "fieldname": "employee_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Employee Name",
"collapsible": 0, "read_only": 1
"columns": 0, },
"fetch_from": "employee.employee_name",
"fieldname": "employee_name",
"fieldtype": "Data",
"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": "Employee Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fetch_from": "employee.department",
"allow_in_quick_entry": 0, "fieldname": "department",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "Department",
"columns": 0, "options": "Department",
"fetch_from": "employee.department", "read_only": 1
"fieldname": "department", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fetch_from": "employee.designation",
"allow_in_quick_entry": 0, "fieldname": "designation",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "Designation",
"columns": 0, "options": "Designation",
"fetch_from": "employee.designation", "read_only": 1
"fieldname": "designation", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Designation",
"length": 0,
"no_copy": 0,
"options": "Designation",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"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, "fieldname": "column_break_6",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6",
"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, "fieldname": "salary_structure",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "in_standard_filter": 1,
"collapsible": 0, "label": "Salary Structure",
"columns": 0, "options": "Salary Structure",
"fieldname": "salary_structure", "reqd": 1,
"fieldtype": "Link", "search_index": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Salary Structure",
"length": 0,
"no_copy": 0,
"options": "Salary Structure",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "from_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "label": "From Date",
"bold": 0, "reqd": 1
"collapsible": 0, },
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"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": "From 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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "company",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Company",
"bold": 0, "options": "Company",
"collapsible": 0, "read_only": 1,
"columns": 0, "reqd": 1
"fieldname": "company", },
"fieldtype": "Link",
"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": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "section_break_7",
"allow_in_quick_entry": 0, "fieldtype": "Section Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"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,
"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, "fieldname": "base",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Base",
"bold": 0, "options": "Company:company:default_currency"
"collapsible": 0, },
"columns": 0,
"fieldname": "base",
"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": "Base",
"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, "fieldname": "column_break_9",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"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, "fieldname": "variable",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Variable",
"bold": 0, "options": "Company:company:default_currency"
"collapsible": 0, },
"columns": 0,
"fieldname": "variable",
"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": "Variable",
"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, "fieldname": "amended_from",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Amended From",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Salary Structure Assignment",
"columns": 0, "print_hide": 1,
"fieldname": "amended_from", "read_only": 1
"fieldtype": "Link",
"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": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Salary Structure Assignment",
"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, "is_submittable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2019-12-31 17:05:28.637510",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "HR",
"in_create": 0, "name": "Salary Structure Assignment",
"is_submittable": 1, "owner": "Administrator",
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:48.755450",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Structure Assignment",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "System Manager",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "print": 1,
"import": 0, "read": 1,
"permlevel": 0, "report": 1,
"print": 1, "role": "HR Manager",
"read": 1, "share": 1,
"report": 1, "submit": 1,
"role": "HR Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 0, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "HR User",
"import": 0, "share": 1,
"permlevel": 0, "submit": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "HR User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "sort_field": "modified",
"read_only": 0, "sort_order": "DESC",
"read_only_onload": 0, "title_field": "employee_name",
"show_name_in_global_search": 0, "track_changes": 1
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "employee_name",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -633,7 +633,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
is_stock_item=is_stock_item, is_stock_item=is_stock_item,
qty_field="stock_qty", qty_field="stock_qty",
select_columns = """, bom_item.source_warehouse, bom_item.operation, select_columns = """, bom_item.source_warehouse, bom_item.operation,
bom_item.include_item_in_manufacturing, bom_item.description, bom_item.include_item_in_manufacturing, bom_item.description, bom_item.rate,
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""") (Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True) items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
@@ -647,7 +647,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty", qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, select_columns = """, bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse,
bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing,
bom_item.description """) bom_item.description, bom_item.base_rate as rate """)
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True) items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
for item in items: for item in items:
@@ -760,23 +760,53 @@ def get_boms_in_bottom_up_order(bom_no=None):
def add_additional_cost(stock_entry, work_order): def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost # Add non stock items cost in the additional cost
bom = frappe.get_doc('BOM', work_order.bom_no) stock_entry.additional_costs = []
table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items'
expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company, expenses_included_in_valuation = frappe.get_cached_value("Company", work_order.company,
"expenses_included_in_valuation") "expenses_included_in_valuation")
add_non_stock_items_cost(stock_entry, work_order, expenses_included_in_valuation)
add_operations_cost(stock_entry, work_order, expenses_included_in_valuation)
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
bom = frappe.get_doc('BOM', work_order.bom_no)
table = 'exploded_items' if work_order.get('use_multi_level_bom') else 'items'
items = {} items = {}
for d in bom.get(table): for d in bom.get(table):
items.setdefault(d.item_code, d.rate) items.setdefault(d.item_code, d.amount)
non_stock_items = frappe.get_all('Item', non_stock_items = frappe.get_all('Item',
fields="name", filters={'name': ('in', list(items.keys())), 'ifnull(is_stock_item, 0)': 0}, as_list=1) fields="name", filters={'name': ('in', list(items.keys())), 'ifnull(is_stock_item, 0)': 0}, as_list=1)
non_stock_items_cost = 0.0
for name in non_stock_items: for name in non_stock_items:
non_stock_items_cost += flt(items.get(name[0])) * flt(stock_entry.fg_completed_qty) / flt(bom.quantity)
stock_entry.append('additional_costs', {
'expense_account': expense_account,
'description': _("Non stock items"),
'amount': non_stock_items_cost
})
def add_operations_cost(stock_entry, work_order=None, expense_account=None):
from erpnext.stock.doctype.stock_entry.stock_entry import get_operating_cost_per_unit
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
if operating_cost_per_unit:
stock_entry.append('additional_costs', { stock_entry.append('additional_costs', {
'expense_account': expenses_included_in_valuation, "expense_account": expense_account,
'description': name[0], "description": _("Operating Cost as per Work Order / BOM"),
'amount': items.get(name[0]) "amount": operating_cost_per_unit * flt(stock_entry.fg_completed_qty)
})
if work_order and work_order.additional_operating_cost and work_order.qty:
additional_operating_cost_per_unit = \
flt(work_order.additional_operating_cost) / flt(work_order.qty)
stock_entry.append('additional_costs', {
"expense_account": expense_account,
"description": "Additional Operating Cost",
"amount": additional_operating_cost_per_unit * flt(stock_entry.fg_completed_qty)
}) })
@frappe.whitelist() @frappe.whitelist()

View File

@@ -3,6 +3,11 @@
frappe.ui.form.on('Job Card', { frappe.ui.form.on('Job Card', {
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.docstatus == 0) {
frm.set_df_property("operation", "read_only", frm.doc.operation_id ? 1 : 0);
}
if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) { if(!frm.doc.__islocal && frm.doc.items && frm.doc.items.length) {
if (frm.doc.for_quantity != frm.doc.transferred_qty) { if (frm.doc.for_quantity != frm.doc.transferred_qty) {
frm.add_custom_button(__("Material Request"), () => { frm.add_custom_button(__("Material Request"), () => {

View File

@@ -6,6 +6,7 @@ frappe.ui.form.on("Work Order", {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Stock Entry': 'Start', 'Stock Entry': 'Start',
'Pick List': 'Create Pick List', 'Pick List': 'Create Pick List',
'Job Card': 'Create Job Card',
}; };
// Set query for warehouses // Set query for warehouses
@@ -136,7 +137,7 @@ frappe.ui.form.on("Work Order", {
if (frm.doc.docstatus === 1 if (frm.doc.docstatus === 1
&& frm.doc.operations && frm.doc.operations.length && frm.doc.operations && frm.doc.operations.length
&& frm.doc.qty != frm.doc.material_transferred_for_manufacturing) { && frm.doc.qty != frm.doc.produced_qty) {
const not_completed = frm.doc.operations.filter(d => { const not_completed = frm.doc.operations.filter(d => {
if(d.status != 'Completed') { if(d.status != 'Completed') {

View File

@@ -13,7 +13,6 @@ from dateutil.relativedelta import relativedelta
from erpnext.stock.doctype.item.item import validate_end_of_life from erpnext.stock.doctype.item.item import validate_end_of_life
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError
from erpnext.projects.doctype.timesheet.timesheet import OverlapError from erpnext.projects.doctype.timesheet.timesheet import OverlapError
from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
from frappe.utils.csvutils import getlink from frappe.utils.csvutils import getlink
@@ -671,10 +670,6 @@ def make_stock_entry(work_order_id, purpose, qty=None):
stock_entry.from_warehouse = wip_warehouse stock_entry.from_warehouse = wip_warehouse
stock_entry.to_warehouse = work_order.fg_warehouse stock_entry.to_warehouse = work_order.fg_warehouse
stock_entry.project = work_order.project stock_entry.project = work_order.project
if purpose=="Manufacture":
additional_costs = get_additional_costs(work_order, fg_qty=stock_entry.fg_completed_qty,
company=work_order.company)
stock_entry.set("additional_costs", additional_costs)
stock_entry.set_stock_entry_type() stock_entry.set_stock_entry_type()
stock_entry.get_items() stock_entry.get_items()

View File

@@ -13,6 +13,7 @@ erpnext.patches.v4_0.apply_user_permissions
erpnext.patches.v4_0.move_warehouse_user_to_restrictions erpnext.patches.v4_0.move_warehouse_user_to_restrictions
erpnext.patches.v4_0.global_defaults_to_system_settings erpnext.patches.v4_0.global_defaults_to_system_settings
erpnext.patches.v4_0.update_incharge_name_to_sales_person_in_maintenance_schedule erpnext.patches.v4_0.update_incharge_name_to_sales_person_in_maintenance_schedule
execute:frappe.reload_doc("HR", "doctype", "HR Settings") #2020-01-16
execute:frappe.reload_doc('stock', 'doctype', 'warehouse') # 2017-04-24 execute:frappe.reload_doc('stock', 'doctype', 'warehouse') # 2017-04-24
execute:frappe.reload_doc('accounts', 'doctype', 'sales_invoice') # 2016-08-31 execute:frappe.reload_doc('accounts', 'doctype', 'sales_invoice') # 2016-08-31
execute:frappe.reload_doc('selling', 'doctype', 'sales_order') # 2014-01-29 execute:frappe.reload_doc('selling', 'doctype', 'sales_order') # 2014-01-29
@@ -513,7 +514,6 @@ erpnext.patches.v11_0.rename_employee_loan_to_loan
erpnext.patches.v11_0.move_leave_approvers_from_employee #13-06-2018 erpnext.patches.v11_0.move_leave_approvers_from_employee #13-06-2018
erpnext.patches.v11_0.update_department_lft_rgt erpnext.patches.v11_0.update_department_lft_rgt
erpnext.patches.v11_0.add_default_email_template_for_leave erpnext.patches.v11_0.add_default_email_template_for_leave
execute:frappe.reload_doc("HR", "doctype", "HR Settings")
erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018 erpnext.patches.v11_0.set_default_email_template_in_hr #08-06-2018
erpnext.patches.v11_0.uom_conversion_data #30-06-2018 erpnext.patches.v11_0.uom_conversion_data #30-06-2018
erpnext.patches.v10_0.taxes_issue_with_pos erpnext.patches.v10_0.taxes_issue_with_pos
@@ -586,6 +586,7 @@ erpnext.patches.v11_0.add_permissions_in_gst_settings
erpnext.patches.v11_1.setup_guardian_role erpnext.patches.v11_1.setup_guardian_role
execute:frappe.delete_doc('DocType', 'Notification Control') execute:frappe.delete_doc('DocType', 'Notification Control')
erpnext.patches.v12_0.set_gst_category erpnext.patches.v12_0.set_gst_category
erpnext.patches.v12_0.update_gst_category
erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants
erpnext.patches.v12_0.set_task_status erpnext.patches.v12_0.set_task_status
erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019 erpnext.patches.v11_0.make_italian_localization_fields # 26-03-2019
@@ -644,7 +645,6 @@ erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
erpnext.patches.v12_0.set_payment_entry_status erpnext.patches.v12_0.set_payment_entry_status
erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields erpnext.patches.v12_0.update_owner_fields_in_acc_dimension_custom_fields
erpnext.patches.v12_0.set_default_for_add_taxes_from_item_tax_template
erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger erpnext.patches.v12_0.remove_denied_leaves_from_leave_ledger
erpnext.patches.v12_0.update_price_or_product_discount erpnext.patches.v12_0.update_price_or_product_discount
erpnext.patches.v12_0.add_export_type_field_in_party_master erpnext.patches.v12_0.add_export_type_field_in_party_master

View File

@@ -49,21 +49,22 @@ def execute():
item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code) item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_map, item_code)
# update the item tax table # update the item tax table
item = frappe.get_doc("Item", item_code)
item.set("taxes", [])
item.append("taxes", {"item_tax_template": item_tax_template_name, "tax_category": ""})
frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code) frappe.db.sql("delete from `tabItem Tax` where parent=%s and parenttype='Item'", item_code)
for d in item.taxes: if item_tax_template_name:
d.db_insert() item = frappe.get_doc("Item", item_code)
item.set("taxes", [])
item.append("taxes", {"item_tax_template": item_tax_template_name, "tax_category": ""})
for d in item.taxes:
d.db_insert()
doctypes = [ doctypes = [
'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice',
'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice' 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'
] ]
for dt in doctypes: for dt in doctypes:
for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item` for d in frappe.db.sql("""select name, parenttype, parent, item_code, item_tax_rate from `tab{0} Item`
where ifnull(item_tax_rate, '') not in ('', '{{}}') where ifnull(item_tax_rate, '') not in ('', '{{}}')
and item_tax_template is NULL""".format(dt), as_dict=1): and item_tax_template is NULL""".format(dt), as_dict=1):
item_tax_map = json.loads(d.item_tax_rate) item_tax_map = json.loads(d.item_tax_rate)
item_tax_template_name = get_item_tax_template(item_tax_templates, item_tax_template_name = get_item_tax_template(item_tax_templates,
@@ -95,27 +96,35 @@ def get_item_tax_template(item_tax_templates, item_tax_map, item_code, parenttyp
else: else:
parts = tax_type.strip().split(" - ") parts = tax_type.strip().split(" - ")
account_name = " - ".join(parts[:-1]) account_name = " - ".join(parts[:-1])
company = get_company(parts[-1], parenttype, parent) if not account_name:
parent_account = frappe.db.get_value("Account", tax_type = None
filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account") else:
filters = { company = get_company(parts[-1], parenttype, parent)
"account_name": account_name, parent_account = frappe.db.get_value("Account",
"company": company, filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account")
"account_type": "Tax", if not parent_account:
"parent_account": parent_account parent_account = frappe.db.get_value("Account",
} filters={"account_type": "Tax", "root_type": "Liability", "is_group": 1, "company": company})
tax_type = frappe.db.get_value("Account", filters) filters = {
if not tax_type: "account_name": account_name,
account = frappe.new_doc("Account") "company": company,
account.update(filters) "account_type": "Tax",
account.insert() "parent_account": parent_account
tax_type = account.name }
tax_type = frappe.db.get_value("Account", filters)
if not tax_type:
account = frappe.new_doc("Account")
account.update(filters)
account.insert()
tax_type = account.name
item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate}) if tax_type:
item_tax_templates.setdefault(item_tax_template.title, {}) item_tax_template.append("taxes", {"tax_type": tax_type, "tax_rate": tax_rate})
item_tax_templates[item_tax_template.title][tax_type] = tax_rate item_tax_templates.setdefault(item_tax_template.title, {})
item_tax_template.save() item_tax_templates[item_tax_template.title][tax_type] = tax_rate
return item_tax_template.name if item_tax_template.get("taxes"):
item_tax_template.save()
return item_tax_template.name
def get_company(company_abbr, parenttype=None, parent=None): def get_company(company_abbr, parenttype=None, parent=None):
if parenttype and parent: if parenttype and parent:

View File

@@ -1,5 +0,0 @@
import frappe
def execute():
frappe.db.set_value("Accounts Settings", None, "add_taxes_from_item_tax_template", 1)
frappe.db.set_default("add_taxes_from_item_tax_template", 1)

View File

@@ -0,0 +1,19 @@
from __future__ import unicode_literals
import frappe
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
frappe.db.sql(""" UPDATE `tabSales Invoice` set gst_category = 'Unregistered'
where gst_category = 'Registered Regular'
and ifnull(customer_gstin, '')=''
and ifnull(billing_address_gstin,'')=''
""")
frappe.db.sql(""" UPDATE `tabPurchase Invoice` set gst_category = 'Unregistered'
where gst_category = 'Registered Regular'
and ifnull(supplier_gstin, '')=''
""")

View File

@@ -13,6 +13,8 @@ def execute():
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration_category') frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_declaration_category')
frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission_detail') frappe.reload_doc('hr', 'doctype', 'employee_tax_exemption_proof_submission_detail')
frappe.reload_doc('accounts', 'doctype', 'tax_category')
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
and fieldname in ('port_code', 'shipping_bill_number', 'shipping_bill_date')""", doctype) and fieldname in ('port_code', 'shipping_bill_number', 'shipping_bill_date')""", doctype)
@@ -29,4 +31,4 @@ def execute():
update tabAddress update tabAddress
set gst_state_number=concat("0", gst_state_number) set gst_state_number=concat("0", gst_state_number)
where ifnull(gst_state_number, '') != '' and gst_state_number<10 where ifnull(gst_state_number, '') != '' and gst_state_number<10
""") """)

View File

@@ -107,6 +107,12 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
filters:{ 'item_code': row.item_code } filters:{ 'item_code': row.item_code }
} }
}); });
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
return me.set_query_for_item_tax_template(doc, cdt, cdn)
});
}
}, },
refresh: function(doc) { refresh: function(doc) {

View File

@@ -388,9 +388,14 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
var diff = me.frm.doc.total + non_inclusive_tax_amount var diff = me.frm.doc.total + non_inclusive_tax_amount
- flt(last_tax.total, precision("grand_total")); - flt(last_tax.total, precision("grand_total"));
if(me.discount_amount_applied && me.frm.doc.discount_amount) {
diff -= flt(me.frm.doc.discount_amount);
}
diff = flt(diff, precision("rounding_adjustment"));
if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) { if ( diff && Math.abs(diff) <= (5.0 / Math.pow(10, precision("tax_amount", last_tax))) ) {
this.frm.doc.rounding_adjustment = flt(flt(this.frm.doc.rounding_adjustment) + diff, me.frm.doc.rounding_adjustment = diff;
precision("rounding_adjustment"));
} }
} }
} }

View File

@@ -968,7 +968,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
qty: function(doc, cdt, cdn) { qty: function(doc, cdt, cdn) {
let item = frappe.get_doc(cdt, cdn); let item = frappe.get_doc(cdt, cdn);
this.conversion_factor(doc, cdt, cdn, true); this.conversion_factor(doc, cdt, cdn, false);
this.apply_pricing_rule(item, true); this.apply_pricing_rule(item, true);
}, },
@@ -1398,7 +1398,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
remove_pricing_rule: function(item) { remove_pricing_rule: function(item) {
let me = this; let me = this;
const fields = ["discount_percentage", "discount_amount", "pricing_rules"]; const fields = ["discount_percentage",
"discount_amount", "margin_rate_or_amount", "rate_with_margin"];
if(item.remove_free_item) { if(item.remove_free_item) {
var items = []; var items = [];
@@ -1418,6 +1419,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
fields.forEach(f => { fields.forEach(f => {
row[f] = 0; row[f] = 0;
}); });
["pricing_rules", "margin_type"].forEach(field => {
if (row[field]) {
row[field] = '';
}
})
} }
}); });
@@ -1693,6 +1700,29 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
set_query_for_item_tax_template: function(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
if(!item.item_code) {
frappe.throw(__("Please enter Item Code to get item taxes"));
} else {
let filters = {
'item_code': item.item_code,
'valid_from': doc.transaction_date || doc.bill_date || doc.posting_date,
'item_group': item.item_group,
}
if (doc.tax_category)
filters['tax_category'] = doc.tax_category;
return {
query: "erpnext.controllers.queries.get_tax_template",
filters: filters
}
}
},
payment_terms_template: function() { payment_terms_template: function() {
var me = this; var me = this;
const doc = this.frm.doc; const doc = this.frm.doc;
@@ -1767,14 +1797,28 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
}, },
set_reserve_warehouse: function() {
this.autofill_warehouse("reserve_warehouse");
},
set_warehouse: function() { set_warehouse: function() {
this.autofill_warehouse("warehouse");
},
autofill_warehouse : function (warehouse_field) {
// set warehouse in all child table rows
var me = this; var me = this;
if(this.frm.doc.set_warehouse) { let warehouse = (warehouse_field === "warehouse") ? me.frm.doc.set_warehouse : me.frm.doc.set_reserve_warehouse;
$.each(this.frm.doc.items || [], function(i, item) { let child_table = (warehouse_field === "warehouse") ? me.frm.doc.items : me.frm.doc.supplied_items;
frappe.model.set_value(me.frm.doctype + " Item", item.name, "warehouse", me.frm.doc.set_warehouse); let doctype = (warehouse_field === "warehouse") ? (me.frm.doctype + " Item") : (me.frm.doctype + " Item Supplied");
if(warehouse) {
$.each(child_table || [], function(i, item) {
frappe.model.set_value(doctype, item.name, warehouse_field, warehouse);
}); });
} }
}, },
coupon_code: function() { coupon_code: function() {
var me = this; var me = this;
frappe.run_serially([ frappe.run_serially([

View File

@@ -458,7 +458,8 @@ erpnext.utils.update_child_items = function(opts) {
fieldname:"item_code", fieldname:"item_code",
options: 'Item', options: 'Item',
in_list_view: 1, in_list_view: 1,
read_only: 1, read_only: 0,
disabled: 0,
label: __('Item Code') label: __('Item Code')
}, { }, {
fieldtype:'Float', fieldtype:'Float',
@@ -501,6 +502,7 @@ erpnext.utils.update_child_items = function(opts) {
frm.doc[opts.child_docname].forEach(d => { frm.doc[opts.child_docname].forEach(d => {
dialog.fields_dict.trans_items.df.data.push({ dialog.fields_dict.trans_items.df.data.push({
"docname": d.name, "docname": d.name,
"name": d.name,
"item_code": d.item_code, "item_code": d.item_code,
"qty": d.qty, "qty": d.qty,
"rate": d.rate, "rate": d.rate,

View File

@@ -139,7 +139,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
this.dialog.set_value('serial_no', d.serial_no); this.dialog.set_value('serial_no', d.serial_no);
} }
if (d.batch_no) { if (d.has_batch_no && d.batch_no) {
this.frm.doc.items.forEach(data => { this.frm.doc.items.forEach(data => {
if(data.item_code == d.item_code) { if(data.item_code == d.item_code) {
this.dialog.fields_dict.batches.df.data.push({ this.dialog.fields_dict.batches.df.data.push({

View File

@@ -3,6 +3,26 @@
frappe.ui.form.on('GST HSN Code', { frappe.ui.form.on('GST HSN Code', {
refresh: function(frm) { refresh: function(frm) {
if(! frm.doc.__islocal && frm.doc.taxes.length){
frm.add_custom_button(__('Update Taxes for Items'), function(){
frappe.confirm(
'Are you sure? It will overwrite taxes for all items with HSN Code <b>'+frm.doc.name+'</b>.',
function(){
frappe.call({
args:{
taxes: frm.doc.taxes,
hsn_code: frm.doc.name
},
method: 'erpnext.regional.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master',
callback: function(r) {
if(r.message){
frappe.show_alert(__('Item taxes updated'));
}
}
});
}
);
});
}
} }
}); });

View File

@@ -1,104 +1,48 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "autoname": "field:hsn_code",
"allow_import": 0, "creation": "2017-06-21 10:48:56.422086",
"allow_rename": 0, "doctype": "DocType",
"autoname": "field:hsn_code", "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2017-06-21 10:48:56.422086", "field_order": [
"custom": 0, "hsn_code",
"docstatus": 0, "description",
"doctype": "DocType", "taxes"
"document_type": "", ],
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "hsn_code",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "HSN Code",
"columns": 0, "reqd": 1,
"fieldname": "hsn_code", "unique": 1
"fieldtype": "Data", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "HSN Code",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "description",
"allow_on_submit": 0, "fieldtype": "Small Text",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Description"
"columns": 0, },
"fieldname": "description", {
"fieldtype": "Small Text", "fieldname": "taxes",
"hidden": 0, "fieldtype": "Table",
"ignore_user_permissions": 0, "label": "Taxes",
"ignore_xss_filter": 0, "options": "Item Tax"
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"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,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2019-12-29 11:32:06.849631",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "Regional",
"image_view": 0, "name": "GST HSN Code",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0, "permissions": [],
"issingle": 0, "quick_entry": 1,
"istable": 0, "search_fields": "hsn_code, description",
"max_attachments": 0, "sort_field": "modified",
"modified": "2017-09-29 14:38:52.220743", "sort_order": "DESC",
"modified_by": "Administrator", "title_field": "hsn_code",
"module": "Regional", "track_changes": 1
"name": "GST HSN Code",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "hsn_code, description",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "hsn_code",
"track_changes": 1,
"track_seen": 0
} }

View File

@@ -8,3 +8,26 @@ from frappe.model.document import Document
class GSTHSNCode(Document): class GSTHSNCode(Document):
pass pass
@frappe.whitelist()
def update_taxes_in_item_master(taxes, hsn_code):
items = frappe.get_list("Item", filters={
'gst_hsn_code': hsn_code
})
taxes = frappe.parse_json(taxes)
frappe.enqueue(update_item_document, items=items, taxes=taxes)
return 1
def update_item_document(items, taxes):
for item in items:
item_to_be_updated=frappe.get_doc("Item", item.name)
item_to_be_updated.taxes = []
for tax in taxes:
tax = frappe._dict(tax)
item_to_be_updated.append("taxes", {
'item_tax_template': tax.item_tax_template,
'tax_category': tax.tax_category,
'valid_from': tax.valid_from
})
item_to_be_updated.save()

View File

@@ -10,7 +10,9 @@ Provide a report and downloadable CSV according to the German DATEV format.
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import json import json
import six
from six import string_types from six import string_types
import frappe import frappe
from frappe import _ from frappe import _
import pandas as pd import pandas as pd
@@ -158,7 +160,7 @@ def get_gl_entries(filters, as_dict):
where gl.company = %(company)s where gl.company = %(company)s
and DATE(gl.posting_date) >= %(from_date)s and DATE(gl.posting_date) >= %(from_date)s
and DATE(gl.posting_date) <= %(to_date)s and DATE(gl.posting_date) <= %(to_date)s
order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict) order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict, as_utf8=1)
return gl_entries return gl_entries
@@ -404,7 +406,8 @@ def get_datev_csv(data, filters):
header = ';'.join(header).encode('latin_1') header = ';'.join(header).encode('latin_1')
data = result.to_csv( data = result.to_csv(
sep=b';', # Reason for str(';'): https://github.com/pandas-dev/pandas/issues/6035
sep=str(';'),
# European decimal seperator # European decimal seperator
decimal=',', decimal=',',
# Windows "ANSI" encoding # Windows "ANSI" encoding
@@ -412,13 +415,16 @@ def get_datev_csv(data, filters):
# format date as DDMM # format date as DDMM
date_format='%d%m', date_format='%d%m',
# Windows line terminator # Windows line terminator
line_terminator=b'\r\n', line_terminator='\r\n',
# Do not number rows # Do not number rows
index=False, index=False,
# Use all columns defined above # Use all columns defined above
columns=columns columns=columns
) )
if not six.PY2:
data = data.encode('latin_1')
return header + b'\r\n' + data return header + b'\r\n' + data
@frappe.whitelist() @frappe.whitelist()

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:08", "creation": "2013-05-24 19:29:08",
@@ -536,7 +537,7 @@
}, },
{ {
"fieldname": "other_charges_calculation", "fieldname": "other_charges_calculation",
"fieldtype": "Text", "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation", "label": "Taxes and Charges Calculation",
"no_copy": 1, "no_copy": 1,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
@@ -927,8 +928,9 @@
"icon": "fa fa-shopping-cart", "icon": "fa fa-shopping-cart",
"idx": 82, "idx": 82,
"is_submittable": 1, "is_submittable": 1,
"links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2019-11-12 13:19:11.895715", "modified": "2019-12-30 19:14:56.630270",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@@ -6,13 +6,14 @@
frappe.ui.form.on("Sales Order", { frappe.ui.form.on("Sales Order", {
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery Note',
'Pick List': 'Pick List', 'Pick List': 'Pick List',
'Sales Invoice': 'Invoice', 'Sales Invoice': 'Invoice',
'Material Request': 'Material Request', 'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order', 'Purchase Order': 'Purchase Order',
'Project': 'Project', 'Project': 'Project',
'Payment Entry': "Payment" 'Payment Entry': "Payment",
'Work Order': "Work Order"
} }
frm.add_fetch('customer', 'tax_id', 'tax_id'); frm.add_fetch('customer', 'tax_id', 'tax_id');
@@ -134,7 +135,6 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
} }
if(doc.status !== 'Closed') { if(doc.status !== 'Closed') {
if(doc.status !== 'On Hold') { if(doc.status !== 'On Hold') {
allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty)) allow_delivery = this.frm.doc.items.some(item => item.delivered_by_supplier === 0 && item.qty > flt(item.delivered_qty))
&& !this.frm.doc.skip_delivery_note && !this.frm.doc.skip_delivery_note
@@ -696,4 +696,4 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
}); });
} }
}); });
$.extend(cur_frm.cscript, new erpnext.selling.SalesOrderController({frm: cur_frm})); $.extend(cur_frm.cscript, new erpnext.selling.SalesOrderController({frm: cur_frm}));

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-06-18 12:39:59", "creation": "2013-06-18 12:39:59",
@@ -619,7 +620,7 @@
}, },
{ {
"fieldname": "other_charges_calculation", "fieldname": "other_charges_calculation",
"fieldtype": "Text", "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation", "label": "Taxes and Charges Calculation",
"no_copy": 1, "no_copy": 1,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
@@ -1194,7 +1195,8 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-10-23 14:26:42.767189", "links": [],
"modified": "2019-12-30 19:15:28.605085",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",

View File

@@ -380,6 +380,9 @@ class SalesOrder(SellingController):
def get_work_order_items(self, for_raw_material_request=0): def get_work_order_items(self, for_raw_material_request=0):
'''Returns items with BOM that already do not have a linked work order''' '''Returns items with BOM that already do not have a linked work order'''
items = [] items = []
item_codes = [i.item_code for i in self.items]
product_bundle_parents = [pb.new_item_code for pb in frappe.get_all("Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"])]
for table in [self.items, self.packed_items]: for table in [self.items, self.packed_items]:
for i in table: for i in table:
bom = get_default_bom_item(i.item_code) bom = get_default_bom_item(i.item_code)
@@ -391,7 +394,7 @@ class SalesOrder(SellingController):
else: else:
pending_qty = stock_qty pending_qty = stock_qty
if pending_qty: if pending_qty and i.item_code not in product_bundle_parents:
if bom: if bom:
items.append(dict( items.append(dict(
name= i.name, name= i.name,
@@ -420,7 +423,7 @@ class SalesOrder(SellingController):
def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date): def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
delivery_date = get_next_schedule_date(ref_doc_delivery_date, delivery_date = get_next_schedule_date(ref_doc_delivery_date,
auto_repeat_doc.frequency, cint(auto_repeat_doc.repeat_on_day)) auto_repeat_doc.frequency, auto_repeat_doc.start_date, cint(auto_repeat_doc.repeat_on_day))
if delivery_date <= transaction_date: if delivery_date <= transaction_date:
delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date) delivery_date_diff = frappe.utils.date_diff(ref_doc_delivery_date, red_doc_transaction_date)

View File

@@ -320,7 +320,12 @@ class TestSalesOrder(unittest.TestCase):
create_dn_against_so(so.name, 4) create_dn_against_so(so.name, 4)
make_sales_invoice(so.name) make_sales_invoice(so.name)
trans_item = json.dumps([{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7}]) first_item_of_so = so.get("items")[0]
trans_item = json.dumps([
{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
'qty' : first_item_of_so.qty, 'docname': first_item_of_so.name},
{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 7}
])
update_child_qty_rate('Sales Order', trans_item, so.name) update_child_qty_rate('Sales Order', trans_item, so.name)
so.reload() so.reload()
@@ -329,6 +334,48 @@ class TestSalesOrder(unittest.TestCase):
self.assertEqual(so.get("items")[-1].qty, 7) self.assertEqual(so.get("items")[-1].qty, 7)
self.assertEqual(so.get("items")[-1].amount, 1400) self.assertEqual(so.get("items")[-1].amount, 1400)
self.assertEqual(so.status, 'To Deliver and Bill') self.assertEqual(so.status, 'To Deliver and Bill')
def test_remove_item_in_update_child_qty_rate(self):
so = make_sales_order(**{
"item_list": [{
"item_code": '_Test Item',
"qty": 5,
"rate":1000
}]
})
create_dn_against_so(so.name, 2)
make_sales_invoice(so.name)
# add an item so as to try removing items
trans_item = json.dumps([
{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
{"item_code": '_Test Item 2', "qty": 2, "rate":500}
])
update_child_qty_rate('Sales Order', trans_item, so.name)
so.reload()
self.assertEqual(len(so.get("items")), 2)
# check if delivered items can be removed
trans_item = json.dumps([{
"item_code": '_Test Item 2',
"qty": 2,
"rate":500,
"docname": so.get("items")[1].name
}])
self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Sales Order', trans_item, so.name)
#remove last added item
trans_item = json.dumps([{
"item_code": '_Test Item',
"qty": 5,
"rate":1000,
"docname": so.get("items")[0].name
}])
update_child_qty_rate('Sales Order', trans_item, so.name)
so.reload()
self.assertEqual(len(so.get("items")), 1)
self.assertEqual(so.status, 'To Deliver and Bill')
def test_update_child_qty_rate(self): def test_update_child_qty_rate(self):

View File

@@ -84,6 +84,13 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
return me.set_query_for_batch(doc, cdt, cdn) return me.set_query_for_batch(doc, cdt, cdn)
}); });
} }
if(this.frm.fields_dict["items"].grid.get_field('item_code')) {
this.frm.set_query("item_tax_template", "items", function(doc, cdt, cdn) {
return me.set_query_for_item_tax_template(doc, cdt, cdn)
});
}
}, },
refresh: function() { refresh: function() {

View File

@@ -76,7 +76,7 @@ class AuthorizationControl(TransactionBase):
add_cond = '' add_cond = ''
auth_value = av_dis auth_value = av_dis
if val == 1: add_cond += " and system_user = '"+session['user'].replace("'", "\\'")+"'" if val == 1: add_cond += " and system_user = '"+ frappe.db.escape(session['user']) +"'"
elif val == 2: add_cond += " and system_role IN %s" % ("('"+"','".join(frappe.get_roles())+"')") elif val == 2: add_cond += " and system_role IN %s" % ("('"+"','".join(frappe.get_roles())+"')")
else: add_cond += " and ifnull(system_user,'') = '' and ifnull(system_role,'') = ''" else: add_cond += " and ifnull(system_user,'') = '' and ifnull(system_role,'') = ''"
@@ -85,7 +85,7 @@ class AuthorizationControl(TransactionBase):
if doc_obj: if doc_obj:
if doc_obj.doctype == 'Sales Invoice': customer = doc_obj.customer if doc_obj.doctype == 'Sales Invoice': customer = doc_obj.customer
else: customer = doc_obj.customer_name else: customer = doc_obj.customer_name
add_cond = " and master_name = '"+cstr(customer).replace("'", "\\'")+"'" add_cond = " and master_name = '"+ frappe.db.escape(customer) +"'"
if based_on == 'Itemwise Discount': if based_on == 'Itemwise Discount':
if doc_obj: if doc_obj:
for t in doc_obj.get("items"): for t in doc_obj.get("items"):

View File

@@ -106,7 +106,11 @@ def delete_lead_addresses(company_name):
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads))) frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
def delete_communications(doctype, company_name, company_fieldname): def delete_communications(doctype, company_name, company_fieldname):
frappe.db.sql("""
DELETE FROM `tabCommunication` WHERE reference_doctype = %s AND refrence_docs = frappe.get_all(doctype, filters={company_fieldname:company_name})
EXISTS (SELECT name FROM `tab{0}` WHERE {1} = %s AND `tabCommunication`.reference_name = name) reference_doctype_names = [r.name for r in refrence_docs]
""".format(doctype, company_fieldname), (doctype, company_name))
communications = frappe.get_all("Communication", filters={"reference_doctype":doctype,"reference_name":["in",reference_doctype_names]})
communication_names = [c.name for c in communications]
frappe.delete_doc("Communication",communication_names)

View File

@@ -88,6 +88,56 @@ class TestCompany(unittest.TestCase):
self.delete_mode_of_payment(template) self.delete_mode_of_payment(template)
frappe.delete_doc("Company", template) frappe.delete_doc("Company", template)
def test_delete_communication(self):
from erpnext.setup.doctype.company.delete_company_transactions import delete_communications
company = create_child_company()
lead = create_test_lead_in_company(company)
communication = create_company_communication("Lead", lead)
delete_communications("Lead", "Test Company", "company")
self.assertFalse(frappe.db.exists("Communcation", communication))
self.assertFalse(frappe.db.exists({"doctype":"Comunication Link", "link_name": communication}))
def delete_mode_of_payment(self, company): def delete_mode_of_payment(self, company):
frappe.db.sql(""" delete from `tabMode of Payment Account` frappe.db.sql(""" delete from `tabMode of Payment Account`
where company =%s """, (company)) where company =%s """, (company))
def create_company_communication(doctype, docname):
comm = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"content": "Deduplication of Links",
"communication_medium": "Email",
"reference_doctype":doctype,
"reference_name":docname
})
comm.insert()
def create_child_company():
child_company = frappe.db.exists("Company", "Test Company")
if not child_company:
child_company = frappe.get_doc({
"doctype":"Company",
"company_name":"Test Company",
"abbr":"test_company",
"default_currency":"INR"
})
child_company.insert()
else:
child_company = frappe.get_doc("Company", child_company)
return child_company.name
def create_test_lead_in_company(company):
lead = frappe.db.exists("Lead", "Test Lead in new company")
if not lead:
lead = frappe.get_doc({
"doctype": "Lead",
"lead_name": "Test Lead in new company",
"scompany": company
})
lead.insert()
else:
lead = frappe.get_doc("Lead", lead)
lead.company = company
lead.save()
return lead.name

View File

@@ -226,16 +226,14 @@ def set_batch_nos(doc, warehouse_field, throw=False):
warehouse = d.get(warehouse_field, None) warehouse = d.get(warehouse_field, None)
if has_batch_no and warehouse and qty > 0: if has_batch_no and warehouse and qty > 0:
if not d.batch_no: if not d.batch_no:
d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw) d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no)
else: else:
batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse) batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse)
if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")): if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")):
frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty)) frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
@frappe.whitelist() @frappe.whitelist()
def get_batch_no(item_code, warehouse, qty=1, throw=False): def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
""" """
Get batch number using First Expiring First Out method. Get batch number using First Expiring First Out method.
:param item_code: `item_code` of Item Document :param item_code: `item_code` of Item Document
@@ -245,7 +243,7 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False):
""" """
batch_no = None batch_no = None
batches = get_batches(item_code, warehouse, qty, throw) batches = get_batches(item_code, warehouse, qty, throw, serial_no)
for batch in batches: for batch in batches:
if cint(qty) <= cint(batch.qty): if cint(qty) <= cint(batch.qty):
@@ -260,16 +258,31 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False):
return batch_no return batch_no
def get_batches(item_code, warehouse, qty=1, throw=False): def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
batches = frappe.db.sql( from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
'select batch_id, sum(actual_qty) as qty from `tabBatch` join `tabStock Ledger Entry` ignore index (item_code, warehouse) ' cond = ''
'on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )' if serial_no:
'where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s ' batch = frappe.get_all("Serial No",
'and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL)' fields = ["distinct batch_no"],
'group by batch_id ' filters= {
'order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC', "item_code": item_code,
(item_code, warehouse), "warehouse": warehouse,
as_dict=True "name": ("in", get_serial_nos(serial_no))
) }
)
return batches if batch and len(batch) > 1:
return []
cond = " and `tabBatch`.name = %s" %(frappe.db.escape(batch[0].batch_no))
return frappe.db.sql("""
select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty
from `tabBatch`
join `tabStock Ledger Entry` ignore index (item_code, warehouse)
on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
""".format(cond), (item_code, warehouse), as_dict=True)

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:09", "creation": "2013-05-24 19:29:09",
@@ -682,7 +683,7 @@
}, },
{ {
"fieldname": "other_charges_calculation", "fieldname": "other_charges_calculation",
"fieldtype": "Text", "fieldtype": "Long Text",
"label": "Taxes and Charges Calculation", "label": "Taxes and Charges Calculation",
"no_copy": 1, "no_copy": 1,
"oldfieldtype": "HTML", "oldfieldtype": "HTML",
@@ -1238,7 +1239,8 @@
"icon": "fa fa-truck", "icon": "fa fa-truck",
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-09-27 14:24:20.269682", "links": [],
"modified": "2019-12-30 19:17:13.122644",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@@ -49,7 +49,7 @@ frappe.ui.form.on("Item", {
if (!frm.doc.is_fixed_asset) { if (!frm.doc.is_fixed_asset) {
erpnext.item.make_dashboard(frm); erpnext.item.make_dashboard(frm);
} }
if (frm.doc.is_fixed_asset) { if (frm.doc.is_fixed_asset) {
frm.trigger('is_fixed_asset'); frm.trigger('is_fixed_asset');
frm.trigger('auto_create_assets'); frm.trigger('auto_create_assets');
@@ -136,6 +136,20 @@ frappe.ui.form.on("Item", {
frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0); frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
}, },
gst_hsn_code: function(frm) {
if(!frm.doc.taxes || !frm.doc.taxes.length) {
frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
$.each(hsn_doc.taxes || [], function(i, tax) {
let a = frappe.model.add_child(frm.doc, 'Item Tax', 'taxes');
a.item_tax_template = tax.item_tax_template;
a.tax_category = tax.tax_category;
a.valid_from = tax.valid_from;
frm.refresh_field('taxes');
});
});
}
},
is_fixed_asset: function(frm) { is_fixed_asset: function(frm) {
// set serial no to false & toggles its visibility // set serial no to false & toggles its visibility
frm.set_value('has_serial_no', 0); frm.set_value('has_serial_no', 0);

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_guest_to_view": 1, "allow_guest_to_view": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
@@ -569,6 +570,7 @@
{ {
"default": "0.00", "default": "0.00",
"depends_on": "is_stock_item", "depends_on": "is_stock_item",
"description": "Minimum quantity should be as per Stock UOM",
"fieldname": "min_order_qty", "fieldname": "min_order_qty",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Minimum Order Qty", "label": "Minimum Order Qty",
@@ -1041,8 +1043,9 @@
"icon": "fa fa-tag", "icon": "fa fa-tag",
"idx": 2, "idx": 2,
"image_field": "image", "image_field": "image",
"links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2019-12-13 12:15:56.197246", "modified": "2020-01-02 19:13:59.295963",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -551,7 +551,7 @@ class Item(WebsiteGenerator):
"""select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name)) """select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name))
if duplicate: if duplicate:
frappe.throw(_("Barcode {0} already used in Item {1}").format( frappe.throw(_("Barcode {0} already used in Item {1}").format(
item_barcode.barcode, duplicate[0][0]), frappe.DuplicateEntryError) item_barcode.barcode, duplicate[0][0]))
item_barcode.barcode_type = "" if item_barcode.barcode_type not in options else item_barcode.barcode_type item_barcode.barcode_type = "" if item_barcode.barcode_type not in options else item_barcode.barcode_type
if item_barcode.barcode_type and item_barcode.barcode_type.upper() in ('EAN', 'UPC-A', 'EAN-13', 'EAN-8'): if item_barcode.barcode_type and item_barcode.barcode_type.upper() in ('EAN', 'UPC-A', 'EAN-13', 'EAN-8'):

File diff suppressed because it is too large Load Diff

View File

@@ -1,107 +1,50 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2013-02-22 01:28:01",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2013-02-22 01:28:01", "item_tax_template",
"custom": 0, "tax_category",
"docstatus": 0, "valid_from"
"doctype": "DocType", ],
"editable_grid": 1,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "item_tax_template",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Item Tax Template",
"collapsible": 0, "oldfieldname": "tax_type",
"columns": 0, "oldfieldtype": "Link",
"fieldname": "item_tax_template", "options": "Item Tax Template",
"fieldtype": "Link", "reqd": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Tax Template",
"length": 0,
"no_copy": 0,
"oldfieldname": "tax_type",
"oldfieldtype": "Link",
"options": "Item Tax Template",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "tax_category",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Tax Category",
"collapsible": 0, "oldfieldname": "tax_rate",
"columns": 0, "oldfieldtype": "Currency",
"fetch_from": "", "options": "Tax Category"
"fieldname": "tax_category", },
"fieldtype": "Link", {
"hidden": 0, "fieldname": "valid_from",
"ignore_user_permissions": 0, "fieldtype": "Date",
"ignore_xss_filter": 0, "in_list_view": 1,
"in_filter": 0, "label": "Valid From"
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Tax Category",
"length": 0,
"no_copy": 0,
"oldfieldname": "tax_rate",
"oldfieldtype": "Currency",
"options": "Tax Category",
"permlevel": 0,
"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, "idx": 1,
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 1, "modified": "2019-12-28 21:54:40.807849",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Stock",
"is_submittable": 0, "name": "Item Tax",
"issingle": 0, "owner": "Administrator",
"istable": 1, "permissions": [],
"max_attachments": 0, "sort_field": "modified",
"modified": "2018-12-21 23:52:40.798944", "sort_order": "DESC"
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Tax",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

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