Merge branch 'develop' into datev_report_headers

This commit is contained in:
barredterra 2019-09-14 20:09:39 +02:00
commit 5bd8562b5a
256 changed files with 12033 additions and 22050 deletions

View File

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

View File

@ -12,11 +12,14 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist() @frappe.whitelist()
@cache_source @cache_source
def get(chart_name=None, from_date = None, to_date = None): def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_date = None):
if chart_name:
chart = frappe.get_doc('Dashboard Chart', chart_name) chart = frappe.get_doc('Dashboard Chart', chart_name)
else:
chart = frappe._dict(frappe.parse_json(chart))
timespan = chart.timespan timespan = chart.timespan
timegrain = chart.time_interval timegrain = chart.time_interval
filters = json.loads(chart.filters_json) filters = frappe.parse_json(chart.filters_json)
account = filters.get("account") account = filters.get("account")
company = filters.get("company") company = filters.get("company")

View File

@ -128,6 +128,7 @@ class Account(NestedSet):
"account_currency": self.account_currency, "account_currency": self.account_currency,
"parent_account": parent_acc_name_map[company] "parent_account": parent_acc_name_map[company]
}) })
if not self.check_if_child_acc_exists(doc):
doc.save() doc.save()
frappe.msgprint(_("Account {0} is added in the child company {1}") frappe.msgprint(_("Account {0} is added in the child company {1}")
.format(doc.name, company)) .format(doc.name, company))
@ -172,6 +173,24 @@ class Account(NestedSet):
if frappe.db.get_value("GL Entry", {"account": self.name}): if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency")) frappe.throw(_("Currency can not be changed after making entries using some other currency"))
def check_if_child_acc_exists(self, doc):
''' Checks if a account in parent company exists in the '''
info = frappe.db.get_value("Account", {
"account_name": doc.account_name,
"account_number": doc.account_number
}, ['company', 'account_currency', 'is_group', 'root_type', 'account_type', 'balance_must_be', 'account_name'], as_dict=1)
if not info:
return
doc = vars(doc)
dict_diff = [k for k in info if k in doc and info[k] != doc[k] and k != "company"]
if dict_diff:
frappe.throw(_("Account {0} already exists in child company {1}. The following fields have different values, they should be same:<ul><li>{2}</li></ul>")
.format(info.account_name, info.company, '</li><li>'.join(dict_diff)))
else:
return True
def convert_group_to_ledger(self): def convert_group_to_ledger(self):
if self.check_if_child_exists(): if self.check_if_child_exists():
throw(_("Account with child nodes cannot be converted to ledger")) throw(_("Account with child nodes cannot be converted to ledger"))

View File

@ -123,7 +123,8 @@ frappe.treeview_settings["Account"] = {
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){ if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
// show Dr if positive since balance is calculated as debit - credit else show Cr // show Dr if positive since balance is calculated as debit - credit else show Cr
let dr_or_cr = node.data.balance_in_account_currency > 0 ? "Dr": "Cr"; let balance = node.data.balance_in_account_currency || node.data.balance;
let dr_or_cr = balance > 0 ? "Dr": "Cr";
if (node.data && node.data.balance!==undefined) { if (node.data && node.data.balance!==undefined) {
$('<span class="balance-area pull-right text-muted small">' $('<span class="balance-area pull-right text-muted small">'

View File

@ -407,6 +407,10 @@
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960" "account_number": "9960"
}, },
"Debitoren": {
"is_group": 1,
"account_number": "10000"
},
"Forderungen aus Lieferungen und Leistungen": { "Forderungen aus Lieferungen und Leistungen": {
"account_number": "1200", "account_number": "1200",
"account_type": "Receivable" "account_type": "Receivable"
@ -1077,7 +1081,7 @@
} }
} }
}, },
"C - Verb.": { "C - Verbindlichkeiten": {
"account_type": "Payable", "account_type": "Payable",
"1 - Anleihen": { "1 - Anleihen": {
"is_group": 1, "is_group": 1,
@ -1194,6 +1198,14 @@
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964" "account_number": "9964"
}, },
"Kreditoren": {
"account_number": "70000",
"is_group": 1,
"Wareneingangs-­Verrechnungskonto" : {
"account_number": "70001",
"account_type": "Stock Received But Not Billed"
}
},
"Verb. aus Lieferungen und Leistungen": { "Verb. aus Lieferungen und Leistungen": {
"account_number": "3300", "account_number": "3300",
"account_type": "Payable" "account_type": "Payable"
@ -1682,90 +1694,6 @@
"account_type": "Income Account" "account_type": "Income Account"
} }
}, },
"Erl\u00f6sschm\u00e4lerungen (Gruppe)": {
"is_group": 1,
"Erl\u00f6sschm\u00e4lerungen": {
"account_number": "4700"
},
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien Ums\u00e4tzen \u00a7 4 Nr. 1a UStG": {
"account_number": "4705"
},
"Erl\u00f6sschm\u00e4lerungen 7 % USt": {
"account_number": "4710"
},
"Erl\u00f6sschm\u00e4lerungen 19 % USt": {
"account_number": "4720"
},
"Erl\u00f6sschm\u00e4lerungen 16 % USt": {
"account_number": "4723"
},
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien innergem. Lieferungen": {
"account_number": "4724"
},
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 7 % USt": {
"account_number": "4725"
},
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 19 % USt": {
"account_number": "4726"
},
"Erl\u00f6sschm\u00e4lerungen aus im anderen EU-Land steuerpfl. Lieferungen": {
"account_number": "4727"
},
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
"account_number": "4729"
},
"Gew\u00e4hrte Skonti (Gruppe)": {
"is_group": 1,
"Gew. Skonti": {
"account_number": "4730"
},
"Gew. Skonti 7 % USt": {
"account_number": "4731"
},
"Gew. Skonti 19 % USt": {
"account_number": "4736"
},
"Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4738"
},
"Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
"account_number": "4741"
},
"Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4742"
},
"Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
"account_number": "4743"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
"account_number": "4745"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
"account_number": "4746"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
"account_number": "4748"
}
},
"Gew\u00e4hrte Boni 7 % USt": {
"account_number": "4750"
},
"Gew\u00e4hrte Boni 19 % USt": {
"account_number": "4760"
},
"Gew\u00e4hrte Boni": {
"account_number": "4769"
},
"Gew\u00e4hrte Rabatte": {
"account_number": "4770"
},
"Gew\u00e4hrte Rabatte 7 % USt": {
"account_number": "4780"
},
"Gew\u00e4hrte Rabatte 19 % USt": {
"account_number": "4790"
}
},
"Grundst\u00fccksertr\u00e4ge (Gruppe)": { "Grundst\u00fccksertr\u00e4ge (Gruppe)": {
"is_group": 1, "is_group": 1,
"Grundst\u00fccksertr\u00e4ge": { "Grundst\u00fccksertr\u00e4ge": {
@ -2049,48 +1977,6 @@
"Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": { "Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": {
"account_number": "5793" "account_number": "5793"
} }
},
"Erhaltene Boni (Gruppe)": {
"is_group": 1,
"Erhaltene Boni 7 % Vorsteuer": {
"account_number": "5750"
},
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
"account_number": "5753"
},
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
"account_number": "5754"
},
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
"account_number": "5755"
},
"Erhaltene Boni 19 % Vorsteuer": {
"account_number": "5760"
},
"Erhaltene Boni": {
"account_number": "5769"
}
},
"Erhaltene Rabatte (Gruppe)": {
"is_group": 1,
"Erhaltene Rabatte": {
"account_number": "5770"
},
"Erhaltene Rabatte 7 % Vorsteuer": {
"account_number": "5780"
},
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
"account_number": "5783"
},
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
"account_number": "5784"
},
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
"account_number": "5785"
},
"Erhaltene Rabatte 19 % Vorsteuer": {
"account_number": "5790"
}
} }
}, },
"Bezugsnebenkosten (Gruppe)": { "Bezugsnebenkosten (Gruppe)": {
@ -2410,6 +2296,48 @@
"6 - sonstige betriebliche Ertr\u00e4ge": { "6 - sonstige betriebliche Ertr\u00e4ge": {
"root_type": "Income", "root_type": "Income",
"is_group": 1, "is_group": 1,
"Erhaltene Boni (Gruppe)": {
"is_group": 1,
"Erhaltene Boni 7 % Vorsteuer": {
"account_number": "5750"
},
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
"account_number": "5753"
},
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
"account_number": "5754"
},
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
"account_number": "5755"
},
"Erhaltene Boni 19 % Vorsteuer": {
"account_number": "5760"
},
"Erhaltene Boni": {
"account_number": "5769"
}
},
"Erhaltene Rabatte (Gruppe)": {
"is_group": 1,
"Erhaltene Rabatte": {
"account_number": "5770"
},
"Erhaltene Rabatte 7 % Vorsteuer": {
"account_number": "5780"
},
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
"account_number": "5783"
},
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
"account_number": "5784"
},
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
"account_number": "5785"
},
"Erhaltene Rabatte 19 % Vorsteuer": {
"account_number": "5790"
}
},
"Andere aktivierte Eigenleistungen": { "Andere aktivierte Eigenleistungen": {
"account_number": "4820" "account_number": "4820"
}, },
@ -2733,6 +2661,90 @@
"7 - sonstige betriebliche Aufwendungen": { "7 - sonstige betriebliche Aufwendungen": {
"root_type": "Expense", "root_type": "Expense",
"is_group": 1, "is_group": 1,
"Erl\u00f6sschm\u00e4lerungen (Gruppe)": {
"is_group": 1,
"Erl\u00f6sschm\u00e4lerungen": {
"account_number": "4700"
},
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien Ums\u00e4tzen \u00a7 4 Nr. 1a UStG": {
"account_number": "4705"
},
"Erl\u00f6sschm\u00e4lerungen 7 % USt": {
"account_number": "4710"
},
"Erl\u00f6sschm\u00e4lerungen 19 % USt": {
"account_number": "4720"
},
"Erl\u00f6sschm\u00e4lerungen 16 % USt": {
"account_number": "4723"
},
"Erl\u00f6sschm\u00e4lerungen aus steuerfreien innergem. Lieferungen": {
"account_number": "4724"
},
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 7 % USt": {
"account_number": "4725"
},
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 19 % USt": {
"account_number": "4726"
},
"Erl\u00f6sschm\u00e4lerungen aus im anderen EU-Land steuerpfl. Lieferungen": {
"account_number": "4727"
},
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
"account_number": "4729"
},
"Gew\u00e4hrte Skonti (Gruppe)": {
"is_group": 1,
"Gew. Skonti": {
"account_number": "4730"
},
"Gew. Skonti 7 % USt": {
"account_number": "4731"
},
"Gew. Skonti 19 % USt": {
"account_number": "4736"
},
"Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4738"
},
"Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
"account_number": "4741"
},
"Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4742"
},
"Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
"account_number": "4743"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
"account_number": "4745"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
"account_number": "4746"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
"account_number": "4748"
}
},
"Gew\u00e4hrte Boni 7 % USt": {
"account_number": "4750"
},
"Gew\u00e4hrte Boni 19 % USt": {
"account_number": "4760"
},
"Gew\u00e4hrte Boni": {
"account_number": "4769"
},
"Gew\u00e4hrte Rabatte": {
"account_number": "4770"
},
"Gew\u00e4hrte Rabatte 7 % USt": {
"account_number": "4780"
},
"Gew\u00e4hrte Rabatte 19 % USt": {
"account_number": "4790"
}
},
"Sonstige betriebliche Aufwendungen": { "Sonstige betriebliche Aufwendungen": {
"account_number": "6300" "account_number": "6300"
}, },
@ -3609,18 +3621,6 @@
"Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen f. sonstige Steuern": { "Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen f. sonstige Steuern": {
"account_number": "7694" "account_number": "7694"
} }
},
"Debitoren": {
"root_type": "Asset",
"is_group": 1
},
"Kreditoren": {
"root_type": "Liability",
"is_group": 1,
"Wareneingangs-­Verrechnungskonto" : {
"account_number": "70001",
"account_type": "Stock Received But Not Billed"
}
} }
} }
} }

View File

@ -17,8 +17,7 @@
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company"
"reqd": 1
}, },
{ {
"fieldname": "reference_document", "fieldname": "reference_document",
@ -34,8 +33,7 @@
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Default Dimension", "label": "Default Dimension",
"options": "reference_document", "options": "reference_document"
"reqd": 1
}, },
{ {
"columns": 3, "columns": 3,
@ -55,7 +53,7 @@
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-07-17 23:34:33.026883", "modified": "2019-08-15 11:59:09.389891",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension Detail", "name": "Accounting Dimension Detail",

View File

@ -8,7 +8,7 @@ def get_data():
'fieldname': 'bank', 'fieldname': 'bank',
'transactions': [ 'transactions': [
{ {
'label': _('Bank Deatils'), 'label': _('Bank Details'),
'items': ['Bank Account', 'Bank Guarantee'] 'items': ['Bank Account', 'Bank Guarantee']
} }
] ]

View File

@ -45,12 +45,12 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self): def clear_linked_payment_entries(self):
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
allocated_amount = get_total_allocated_amount(payment_entry) allocated_amount = get_total_allocated_amount(payment_entry)
paid_amount = get_paid_amount(payment_entry) paid_amount = get_paid_amount(payment_entry, self.currency)
if paid_amount and allocated_amount: if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount): if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))) frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount))))
elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount): else:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry) self.clear_simple_entry(payment_entry)
@ -80,9 +80,17 @@ def get_total_allocated_amount(payment_entry):
AND AND
bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True) bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True)
def get_paid_amount(payment_entry): def get_paid_amount(payment_entry, currency):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]: if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "paid_amount")
paid_amount_field = "paid_amount"
if payment_entry.payment_document == 'Payment Entry':
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
paid_amount_field = ("base_paid_amount"
if doc.paid_to_account_currency == currency else "paid_amount")
return frappe.db.get_value(payment_entry.payment_document,
payment_entry.payment_entry, paid_amount_field)
elif payment_entry.payment_document == "Journal Entry": elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit") return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit")

View File

@ -239,8 +239,6 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
jv.cancel() jv.cancel()
frappe.delete_doc('Journal Entry', jv.name)
frappe.delete_doc('Cost Center', cost_center)
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project": if budget_against_field == "Project":

View File

@ -150,7 +150,7 @@ def validate_accounts(file_name):
accounts_dict = {} accounts_dict = {}
for account in accounts: for account in accounts:
accounts_dict.setdefault(account["account_name"], account) accounts_dict.setdefault(account["account_name"], account)
if account["parent_account"] and accounts_dict[account["parent_account"]]: if account["parent_account"] and accounts_dict.get(account["parent_account"]):
accounts_dict[account["parent_account"]]["is_group"] = 1 accounts_dict[account["parent_account"]]["is_group"] = 1
message = validate_root(accounts_dict) message = validate_root(accounts_dict)

View File

@ -1,457 +1,171 @@
{ {
"allow_copy": 1, "allow_copy": 1,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:cost_center_name", "autoname": "field:cost_center_name",
"beta": 0,
"creation": "2013-01-23 19:57:17", "creation": "2013-01-23 19:57:17",
"custom": 0,
"description": "Track separate Income and Expense for product verticals or divisions.", "description": "Track separate Income and Expense for product verticals or divisions.",
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 0, "engine": "InnoDB",
"field_order": [
"sb0",
"cost_center_name",
"cost_center_number",
"parent_cost_center",
"company",
"cb0",
"is_group",
"enabled",
"lft",
"rgt",
"old_parent"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb0", "fieldname": "sb0",
"fieldtype": "Section Break", "fieldtype": "Section Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cost_center_name", "fieldname": "cost_center_name",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Cost Center Name", "label": "Cost Center Name",
"length": 0,
"no_copy": 1, "no_copy": 1,
"oldfieldname": "cost_center_name", "oldfieldname": "cost_center_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "reqd": 1
"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,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cost_center_number", "fieldname": "cost_center_number",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Cost Center Number", "label": "Cost Center Number",
"length": 0, "read_only": 1
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "parent_cost_center", "fieldname": "parent_cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Parent Cost Center", "label": "Parent Cost Center",
"length": 0,
"no_copy": 0,
"oldfieldname": "parent_cost_center", "oldfieldname": "parent_cost_center",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Cost Center", "options": "Cost Center",
"permlevel": 0, "reqd": 1
"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,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Company", "label": "Company",
"length": 0,
"no_copy": 0,
"oldfieldname": "company_name", "oldfieldname": "company_name",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Company", "options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"report_hide": 0, "reqd": 1
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb0", "fieldname": "cb0",
"fieldtype": "Column Break", "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,
"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,
"width": "50%" "width": "50%"
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0", "default": "0",
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Is Group"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Group",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lft", "fieldname": "lft",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "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": "lft", "label": "lft",
"length": 0,
"no_copy": 1, "no_copy": 1,
"oldfieldname": "lft", "oldfieldname": "lft",
"oldfieldtype": "Int", "oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 1, "report_hide": 1,
"reqd": 0, "search_index": 1
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "rgt", "fieldname": "rgt",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "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": "rgt", "label": "rgt",
"length": 0,
"no_copy": 1, "no_copy": 1,
"oldfieldname": "rgt", "oldfieldname": "rgt",
"oldfieldtype": "Int", "oldfieldtype": "Int",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 1, "report_hide": 1,
"reqd": 0, "search_index": 1
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "old_parent", "fieldname": "old_parent",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "old_parent", "label": "old_parent",
"length": 0,
"no_copy": 1, "no_copy": 1,
"oldfieldname": "old_parent", "oldfieldname": "old_parent",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Cost Center", "options": "Cost Center",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "report_hide": 1
"read_only": 0, },
"remember_last_selected_value": 0, {
"report_hide": 1, "default": "0",
"reqd": 0, "fieldname": "enabled",
"search_index": 0, "fieldtype": "Check",
"set_only_once": 0, "label": "Enabled"
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"image_view": 0, "modified": "2019-08-22 15:05:05.559862",
"in_create": 0, "modified_by": "sammish.thundiyil@gmail.com",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-04-26 15:26:25.325778",
"modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 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,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Auditor", "role": "Auditor"
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 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,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1, "read": 1,
"report": 0, "role": "Sales User"
"role": "Sales User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1, "read": 1,
"report": 0, "role": "Purchase User"
"role": "Purchase User",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "parent_cost_center, is_group", "search_fields": "parent_cost_center, is_group",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_order": "ASC", "sort_field": "modified",
"track_changes": 0, "sort_order": "ASC"
"track_seen": 0
} }

View File

@ -8,7 +8,8 @@
"customer", "customer",
"column_break_3", "column_break_3",
"posting_date", "posting_date",
"outstanding_amount" "outstanding_amount",
"debit_to"
], ],
"fields": [ "fields": [
{ {
@ -48,10 +49,18 @@
{ {
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fetch_from": "sales_invoice.debit_to",
"fieldname": "debit_to",
"fieldtype": "Link",
"label": "Debit to",
"options": "Account",
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-05-30 19:27:29.436153", "modified": "2019-08-07 15:13:55.808349",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Discounted Invoice", "name": "Discounted Invoice",

View File

@ -13,41 +13,57 @@ frappe.ui.form.on('Invoice Discounting', {
}; };
}); });
frm.events.filter_accounts("bank_account", frm, {"account_type": "Bank"});
frm.events.filter_accounts("bank_charges_account", frm, {"root_type": "Expense"}); frm.events.filter_accounts("bank_account", frm, [["account_type", "=", "Bank"]]);
frm.events.filter_accounts("short_term_loan", frm, {"root_type": "Liability"}); frm.events.filter_accounts("bank_charges_account", frm, [["root_type", "=", "Expense"]]);
frm.events.filter_accounts("accounts_receivable_credit", frm, {"account_type": "Receivable"}); frm.events.filter_accounts("short_term_loan", frm, [["root_type", "=", "Liability"]]);
frm.events.filter_accounts("accounts_receivable_discounted", frm, {"account_type": "Receivable"}); frm.events.filter_accounts("accounts_receivable_discounted", frm, [["account_type", "=", "Receivable"]]);
frm.events.filter_accounts("accounts_receivable_unpaid", frm, {"account_type": "Receivable"}); frm.events.filter_accounts("accounts_receivable_credit", frm, [["account_type", "=", "Receivable"]]);
frm.events.filter_accounts("accounts_receivable_unpaid", frm, [["account_type", "=", "Receivable"]]);
}, },
filter_accounts: (fieldname, frm, addl_filters) => { filter_accounts: (fieldname, frm, addl_filters) => {
let filters = { let filters = [
"company": frm.doc.company, ["company", "=", frm.doc.company],
"is_group": 0 ["is_group", "=", 0]
}; ];
if(addl_filters) Object.assign(filters, addl_filters); if(addl_filters){
filters = $.merge(filters , addl_filters);
}
frm.set_query(fieldname, () => { return { "filters": filters }; }); frm.set_query(fieldname, () => { return { "filters": filters }; });
}, },
refresh_filters: (frm) =>{
let invoice_accounts = Object.keys(frm.doc.invoices).map(function(key) {
return frm.doc.invoices[key].debit_to;
});
let filters = [
["account_type", "=", "Receivable"],
["name", "not in", invoice_accounts]
];
frm.events.filter_accounts("accounts_receivable_credit", frm, filters);
frm.events.filter_accounts("accounts_receivable_discounted", frm, filters);
frm.events.filter_accounts("accounts_receivable_unpaid", frm, filters);
},
refresh: (frm) => { refresh: (frm) => {
frm.events.show_general_ledger(frm); frm.events.show_general_ledger(frm);
if(frm.doc.docstatus === 0) { if (frm.doc.docstatus === 0) {
frm.add_custom_button(__('Get Invoices'), function() { frm.add_custom_button(__('Get Invoices'), function() {
frm.events.get_invoices(frm); frm.events.get_invoices(frm);
}); });
} }
if(frm.doc.docstatus === 1 && frm.doc.status !== "Settled") { if (frm.doc.docstatus === 1 && frm.doc.status !== "Settled") {
if(frm.doc.status == "Sanctioned") { if (frm.doc.status == "Sanctioned") {
frm.add_custom_button(__('Disburse Loan'), function() { frm.add_custom_button(__('Disburse Loan'), function() {
frm.events.create_disbursement_entry(frm); frm.events.create_disbursement_entry(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
} }
if(frm.doc.status == "Disbursed") { if (frm.doc.status == "Disbursed") {
frm.add_custom_button(__('Close Loan'), function() { frm.add_custom_button(__('Close Loan'), function() {
frm.events.close_loan(frm); frm.events.close_loan(frm);
}).addClass("btn-primary"); }).addClass("btn-primary");
@ -64,7 +80,7 @@ frappe.ui.form.on('Invoice Discounting', {
}, },
set_end_date: (frm) => { set_end_date: (frm) => {
if(frm.doc.loan_start_date && frm.doc.loan_period) { if (frm.doc.loan_start_date && frm.doc.loan_period) {
let end_date = frappe.datetime.add_days(frm.doc.loan_start_date, frm.doc.loan_period); let end_date = frappe.datetime.add_days(frm.doc.loan_start_date, frm.doc.loan_period);
frm.set_value("loan_end_date", end_date); frm.set_value("loan_end_date", end_date);
} }
@ -132,6 +148,7 @@ frappe.ui.form.on('Invoice Discounting', {
frm.doc.invoices = frm.doc.invoices.filter(row => row.sales_invoice); frm.doc.invoices = frm.doc.invoices.filter(row => row.sales_invoice);
let row = frm.add_child("invoices"); let row = frm.add_child("invoices");
$.extend(row, v); $.extend(row, v);
frm.events.refresh_filters(frm);
}); });
refresh_field("invoices"); refresh_field("invoices");
} }
@ -190,8 +207,10 @@ frappe.ui.form.on('Invoice Discounting', {
frappe.ui.form.on('Discounted Invoice', { frappe.ui.form.on('Discounted Invoice', {
sales_invoice: (frm) => { sales_invoice: (frm) => {
frm.events.calculate_total_amount(frm); frm.events.calculate_total_amount(frm);
frm.events.refresh_filters(frm);
}, },
invoices_remove: (frm) => { invoices_remove: (frm) => {
frm.events.calculate_total_amount(frm); frm.events.calculate_total_amount(frm);
frm.events.refresh_filters(frm);
} }
}); });

View File

@ -12,6 +12,7 @@ from erpnext.accounts.general_ledger import make_gl_entries
class InvoiceDiscounting(AccountsController): class InvoiceDiscounting(AccountsController):
def validate(self): def validate(self):
self.validate_mandatory() self.validate_mandatory()
self.validate_invoices()
self.calculate_total_amount() self.calculate_total_amount()
self.set_status() self.set_status()
self.set_end_date() self.set_end_date()
@ -24,6 +25,15 @@ class InvoiceDiscounting(AccountsController):
if self.docstatus == 1 and not (self.loan_start_date and self.loan_period): if self.docstatus == 1 and not (self.loan_start_date and self.loan_period):
frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting")) frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting"))
def validate_invoices(self):
discounted_invoices = [record.sales_invoice for record in
frappe.get_all("Discounted Invoice",fields = ["sales_invoice"], filters= {"docstatus":1})]
for record in self.invoices:
if record.sales_invoice in discounted_invoices:
frappe.throw("Row({0}): {1} is already discounted in {2}"
.format(record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent)))
def calculate_total_amount(self): def calculate_total_amount(self):
self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices]) self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices])
@ -212,7 +222,8 @@ def get_invoices(filters):
name as sales_invoice, name as sales_invoice,
customer, customer,
posting_date, posting_date,
outstanding_amount outstanding_amount,
debit_to
from `tabSales Invoice` si from `tabSales Invoice` si
where where
docstatus = 1 docstatus = 1

View File

@ -49,7 +49,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete # cancel and delete
for d in [si_redeem, si_original]: for d in [si_redeem, si_original]:
d.cancel() d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_earned_multiple_tier(self): def test_loyalty_points_earned_multiple_tier(self):
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty") frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty")
@ -91,7 +90,6 @@ class TestLoyaltyProgram(unittest.TestCase):
# cancel and delete # cancel and delete
for d in [si_redeem, si_original]: for d in [si_redeem, si_original]:
d.cancel() d.cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_cancel_sales_invoice(self): def test_cancel_sales_invoice(self):
''' cancelling the sales invoice should cancel the earned points''' ''' cancelling the sales invoice should cancel the earned points'''
@ -143,7 +141,6 @@ class TestLoyaltyProgram(unittest.TestCase):
d.cancel() d.cancel()
except frappe.TimestampMismatchError: except frappe.TimestampMismatchError:
frappe.get_doc('Sales Invoice', d.name).cancel() frappe.get_doc('Sales Invoice', d.name).cancel()
frappe.delete_doc('Sales Invoice', d.name)
def test_loyalty_points_for_dashboard(self): def test_loyalty_points_for_dashboard(self):
doc = frappe.get_doc('Customer', 'Test Loyalty Customer') doc = frappe.get_doc('Customer', 'Test Loyalty Customer')

View File

@ -1,171 +1,74 @@
{ {
"allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:mode_of_payment", "autoname": "field:mode_of_payment",
"beta": 0,
"creation": "2012-12-04 17:49:20", "creation": "2012-12-04 17:49:20",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 0,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"mode_of_payment",
"enabled",
"type",
"accounts"
],
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "mode_of_payment", "fieldname": "mode_of_payment",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "in_list_view": 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": "Mode of Payment", "label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"oldfieldname": "mode_of_payment", "oldfieldname": "mode_of_payment",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0, "unique": 1
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "type", "fieldname": "type",
"fieldtype": "Select", "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": 1, "in_standard_filter": 1,
"label": "Type", "label": "Type",
"length": 0, "options": "Cash\nBank\nGeneral"
"no_copy": 0,
"options": "Cash\nBank\nGeneral",
"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
}, },
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "accounts", "fieldname": "accounts",
"fieldtype": "Table", "fieldtype": "Table",
"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": "Accounts", "label": "Accounts",
"length": 0, "options": "Mode of Payment Account"
"no_copy": 0, },
"options": "Mode of Payment Account", {
"permlevel": 0, "default": "1",
"precision": "", "fieldname": "enabled",
"print_hide": 0, "fieldtype": "Check",
"print_hide_if_no_value": 0, "label": "Enabled"
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-credit-card", "icon": "fa fa-credit-card",
"idx": 1, "idx": 1,
"image_view": 0, "modified": "2019-08-14 14:58:42.079115",
"in_create": 0, "modified_by": "sammish.thundiyil@gmail.com",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-02-17 16:31:34.207683",
"modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Mode of Payment", "name": "Mode of Payment",
"owner": "harshada@webnotestech.com", "owner": "harshada@webnotestech.com",
"permissions": [ "permissions": [
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 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,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User", "role": "Accounts User"
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_order": "ASC", "sort_field": "modified",
"track_changes": 0, "sort_order": "ASC"
"track_seen": 0
} }

View File

@ -720,7 +720,7 @@ frappe.ui.form.on('Payment Entry', {
$.each(frm.doc.references || [], function(i, row) { $.each(frm.doc.references || [], function(i, row) {
row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
if(frappe.flags.allocate_payment_amount){ if(frappe.flags.allocate_payment_amount != 0){
if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
if(row.outstanding_amount >= allocated_positive_outstanding) { if(row.outstanding_amount >= allocated_positive_outstanding) {
row.allocated_amount = allocated_positive_outstanding; row.allocated_amount = allocated_positive_outstanding;

View File

@ -126,7 +126,7 @@ class PaymentEntry(AccountsController):
if not self.party: if not self.party:
frappe.throw(_("Party is mandatory")) frappe.throw(_("Party is mandatory"))
_party_name = "title" if self.party_type == "Student" else self.party_type.lower() + "_name" _party_name = "title" if self.party_type in ("Student", "Shareholder") else self.party_type.lower() + "_name"
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name) self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
if self.party: if self.party:
@ -624,8 +624,8 @@ def get_outstanding_reference_documents(args):
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
if not data: if not data:
frappe.msgprint(_("No outstanding invoices found for the {0} <b>{1}</b> which qualify the filters you have specified.") frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
.format(args.get("party_type").lower(), args.get("party"))) .format(args.get("party_type").lower(), frappe.bold(args.get("party"))))
return data return data
@ -683,8 +683,8 @@ def get_orders_to_be_billed(posting_date, party_type, party,
order_list = [] order_list = []
for d in orders: for d in orders:
if not (d.outstanding_amount >= filters.get("outstanding_amt_greater_than") if not (flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
and d.outstanding_amount <= filters.get("outstanding_amt_less_than")): and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))):
continue continue
d["voucher_type"] = voucher_type d["voucher_type"] = voucher_type
@ -947,10 +947,15 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
paid_amount = abs(outstanding_amount) paid_amount = abs(outstanding_amount)
if bank_amount: if bank_amount:
received_amount = bank_amount received_amount = bank_amount
else:
received_amount = paid_amount * doc.conversion_rate
else: else:
received_amount = abs(outstanding_amount) received_amount = abs(outstanding_amount)
if bank_amount: if bank_amount:
paid_amount = bank_amount paid_amount = bank_amount
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.conversion_rate
pe = frappe.new_doc("Payment Entry") pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type pe.payment_type = payment_type

View File

@ -66,6 +66,7 @@ frappe.ui.form.on('Payment Order', {
get_query_filters: { get_query_filters: {
bank: frm.doc.bank, bank: frm.doc.bank,
docstatus: 1, docstatus: 1,
payment_type: ("!=", "Receive"),
bank_account: frm.doc.company_bank_account, bank_account: frm.doc.company_bank_account,
paid_from: frm.doc.account, paid_from: frm.doc.account,
payment_order_status: ["=", "Initiated"], payment_order_status: ["=", "Initiated"],

View File

@ -1,29 +0,0 @@
frappe.ui.form.on('Payment Order', {
refresh: function(frm) {
if (frm.doc.docstatus==1 && frm.doc.payment_order_type==='Payment Entry') {
frm.add_custom_button(__('Generate Text File'), function() {
frm.trigger("generate_text_and_download_file");
});
}
},
generate_text_and_download_file: (frm) => {
return frappe.call({
method: "erpnext.regional.india.bank_remittance.generate_report",
args: {
name: frm.doc.name
},
freeze: true,
callback: function(r) {
{
frm.reload_doc();
const a = document.createElement('a');
let file_obj = r.message;
a.href = file_obj.file_url;
a.target = '_blank';
a.download = file_obj.file_name;
a.click();
}
}
});
}
});

View File

@ -93,7 +93,7 @@ class PaymentReconciliation(Document):
and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s
GROUP BY `tabSales Invoice`.name GROUP BY `tab{doc}`.name
Having Having
amount > 0 amount > 0
""".format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), { """.format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), {
@ -257,11 +257,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
voucher_type = ('Credit Note' voucher_type = ('Credit Note'
if d.voucher_type == 'Sales Invoice' else 'Debit Note') if d.voucher_type == 'Sales Invoice' else 'Debit Note')
dr_or_cr = ('credit_in_account_currency'
if d.reference_type == 'Sales Invoice' else 'debit_in_account_currency')
reconcile_dr_or_cr = ('debit_in_account_currency' reconcile_dr_or_cr = ('debit_in_account_currency'
if dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
jv = frappe.get_doc({ jv = frappe.get_doc({
"doctype": "Journal Entry", "doctype": "Journal Entry",
@ -272,8 +269,7 @@ def reconcile_dr_cr_note(dr_cr_notes):
'account': d.account, 'account': d.account,
'party': d.party, 'party': d.party,
'party_type': d.party_type, 'party_type': d.party_type,
reconcile_dr_or_cr: (abs(d.allocated_amount) d.dr_or_cr: abs(d.allocated_amount),
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
'reference_type': d.against_voucher_type, 'reference_type': d.against_voucher_type,
'reference_name': d.against_voucher 'reference_name': d.against_voucher
}, },
@ -281,7 +277,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
'account': d.account, 'account': d.account,
'party': d.party, 'party': d.party,
'party_type': d.party_type, 'party_type': d.party_type,
dr_or_cr: abs(d.allocated_amount), reconcile_dr_or_cr: (abs(d.allocated_amount)
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
'reference_type': d.voucher_type, 'reference_type': d.voucher_type,
'reference_name': d.voucher_no 'reference_name': d.voucher_no
} }

View File

@ -1,7 +1,6 @@
cur_frm.add_fetch("payment_gateway", "payment_account", "payment_account") cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account")
cur_frm.add_fetch("payment_gateway", "payment_gateway", "payment_gateway") cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway")
cur_frm.add_fetch("payment_gateway", "message", "message") cur_frm.add_fetch("payment_gateway_account", "message", "message")
cur_frm.add_fetch("payment_gateway", "payment_url_message", "payment_url_message")
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) {

View File

@ -20,7 +20,7 @@ class PaymentRequest(Document):
if self.get("__islocal"): if self.get("__islocal"):
self.status = 'Draft' self.status = 'Draft'
self.validate_reference_document() self.validate_reference_document()
self.validate_payment_request() self.validate_payment_request_amount()
self.validate_currency() self.validate_currency()
self.validate_subscription_details() self.validate_subscription_details()
@ -28,10 +28,19 @@ class PaymentRequest(Document):
if not self.reference_doctype or not self.reference_name: if not self.reference_doctype or not self.reference_name:
frappe.throw(_("To create a Payment Request reference document is required")) frappe.throw(_("To create a Payment Request reference document is required"))
def validate_payment_request(self): def validate_payment_request_amount(self):
if frappe.db.get_value("Payment Request", {"reference_name": self.reference_name, existing_payment_request_amount = \
"name": ("!=", self.name), "status": ("not in", ["Initiated", "Paid"]), "docstatus": 1}, "name"): get_existing_payment_request_amount(self.reference_doctype, self.reference_name)
frappe.throw(_("Payment Request already exists {0}".format(self.reference_name)))
if existing_payment_request_amount:
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if (hasattr(ref_doc, "order_type") \
and getattr(ref_doc, "order_type") != "Shopping Cart"):
ref_amount = get_amount(ref_doc)
if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount"
.format(self.reference_doctype)))
def validate_currency(self): def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
@ -271,7 +280,7 @@ def make_payment_request(**args):
args = frappe._dict(args) args = frappe._dict(args)
ref_doc = frappe.get_doc(args.dt, args.dn) ref_doc = frappe.get_doc(args.dt, args.dn)
grand_total = get_amount(ref_doc, args.dt) grand_total = get_amount(ref_doc)
if args.loyalty_points and args.dt == "Sales Order": if args.loyalty_points and args.dt == "Sales Order":
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points)) loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
@ -281,17 +290,25 @@ def make_payment_request(**args):
gateway_account = get_gateway_details(args) or frappe._dict() gateway_account = get_gateway_details(args) or frappe._dict()
existing_payment_request = frappe.db.get_value("Payment Request",
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ["!=", 2]})
bank_account = (get_party_bank_account(args.get('party_type'), args.get('party')) bank_account = (get_party_bank_account(args.get('party_type'), args.get('party'))
if args.get('party_type') else '') if args.get('party_type') else '')
existing_payment_request = None
if args.order_type == "Shopping Cart":
existing_payment_request = frappe.db.get_value("Payment Request",
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)})
if existing_payment_request: if existing_payment_request:
frappe.db.set_value("Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False) frappe.db.set_value("Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False)
pr = frappe.get_doc("Payment Request", existing_payment_request) pr = frappe.get_doc("Payment Request", existing_payment_request)
else: else:
if args.order_type != "Shopping Cart":
existing_payment_request_amount = \
get_existing_payment_request_amount(args.dt, args.dn)
if existing_payment_request_amount:
grand_total -= existing_payment_request_amount
pr = frappe.new_doc("Payment Request") pr = frappe.new_doc("Payment Request")
pr.update({ pr.update({
"payment_gateway_account": gateway_account.get("name"), "payment_gateway_account": gateway_account.get("name"),
@ -327,8 +344,9 @@ def make_payment_request(**args):
return pr.as_dict() return pr.as_dict()
def get_amount(ref_doc, dt): def get_amount(ref_doc):
"""get amount based on doctype""" """get amount based on doctype"""
dt = ref_doc.doctype
if dt in ["Sales Order", "Purchase Order"]: if dt in ["Sales Order", "Purchase Order"]:
grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid) grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid)
@ -347,6 +365,17 @@ def get_amount(ref_doc, dt):
else: else:
frappe.throw(_("Payment Entry is already created")) frappe.throw(_("Payment Entry is already created"))
def get_existing_payment_request_amount(ref_dt, ref_dn):
existing_payment_request_amount = frappe.db.sql("""
select sum(grand_total)
from `tabPayment Request`
where
reference_doctype = %s
and reference_name = %s
and docstatus = 1
""", (ref_dt, ref_dn))
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
def get_gateway_details(args): def get_gateway_details(args):
"""return gateway and payment account of default payment gateway""" """return gateway and payment account of default payment gateway"""
if args.get("payment_gateway"): if args.get("payment_gateway"):

View File

@ -100,3 +100,23 @@ class TestPaymentRequest(unittest.TestCase):
self.assertEqual(expected_gle[gle.account][1], gle.debit) self.assertEqual(expected_gle[gle.account][1], gle.debit)
self.assertEqual(expected_gle[gle.account][2], gle.credit) self.assertEqual(expected_gle[gle.account][2], gle.credit)
self.assertEqual(expected_gle[gle.account][3], gle.against_voucher) self.assertEqual(expected_gle[gle.account][3], gle.against_voucher)
def test_multiple_payment_entries_against_sales_order(self):
# Make Sales Order, grand_total = 1000
so = make_sales_order()
# Payment Request amount = 200
pr1 = make_payment_request(dt="Sales Order", dn=so.name,
recipient_id="nabin@erpnext.com", return_doc=1)
pr1.grand_total = 200
pr1.submit()
# Make a 2nd Payment Request
pr2 = make_payment_request(dt="Sales Order", dn=so.name,
recipient_id="nabin@erpnext.com", return_doc=1)
self.assertEqual(pr2.grand_total, 800)
# Try to make Payment Request more than SO amount, should give validation
pr2.grand_total = 900
self.assertRaises(frappe.ValidationError, pr2.save)

View File

@ -227,8 +227,8 @@ def get_contacts(customers):
customers = [frappe._dict({'name': customers})] customers = [frappe._dict({'name': customers})]
for data in customers: for data in customers:
contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` contact = frappe.db.sql(""" select email_id, phone from `tabContact`
where is_primary_contact =1 and name in where is_primary_contact=1 and name in
(select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s
and parenttype = 'Contact')""", data.name, as_dict=1) and parenttype = 'Contact')""", data.name, as_dict=1)
if contact: if contact:
@ -307,7 +307,7 @@ def get_item_tax_data():
# example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}}
itemwise_tax = {} itemwise_tax = {}
taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1) taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1)
for tax in taxes: for tax in taxes:
if tax.parent not in itemwise_tax: if tax.parent not in itemwise_tax:
@ -432,7 +432,6 @@ def get_customer_id(doc, customer=None):
return cust_id return cust_id
def make_customer_and_address(customers): def make_customer_and_address(customers):
customers_list = [] customers_list = []
for customer, data in iteritems(customers): for customer, data in iteritems(customers):
@ -449,7 +448,6 @@ def make_customer_and_address(customers):
frappe.db.commit() frappe.db.commit()
return customers_list return customers_list
def add_customer(data): def add_customer(data):
customer = data.get('full_name') or data.get('customer') customer = data.get('full_name') or data.get('customer')
if frappe.db.exists("Customer", customer.strip()): if frappe.db.exists("Customer", customer.strip()):
@ -466,21 +464,18 @@ def add_customer(data):
frappe.db.commit() frappe.db.commit()
return customer_doc.name return customer_doc.name
def get_territory(data): def get_territory(data):
if data.get('territory'): if data.get('territory'):
return data.get('territory') return data.get('territory')
return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories') return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories')
def get_customer_group(data): def get_customer_group(data):
if data.get('customer_group'): if data.get('customer_group'):
return data.get('customer_group') return data.get('customer_group')
return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name') return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name')
def make_contact(args, customer): def make_contact(args, customer):
if args.get('email_id') or args.get('phone'): if args.get('email_id') or args.get('phone'):
name = frappe.db.get_value('Dynamic Link', name = frappe.db.get_value('Dynamic Link',
@ -506,7 +501,6 @@ def make_contact(args, customer):
doc.flags.ignore_mandatory = True doc.flags.ignore_mandatory = True
doc.save(ignore_permissions=True) doc.save(ignore_permissions=True)
def make_address(args, customer): def make_address(args, customer):
if not args.get('address_line1'): if not args.get('address_line1'):
return return
@ -521,7 +515,10 @@ def make_address(args, customer):
address = frappe.get_doc('Address', name) address = frappe.get_doc('Address', name)
else: else:
address = frappe.new_doc('Address') address = frappe.new_doc('Address')
address.country = frappe.get_cached_value('Company', args.get('company'), 'country') if args.get('company'):
address.country = frappe.get_cached_value('Company',
args.get('company'), 'country')
address.append('links', { address.append('links', {
'link_doctype': 'Customer', 'link_doctype': 'Customer',
'link_name': customer 'link_name': customer
@ -533,7 +530,6 @@ def make_address(args, customer):
address.flags.ignore_mandatory = True address.flags.ignore_mandatory = True
address.save(ignore_permissions=True) address.save(ignore_permissions=True)
def make_email_queue(email_queue): def make_email_queue(email_queue):
name_list = [] name_list = []
for key, data in iteritems(email_queue): for key, data in iteritems(email_queue):
@ -550,7 +546,6 @@ def make_email_queue(email_queue):
return name_list return name_list
def validate_item(doc): def validate_item(doc):
for item in doc.get('items'): for item in doc.get('items'):
if not frappe.db.exists('Item', item.get('item_code')): if not frappe.db.exists('Item', item.get('item_code')):
@ -569,7 +564,6 @@ def validate_item(doc):
item_doc.save(ignore_permissions=True) item_doc.save(ignore_permissions=True)
frappe.db.commit() frappe.db.commit()
def submit_invoice(si_doc, name, doc, name_list): def submit_invoice(si_doc, name, doc, name_list):
try: try:
si_doc.insert() si_doc.insert()
@ -585,7 +579,6 @@ def submit_invoice(si_doc, name, doc, name_list):
return name_list return name_list
def save_invoice(doc, name, name_list): def save_invoice(doc, name, name_list):
try: try:
if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}):

View File

@ -158,7 +158,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format; cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.pos_print_format; cur_frm.meta.default_print_format = cur_frm.pos_print_format;
} }
} else if(cur_frm.doc.is_return) { } else if(cur_frm.doc.is_return && !cur_frm.meta.default_print_format) {
if(cur_frm.return_print_format) { if(cur_frm.return_print_format) {
cur_frm.meta._default_print_format = cur_frm.meta.default_print_format; cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
cur_frm.meta.default_print_format = cur_frm.return_print_format; cur_frm.meta.default_print_format = cur_frm.return_print_format;

View File

@ -78,6 +78,7 @@ class SalesInvoice(SellingController):
self.so_dn_required() self.so_dn_required()
self.validate_proj_cust() self.validate_proj_cust()
self.validate_pos_return()
self.validate_with_previous_doc() self.validate_with_previous_doc()
self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("uom", "qty")
@ -199,6 +200,16 @@ class SalesInvoice(SellingController):
if "Healthcare" in active_domains: if "Healthcare" in active_domains:
manage_invoice_submit_cancel(self, "on_submit") manage_invoice_submit_cancel(self, "on_submit")
def validate_pos_return(self):
if self.is_pos and self.is_return:
total_amount_in_payments = 0
for payment in self.payments:
total_amount_in_payments += payment.amount
invoice_total = self.rounded_total or self.grand_total
if total_amount_in_payments < invoice_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
def validate_pos_paid_amount(self): def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos: if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice.")) frappe.throw(_("At least one mode of payment is required for POS invoice."))
@ -293,8 +304,10 @@ class SalesInvoice(SellingController):
from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.selling.doctype.customer.customer import check_credit_limit
validate_against_credit_limit = False validate_against_credit_limit = False
bypass_credit_limit_check_at_sales_order = cint(frappe.get_cached_value("Customer", self.customer, bypass_credit_limit_check_at_sales_order = frappe.db.get_value("Customer Credit Limit",
"bypass_credit_limit_check_at_sales_order")) filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company},
fieldname=["bypass_credit_limit_check"])
if bypass_credit_limit_check_at_sales_order: if bypass_credit_limit_check_at_sales_order:
validate_against_credit_limit = True validate_against_credit_limit = True

View File

@ -818,7 +818,6 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_gl_entries[i][2], gle.credit) self.assertEqual(expected_gl_entries[i][2], gle.credit)
si.cancel() si.cancel()
frappe.delete_doc('Sales Invoice', si.name)
gle = frappe.db.sql("""select * from `tabGL Entry` gle = frappe.db.sql("""select * from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) where voucher_type='Sales Invoice' and voucher_no=%s""", si.name)

View File

@ -86,17 +86,23 @@ class ShareTransfer(Document):
frappe.throw(_('The field From Shareholder cannot be blank')) frappe.throw(_('The field From Shareholder cannot be blank'))
if self.from_folio_no is None or self.from_folio_no is '': if self.from_folio_no is None or self.from_folio_no is '':
self.to_folio_no = self.autoname_folio(self.to_shareholder) self.to_folio_no = self.autoname_folio(self.to_shareholder)
if self.asset_account is None:
frappe.throw(_('The field Asset Account cannot be blank'))
elif (self.transfer_type == 'Issue'): elif (self.transfer_type == 'Issue'):
self.from_shareholder = '' self.from_shareholder = ''
if self.to_shareholder is None or self.to_shareholder == '': if self.to_shareholder is None or self.to_shareholder == '':
frappe.throw(_('The field To Shareholder cannot be blank')) frappe.throw(_('The field To Shareholder cannot be blank'))
if self.to_folio_no is None or self.to_folio_no is '': if self.to_folio_no is None or self.to_folio_no is '':
self.to_folio_no = self.autoname_folio(self.to_shareholder) self.to_folio_no = self.autoname_folio(self.to_shareholder)
if self.asset_account is None:
frappe.throw(_('The field Asset Account cannot be blank'))
else: else:
if self.from_shareholder is None or self.to_shareholder is None: if self.from_shareholder is None or self.to_shareholder is None:
frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank')) frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank'))
if self.to_folio_no is None or self.to_folio_no is '': if self.to_folio_no is None or self.to_folio_no is '':
self.to_folio_no = self.autoname_folio(self.to_shareholder) self.to_folio_no = self.autoname_folio(self.to_shareholder)
if self.equity_or_liability_account is None:
frappe.throw(_('The field Equity/Liability Account cannot be blank'))
if self.from_shareholder == self.to_shareholder: if self.from_shareholder == self.to_shareholder:
frappe.throw(_('The seller and the buyer cannot be the same')) frappe.throw(_('The seller and the buyer cannot be the same'))
if self.no_of_shares != self.to_no - self.from_no + 1: if self.no_of_shares != self.to_no - self.from_no + 1:

View File

@ -24,7 +24,9 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 500, "to_no" : 500,
"no_of_shares" : 500, "no_of_shares" : 500,
"rate" : 10, "rate" : 10,
"company" : "_Test Company" "company" : "_Test Company",
"asset_account" : "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
}, },
{ {
"doctype" : "Share Transfer", "doctype" : "Share Transfer",
@ -37,7 +39,8 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 200, "to_no" : 200,
"no_of_shares" : 100, "no_of_shares" : 100,
"rate" : 15, "rate" : 15,
"company" : "_Test Company" "company" : "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
}, },
{ {
"doctype" : "Share Transfer", "doctype" : "Share Transfer",
@ -50,7 +53,8 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 500, "to_no" : 500,
"no_of_shares" : 300, "no_of_shares" : 300,
"rate" : 20, "rate" : 20,
"company" : "_Test Company" "company" : "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
}, },
{ {
"doctype" : "Share Transfer", "doctype" : "Share Transfer",
@ -63,7 +67,8 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 400, "to_no" : 400,
"no_of_shares" : 200, "no_of_shares" : 200,
"rate" : 15, "rate" : 15,
"company" : "_Test Company" "company" : "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
}, },
{ {
"doctype" : "Share Transfer", "doctype" : "Share Transfer",
@ -75,7 +80,9 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 500, "to_no" : 500,
"no_of_shares" : 100, "no_of_shares" : 100,
"rate" : 25, "rate" : 25,
"company" : "_Test Company" "company" : "_Test Company",
"asset_account" : "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
} }
] ]
for d in share_transfers: for d in share_transfers:
@ -94,7 +101,8 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 100, "to_no" : 100,
"no_of_shares" : 100, "no_of_shares" : 100,
"rate" : 15, "rate" : 15,
"company" : "_Test Company" "company" : "_Test Company",
"equity_or_liability_account": "Creditors - _TC"
}) })
self.assertRaises(ShareDontExists, doc.insert) self.assertRaises(ShareDontExists, doc.insert)
@ -108,6 +116,8 @@ class TestShareTransfer(unittest.TestCase):
"to_no" : 200, "to_no" : 200,
"no_of_shares" : 200, "no_of_shares" : 200,
"rate" : 15, "rate" : 15,
"company" : "_Test Company" "company" : "_Test Company",
"asset_account" : "Cash - _TC",
"equity_or_liability_account": "Creditors - _TC"
}) })
self.assertRaises(ShareDontExists, doc.insert) self.assertRaises(ShareDontExists, doc.insert)

View File

@ -48,7 +48,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
#delete invoices to avoid clashing #delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds(self): def test_single_threshold_tds(self):
invoices = [] invoices = []
@ -83,7 +82,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def test_single_threshold_tds_with_previous_vouchers(self): def test_single_threshold_tds_with_previous_vouchers(self):
invoices = [] invoices = []
@ -102,7 +100,6 @@ class TestTaxWithholdingCategory(unittest.TestCase):
# delete invoices to avoid clashing # delete invoices to avoid clashing
for d in invoices: for d in invoices:
d.cancel() d.cancel()
frappe.delete_doc("Purchase Invoice", d.name)
def create_purchase_invoice(**args): def create_purchase_invoice(**args):
# return sales invoice doc object # return sales invoice doc object

View File

@ -124,8 +124,6 @@ def check_matching_amount(bank_account, company, transaction):
'txt': '%%%s%%' % amount 'txt': '%%%s%%' % amount
}, as_dict=True) }, as_dict=True)
frappe.errprint(journal_entries)
if transaction.credit > 0: if transaction.credit > 0:
sales_invoices = frappe.db.sql(""" sales_invoices = frappe.db.sql("""
SELECT SELECT

View File

@ -816,7 +816,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
contact = me.contacts[data.name]; contact = me.contacts[data.name];
if(reg.test(data.name.toLowerCase()) if(reg.test(data.name.toLowerCase())
|| reg.test(data.customer_name.toLowerCase()) || reg.test(data.customer_name.toLowerCase())
|| (contact && reg.test(contact["mobile_no"]))
|| (contact && reg.test(contact["phone"])) || (contact && reg.test(contact["phone"]))
|| (data.customer_group && reg.test(data.customer_group.toLowerCase()))){ || (data.customer_group && reg.test(data.customer_group.toLowerCase()))){
return data; return data;
@ -834,7 +833,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if(contact && !c['phone']) { if(contact && !c['phone']) {
c["phone"] = contact["phone"]; c["phone"] = contact["phone"];
c["email_id"] = contact["email_id"]; c["email_id"] = contact["email_id"];
c["mobile_no"] = contact["mobile_no"];
} }
me.customers_mapper.push({ me.customers_mapper.push({
@ -844,10 +842,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
customer_group: c.customer_group, customer_group: c.customer_group,
territory: c.territory, territory: c.territory,
phone: contact ? contact["phone"] : '', phone: contact ? contact["phone"] : '',
mobile_no: contact ? contact["mobile_no"] : '',
email_id: contact ? contact["email_id"] : '', email_id: contact ? contact["email_id"] : '',
searchtext: ['customer_name', 'customer_group', 'name', 'value', searchtext: ['customer_name', 'customer_group', 'name', 'value',
'label', 'email_id', 'phone', 'mobile_no'] 'label', 'email_id', 'phone']
.map(key => c[key]).join(' ') .map(key => c[key]).join(' ')
.toLowerCase() .toLowerCase()
}); });
@ -1121,7 +1118,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if (key) { if (key) {
return $.grep(this.items_list, function (item) { return $.grep(this.items_list, function (item) {
if (search_status) { if (search_status) {
if (in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) { if (me.batch_no_data[item.item_code] &&
in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) {
search_status = false; search_status = false;
return me.item_batch_no[item.item_code] = me.search_item.$input.val() return me.item_batch_no[item.item_code] = me.search_item.$input.val()
} else if (me.serial_no_data[item.item_code] } else if (me.serial_no_data[item.item_code]
@ -1129,7 +1127,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
search_status = false; search_status = false;
me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]] me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]]
return true return true
} else if (in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) { } else if (me.barcode_data[item.item_code] &&
in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) {
search_status = false; search_status = false;
return true; return true;
} else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) || } else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) ||
@ -1692,20 +1691,13 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
if(this.si_docs) { if(this.si_docs) {
this.si_docs.forEach((row) => { this.si_docs.forEach((row) => {
existing_pos_list.push(Object.keys(row)); existing_pos_list.push(Object.keys(row)[0]);
}); });
} }
if (this.frm.doc.offline_pos_name if (this.frm.doc.offline_pos_name
&& in_list(existing_pos_list, this.frm.doc.offline_pos_name)) { && in_list(existing_pos_list, cstr(this.frm.doc.offline_pos_name))) {
this.update_invoice() this.update_invoice()
//to retrieve and set the default payment
invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc;
invoice_data[this.frm.doc.offline_pos_name].payments[0].amount = this.frm.doc.net_total
invoice_data[this.frm.doc.offline_pos_name].payments[0].base_amount = this.frm.doc.net_total
this.frm.doc.paid_amount = this.frm.doc.net_total
this.frm.doc.outstanding_amount = 0
} else if(!this.frm.doc.offline_pos_name) { } else if(!this.frm.doc.offline_pos_name) {
this.frm.doc.offline_pos_name = frappe.datetime.now_datetime(); this.frm.doc.offline_pos_name = frappe.datetime.now_datetime();
this.frm.doc.posting_date = frappe.datetime.get_today(); this.frm.doc.posting_date = frappe.datetime.get_today();
@ -1762,18 +1754,11 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
this.si_docs = this.get_submitted_invoice() || []; this.si_docs = this.get_submitted_invoice() || [];
this.email_queue_list = this.get_email_queue() || {}; this.email_queue_list = this.get_email_queue() || {};
this.customers_list = this.get_customers_details() || {}; this.customers_list = this.get_customers_details() || {};
if(this.customer_doc) {
this.freeze = this.customer_doc.display
}
freeze_screen = this.freeze_screen || false;
if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) {
this.freeze = true;
if (this.si_docs.length || this.email_queue_list || this.customers_list) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice", method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
freeze: freeze_screen, freeze: true,
args: { args: {
doc_list: me.si_docs, doc_list: me.si_docs,
email_queue_list: me.email_queue_list, email_queue_list: me.email_queue_list,

View File

@ -292,8 +292,11 @@ def validate_party_accounts(doc):
party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True) party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True)
existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company) existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company)
if frappe.db.get_default("Company"):
company_default_currency = frappe.get_cached_value('Company', company_default_currency = frappe.get_cached_value('Company',
frappe.db.get_default("Company"), "default_currency") frappe.db.get_default("Company"), "default_currency")
else:
company_default_currency = frappe.db.get_value('Company', account.company, "default_currency")
if existing_gle_currency and party_account_currency != existing_gle_currency: if existing_gle_currency and party_account_currency != existing_gle_currency:
frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company)) frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
@ -365,7 +368,7 @@ def validate_due_date(posting_date, due_date, party_type, party, company=None, b
.format(formatdate(default_due_date))) .format(formatdate(default_due_date)))
@frappe.whitelist() @frappe.whitelist()
def get_address_tax_category(tax_category, billing_address=None, shipping_address=None): def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None):
addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from") addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from")
if addr_tax_category_from == "Shipping Address": if addr_tax_category_from == "Shipping Address":
if shipping_address: if shipping_address:
@ -469,7 +472,9 @@ def get_timeline_data(doctype, name):
# fetch and append data from Activity Log # fetch and append data from Activity Log
data += frappe.db.sql("""select {fields} data += frappe.db.sql("""select {fields}
from `tabActivity Log` from `tabActivity Log`
where reference_doctype={doctype} and reference_name={name} where (reference_doctype="{doctype}" and reference_name="{name}")
or (timeline_doctype in ("{doctype}") and timeline_name="{name}")
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}")
and status!='Success' and creation > {after} and status!='Success' and creation > {after}
{group_by} order by creation desc {group_by} order by creation desc
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields, """.format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,

View File

@ -10,13 +10,14 @@
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Qty Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ qty_total }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>", "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ company }}<br>\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}<br>\n</p>\n<p>\n\t<b>{{ __(\"Customer\") }}:</b> {{ customer }}<br>\n</p>\n\n<p>\n\t<b>{{ __(\"Date\") }}:</b> {{ dateutil.global_date_format(posting_date) }}<br>\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ __(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ __(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{% for item in items %}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_name }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ format_number(item.qty, null,precision(\"difference\")) }}<br>@ {{ format_currency(item.rate, currency) }}</td>\n\t\t\t<td class=\"text-right\">{{ format_currency(item.amount, currency) }}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</tbody>\n</table>\n\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t{{ row.description }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{% endif %}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t</td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ __(\"Qty Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ qty_total }}\n\t\t\t</td>\n\t\t</tr>\n\t</tbody>\n</table>\n\n\n<hr>\n<p>{{ terms }}</p>\n<p class=\"text-center\">{{ __(\"Thank you, please visit again.\") }}</p>",
"idx": 0, "idx": 0,
"line_breaks": 0, "line_breaks": 0,
"modified": "2018-03-21 09:10:16.693732", "modified": "2019-09-05 17:20:30.726659",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Point of Sale", "name": "Point of Sale",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Js", "print_format_type": "JS",
"raw_printing": 0,
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -1,33 +1,33 @@
<style> <style>
.print-format { .print-format {
padding: 4mm; padding: 4mm;
font-size: 8.0pt !important; font-size: 8.0pt !important;
} }
.print-format td { .print-format td {
vertical-align:middle !important; vertical-align:middle !important;
} }
</style> </style>
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2> <h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
<h4 class="text-center"> <h4 class="text-center">
{% if (filters.customer_name) { %} {% if (filters.customer_name) { %}
{%= filters.customer_name %} {%= filters.customer_name %}
{% } else { %} {% } else { %}
{%= filters.customer || filters.supplier %} {%= filters.customer || filters.supplier %}
{% } %} {% } %}
</h4> </h4>
<h6 class="text-center"> <h6 class="text-center">
{% if (filters.tax_id) { %} {% if (filters.tax_id) { %}
{%= __("Tax Id: ")%} {%= filters.tax_id %} {%= __("Tax Id: ")%} {%= filters.tax_id %}
{% } %} {% } %}
</h6> </h6>
<h5 class="text-center"> <h5 class="text-center">
{%= __(filters.ageing_based_on) %} {%= __(filters.ageing_based_on) %}
{%= __("Until") %} {%= __("Until") %}
{%= frappe.datetime.str_to_user(filters.report_date) %} {%= frappe.datetime.str_to_user(filters.report_date) %}
</h5> </h5>
<div class="clearfix"> <div class="clearfix">
<div class="pull-left"> <div class="pull-left">
{% if(filters.payment_terms) { %} {% if(filters.payment_terms) { %}
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %} <strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
@ -38,20 +38,19 @@
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %} <strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
{% } %} {% } %}
</div> </div>
</div> </div>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop(); {% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label; var range1 = report.columns[11].label;
var range2 = report.columns[12].label; var range2 = report.columns[12].label;
var range3 = report.columns[13].label; var range3 = report.columns[13].label;
var range4 = report.columns[14].label; var range4 = report.columns[14].label;
var range5 = report.columns[15].label; var range5 = report.columns[15].label;
var range6 = report.columns[16].label;
%} %}
{% if(balance_row) { %} {% if(balance_row) { %}
<table class="table table-bordered table-condensed"> <table class="table table-bordered table-condensed">
<caption class="text-right">(Amount in {%= data[0][__("currency")] || "" %})</caption> <caption class="text-right">(Amount in {%= data[0]["currency"] || "" %})</caption>
<colgroup> <colgroup>
<col style="width: 30mm;"> <col style="width: 30mm;">
<col style="width: 18mm;"> <col style="width: 18mm;">
@ -61,7 +60,6 @@
<col style="width: 18mm;"> <col style="width: 18mm;">
<col style="width: 18mm;"> <col style="width: 18mm;">
<col style="width: 18mm;"> <col style="width: 18mm;">
<col style="width: 18mm;">
</colgroup> </colgroup>
<thead> <thead>
@ -72,32 +70,29 @@
<th>{%= __(range3) %}</th> <th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th> <th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th> <th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th> <th>{%= __("Total") %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>{%= __("Total Outstanding") %}</td> <td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row[range1], null, 2) %}</td> <td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row[range2]) %}</td> <td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range3]) %}</td> <td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range4]) %}</td> <td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range5]) %}</td> <td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
<td class="text-right">{%= format_currency(balance_row[range6]) %}</td>
<td class="text-right"> <td class="text-right">
{%= format_currency(flt(balance_row[("outstanding_amount")]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td> </td>
</tr> </tr>
<td>{%= __("PDC/LC") %}</td> <td>{%= __("Future Payments") %}</td>
<td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td></td> <td></td>
<td class="text-right"> <td class="text-right">
{%= format_currency(flt(balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %} {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td> </td>
<tr class="cvs-footer"> <tr class="cvs-footer">
<th class="text-left">{%= __("Cheques Required") %}</th> <th class="text-left">{%= __("Cheques Required") %}</th>
@ -106,43 +101,42 @@
<th></th> <th></th>
<th></th> <th></th>
<th></th> <th></th>
<th></th>
<th class="text-right"> <th class="text-right">
{%= format_currency(flt(balance_row[("outstanding_amount")]-balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %}</th> {%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr> </tr>
</tbody> </tbody>
</table> </table>
{% } %} {% } %}
{% } %} {% } %}
<table class="table table-bordered"> <table class="table table-bordered">
<thead> <thead>
<tr> <tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
<th style="width: 10%">{%= __("Date") %}</th> <th style="width: 10%">{%= __("Date") %}</th>
<th style="width: 4%">{%= __("Age (Days)") %}</th> <th style="width: 4%">{%= __("Age (Days)") %}</th>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<th style="width: 14%">{%= __("Reference") %}</th> <th style="width: 14%">{%= __("Reference") %}</th>
<th style="width: 10%">{%= __("Sales Person") %}</th> <th style="width: 10%">{%= __("Sales Person") %}</th>
{% } else { %} {% } else { %}
<th style="width: 24%">{%= __("Reference") %}</th> <th style="width: 24%">{%= __("Reference") %}</th>
{% } %} {% } %}
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th> <th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
{% } %} {% } %}
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th> <th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
{% } %} {% } %}
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th> <th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
<th style="width: 12%">{%= __("Customer LPO No.") %}</th> <th style="width: 12%">{%= __("Customer LPO No.") %}</th>
{% } %} {% } %}
<th style="width: 10%">{%= __("PDC/LC Ref") %}</th> <th style="width: 10%">{%= __("Future Payment Ref") %}</th>
<th style="width: 10%">{%= __("PDC/LC Amount") %}</th> <th style="width: 10%">{%= __("Future Payment Amount") %}</th>
<th style="width: 10%">{%= __("Remaining Balance") %}</th> <th style="width: 10%">{%= __("Remaining Balance") %}</th>
{% } %} {% } %}
{% } else { %} {% } else { %}
@ -158,118 +152,118 @@
{% for(var i=0, l=data.length; i<l; i++) { %} {% for(var i=0, l=data.length; i<l; i++) { %}
<tr> <tr>
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
{% if(data[i][__("Customer")] || data[i][__("Supplier")]) { %} {% if(data[i]["party"]) { %}
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td> <td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
<td style="text-align: right">{%= data[i][__("Age (Days)")] %}</td> <td style="text-align: right">{%= data[i]["age"] %}</td>
<td> <td>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
{%= data[i]["voucher_type"] %} {%= data[i]["voucher_type"] %}
<br> <br>
{% } %} {% } %}
{%= data[i]["voucher_no"] %} {%= data[i]["voucher_no"] %}
</td> </td>
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td>{%= data[i]["sales_person"] %}</td> <td>{%= data[i]["sales_person"] %}</td>
{% } %} {% } %}
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
<td> <td>
{% if(!(filters.customer || filters.supplier)) { %} {% if(!(filters.customer || filters.supplier)) { %}
{%= data[i][__("Customer")] || data[i][__("Supplier")] %} {%= data[i]["party"] %}
{% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %} {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i][__("Customer Name")] %} <br> {%= data[i]["customer_name"] %}
{% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %} {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i][__("Supplier Name")] %} <br> {%= data[i]["supplier_name"] %}
{% } %} {% } %}
{% } %} {% } %}
<div> <div>
{% if data[i][__("Remarks")] %} {% if data[i]["remarks"] %}
{%= __("Remarks") %}: {%= __("Remarks") %}:
{%= data[i][__("Remarks")] %} {%= data[i]["remarks"] %}
{% } %} {% } %}
</div> </div>
</td> </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right"> <td style="text-align: right">
{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right"> <td style="text-align: right">
{%= data[i]["po_no"] %}</td> {%= data[i]["po_no"] %}</td>
{% } %} {% } %}
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td> <td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } else { %} {% } else { %}
<td></td> <td></td>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
<td></td> <td></td>
{% } %} {% } %}
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
<td></td> <td></td>
{% } %} {% } %}
<td></td> <td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td> <td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %}</td> {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_pdc_in_print) { %} {% if(!filters.show_future_payments) { %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} </td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %} {% } %}
<td style="text-align: right"> <td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td> {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_pdc_in_print) { %} {% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %} {% if(report.report_name === "Accounts Receivable") { %}
<td style="text-align: right"> <td style="text-align: right">
{%= data[i][__("Customer LPO")] %}</td> {%= data[i]["po_no"] %}</td>
{% } %} {% } %}
<td style="text-align: right">{%= data[i][("pdc/lc_ref")] %}</td> <td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
{% } else { %} {% } else { %}
{% if(data[i][__("Customer")] || data[i][__("Supplier")]|| "&nbsp;") { %} {% if(data[i]["party"]|| "&nbsp;") { %}
{% if((data[i][__("Customer")] || data[i][__("Supplier")]) != __("'Total'")) { %} {% if((data[i]["party"]) != __("'Total'")) { %}
<td> <td>
{% if(!(filters.customer || filters.supplier)) { %} {% if(!(filters.customer || filters.supplier)) { %}
{%= data[i][__("Customer")] || data[i][__("Supplier")] %} {%= data[i]["party"] %}
{% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %} {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
<br> {%= data[i][__("Customer Name")] %} <br> {%= data[i]["customer_name"] %}
{% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %} {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
<br> {%= data[i][__("Supplier Name")] %} <br> {%= data[i]["supplier_name"] %}
{% } %} {% } %}
{% } %} {% } %}
<br>{%= __("Remarks") %}: <br>{%= __("Remarks") %}:
{%= data[i][__("Remarks")] %} {%= data[i]["remarks"] %}
</td> </td>
{% } else { %} {% } else { %}
<td><b>{%= __("Total") %}</b></td> <td><b>{%= __("Total") %}</b></td>
{% } %} {% } %}
<td style="text-align: right">{%= format_currency(data[i][("total_invoiced_amt")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("total_paid_amt")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= report.report_name === "Accounts Receivable Summary" ? format_currency(data[i][__("credit_note_amt")], data[i]["currency"]) : format_currency(data[i][__("debit_note_amt")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i][("total_outstanding_amt")], data[i]["currency"]) %}</td> <td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %} {% } %}
{% } %} {% } %}
</tr> </tr>
{% } %} {% } %}
</tbody> </tbody>
</table> </table>
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p> <p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>

View File

@ -130,13 +130,18 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Check", "fieldtype": "Check",
}, },
{ {
"fieldname":"show_pdc_in_print", "fieldname":"show_future_payments",
"label": __("Show PDC in Print"), "label": __("Show Future Payments"),
"fieldtype": "Check", "fieldtype": "Check",
}, },
{ {
"fieldname":"show_sales_person_in_print", "fieldname":"show_delivery_notes",
"label": __("Show Sales Person in Print"), "label": __("Show Delivery Notes"),
"fieldtype": "Check",
},
{
"fieldname":"show_sales_person",
"label": __("Show Sales Person"),
"fieldtype": "Check", "fieldtype": "Check",
}, },
{ {

View File

@ -14,33 +14,44 @@ class TestAccountsReceivable(unittest.TestCase):
filters = { filters = {
'company': '_Test Company 2', 'company': '_Test Company 2',
'based_on_payment_terms': 1 'based_on_payment_terms': 1,
'report_date': today(),
'range1': 30,
'range2': 60,
'range3': 90,
'range4': 120
} }
# check invoice grand total and invoiced column's value for 3 payment terms
name = make_sales_invoice() name = make_sales_invoice()
report = execute(filters) report = execute(filters)
expected_data = [[100,30], [100,50], [100,20]] expected_data = [[100, 30], [100, 50], [100, 20]]
self.assertEqual(expected_data[0], report[1][0][7:9]) for i in range(3):
self.assertEqual(expected_data[1], report[1][1][7:9]) row = report[1][i-1]
self.assertEqual(expected_data[2], report[1][2][7:9]) self.assertEqual(expected_data[i-1], [row.invoice_grand_total, row.invoiced])
# check invoice grand total, invoiced, paid and outstanding column's value after payment
make_payment(name) make_payment(name)
report = execute(filters) report = execute(filters)
expected_data_after_payment = [[100,50], [100,20]] expected_data_after_payment = [[100, 50, 10, 40], [100, 20, 0, 20]]
self.assertEqual(expected_data_after_payment[0], report[1][0][7:9]) for i in range(2):
self.assertEqual(expected_data_after_payment[1], report[1][1][7:9]) row = report[1][i-1]
self.assertEqual(expected_data_after_payment[i-1],
[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding])
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
make_credit_note(name) make_credit_note(name)
report = execute(filters) report = execute(filters)
expected_data_after_credit_note = [[100,100,30,100,-30]] expected_data_after_credit_note = [100, 0, 0, 40, -40]
self.assertEqual(expected_data_after_credit_note[0], report[1][0][7:12])
row = report[1][0]
self.assertEqual(expected_data_after_credit_note,
[row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding])
def make_sales_invoice(): def make_sales_invoice():
frappe.set_user("Administrator") frappe.set_user("Administrator")
@ -64,7 +75,7 @@ def make_sales_invoice():
return si.name return si.name
def make_payment(docname): def make_payment(docname):
pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30) pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=40)
pe.paid_from = "Debtors - _TC2" pe.paid_from = "Debtors - _TC2"
pe.insert() pe.insert()
pe.submit() pe.submit()

View File

@ -3,236 +3,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _, scrub from frappe import _
from frappe.utils import flt from frappe.utils import flt, cint
from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.party import get_partywise_advanced_payment_amount
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
from six import iteritems from six import iteritems
from six.moves import zip
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
return self.get_columns(party_naming_by, args), self.get_data(party_naming_by, args)
def get_columns(self, party_naming_by, args):
columns = [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"]
if party_naming_by == "Naming Series":
columns += [ args.get("party_type") + " Name::140"]
credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt"
columns += [{
"label": _("Advance Amount"),
"fieldname": "advance_amount",
"fieldtype": "Currency",
"options": "currency",
"width": 100
},{
"label": _("Total Invoiced Amt"),
"fieldname": "total_invoiced_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 100
},
{
"label": _("Total Paid Amt"),
"fieldname": "total_paid_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 100
}]
columns += [
{
"label": _(credit_debit_label),
"fieldname": scrub(credit_debit_label),
"fieldtype": "Currency",
"options": "currency",
"width": 140
},
{
"label": _("Total Outstanding Amt"),
"fieldname": "total_outstanding_amt",
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _("0-" + str(self.filters.range1)),
"fieldname": scrub("0-" + str(self.filters.range1)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range1) + "-" + str(self.filters.range2)),
"fieldname": scrub(str(self.filters.range1) + "-" + str(self.filters.range2)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range2) + "-" + str(self.filters.range3)),
"fieldname": scrub(str(self.filters.range2) + "-" + str(self.filters.range3)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldname": scrub(str(self.filters.range3) + "-" + str(self.filters.range4)),
"fieldtype": "Currency",
"options": "currency",
"width": 160
},
{
"label": _(str(self.filters.range4) + _("-Above")),
"fieldname": scrub(str(self.filters.range4) + _("-Above")),
"fieldtype": "Currency",
"options": "currency",
"width": 160
}
]
if args.get("party_type") == "Customer":
columns += [{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
"width": 80
},
{
"label": _("Customer Group"),
"fieldname": "customer_group",
"fieldtype": "Link",
"options": "Customer Group",
"width": 80
},
{
"label": _("Sales Person"),
"fieldtype": "Data",
"fieldname": "sales_person",
"width": 120,
}]
if args.get("party_type") == "Supplier":
columns += [{
"label": _("Supplier Group"),
"fieldname": "supplier_group",
"fieldtype": "Link",
"options": "Supplier Group",
"width": 80
}]
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
"width": 80
})
return columns
def get_data(self, party_naming_by, args):
data = []
partywise_total = self.get_partywise_total(party_naming_by, args)
partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type"),
self.filters.get("report_date")) or {}
for party, party_dict in iteritems(partywise_total):
row = [party]
if party_naming_by == "Naming Series":
row += [self.get_party_name(args.get("party_type"), party)]
row += [partywise_advance_amount.get(party, 0)]
paid_amt = 0
if party_dict.paid_amt > 0:
paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0))
row += [
party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt,
party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range5
]
if args.get("party_type") == "Customer":
row += [self.get_territory(party), self.get_customer_group(party), ", ".join(set(party_dict.sales_person))]
if args.get("party_type") == "Supplier":
row += [self.get_supplier_group(party)]
row.append(party_dict.currency)
data.append(row)
return data
def get_partywise_total(self, party_naming_by, args):
party_total = frappe._dict()
for d in self.get_voucherwise_data(party_naming_by, args):
party_total.setdefault(d.party,
frappe._dict({
"invoiced_amt": 0,
"paid_amt": 0,
"credit_amt": 0,
"outstanding_amt": 0,
"range1": 0,
"range2": 0,
"range3": 0,
"range4": 0,
"range5": 0,
"sales_person": []
})
)
for k in list(party_total[d.party]):
if k not in ["currency", "sales_person"]:
party_total[d.party][k] += flt(d.get(k, 0))
party_total[d.party].currency = d.currency
if d.sales_person:
party_total[d.party].sales_person.append(d.sales_person)
return party_total
def get_voucherwise_data(self, party_naming_by, args):
voucherwise_data = ReceivablePayableReport(self.filters).run(args)[1]
cols = ["posting_date", "party"]
if party_naming_by == "Naming Series":
cols += ["party_name"]
if args.get("party_type") == 'Customer':
cols += ["contact"]
cols += ["voucher_type", "voucher_no", "due_date"]
if args.get("party_type") == "Supplier":
cols += ["bill_no", "bill_date"]
cols += ["invoiced_amt", "paid_amt", "credit_amt",
"outstanding_amt", "age", "range1", "range2", "range3", "range4", "range5", "currency", "pdc/lc_date", "pdc/lc_ref",
"pdc/lc_amount"]
if args.get("party_type") == "Supplier":
cols += ["supplier_group", "remarks"]
if args.get("party_type") == "Customer":
cols += ["po_no", "do_no", "territory", "customer_group", "sales_person", "remarks"]
return self.make_data_dict(cols, voucherwise_data)
def make_data_dict(self, cols, data):
data_dict = []
for d in data:
data_dict.append(frappe._dict(zip(cols, d)))
return data_dict
def execute(filters=None): def execute(filters=None):
args = { args = {
@ -241,3 +16,119 @@ def execute(filters=None):
} }
return AccountsReceivableSummary(filters).run(args) return AccountsReceivableSummary(filters).run(args)
class AccountsReceivableSummary(ReceivablePayableReport):
def run(self, args):
self.party_type = args.get('party_type')
self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1])
self.get_columns()
self.get_data(args)
return self.columns, self.data
def get_data(self, args):
self.data = []
self.receivables = ReceivablePayableReport(self.filters).run(args)[1]
self.get_party_total(args)
party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
self.filters.report_date) or {}
for party, party_dict in iteritems(self.party_total):
row = frappe._dict()
row.party = party
if self.party_naming_by == "Naming Series":
row.party_name = frappe.get_cached_value(self.party_type, party, [self.party_type + "_name"])
row.update(party_dict)
# Advance against party
row.advance = party_advance_amount.get(party, 0)
# In AR/AP, advance shown in paid columns,
# but in summary report advance shown in separate column
row.paid -= row.advance
self.data.append(row)
def get_party_total(self, args):
self.party_total = frappe._dict()
for d in self.receivables:
self.init_party_total(d)
# Add all amount columns
for k in list(self.party_total[d.party]):
if k not in ["currency", "sales_person"]:
self.party_total[d.party][k] += d.get(k, 0.0)
# set territory, customer_group, sales person etc
self.set_party_details(d)
def init_party_total(self, row):
self.party_total.setdefault(row.party, frappe._dict({
"invoiced": 0.0,
"paid": 0.0,
"credit_note": 0.0,
"outstanding": 0.0,
"range1": 0.0,
"range2": 0.0,
"range3": 0.0,
"range4": 0.0,
"range5": 0.0,
"sales_person": []
}))
def set_party_details(self, row):
self.party_total[row.party].currency = row.currency
for key in ('territory', 'customer_group', 'supplier_group'):
if row.get(key):
self.party_total[row.party][key] = row.get(key)
if row.sales_person:
self.party_total[row.party].sales_person.append(row.sales_person)
def get_columns(self):
self.columns = []
self.add_column(label=_(self.party_type), fieldname='party',
fieldtype='Link', options=self.party_type, width=180)
if self.party_naming_by == "Naming Series":
self.add_column(_('{0} Name').format(self.party_type),
fieldname = 'party_name', fieldtype='Data')
credit_debit_label = "Credit Note" if self.party_type == 'Customer' else "Debit Note"
self.add_column(_('Advance Amount'), fieldname='advance')
self.add_column(_('Invoiced Amount'), fieldname='invoiced')
self.add_column(_('Paid Amount'), fieldname='paid')
self.add_column(_(credit_debit_label), fieldname='credit_note')
self.add_column(_('Outstanding Amount'), fieldname='outstanding')
self.setup_ageing_columns()
if self.party_type == "Customer":
self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link',
options='Territory')
self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link',
options='Customer Group')
if self.filters.show_sales_person:
self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data')
else:
self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
options='Supplier Group')
self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link',
options='Currency', width=80)
def setup_ageing_columns(self):
for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]),
"{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]),
"{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]),
"{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]),
"{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]):
self.add_column(label=label, fieldname='range' + str(i+1))

View File

@ -135,11 +135,11 @@ def get_chart_data(filters, columns, asset, liability, equity):
datasets = [] datasets = []
if asset_data: if asset_data:
datasets.append({'name':'Assets', 'values': asset_data}) datasets.append({'name': _('Assets'), 'values': asset_data})
if liability_data: if liability_data:
datasets.append({'name':'Liabilities', 'values': liability_data}) datasets.append({'name': _('Liabilities'), 'values': liability_data})
if equity_data: if equity_data:
datasets.append({'name':'Equity', 'values': equity_data}) datasets.append({'name': _('Equity'), 'values': equity_data})
chart = { chart = {
"data": { "data": {

View File

@ -425,9 +425,12 @@ def get_cost_centers_with_children(cost_centers):
all_cost_centers = [] all_cost_centers = []
for d in cost_centers: for d in cost_centers:
if frappe.db.exists("Cost Center", d):
lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"]) lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"])
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_cost_centers += [c.name for c in children] all_cost_centers += [c.name for c in children]
else:
frappe.throw(_("Cost Center: {0} does not exist".format(d)))
return list(set(all_cost_centers)) return list(set(all_cost_centers))

View File

@ -119,19 +119,11 @@ def get_gl_entries(filters):
select_fields = """, debit, credit, debit_in_account_currency, select_fields = """, debit, credit, debit_in_account_currency,
credit_in_account_currency """ credit_in_account_currency """
group_by_statement = ''
order_by_statement = "order by posting_date, account" order_by_statement = "order by posting_date, account"
if filters.get("group_by") == _("Group by Voucher"): if filters.get("group_by") == _("Group by Voucher"):
order_by_statement = "order by posting_date, voucher_type, voucher_no" order_by_statement = "order by posting_date, voucher_type, voucher_no"
if filters.get("group_by") == _("Group by Voucher (Consolidated)"):
group_by_statement = "group by voucher_type, voucher_no, account, cost_center"
select_fields = """, sum(debit) as debit, sum(credit) as credit,
sum(debit_in_account_currency) as debit_in_account_currency,
sum(credit_in_account_currency) as credit_in_account_currency"""
if filters.get("include_default_book_entries"): if filters.get("include_default_book_entries"):
filters['company_fb'] = frappe.db.get_value("Company", filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book') filters.get("company"), 'default_finance_book')
@ -144,11 +136,10 @@ def get_gl_entries(filters):
against_voucher_type, against_voucher, account_currency, against_voucher_type, against_voucher, account_currency,
remarks, against, is_opening {select_fields} remarks, against, is_opening {select_fields}
from `tabGL Entry` from `tabGL Entry`
where company=%(company)s {conditions} {group_by_statement} where company=%(company)s {conditions}
{order_by_statement} {order_by_statement}
""".format( """.format(
select_fields=select_fields, conditions=get_conditions(filters), select_fields=select_fields, conditions=get_conditions(filters),
group_by_statement=group_by_statement,
order_by_statement=order_by_statement order_by_statement=order_by_statement
), ),
filters, as_dict=1) filters, as_dict=1)
@ -185,7 +176,8 @@ def get_conditions(filters):
if not (filters.get("account") or filters.get("party") or if not (filters.get("account") or filters.get("party") or
filters.get("group_by") in ["Group by Account", "Group by Party"]): filters.get("group_by") in ["Group by Account", "Group by Party"]):
conditions.append("posting_date >=%(from_date)s") conditions.append("posting_date >=%(from_date)s")
conditions.append("posting_date <=%(to_date)s")
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
if filters.get("project"): if filters.get("project"):
conditions.append("project in %(project)s") conditions.append("project in %(project)s")
@ -286,6 +278,7 @@ def initialize_gle_map(gl_entries, filters):
def get_accountwise_gle(filters, gl_entries, gle_map): def get_accountwise_gle(filters, gl_entries, gle_map):
totals = get_totals_dict() totals = get_totals_dict()
entries = [] entries = []
consolidated_gle = OrderedDict()
group_by = group_by_field(filters.get('group_by')) group_by = group_by_field(filters.get('group_by'))
def update_value_in_dict(data, key, gle): def update_value_in_dict(data, key, gle):
@ -310,12 +303,20 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
update_value_in_dict(totals, 'total', gle) update_value_in_dict(totals, 'total', gle)
if filters.get("group_by") != _('Group by Voucher (Consolidated)'): if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
gle_map[gle.get(group_by)].entries.append(gle) gle_map[gle.get(group_by)].entries.append(gle)
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
key = (gle.get("voucher_type"), gle.get("voucher_no"),
gle.get("account"), gle.get("cost_center"))
if key not in consolidated_gle:
consolidated_gle.setdefault(key, gle)
else: else:
entries.append(gle) update_value_in_dict(consolidated_gle, key, gle)
update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle) update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle)
update_value_in_dict(totals, 'closing', gle) update_value_in_dict(totals, 'closing', gle)
for key, value in consolidated_gle.items():
entries.append(value)
return totals, entries return totals, entries
def get_result_as_list(data, filters): def get_result_as_list(data, filters):

View File

@ -27,8 +27,8 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
fieldname:"payment_type", fieldname:"payment_type",
label: __("Payment Type"), label: __("Payment Type"),
fieldtype: "Select", fieldtype: "Select",
options: "Incoming\nOutgoing", options: __("Incoming") + "\n" + __("Outgoing"),
default: "Incoming" default: __("Incoming")
}, },
{ {
"fieldname":"party_type", "fieldname":"party_type",

View File

@ -39,8 +39,8 @@ def execute(filters=None):
return columns, data return columns, data
def validate_filters(filters): def validate_filters(filters):
if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \ if (filters.get("payment_type") == _("Incoming") and filters.get("party_type") == "Supplier") or \
(filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"): (filters.get("payment_type") == _("Outgoing") and filters.get("party_type") == "Customer"):
frappe.throw(_("{0} payment entries can not be filtered by {1}")\ frappe.throw(_("{0} payment entries can not be filtered by {1}")\
.format(filters.payment_type, filters.party_type)) .format(filters.payment_type, filters.party_type))
@ -51,7 +51,7 @@ def get_columns(filters):
_("Party Type") + "::100", _("Party Type") + "::100",
_("Party") + ":Dynamic Link/Party Type:140", _("Party") + ":Dynamic Link/Party Type:140",
_("Posting Date") + ":Date:100", _("Posting Date") + ":Date:100",
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"), _("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"),
_("Invoice Posting Date") + ":Date:130", _("Invoice Posting Date") + ":Date:130",
_("Payment Due Date") + ":Date:130", _("Payment Due Date") + ":Date:130",
_("Debit") + ":Currency:120", _("Debit") + ":Currency:120",
@ -69,7 +69,7 @@ def get_conditions(filters):
conditions = [] conditions = []
if not filters.party_type: if not filters.party_type:
if filters.payment_type == "Outgoing": if filters.payment_type == _("Outgoing"):
filters.party_type = "Supplier" filters.party_type = "Supplier"
else: else:
filters.party_type = "Customer" filters.party_type = "Customer"
@ -101,7 +101,7 @@ def get_entries(filters):
def get_invoice_posting_date_map(filters): def get_invoice_posting_date_map(filters):
invoice_details = {} invoice_details = {}
dt = "Sales Invoice" if filters.get("payment_type") == "Incoming" else "Purchase Invoice" dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice"
for t in frappe.db.sql("select name, posting_date, due_date from `tab{0}`".format(dt), as_dict=1): for t in frappe.db.sql("select name, posting_date, due_date from `tab{0}`".format(dt), as_dict=1):
invoice_details[t.name] = t invoice_details[t.name] = t

View File

@ -75,11 +75,11 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
datasets = [] datasets = []
if income_data: if income_data:
datasets.append({'name': 'Income', 'values': income_data}) datasets.append({'name': _('Income'), 'values': income_data})
if expense_data: if expense_data:
datasets.append({'name': 'Expense', 'values': expense_data}) datasets.append({'name': _('Expense'), 'values': expense_data})
if net_profit: if net_profit:
datasets.append({'name': 'Net Profit/Loss', 'values': net_profit}) datasets.append({'name': _('Net Profit/Loss'), 'values': net_profit})
chart = { chart = {
"data": { "data": {

View File

@ -303,14 +303,17 @@ frappe.ui.form.on('Asset', {
}, },
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) {
frappe.call({ frappe.call({
method: "get_depreciation_rate", method: "get_depreciation_rate",
doc: frm.doc, doc: frm.doc,
args: row, args: row,
callback: function(r) { callback: function(r) {
if (r.message) { if (r.message) {
frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message); frappe.flags.dont_change_rate = true;
frappe.model.set_value(row.doctype, row.name,
"rate_of_depreciation", flt(r.message, precision("rate_of_depreciation", row)));
} }
} }
}); });
@ -338,6 +341,14 @@ frappe.ui.form.on('Asset Finance Book', {
total_number_of_depreciations: function(frm, cdt, cdn) { total_number_of_depreciations: function(frm, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
frm.events.set_depreciation_rate(frm, row); frm.events.set_depreciation_rate(frm, row);
},
rate_of_depreciation: function(frm, cdt, cdn) {
if(!frappe.flags.dont_change_rate) {
frappe.model.set_value(cdt, cdn, "expected_value_after_useful_life", 0);
}
frappe.flags.dont_change_rate = false;
} }
}); });

View File

@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, math, json import frappe, erpnext, math, json
from frappe import _ from frappe import _
from six import string_types from six import string_types
from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.assets.doctype.asset.depreciation \ from erpnext.assets.doctype.asset.depreciation \
@ -101,16 +101,15 @@ class Asset(AccountsController):
def set_depreciation_rate(self): def set_depreciation_rate(self):
for d in self.get("finance_books"): for d in self.get("finance_books"):
d.rate_of_depreciation = self.get_depreciation_rate(d, on_validate=True) d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True),
d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self): def make_depreciation_schedule(self):
depreciation_method = [d.depreciation_method for d in self.finance_books] if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
if 'Manual' not in depreciation_method:
self.schedules = [] self.schedules = []
if not self.get("schedules") and self.available_for_use_date: if self.get("schedules") or not self.available_for_use_date:
total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')]) return
for d in self.get('finance_books'): for d in self.get('finance_books'):
self.validate_asset_finance_books(d) self.validate_asset_finance_books(d)
@ -120,71 +119,52 @@ class Asset(AccountsController):
d.value_after_depreciation = value_after_depreciation d.value_after_depreciation = value_after_depreciation
no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked)
end_date = add_months(d.depreciation_start_date,
no_of_depreciations * cint(d.frequency_of_depreciation))
total_days = date_diff(end_date, self.available_for_use_date)
rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
cint(self.number_of_depreciations_booked) cint(self.number_of_depreciations_booked)
from_date = self.available_for_use_date has_pro_rata = self.check_is_pro_rata(d)
if number_of_pending_depreciations:
next_depr_date = getdate(add_months(self.available_for_use_date,
number_of_pending_depreciations * 12))
if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1
and getdate(d.depreciation_start_date) < next_depr_date):
if has_pro_rata:
number_of_pending_depreciations += 1 number_of_pending_depreciations += 1
skip_row = False
for n in range(number_of_pending_depreciations): for n in range(number_of_pending_depreciations):
if n == list(range(number_of_pending_depreciations))[-1]: # If depreciation is already completed (for double declining balance)
schedule_date = add_months(self.available_for_use_date, n * 12) if skip_row: continue
previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12)
depreciation_amount = \
self.get_depreciation_amount_prorata_temporis(value_after_depreciation,
d, previous_scheduled_date, schedule_date)
elif n == list(range(number_of_pending_depreciations))[0]:
schedule_date = d.depreciation_start_date
depreciation_amount = \
self.get_depreciation_amount_prorata_temporis(value_after_depreciation,
d, self.available_for_use_date, schedule_date)
else:
schedule_date = add_months(d.depreciation_start_date, n * 12)
depreciation_amount = \
self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d)
if value_after_depreciation != 0:
value_after_depreciation -= flt(depreciation_amount)
self.append("schedules", {
"schedule_date": schedule_date,
"depreciation_amount": depreciation_amount,
"depreciation_method": d.depreciation_method,
"finance_book": d.finance_book,
"finance_book_id": d.idx
})
else:
for n in range(number_of_pending_depreciations):
schedule_date = add_months(d.depreciation_start_date,
n * cint(d.frequency_of_depreciation))
if d.depreciation_method in ("Straight Line", "Manual"):
days = date_diff(schedule_date, from_date)
if n == 0: days += 1
depreciation_amount = days * rate_per_day
from_date = schedule_date
else:
depreciation_amount = self.get_depreciation_amount(value_after_depreciation, depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
d.total_number_of_depreciations, d) d.total_number_of_depreciations, d)
if depreciation_amount: if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
value_after_depreciation -= flt(depreciation_amount) schedule_date = add_months(d.depreciation_start_date,
n * cint(d.frequency_of_depreciation))
# For first row
if has_pro_rata and n==0:
depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount,
self.available_for_use_date, d.depreciation_start_date)
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
depreciation_amount, days = get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date)
schedule_date = add_days(schedule_date, days)
if not depreciation_amount: continue
value_after_depreciation -= flt(depreciation_amount,
self.precision("gross_purchase_amount"))
# Adjust depreciation amount in the last period based on the expected value after useful life
if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
and value_after_depreciation != d.expected_value_after_useful_life)
or value_after_depreciation < d.expected_value_after_useful_life):
depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life)
skip_row = True
if depreciation_amount > 0:
self.append("schedules", { self.append("schedules", {
"schedule_date": schedule_date, "schedule_date": schedule_date,
"depreciation_amount": depreciation_amount, "depreciation_amount": depreciation_amount,
@ -193,6 +173,17 @@ class Asset(AccountsController):
"finance_book_id": d.idx "finance_book_id": d.idx
}) })
def check_is_pro_rata(self, row):
has_pro_rata = False
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days < total_days:
has_pro_rata = True
return has_pro_rata
def validate_asset_finance_books(self, row): def validate_asset_finance_books(self, row):
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount") frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
@ -261,31 +252,20 @@ class Asset(AccountsController):
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
if row.depreciation_method in ["Straight Line", "Manual"]: precision = self.precision("gross_purchase_amount")
amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) -
flt(self.opening_accumulated_depreciation))
depreciation_amount = amt * row.rate_of_depreciation
else:
depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100)
value_after_depreciation = flt(depreciable_value) - depreciation_amount
if value_after_depreciation < flt(row.expected_value_after_useful_life):
depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life)
return depreciation_amount
def get_depreciation_amount_prorata_temporis(self, depreciable_value, row, start_date=None, end_date=None):
if start_date and end_date:
prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1)
else:
prorata_temporis = 1
if row.depreciation_method in ("Straight Line", "Manual"): if row.depreciation_method in ("Straight Line", "Manual"):
depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked))
if not depreciation_left:
frappe.msgprint(_("All the depreciations has been booked"))
depreciation_amount = flt(row.expected_value_after_useful_life)
return depreciation_amount
depreciation_amount = (flt(row.value_after_depreciation) - depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - flt(row.expected_value_after_useful_life)) / depreciation_left
cint(self.number_of_depreciations_booked)) * prorata_temporis
else: else:
depreciation_amount = self.get_depreciation_amount(depreciable_value, row.total_number_of_depreciations, row) depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision)
return depreciation_amount return depreciation_amount
@ -301,20 +281,17 @@ class Asset(AccountsController):
flt(accumulated_depreciation_after_full_schedule), flt(accumulated_depreciation_after_full_schedule),
self.precision('gross_purchase_amount')) self.precision('gross_purchase_amount'))
if row.expected_value_after_useful_life < asset_value_after_full_schedule: if (row.expected_value_after_useful_life and
row.expected_value_after_useful_life < asset_value_after_full_schedule):
frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}")
.format(row.idx, asset_value_after_full_schedule)) .format(row.idx, asset_value_after_full_schedule))
elif not row.expected_value_after_useful_life:
row.expected_value_after_useful_life = asset_value_after_full_schedule
def validate_cancellation(self): def validate_cancellation(self):
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status))
if self.purchase_invoice:
frappe.throw(_("Please cancel Purchase Invoice {0} first").format(self.purchase_invoice))
if self.purchase_receipt:
frappe.throw(_("Please cancel Purchase Receipt {0} first").format(self.purchase_receipt))
def delete_depreciation_entries(self): def delete_depreciation_entries(self):
for d in self.get("schedules"): for d in self.get("schedules"):
if d.journal_entry: if d.journal_entry:
@ -412,15 +389,7 @@ class Asset(AccountsController):
if isinstance(args, string_types): if isinstance(args, string_types):
args = json.loads(args) args = json.loads(args)
number_of_depreciations_booked = 0
if self.is_existing_asset:
number_of_depreciations_booked = self.number_of_depreciations_booked
float_precision = cint(frappe.db.get_default("float_precision")) or 2 float_precision = cint(frappe.db.get_default("float_precision")) or 2
tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked)
if args.get("depreciation_method") in ["Straight Line", "Manual"]:
return 1.0 / tot_no_of_depreciation
if args.get("depreciation_method") == 'Double Declining Balance': if args.get("depreciation_method") == 'Double Declining Balance':
return 200.0 / args.get("total_number_of_depreciations") return 200.0 / args.get("total_number_of_depreciations")
@ -600,3 +569,15 @@ def make_journal_entry(asset_name):
def is_cwip_accounting_disabled(): def is_cwip_accounting_disabled():
return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")) return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting"))
def get_pro_rata_amt(row, depreciation_amount, from_date, to_date):
days = date_diff(to_date, from_date)
total_days = get_total_days(to_date, row.frequency_of_depreciation)
return (depreciation_amount * flt(days)) / flt(total_days), days
def get_total_days(date, frequency):
period_start_date = add_months(date,
cint(frequency) * -1)
return date_diff(date, period_start_date)

View File

@ -88,23 +88,23 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06' asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2020-06-06' asset.purchase_date = '2030-01-01'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 12,
"depreciation_start_date": "2020-06-06" "depreciation_start_date": "2030-12-31"
}) })
asset.save() asset.save()
self.assertEqual(asset.status, "Draft") self.assertEqual(asset.status, "Draft")
expected_schedules = [ expected_schedules = [
["2020-06-06", 147.54, 147.54], ["2030-12-31", 30000.00, 30000.00],
["2021-04-06", 44852.46, 45000.0], ["2031-12-31", 30000.00, 60000.00],
["2022-02-06", 45000.0, 90000.00] ["2032-12-31", 30000.00, 90000.00]
] ]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@ -118,20 +118,21 @@ class TestAsset(unittest.TestCase):
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.number_of_depreciations_booked = 1 asset.number_of_depreciations_booked = 1
asset.opening_accumulated_depreciation = 40000 asset.opening_accumulated_depreciation = 40000
asset.available_for_use_date = "2030-06-06"
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 12,
"depreciation_start_date": "2020-06-06" "depreciation_start_date": "2030-12-31"
}) })
asset.insert() asset.insert()
self.assertEqual(asset.status, "Draft") self.assertEqual(asset.status, "Draft")
asset.save() asset.save()
expected_schedules = [ expected_schedules = [
["2020-06-06", 164.47, 40164.47], ["2030-12-31", 14246.58, 54246.58],
["2021-04-06", 49835.53, 90000.00] ["2031-12-31", 25000.00, 79246.58],
["2032-06-06", 10753.42, 90000.00]
] ]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
for d in asset.get("schedules")] for d in asset.get("schedules")]
@ -145,24 +146,23 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06' asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2020-06-06' asset.purchase_date = '2030-01-01'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Double Declining Balance", "depreciation_method": "Double Declining Balance",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 12,
"depreciation_start_date": "2020-06-06" "depreciation_start_date": '2030-12-31'
}) })
asset.insert() asset.insert()
self.assertEqual(asset.status, "Draft") self.assertEqual(asset.status, "Draft")
asset.save() asset.save()
expected_schedules = [ expected_schedules = [
["2020-06-06", 66666.67, 66666.67], ['2030-12-31', 66667.00, 66667.00],
["2021-04-06", 22222.22, 88888.89], ['2031-12-31', 22222.11, 88889.11],
["2022-02-06", 1111.11, 90000.0] ['2032-12-31', 1110.89, 90000.0]
] ]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@ -177,23 +177,21 @@ class TestAsset(unittest.TestCase):
asset.is_existing_asset = 1 asset.is_existing_asset = 1
asset.number_of_depreciations_booked = 1 asset.number_of_depreciations_booked = 1
asset.opening_accumulated_depreciation = 50000 asset.opening_accumulated_depreciation = 50000
asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2029-11-30'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"next_depreciation_date": "2020-12-31",
"depreciation_method": "Double Declining Balance", "depreciation_method": "Double Declining Balance",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 12,
"depreciation_start_date": "2020-06-06" "depreciation_start_date": "2030-12-31"
}) })
asset.insert() asset.insert()
self.assertEqual(asset.status, "Draft") self.assertEqual(asset.status, "Draft")
asset.save()
asset.save()
expected_schedules = [ expected_schedules = [
["2020-06-06", 33333.33, 83333.33], ["2030-12-31", 33333.50, 83333.50],
["2021-04-06", 6666.67, 90000.0] ["2031-12-31", 6666.50, 90000.0]
] ]
schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
@ -209,25 +207,25 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.purchase_date = '2020-01-30' asset.purchase_date = '2030-01-30'
asset.is_existing_asset = 0 asset.is_existing_asset = 0
asset.available_for_use_date = "2020-01-30" asset.available_for_use_date = "2030-01-30"
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 12,
"depreciation_start_date": "2020-12-31" "depreciation_start_date": "2030-12-31"
}) })
asset.insert() asset.insert()
asset.save() asset.save()
expected_schedules = [ expected_schedules = [
["2020-12-31", 28000.0, 28000.0], ["2030-12-31", 27534.25, 27534.25],
["2021-12-31", 30000.0, 58000.0], ["2031-12-31", 30000.0, 57534.25],
["2022-12-31", 30000.0, 88000.0], ["2032-12-31", 30000.0, 87534.25],
["2023-01-30", 2000.0, 90000.0] ["2033-01-30", 2465.75, 90000.0]
] ]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@ -266,8 +264,8 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR")
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 0.0, 32129.24), ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
("_Test Depreciations - _TC", 32129.24, 0.0) ("_Test Depreciations - _TC", 30000.0, 0.0)
) )
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@ -277,15 +275,15 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle) self.assertEqual(gle, expected_gle)
self.assertEqual(asset.get("value_after_depreciation"), 0) self.assertEqual(asset.get("value_after_depreciation"), 0)
def test_depreciation_entry_for_wdv(self): def test_depreciation_entry_for_wdv_without_pro_rata(self):
pr = make_purchase_receipt(item_code="Macbook Pro", pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=8000.0, location="Test Location") qty=1, rate=8000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-06-06' asset.available_for_use_date = '2030-01-01'
asset.purchase_date = '2030-06-06' asset.purchase_date = '2030-01-01'
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 1000, "expected_value_after_useful_life": 1000,
"depreciation_method": "Written Down Value", "depreciation_method": "Written Down Value",
@ -298,9 +296,41 @@ class TestAsset(unittest.TestCase):
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [ expected_schedules = [
["2030-12-31", 4000.0, 4000.0], ["2030-12-31", 4000.00, 4000.00],
["2031-12-31", 2000.0, 6000.0], ["2031-12-31", 2000.00, 6000.00],
["2032-12-31", 1000.0, 7000.0], ["2032-12-31", 1000.00, 7000.0],
]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
for d in asset.get("schedules")]
self.assertEqual(schedules, expected_schedules)
def test_pro_rata_depreciation_entry_for_wdv(self):
pr = make_purchase_receipt(item_code="Macbook Pro",
qty=1, rate=8000.0, location="Test Location")
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1
asset.available_for_use_date = '2030-06-06'
asset.purchase_date = '2030-01-01'
asset.append("finance_books", {
"expected_value_after_useful_life": 1000,
"depreciation_method": "Written Down Value",
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.save(ignore_permissions=True)
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
expected_schedules = [
["2030-12-31", 2279.45, 2279.45],
["2031-12-31", 2860.28, 5139.73],
["2032-12-31", 1430.14, 6569.87],
["2033-06-06", 430.13, 7000.0],
] ]
schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@ -346,18 +376,19 @@ class TestAsset(unittest.TestCase):
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name')
asset = frappe.get_doc('Asset', asset_name) asset = frappe.get_doc('Asset', asset_name)
asset.calculate_depreciation = 1 asset.calculate_depreciation = 1
asset.available_for_use_date = '2020-06-06' asset.available_for_use_date = nowdate()
asset.purchase_date = '2020-06-06' asset.purchase_date = nowdate()
asset.append("finance_books", { asset.append("finance_books", {
"expected_value_after_useful_life": 10000, "expected_value_after_useful_life": 10000,
"depreciation_method": "Straight Line", "depreciation_method": "Straight Line",
"total_number_of_depreciations": 3, "total_number_of_depreciations": 3,
"frequency_of_depreciation": 10, "frequency_of_depreciation": 10,
"depreciation_start_date": "2020-06-06" "depreciation_start_date": nowdate()
}) })
asset.insert() asset.insert()
asset.submit() asset.submit()
post_depreciation_entries(date="2021-01-01")
post_depreciation_entries(date=add_months(nowdate(), 10))
scrap_asset(asset.name) scrap_asset(asset.name)
@ -366,9 +397,9 @@ class TestAsset(unittest.TestCase):
self.assertTrue(asset.journal_entry_for_scrap) self.assertTrue(asset.journal_entry_for_scrap)
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 147.54, 0.0), ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 99852.46, 0.0) ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0)
) )
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
@ -412,9 +443,9 @@ class TestAsset(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
expected_gle = ( expected_gle = (
("_Test Accumulated Depreciations - _TC", 23051.47, 0.0), ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0),
("_Test Fixed Asset - _TC", 0.0, 100000.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0),
("_Test Gain/Loss on Asset Disposal - _TC", 51948.53, 0.0), ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0),
("Debtors - _TC", 25000.0, 0.0) ("Debtors - _TC", 25000.0, 0.0)
) )
@ -425,8 +456,6 @@ class TestAsset(unittest.TestCase):
self.assertEqual(gle, expected_gle) self.assertEqual(gle, expected_gle)
si.cancel() si.cancel()
frappe.delete_doc("Sales Invoice", si.name)
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
def test_asset_expected_value_after_useful_life(self): def test_asset_expected_value_after_useful_life(self):

View File

@ -46,75 +46,6 @@
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "schedule_based_on_fiscal_year",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Calculate Prorated Depreciation Schedule Based on Fiscal Year",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "360",
"depends_on": "eval:doc.schedule_based_on_fiscal_year",
"description": "This value is used for pro-rata temporis calculation",
"fetch_if_empty": 0,
"fieldname": "number_of_days_in_fiscal_year",
"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": "Number of Days in Fiscal Year",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_in_quick_entry": 0, "allow_in_quick_entry": 0,
@ -159,7 +90,7 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2019-03-08 10:44:41.924547", "modified": "2019-05-26 18:31:19.930563",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Settings", "name": "Asset Settings",

View File

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Buying Settings', {
// refresh: function(frm) {
// }
});

View File

@ -1,379 +1,111 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-06-25 11:04:03", "creation": "2013-06-25 11:04:03",
"custom": 0,
"description": "Settings for Buying Module", "description": "Settings for Buying Module",
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
"editable_grid": 0, "field_order": [
"supp_master_name",
"supplier_group",
"buying_price_list",
"column_break_3",
"po_required",
"pr_required",
"maintain_same_rate",
"allow_multiple_items",
"subcontract",
"backflush_raw_materials_of_subcontract_based_on",
"column_break_11",
"over_transfer_allowance"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Supplier Name", "default": "Supplier Name",
"fieldname": "supp_master_name", "fieldname": "supp_master_name",
"fieldtype": "Select", "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": "Supplier Naming By", "label": "Supplier Naming By",
"length": 0, "options": "Supplier Name\nNaming Series"
"no_copy": 0,
"options": "Supplier Name\nNaming Series",
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "supplier_group", "fieldname": "supplier_group",
"fieldtype": "Link", "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": "Default Supplier Group", "label": "Default Supplier Group",
"length": 0, "options": "Supplier Group"
"no_copy": 0,
"options": "Supplier Group",
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "buying_price_list", "fieldname": "buying_price_list",
"fieldtype": "Link", "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": "Default Buying Price List", "label": "Default Buying Price List",
"length": 0, "options": "Price List"
"no_copy": 0,
"options": "Price List",
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "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,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "po_required", "fieldname": "po_required",
"fieldtype": "Select", "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": "Purchase Order Required", "label": "Purchase Order Required",
"length": 0, "options": "No\nYes"
"no_copy": 0,
"options": "No\nYes",
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "pr_required", "fieldname": "pr_required",
"fieldtype": "Select", "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": "Purchase Receipt Required", "label": "Purchase Receipt Required",
"length": 0, "options": "No\nYes"
"no_copy": 0,
"options": "No\nYes",
"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
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "maintain_same_rate", "fieldname": "maintain_same_rate",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Maintain same rate throughout purchase cycle"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Maintain same rate throughout purchase cycle",
"length": 0,
"no_copy": 0,
"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
}, },
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allow_multiple_items", "fieldname": "allow_multiple_items",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Allow Item to be added multiple times in a transaction"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Allow Item to be added multiple times in a transaction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subcontract", "fieldname": "subcontract",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Subcontract"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Subcontract",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Material Transferred for Subcontract", "default": "Material Transferred for Subcontract",
"fieldname": "backflush_raw_materials_of_subcontract_based_on", "fieldname": "backflush_raw_materials_of_subcontract_based_on",
"fieldtype": "Select", "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": "Backflush Raw Materials of Subcontract Based On", "label": "Backflush Raw Materials of Subcontract Based On",
"length": 0, "options": "BOM\nMaterial Transferred for Subcontract"
"no_copy": 0, },
"options": "BOM\nMaterial Transferred for Subcontract", {
"permlevel": 0, "depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"BOM\"",
"precision": "", "description": "Percentage you are allowed to transfer more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to transfer 110 units.",
"print_hide": 0, "fieldname": "over_transfer_allowance",
"print_hide_if_no_value": 0, "fieldtype": "Float",
"read_only": 0, "label": "Over Transfer Allowance (%)"
"remember_last_selected_value": 0, },
"report_hide": 0, {
"reqd": 0, "fieldname": "column_break_11",
"search_index": 0, "fieldtype": "Column Break"
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-cog", "icon": "fa fa-cog",
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "modified": "2019-08-20 13:13:09.055189",
"max_attachments": 0,
"modified": "2018-07-31 07:52:38.062488",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Buying Settings", "name": "Buying Settings",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 0,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 0,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ]
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"track_changes": 0,
"track_seen": 0
} }

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestBuyingSettings(unittest.TestCase):
pass

View File

@ -477,13 +477,14 @@ def make_rm_stock_entry(purchase_order, rm_items):
rm_item_code = rm_item_data["rm_item_code"] rm_item_code = rm_item_data["rm_item_code"]
items_dict = { items_dict = {
rm_item_code: { rm_item_code: {
"po_detail": rm_item_data.get("name"),
"item_name": rm_item_data["item_name"], "item_name": rm_item_data["item_name"],
"description": item_wh.get(rm_item_code, {}).get('description', ""), "description": item_wh.get(rm_item_code, {}).get('description', ""),
'qty': rm_item_data["qty"], 'qty': rm_item_data["qty"],
'from_warehouse': rm_item_data["warehouse"], 'from_warehouse': rm_item_data["warehouse"],
'stock_uom': rm_item_data["stock_uom"], 'stock_uom': rm_item_data["stock_uom"],
'main_item_code': rm_item_data["item_code"], 'main_item_code': rm_item_data["item_code"],
'allow_alternative_item': item_wh[rm_item_code].get('allow_alternative_item') 'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item')
} }
} }
stock_entry.add_to_stock_entry_detail(items_dict) stock_entry.add_to_stock_entry_detail(items_dict)

View File

@ -16,9 +16,9 @@ frappe.listview_settings['Purchase Order'] = {
return [__("To Receive"), "orange", return [__("To Receive"), "orange",
"per_received,<,100|per_billed,=,100|status,!=,Closed"]; "per_received,<,100|per_billed,=,100|status,!=,Closed"];
} }
} else if (flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) < 100 && doc.status !== "Closed") { } else if (flt(doc.per_received, 2) >= 100 && flt(doc.per_billed, 2) < 100 && doc.status !== "Closed") {
return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Closed"]; return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Closed"];
} else if (flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) == 100 && doc.status !== "Closed") { } else if (flt(doc.per_received, 2) >= 100 && flt(doc.per_billed, 2) == 100 && doc.status !== "Closed") {
return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Closed"]; return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Closed"];
} }
}, },

View File

@ -1,404 +1,134 @@
{ {
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:42", "creation": "2013-02-22 01:27:42",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"field_order": [
"main_item_code",
"rm_item_code",
"required_qty",
"supplied_qty",
"rate",
"amount",
"column_break_6",
"bom_detail_no",
"reference_name",
"conversion_factor",
"stock_uom",
"reserve_warehouse"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "main_item_code", "fieldname": "main_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Item Code", "label": "Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "main_item_code", "oldfieldname": "main_item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "rm_item_code", "fieldname": "rm_item_code",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Raw Material Item Code", "label": "Raw Material Item Code",
"length": 0,
"no_copy": 0,
"oldfieldname": "rm_item_code", "oldfieldname": "rm_item_code",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "Item", "options": "Item",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "required_qty", "fieldname": "required_qty",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Required Qty",
"label": "Supplied Qty",
"length": 0,
"no_copy": 0,
"oldfieldname": "required_qty", "oldfieldname": "required_qty",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Rate", "label": "Rate",
"length": 0,
"no_copy": 0,
"oldfieldname": "rate", "oldfieldname": "rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency"
"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
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount", "fieldname": "amount",
"fieldtype": "Currency", "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": "Amount", "label": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "amount", "oldfieldname": "amount",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_6", "fieldname": "column_break_6",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "bom_detail_no", "fieldname": "bom_detail_no",
"fieldtype": "Data", "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": "BOM Detail No", "label": "BOM Detail No",
"length": 0,
"no_copy": 0,
"oldfieldname": "bom_detail_no", "oldfieldname": "bom_detail_no",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Data", "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": "Reference Name", "label": "Reference Name",
"length": 0,
"no_copy": 0,
"oldfieldname": "reference_name", "oldfieldname": "reference_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "conversion_factor", "fieldname": "conversion_factor",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 1, "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": "Conversion Factor", "label": "Conversion Factor",
"length": 0,
"no_copy": 0,
"oldfieldname": "conversion_factor", "oldfieldname": "conversion_factor",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stock_uom", "fieldname": "stock_uom",
"fieldtype": "Link", "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": "Stock Uom", "label": "Stock Uom",
"length": 0,
"no_copy": 0,
"oldfieldname": "stock_uom", "oldfieldname": "stock_uom",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"options": "UOM", "options": "UOM",
"permlevel": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fieldname": "reserve_warehouse", "fieldname": "reserve_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Reserve Warehouse", "label": "Reserve Warehouse",
"length": 0, "options": "Warehouse"
"no_copy": 0, },
"options": "Warehouse", {
"permlevel": 0, "fieldname": "supplied_qty",
"precision": "", "fieldtype": "Float",
"print_hide": 0, "in_list_view": 1,
"print_hide_if_no_value": 0, "label": "Supplied Qty",
"read_only": 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,
"hide_heading": 0,
"hide_toolbar": 1, "hide_toolbar": 1,
"idx": 1, "idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "modified": "2019-08-20 13:37:32.702068",
"modified": "2019-01-07 16:51:58.016007",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item Supplied", "name": "Purchase Order Item Supplied",
"owner": "dhanalekshmi@webnotestech.com", "owner": "dhanalekshmi@webnotestech.com",
"permissions": [], "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
} }

View File

@ -0,0 +1,41 @@
# Version 12 Release Notes
### Accounting
1. [Accounting Dimensions](https://erpnext.com/docs/user/manual/en/accounts/accounting-dimensions)
1. [Chart of Accounts Importer](https://erpnext.com/docs/user/manual/en/setting-up/chart-of-accounts-importer)
1. [Invoice Discounting](https://erpnext.com/docs/user/manual/en/accounts/invoice_discounting)
1. [Tally Migrator](https://github.com/frappe/erpnext/pull/17405)
### Stock
1. [Serialized & Batched Item Reconciliation](https://erpnext.com/docs/user/manual/en/setting-up/stock-reconciliation#12-for-serialized-items)
1. [Auto Fetch Serialized Items](https://erpnext.com/version-12/release-notes/features#new-upload-dialog)
1. [Item Tax Templates](https://erpnext.com/docs/user/manual/en/accounts/item-tax-template)
### HR
1. [Auto Attendance](https://erpnext.com/docs/user/manual/en/human-resources/auto-attendance)
1. [Employee Skill Map](https://erpnext.com/docs/user/manual/en/human-resources/employee_skill_map)
1. [Encrypted Salary Slips](https://erpnext.com/docs/user/manual/en/human-resources/hr-settings#24-encrypt-salary-slips-in-emails)
1. [Leave Ledger](https://erpnext.com/docs/user/manual/en/human-resources/leave-ledger-entry)
1. [Staffing Plan](https://erpnext.com/docs/user/manual/en/human-resources/staffing-plan)
### CRM
1. [Promotional Scheme](https://erpnext.com/docs/user/manual/en/accounts/promotional-schemes)
1. [SLA](https://erpnext.com/docs/user/manual/en/support/service-level-agreement)
1. [Exotel Call Integration](https://erpnext.com/docs/user/manual/en/erpnext_integration/exotel_integration)
1. [Email Campaign](https://erpnext.com/docs/user/manual/en/CRM/email-campaign)
### Domain Specific Features
1. [Learning Management System](https://erpnext.com/docs/user/manual/en/education/setting-up-lms)
1. [Quality Management System](https://erpnext.com/docs/user/manual/en/quality-management)
1. [Production Planning Enhancements](https://erpnext.com/docs/user/manual/en/manufacturing/production-plan/planning-for-material-requests)
1. [Project Template](https://erpnext.com/docs/user/manual/en/projects/project-template)
### New Reports
1. [Bank Remittance](https://erpnext.com/docs/user/manual/en/human-resources/human-resources-reports#bank-remittance-report)
1. [BOM Explorer](https://erpnext.com/docs/user/manual/en/stock/articles/bom_explorer)
1. [Billing Summary Report](https://erpnext.com/docs/user/manual/en/projects/reports/billing_summary_reports)
1. [Procurement Tracker Report](docs/user/manual/en/buying/articles/procurement-tracker-report)
1. [Loan Repayment](https://erpnext.com/docs/user/manual/en/human-resources/human-resources-reports#loan-repayment-report)
1. [GSTR-3B](https://erpnext.com/docs/user/manual/en/regional/india/gst-3b-report)
1. [Sales Partner](https://erpnext.com/docs/user/manual/en/selling/sales-partner#sales-partner-reports)
1. [Sales Partner Target Variance based on Item Group](https://erpnext.com/docs/user/manual/en/selling/sales-partner#sales-partner-target-variance-based-on-item-group)

View File

@ -0,0 +1,6 @@
# Version 12.1.0 Release Notes
### Stock
1. [Pick List](https://erpnext.com/docs/user/manual/en/stock/pick-list)
2. [Refactored Accounts Receivable Reports](https://erpnext.com/docs/user/manual/en/accounts/accounting-reports#2-accounting-statements)

View File

@ -6,15 +6,13 @@ 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 erpnext.crm.doctype.utils import get_scheduled_employees_for_popup from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number
from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number
from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
class CallLog(Document): class CallLog(Document):
def before_insert(self): def before_insert(self):
# strip 0 from the start of the number for proper number comparisions number = strip_number(self.get('from'))
# eg. 07888383332 should match with 7888383332
number = self.get('from').lstrip('0')
self.contact = get_contact_with_phone_number(number) self.contact = get_contact_with_phone_number(number)
self.lead = get_lead_with_phone_number(number) self.lead = get_lead_with_phone_number(number)
@ -30,7 +28,7 @@ class CallLog(Document):
self.trigger_call_popup() self.trigger_call_popup()
def trigger_call_popup(self): def trigger_call_popup(self):
scheduled_employees = get_scheduled_employees_for_popup(self.to) scheduled_employees = get_scheduled_employees_for_popup(self.medium)
employee_emails = get_employees_with_number(self.to) employee_emails = get_employees_with_number(self.to)
# check if employees with matched number are scheduled to receive popup # check if employees with matched number are scheduled to receive popup
@ -48,13 +46,14 @@ def add_call_summary(call_log, summary):
doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary) doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '<br><br>' + summary)
def get_employees_with_number(number): def get_employees_with_number(number):
number = strip_number(number)
if not number: return [] if not number: return []
employee_emails = frappe.cache().hget('employees_with_number', number) employee_emails = frappe.cache().hget('employees_with_number', number)
if employee_emails: return employee_emails if employee_emails: return employee_emails
employees = frappe.get_all('Employee', filters={ employees = frappe.get_all('Employee', filters={
'cell_number': ['like', '%{}'.format(number.lstrip('0'))], 'cell_number': ['like', '%{}%'.format(number)],
'user_id': ['!=', ''] 'user_id': ['!=', '']
}, fields=['user_id']) }, fields=['user_id'])
@ -64,23 +63,33 @@ def get_employees_with_number(number):
return employee return employee
def set_caller_information(doc, state): def set_caller_information(doc, state):
'''Called from hoooks on creation of Lead or Contact''' '''Called from hooks on creation of Lead or Contact'''
if doc.doctype not in ['Lead', 'Contact']: return if doc.doctype not in ['Lead', 'Contact']: return
numbers = [doc.get('phone'), doc.get('mobile_no')] numbers = [doc.get('phone'), doc.get('mobile_no')]
for_doc = doc.doctype.lower() # contact for Contact and lead for Lead
fieldname = doc.doctype.lower()
# contact_name or lead_name
display_name_field = '{}_name'.format(fieldname)
# Contact now has all the nos saved in child table
if doc.doctype == 'Contact':
numbers = [d.phone for d in doc.phone_nos]
for number in numbers: for number in numbers:
number = strip_number(number)
if not number: continue if not number: continue
print(number)
filters = frappe._dict({ filters = frappe._dict({
'from': ['like', '%{}'.format(number.lstrip('0'))], 'from': ['like', '%{}'.format(number)],
for_doc: '' fieldname: ''
}) })
logs = frappe.get_all('Call Log', filters=filters) logs = frappe.get_all('Call Log', filters=filters)
for log in logs: for log in logs:
call_log = frappe.get_doc('Call Log', log.name) frappe.db.set_value('Call Log', log.name, {
call_log.set(for_doc, doc.name) fieldname: doc.name,
call_log.save(ignore_permissions=True) display_name_field: doc.get_title()
}, update_modified=False)

View File

@ -14,6 +14,12 @@ def get_data():
"dependencies": ["Item", "Supplier"], "dependencies": ["Item", "Supplier"],
"description": _("Purchase Orders given to Suppliers."), "description": _("Purchase Orders given to Suppliers."),
}, },
{
"type": "doctype",
"name": "Purchase Invoice",
"onboard": 1,
"dependencies": ["Item", "Supplier"]
},
{ {
"type": "doctype", "type": "doctype",
"name": "Material Request", "name": "Material Request",

View File

@ -41,6 +41,11 @@ def get_data():
"name": "Lead Source", "name": "Lead Source",
"description": _("Track Leads by Lead Source.") "description": _("Track Leads by Lead Source.")
}, },
{
"type": "doctype",
"name": "Contract",
"description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"),
},
] ]
}, },
{ {

View File

@ -134,6 +134,12 @@ def get_data():
"name": "Employee Leave Balance", "name": "Employee Leave Balance",
"doctype": "Leave Application" "doctype": "Leave Application"
}, },
{
"type": "report",
"is_query_report": True,
"name": "Leave Ledger Entry",
"doctype": "Leave Ledger Entry"
},
] ]
}, },
{ {
@ -160,6 +166,10 @@ def get_data():
"name": "Salary Slip", "name": "Salary Slip",
"onboard": 1, "onboard": 1,
}, },
{
"type": "doctype",
"name": "Payroll Period",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Salary Component", "name": "Salary Component",

View File

@ -40,6 +40,11 @@ def get_data():
"type": "doctype", "type": "doctype",
"name": "Plaid Settings", "name": "Plaid Settings",
"description": _("Connect your bank accounts to ERPNext"), "description": _("Connect your bank accounts to ERPNext"),
},
{
"type": "doctype",
"name": "Exotel Settings",
"description": _("Connect your Exotel Account to ERPNext and track call logs"),
} }
] ]
} }

View File

@ -94,6 +94,13 @@ def get_data():
"name": "BOM Update Tool", "name": "BOM Update Tool",
"description": _("Replace BOM and update latest price in all BOMs"), "description": _("Replace BOM and update latest price in all BOMs"),
}, },
{
"type": "page",
"label": _("BOM Comparison Tool"),
"name": "bom-comparison-tool",
"description": _("Compare BOMs for changes in Raw Materials and Operations"),
"data_doctype": "BOM"
},
] ]
}, },
{ {

View File

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

View File

@ -30,6 +30,12 @@ def get_data():
"onboard": 1, "onboard": 1,
"dependencies": ["Item"], "dependencies": ["Item"],
}, },
{
"type": "doctype",
"name": "Pick List",
"onboard": 1,
"dependencies": ["Item"],
},
{ {
"type": "doctype", "type": "doctype",
"name": "Delivery Trip" "name": "Delivery Trip"

View File

@ -89,7 +89,7 @@ class AccountsController(TransactionBase):
self.validate_currency() self.validate_currency()
if self.doctype == 'Purchase Invoice': if self.doctype == 'Purchase Invoice':
self.validate_paid_amount() self.calculate_paid_amount()
if self.doctype in ['Purchase Invoice', 'Sales Invoice']: if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
@ -135,22 +135,23 @@ class AccountsController(TransactionBase):
else: else:
df.set("print_hide", 1) df.set("print_hide", 1)
def validate_paid_amount(self): def calculate_paid_amount(self):
if hasattr(self, "is_pos") or hasattr(self, "is_paid"): if hasattr(self, "is_pos") or hasattr(self, "is_paid"):
is_paid = self.get("is_pos") or self.get("is_paid") is_paid = self.get("is_pos") or self.get("is_paid")
if cint(is_paid) == 1:
if flt(self.paid_amount) == 0 and flt(self.outstanding_amount) > 0: if is_paid:
if self.cash_bank_account: if not self.cash_bank_account:
# show message that the amount is not paid
frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
if cint(self.is_return) and self.grand_total > self.paid_amount:
self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount"))
elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0:
self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount")) self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount"))
self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, self.base_paid_amount = flt(self.paid_amount * self.conversion_rate,
self.precision("base_paid_amount")) self.precision("base_paid_amount"))
else:
# show message that the amount is not paid
self.paid_amount = 0
frappe.throw(
_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified"))
else:
frappe.db.set(self, 'paid_amount', 0)
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
if frappe.flags.in_test: if frappe.flags.in_test:
@ -263,7 +264,7 @@ class AccountsController(TransactionBase):
if self.get("is_subcontracted"): if self.get("is_subcontracted"):
args["is_subcontracted"] = self.is_subcontracted args["is_subcontracted"] = self.is_subcontracted
ret = get_item_details(args, self) ret = get_item_details(args, self, overwrite_warehouse=False)
for fieldname, value in ret.items(): for fieldname, value in ret.items():
if item.meta.get_field(fieldname) and value is not None: if item.meta.get_field(fieldname) and value is not None:

View File

@ -337,7 +337,7 @@ class BuyingController(StockController):
if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: if self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
rm.consumed_qty = required_qty rm.consumed_qty = required_qty
rm.description = bom_item.description rm.description = bom_item.description
if item.batch_no and not rm.batch_no: if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no:
rm.batch_no = item.batch_no rm.batch_no = item.batch_no
# get raw materials rate # get raw materials rate
@ -727,7 +727,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1):
where where
t2.parent = t1.name and t1.item = %s t2.parent = t1.name and t1.item = %s
and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s
and t2.item_code = t3.name and t3.is_stock_item = 1""".format(doctype), and t2.item_code = t3.name""".format(doctype),
(item_code, bom), as_dict=1) (item_code, bom), as_dict=1)
if not bom_items: if not bom_items:

View File

@ -371,7 +371,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select tabAccount.name from `tabAccount` return frappe.db.sql("""select tabAccount.name from `tabAccount`
where (tabAccount.report_type = "Profit and Loss" where (tabAccount.report_type = "Profit and Loss"
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed")) or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
and tabAccount.is_group=0 and tabAccount.is_group=0
and tabAccount.docstatus!=2 and tabAccount.docstatus!=2
and tabAccount.{key} LIKE %(txt)s and tabAccount.{key} LIKE %(txt)s

View File

@ -18,18 +18,15 @@ def validate_return(doc):
validate_returned_items(doc) validate_returned_items(doc)
def validate_return_against(doc): def validate_return_against(doc):
filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company} if not frappe.db.exists(doc.doctype, doc.return_against):
if doc.meta.get_field("customer") and doc.customer:
filters["customer"] = doc.customer
elif doc.meta.get_field("supplier") and doc.supplier:
filters["supplier"] = doc.supplier
if not frappe.db.exists(filters):
frappe.throw(_("Invalid {0}: {1}") frappe.throw(_("Invalid {0}: {1}")
.format(doc.meta.get_label("return_against"), doc.return_against)) .format(doc.meta.get_label("return_against"), doc.return_against))
else: else:
ref_doc = frappe.get_doc(doc.doctype, doc.return_against) ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier"
if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1:
# validate posting date time # validate posting date time
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
@ -249,6 +246,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
elif doc.doctype == 'Purchase Invoice': elif doc.doctype == 'Purchase Invoice':
doc.paid_amount = -1 * source.paid_amount doc.paid_amount = -1 * source.paid_amount
doc.base_paid_amount = -1 * source.base_paid_amount doc.base_paid_amount = -1 * source.base_paid_amount
doc.payment_terms_template = ''
doc.payment_schedule = []
if doc.get("is_return") and hasattr(doc, "packed_items"): if doc.get("is_return") and hasattr(doc, "packed_items"):
for d in doc.get("packed_items"): for d in doc.get("packed_items"):

View File

@ -45,6 +45,7 @@ class SellingController(StockController):
self.set_gross_profit() self.set_gross_profit()
set_default_income_account_for_item(self) set_default_income_account_for_item(self)
self.set_customer_address() self.set_customer_address()
self.validate_for_duplicate_items()
def set_missing_values(self, for_validate=False): def set_missing_values(self, for_validate=False):
@ -381,6 +382,34 @@ class SellingController(StockController):
if self.get(address_field): if self.get(address_field):
self.set(address_display_field, get_address_display(self.get(address_field))) self.set(address_display_field, get_address_display(self.get(address_field)))
def validate_for_duplicate_items(self):
check_list, chk_dupl_itm = [], []
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
return
for d in self.get('items'):
if self.doctype == "Sales Invoice":
e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
f = [d.item_code, d.description, d.sales_order or d.delivery_note]
elif self.doctype == "Delivery Note":
e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or '']
f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice]
elif self.doctype in ["Sales Order", "Quotation"]:
e = [d.item_code, d.description, d.warehouse, '']
f = [d.item_code, d.description]
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1:
if e in check_list:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
check_list.append(e)
else:
if f in chk_dupl_itm:
frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code))
else:
chk_dupl_itm.append(f)
def validate_items(self): def validate_items(self):
# validate items to see if they have is_sales_item enabled # validate items to see if they have is_sales_item enabled
from erpnext.controllers.buying_controller import validate_item_type from erpnext.controllers.buying_controller import validate_item_type

View File

@ -57,9 +57,9 @@ status_map = {
"Purchase Order": [ "Purchase Order": [
["Draft", None], ["Draft", None],
["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"], ["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Bill", "eval:self.per_received == 100 and self.per_billed < 100 and self.docstatus == 1"], ["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"],
["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"], ["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"],
["Completed", "eval:self.per_received == 100 and self.per_billed == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1"],
["Delivered", "eval:self.status=='Delivered'"], ["Delivered", "eval:self.status=='Delivered'"],
["Cancelled", "eval:self.docstatus==2"], ["Cancelled", "eval:self.docstatus==2"],
["On Hold", "eval:self.status=='On Hold'"], ["On Hold", "eval:self.status=='On Hold'"],

View File

@ -39,7 +39,6 @@ def validate_filters(filters):
frappe.throw(_("'Based On' and 'Group By' can not be same")) frappe.throw(_("'Based On' and 'Group By' can not be same"))
def get_data(filters, conditions): def get_data(filters, conditions):
data = [] data = []
inc, cond= '','' inc, cond= '',''
query_details = conditions["based_on_select"] + conditions["period_wise_select"] query_details = conditions["based_on_select"] + conditions["period_wise_select"]
@ -47,13 +46,17 @@ def get_data(filters, conditions):
posting_date = 't1.transaction_date' posting_date = 't1.transaction_date'
if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']: if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']:
posting_date = 't1.posting_date' posting_date = 't1.posting_date'
if filters.period_based_on:
posting_date = 't1.'+filters.period_based_on
if conditions["based_on_select"] in ["t1.project,", "t2.project,"]: if conditions["based_on_select"] in ["t1.project,", "t2.project,"]:
cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL' cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL'
if conditions.get('trans') in ['Sales Order', 'Purchase Order']: if conditions.get('trans') in ['Sales Order', 'Purchase Order']:
cond += " and t1.status != 'Closed'" cond += " and t1.status != 'Closed'"
if conditions.get('trans') == 'Quotation' and filters.get("group_by") == 'Customer':
cond += " and t1.quotation_to = 'Customer'"
year_start_date, year_end_date = frappe.db.get_value("Fiscal Year", year_start_date, year_end_date = frappe.db.get_value("Fiscal Year",
filters.get('fiscal_year'), ["year_start_date", "year_end_date"]) filters.get('fiscal_year'), ["year_start_date", "year_end_date"])
@ -64,7 +67,7 @@ def get_data(filters, conditions):
if filters.get("group_by") == 'Item': if filters.get("group_by") == 'Item':
sel_col = 't2.item_code' sel_col = 't2.item_code'
elif filters.get("group_by") == 'Customer': elif filters.get("group_by") == 'Customer':
sel_col = 't1.customer' sel_col = 't1.party_name' if conditions.get('trans') == 'Quotation' else 't1.customer'
elif filters.get("group_by") == 'Supplier': elif filters.get("group_by") == 'Supplier':
sel_col = 't1.supplier' sel_col = 't1.supplier'
@ -225,7 +228,7 @@ def based_wise_columns_query(based_on, trans):
elif based_on == "Customer": elif based_on == "Customer":
based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"] based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"]
based_on_details["based_on_select"] = "t1.customer_name, t1.territory, " based_on_details["based_on_select"] = "t1.customer_name, t1.territory, "
based_on_details["based_on_group_by"] = 't1.customer' based_on_details["based_on_group_by"] = 't1.party_name' if trans == 'Quotation' else 't1.customer'
based_on_details["addl_tables"] = '' based_on_details["addl_tables"] = ''
elif based_on == "Customer Group": elif based_on == "Customer Group":

View File

@ -21,42 +21,45 @@ def get_list_context(context=None):
def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"):
user = frappe.session.user user = frappe.session.user
key = None ignore_permissions = False
if not filters: filters = [] if not filters: filters = []
if doctype == 'Supplier Quotation': if doctype == 'Supplier Quotation':
filters.append((doctype, "docstatus", "<", 2)) filters.append((doctype, 'docstatus', '<', 2))
else: else:
filters.append((doctype, "docstatus", "=", 1)) filters.append((doctype, 'docstatus', '=', 1))
if (user != "Guest" and is_website_user()) or doctype == 'Request for Quotation': if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation':
parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
# find party for this contact # find party for this contact
customers, suppliers = get_customers_suppliers(parties_doctype, user) customers, suppliers = get_customers_suppliers(parties_doctype, user)
if not customers and not suppliers: return [] if customers:
if doctype == 'Quotation':
key, parties = get_party_details(customers, suppliers) filters.append(('quotation_to', '=', 'Customer'))
filters.append(('party_name', 'in', customers))
if doctype == 'Request for Quotation': else:
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length) filters.append(('customer', 'in', customers))
elif suppliers:
filters.append((doctype, key, "in", parties)) filters.append(('supplier', 'in', suppliers))
if key:
return post_process(doctype, get_list_for_transactions(doctype, txt,
filters=filters, fields="name",limit_start=limit_start,
limit_page_length=limit_page_length,ignore_permissions=True,
order_by="modified desc"))
else: else:
return [] return []
return post_process(doctype, get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length, if doctype == 'Request for Quotation':
fields="name", order_by="modified desc")) parties = customers or suppliers
return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length)
# Since customers and supplier do not have direct access to internal doctypes
ignore_permissions = True
transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length,
fields='name', ignore_permissions=ignore_permissions, order_by='modified desc')
return post_process(doctype, transactions)
def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20, def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20,
ignore_permissions=False,fields=None, order_by=None): ignore_permissions=False, fields=None, order_by=None):
""" Get List of transactions like Invoices, Orders """ """ Get List of transactions like Invoices, Orders """
from frappe.www.list import get_list from frappe.www.list import get_list
meta = frappe.get_meta(doctype) meta = frappe.get_meta(doctype)
@ -83,16 +86,6 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len
return data return data
def get_party_details(customers, suppliers):
if customers:
key, parties = "customer", customers
elif suppliers:
key, parties = "supplier", suppliers
else:
key, parties = "customer", []
return key, parties
def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length): def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length):
data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}` data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}`
where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""". where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""".
@ -130,6 +123,11 @@ def get_customers_suppliers(doctype, user):
suppliers = [] suppliers = []
meta = frappe.get_meta(doctype) meta = frappe.get_meta(doctype)
customer_field_name = get_customer_field_name(doctype)
has_customer_field = meta.has_field(customer_field_name)
has_supplier_field = meta.has_field('supplier')
if has_common(["Supplier", "Customer"], frappe.get_roles(user)): if has_common(["Supplier", "Customer"], frappe.get_roles(user)):
contacts = frappe.db.sql(""" contacts = frappe.db.sql("""
select select
@ -141,27 +139,40 @@ def get_customers_suppliers(doctype, user):
where where
`tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s `tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s
""", user, as_dict=1) """, user, as_dict=1)
customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \ customers = [c.link_name for c in contacts if c.link_doctype == 'Customer']
if meta.get_field("customer") else None suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier']
suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \
if meta.get_field("supplier") else None
elif frappe.has_permission(doctype, 'read', user=user): elif frappe.has_permission(doctype, 'read', user=user):
customers = [customer.name for customer in frappe.get_list("Customer")] \ customer_list = frappe.get_list("Customer")
if meta.get_field("customer") else None customers = suppliers = [customer.name for customer in customer_list]
suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \
if meta.get_field("supplier") else None
return customers, suppliers return customers if has_customer_field else None, \
suppliers if has_supplier_field else None
def has_website_permission(doc, ptype, user, verbose=False): def has_website_permission(doc, ptype, user, verbose=False):
doctype = doc.doctype doctype = doc.doctype
customers, suppliers = get_customers_suppliers(doctype, user) customers, suppliers = get_customers_suppliers(doctype, user)
if customers: if customers:
return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers), return frappe.db.exists(doctype, get_customer_filter(doc, customers))
(doctype, "name", "=", doc.name)]) and True or False
elif suppliers: elif suppliers:
fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier' fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier'
return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers), return frappe.db.exists(doctype, filters={
(doctype, "name", "=", doc.name)]) and True or False 'name': doc.name,
fieldname: ["in", suppliers]
})
else: else:
return False return False
def get_customer_filter(doc, customers):
doctype = doc.doctype
filters = frappe._dict()
filters.name = doc.name
filters[get_customer_field_name(doctype)] = ['in', customers]
if doctype == 'Quotation':
filters.quotation_to = 'Customer'
return filters
def get_customer_field_name(doctype):
if doctype == 'Quotation':
return 'party_name'
else:
return 'customer'

View File

@ -88,7 +88,7 @@ def get_status(start_date, end_date):
end_date = getdate(end_date) end_date = getdate(end_date)
now_date = getdate(nowdate()) now_date = getdate(nowdate())
return "Active" if start_date < now_date < end_date else "Inactive" return "Active" if start_date <= now_date <= end_date else "Inactive"
def update_status_for_contracts(): def update_status_for_contracts():

View File

@ -45,15 +45,16 @@ class TestOpportunity(unittest.TestCase):
# create new customer and create new contact against 'new.opportunity@example.com' # create new customer and create new contact against 'new.opportunity@example.com'
customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True) customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True)
frappe.get_doc({ contact = frappe.get_doc({
"doctype": "Contact", "doctype": "Contact",
"email_id": new_lead_email_id,
"first_name": "_Test Opportunity Customer", "first_name": "_Test Opportunity Customer",
"links": [{ "links": [{
"link_doctype": "Customer", "link_doctype": "Customer",
"link_name": customer.name "link_name": customer.name
}] }]
}).insert(ignore_permissions=True) })
contact.add_email(new_lead_email_id)
contact.insert(ignore_permissions=True)
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
self.assertTrue(opp_doc.party_name) self.assertTrue(opp_doc.party_name)

View File

@ -54,6 +54,8 @@ def get_last_issue_from_customer(customer_name):
def get_scheduled_employees_for_popup(communication_medium): def get_scheduled_employees_for_popup(communication_medium):
if not communication_medium: return []
now_time = frappe.utils.nowtime() now_time = frappe.utils.nowtime()
weekday = frappe.utils.get_weekday() weekday = frappe.utils.get_weekday()
@ -73,3 +75,10 @@ def get_scheduled_employees_for_popup(communication_medium):
employee_emails = set([employee.user_id for employee in employees]) employee_emails = set([employee.user_id for employee in employees])
return employee_emails return employee_emails
def strip_number(number):
if not number: return
# strip 0 from the start of the number for proper number comparisions
# eg. 07888383332 should match with 7888383332
number = number.lstrip('0')
return number

View File

@ -30,7 +30,7 @@
"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": "Enrollment", "label": "Course Enrollment",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Course Enrollment", "options": "Course Enrollment",

View File

@ -5,6 +5,7 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"course", "course",
"course_name",
"required" "required"
], ],
"fields": [ "fields": [
@ -16,6 +17,14 @@
"label": "Course", "label": "Course",
"options": "Course", "options": "Course",
"reqd": 1 "reqd": 1
},
{
"fieldname": "course_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Course Name",
"fetch_from": "course.course_name",
"read_only":1
}, },
{ {
"default": "0", "default": "0",

View File

@ -54,6 +54,7 @@ class Student(Document):
'send_welcome_email': 1, 'send_welcome_email': 1,
'user_type': 'Website User' 'user_type': 'Website User'
}) })
student_user.flags.ignore_permissions = True
student_user.add_roles("Student") student_user.add_roles("Student")
student_user.save() student_user.save()
update_password_link = student_user.reset_password() update_password_link = student_user.reset_password()

View File

@ -705,7 +705,6 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"default": "INDIAN",
"fieldname": "nationality", "fieldname": "nationality",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,

View File

@ -55,7 +55,7 @@ def preview_report_card(doc):
"courses": courses, "courses": courses,
"assessment_groups": assessment_groups, "assessment_groups": assessment_groups,
"course_criteria": course_criteria, "course_criteria": course_criteria,
"letterhead": letterhead.content, "letterhead": letterhead and letterhead.get('content', None),
"add_letterhead": doc.add_letterhead if doc.add_letterhead else 0 "add_letterhead": doc.add_letterhead if doc.add_letterhead else 0
}) })
final_template = frappe.render_template(base_template_path, {"body": html, "title": "Report Card"}) final_template = frappe.render_template(base_template_path, {"body": html, "title": "Report Card"})

View File

@ -71,7 +71,7 @@ def remove_empty(d):
Helper function that removes all keys from a dictionary (d), Helper function that removes all keys from a dictionary (d),
that have an empty value. that have an empty value.
""" """
for key in d.keys(): for key in list(d):
if not d[key]: if not d[key]:
del d[key] del d[key]
return d return d

View File

@ -3,7 +3,6 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document from frappe.model.document import Document
import requests import requests
import frappe import frappe

View File

@ -3,29 +3,30 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe
from frappe import _ from frappe import _
import requests from frappe.utils.password import get_decrypted_password
from plaid import Client from plaid import Client
from plaid.errors import APIError, ItemError from plaid.errors import APIError, ItemError
import frappe
import requests
class PlaidConnector(): class PlaidConnector():
def __init__(self, access_token=None): def __init__(self, access_token=None):
if not(frappe.conf.get("plaid_client_id") and frappe.conf.get("plaid_secret") and frappe.conf.get("plaid_public_key")): plaid_settings = frappe.get_single("Plaid Settings")
frappe.throw(_("Please complete your Plaid API configuration before synchronizing your account"))
self.config = { self.config = {
"plaid_client_id": frappe.conf.get("plaid_client_id"), "plaid_client_id": plaid_settings.plaid_client_id,
"plaid_secret": frappe.conf.get("plaid_secret"), "plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'),
"plaid_public_key": frappe.conf.get("plaid_public_key"), "plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": frappe.conf.get("plaid_env") "plaid_env": plaid_settings.plaid_env
} }
self.client = Client(client_id=self.config["plaid_client_id"], self.client = Client(client_id=self.config.get("plaid_client_id"),
secret=self.config["plaid_secret"], secret=self.config.get("plaid_secret"),
public_key=self.config["plaid_public_key"], public_key=self.config.get("plaid_public_key"),
environment=self.config["plaid_env"] environment=self.config.get("plaid_env")
) )
self.access_token = access_token self.access_token = access_token

View File

@ -4,8 +4,18 @@
frappe.provide("erpnext.integrations"); frappe.provide("erpnext.integrations");
frappe.ui.form.on('Plaid Settings', { frappe.ui.form.on('Plaid Settings', {
link_new_account: function(frm) { enabled: function(frm) {
frm.toggle_reqd('plaid_client_id', frm.doc.enabled);
frm.toggle_reqd('plaid_secret', frm.doc.enabled);
frm.toggle_reqd('plaid_public_key', frm.doc.enabled);
frm.toggle_reqd('plaid_env', frm.doc.enabled);
},
refresh: function(frm) {
if(frm.doc.enabled) {
frm.add_custom_button('Link a new bank account', () => {
new erpnext.integrations.plaidLink(frm); new erpnext.integrations.plaidLink(frm);
});
}
} }
}); });
@ -19,20 +29,10 @@ erpnext.integrations.plaidLink = class plaidLink {
init_config() { init_config() {
const me = this; const me = this;
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') me.plaid_env = me.frm.doc.plaid_env;
.then(result => { me.plaid_public_key = me.frm.doc.plaid_public_key;
if (result !== "disabled") { me.client_name = frappe.boot.sitename;
if (result.plaid_env == undefined || result.plaid_public_key == undefined) {
frappe.throw(__("Please add valid Plaid api keys in site_config.json first"));
}
me.plaid_env = result.plaid_env;
me.plaid_public_key = result.plaid_public_key;
me.client_name = result.client_name;
me.init_plaid(); me.init_plaid();
} else {
frappe.throw(__("Please save your document before adding a new account"));
}
});
} }
init_plaid() { init_plaid() {

View File

@ -1,161 +1,96 @@
{ {
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-10-25 10:02:48.656165", "creation": "2018-10-25 10:02:48.656165",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"enabled",
"column_break_2",
"automatic_sync",
"section_break_4",
"plaid_client_id",
"plaid_secret",
"column_break_7",
"plaid_public_key",
"plaid_env"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "enabled", "fieldname": "enabled",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Enabled"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Enabled",
"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": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.enabled==1", "depends_on": "eval:doc.enabled==1",
"fieldname": "automatic_sync", "fieldname": "automatic_sync",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Synchronize all accounts every hour"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Synchronize all accounts every hour",
"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, "depends_on": "eval:doc.enabled==1",
"allow_in_quick_entry": 0, "fieldname": "plaid_client_id",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Plaid Client ID",
"columns": 0, "reqd": 1
"depends_on": "eval:(doc.enabled==1)&&(!doc.__islocal)", },
"fieldname": "link_new_account", {
"fieldtype": "Button", "depends_on": "eval:doc.enabled==1",
"hidden": 0, "fieldname": "plaid_secret",
"ignore_user_permissions": 0, "fieldtype": "Password",
"ignore_xss_filter": 0, "in_list_view": 1,
"in_filter": 0, "label": "Plaid Secret",
"in_global_search": 0, "reqd": 1
"in_list_view": 0, },
"in_standard_filter": 0, {
"label": "Link a new bank account", "depends_on": "eval:doc.enabled==1",
"length": 0, "fieldname": "plaid_public_key",
"no_copy": 0, "fieldtype": "Data",
"permlevel": 0, "in_list_view": 1,
"precision": "", "label": "Plaid Public Key",
"print_hide": 0, "reqd": 1
"print_hide_if_no_value": 0, },
"read_only": 0, {
"remember_last_selected_value": 0, "depends_on": "eval:doc.enabled==1",
"report_hide": 0, "fieldname": "plaid_env",
"reqd": 0, "fieldtype": "Data",
"search_index": 0, "in_list_view": 1,
"set_only_once": 0, "label": "Plaid Environment",
"translatable": 0, "reqd": 1
"unique": 0 },
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_7",
"fieldtype": "Column Break"
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1, "issingle": 1,
"istable": 0, "modified": "2019-08-13 17:00:06.939422",
"max_attachments": 0,
"modified": "2018-12-14 12:51:12.331395",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "ERPNext Integrations", "module": "ERPNext Integrations",
"name": "Plaid Settings", "name": "Plaid Settings",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 0,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC"
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@ -16,8 +16,13 @@ class PlaidSettings(Document):
@frappe.whitelist() @frappe.whitelist()
def plaid_configuration(): def plaid_configuration():
if frappe.db.get_value("Plaid Settings", None, "enabled") == "1": if frappe.db.get_single_value("Plaid Settings", "enabled"):
return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site } plaid_settings = frappe.get_single("Plaid Settings")
return {
"plaid_public_key": plaid_settings.plaid_public_key,
"plaid_env": plaid_settings.plaid_env,
"client_name": frappe.local.site
}
else: else:
return "disabled" return "disabled"

View File

@ -58,7 +58,7 @@ class ShopifySettings(Document):
d.raise_for_status() d.raise_for_status()
self.update_webhook_table(method, d.json()) self.update_webhook_table(method, d.json())
except Exception as e: except Exception as e:
make_shopify_log(status="Warning", message=e.message, exception=False) make_shopify_log(status="Warning", message=e, exception=False)
def unregister_webhooks(self): def unregister_webhooks(self):
session = get_request_session() session = get_request_session()
@ -71,7 +71,7 @@ class ShopifySettings(Document):
res.raise_for_status() res.raise_for_status()
deleted_webhooks.append(d) deleted_webhooks.append(d)
except Exception as e: except Exception as e:
frappe.log_error(message=frappe.get_traceback(), title=e.message[:140]) frappe.log_error(message=frappe.get_traceback(), title=e)
for d in deleted_webhooks: for d in deleted_webhooks:
self.remove(d) self.remove(d)

View File

@ -69,33 +69,10 @@ def validate_service_item(item, msg):
def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None): def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None):
fields = ["name", "first_name", "mobile_phone"] fields = ["name", "first_name", "mobile_phone"]
match_conditions = build_match_conditions("Healthcare Practitioner")
match_conditions = "and {}".format(match_conditions) if match_conditions else ""
if filters: filters = {
filter_conditions = get_filters_cond(doctype, filters, []) 'name': ("like", "%%%s%%" % txt)
match_conditions += "{}".format(filter_conditions) }
return frappe.db.sql("""select %s from `tabHealthcare Practitioner` where docstatus < 2 return frappe.get_all("Healthcare Practitioner", fields = fields,
and (%s like %s or first_name like %s) filters = filters, start=start, page_length=page_len, order_by="name, first_name", as_list=1)
and active = 1
{match_conditions}
order by
case when name like %s then 0 else 1 end,
case when first_name like %s then 0 else 1 end,
name, first_name limit %s, %s""".format(
match_conditions=match_conditions) %
(
", ".join(fields),
frappe.db.escape(searchfield),
"%s", "%s", "%s", "%s", "%s", "%s"
),
(
"%%%s%%" % frappe.db.escape(txt),
"%%%s%%" % frappe.db.escape(txt),
"%%%s%%" % frappe.db.escape(txt),
"%%%s%%" % frappe.db.escape(txt),
start,
page_len
)
)

View File

@ -6,7 +6,7 @@ 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, cstr, getdate from frappe.utils import cint, cstr, getdate, flt
import dateutil import dateutil
from frappe.model.naming import set_name_by_naming_series from frappe.model.naming import set_name_by_naming_series
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms
@ -64,7 +64,7 @@ class Patient(Document):
def invoice_patient_registration(self): def invoice_patient_registration(self):
frappe.db.set_value("Patient", self.name, "disabled", 0) frappe.db.set_value("Patient", self.name, "disabled", 0)
send_registration_sms(self) send_registration_sms(self)
if(frappe.get_value("Healthcare Settings", None, "registration_fee")>0): if(flt(frappe.get_value("Healthcare Settings", None, "registration_fee"))>0):
company = frappe.defaults.get_user_default('company') company = frappe.defaults.get_user_default('company')
if not company: if not company:
company = frappe.db.get_value("Global Defaults", None, "default_company") company = frappe.db.get_value("Global Defaults", None, "default_company")

View File

@ -283,7 +283,9 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status" "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status"
], ],
"daily_long": [ "daily_long": [
"erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms",
"erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation",
"erpnext.hr.utils.generate_leave_encashment"
], ],
"monthly_long": [ "monthly_long": [
"erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income", "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income",

View File

@ -11,7 +11,7 @@ from frappe.utils import getdate, date_diff
class AdditionalSalary(Document): class AdditionalSalary(Document):
def before_insert(self): def before_insert(self):
if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component, if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company}): "amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
frappe.throw(_("Additional Salary Component Exists.")) frappe.throw(_("Additional Salary Component Exists."))

View File

@ -4,6 +4,7 @@
"creation": "2013-01-10 16:34:13", "creation": "2013-01-10 16:34:13",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"engine": "InnoDB",
"field_order": [ "field_order": [
"attendance_details", "attendance_details",
"naming_series", "naming_series",
@ -19,7 +20,9 @@
"department", "department",
"shift", "shift",
"attendance_request", "attendance_request",
"amended_from" "amended_from",
"late_entry",
"early_exit"
], ],
"fields": [ "fields": [
{ {
@ -153,12 +156,24 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Shift", "label": "Shift",
"options": "Shift Type" "options": "Shift Type"
},
{
"default": "0",
"fieldname": "late_entry",
"fieldtype": "Check",
"label": "Late Entry"
},
{
"default": "0",
"fieldname": "early_exit",
"fieldtype": "Check",
"label": "Early Exit"
} }
], ],
"icon": "fa fa-ok", "icon": "fa fa-ok",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-06-05 19:37:30.410071", "modified": "2019-07-29 20:35:40.845422",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Attendance", "name": "Attendance",

View File

@ -7,6 +7,7 @@
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB",
"field_order": [ "field_order": [
"basic_information", "basic_information",
"employee", "employee",
@ -54,6 +55,7 @@
"column_break_44", "column_break_44",
"holiday_list", "holiday_list",
"default_shift", "default_shift",
"leave_approver",
"salary_information", "salary_information",
"salary_mode", "salary_mode",
"bank_name", "bank_name",
@ -169,6 +171,8 @@
"read_only": 1 "read_only": 1
}, },
{ {
"fetch_from": "user_id.user_image",
"fetch_if_empty": 1,
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach Image", "fieldtype": "Attach Image",
"hidden": 1, "hidden": 1,
@ -767,12 +771,18 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Default Shift", "label": "Default Shift",
"options": "Shift Type" "options": "Shift Type"
},
{
"fieldname": "leave_approver",
"fieldtype": "Link",
"label": "Leave Approver",
"options": "User"
} }
], ],
"icon": "fa fa-user", "icon": "fa fa-user",
"idx": 24, "idx": 24,
"image_field": "image", "image_field": "image",
"modified": "2019-06-01 16:05:55.132180", "modified": "2019-09-12 14:21:12.711280",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee", "name": "Employee",

View File

@ -14,8 +14,6 @@
"device_id", "device_id",
"skip_auto_attendance", "skip_auto_attendance",
"attendance", "attendance",
"entry_grace_period_consequence",
"exit_grace_period_consequence",
"shift_start", "shift_start",
"shift_end", "shift_end",
"shift_actual_start", "shift_actual_start",
@ -80,20 +78,6 @@
"options": "Attendance", "options": "Attendance",
"read_only": 1 "read_only": 1
}, },
{
"default": "0",
"fieldname": "entry_grace_period_consequence",
"fieldtype": "Check",
"hidden": 1,
"label": "Entry Grace Period Consequence"
},
{
"default": "0",
"fieldname": "exit_grace_period_consequence",
"fieldtype": "Check",
"hidden": 1,
"label": "Exit Grace Period Consequence"
},
{ {
"fieldname": "shift_start", "fieldname": "shift_start",
"fieldtype": "Datetime", "fieldtype": "Datetime",
@ -119,7 +103,7 @@
"label": "Shift Actual End" "label": "Shift Actual End"
} }
], ],
"modified": "2019-06-10 15:33:22.731697", "modified": "2019-07-23 23:47:33.975263",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Checkin", "name": "Employee Checkin",

View File

@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
return doc return doc
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, shift=None): def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None):
"""Creates an attendance and links the attendance to the Employee Checkin. """Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown. Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@ -98,7 +98,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
'status': attendance_status, 'status': attendance_status,
'working_hours': working_hours, 'working_hours': working_hours,
'company': employee_doc.company, 'company': employee_doc.company,
'shift': shift 'shift': shift,
'late_entry': late_entry,
'early_exit': early_exit
} }
attendance = frappe.get_doc(doc_dict).insert() attendance = frappe.get_doc(doc_dict).insert()
attendance.submit() attendance.submit()
@ -124,25 +126,36 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
""" """
total_hours = 0 total_hours = 0
in_time = out_time = None
if check_in_out_type == 'Alternating entries as IN and OUT during the same shift': if check_in_out_type == 'Alternating entries as IN and OUT during the same shift':
in_time = logs[0].time
if len(logs) >= 2:
out_time = logs[-1].time
if working_hours_calc_type == 'First Check-in and Last Check-out': if working_hours_calc_type == 'First Check-in and Last Check-out':
# assumption in this case: First log always taken as IN, Last log always taken as OUT # assumption in this case: First log always taken as IN, Last log always taken as OUT
total_hours = time_diff_in_hours(logs[0].time, logs[-1].time) total_hours = time_diff_in_hours(in_time, logs[-1].time)
elif working_hours_calc_type == 'Every Valid Check-in and Check-out': elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
logs = logs[:]
while len(logs) >= 2: while len(logs) >= 2:
total_hours += time_diff_in_hours(logs[0].time, logs[1].time) total_hours += time_diff_in_hours(logs[0].time, logs[1].time)
del logs[:2] del logs[:2]
elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin': elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin':
if working_hours_calc_type == 'First Check-in and Last Check-out': if working_hours_calc_type == 'First Check-in and Last Check-out':
first_in_log = logs[find_index_in_dict(logs, 'log_type', 'IN')] first_in_log_index = find_index_in_dict(logs, 'log_type', 'IN')
last_out_log = logs[len(logs)-1-find_index_in_dict(reversed(logs), 'log_type', 'OUT')] first_in_log = logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None
last_out_log_index = find_index_in_dict(reversed(logs), 'log_type', 'OUT')
last_out_log = logs[len(logs)-1-last_out_log_index] if last_out_log_index or last_out_log_index == 0 else None
if first_in_log and last_out_log: if first_in_log and last_out_log:
total_hours = time_diff_in_hours(first_in_log.time, last_out_log.time) in_time, out_time = first_in_log.time, last_out_log.time
total_hours = time_diff_in_hours(in_time, out_time)
elif working_hours_calc_type == 'Every Valid Check-in and Check-out': elif working_hours_calc_type == 'Every Valid Check-in and Check-out':
in_log = out_log = None in_log = out_log = None
for log in logs: for log in logs:
if in_log and out_log: if in_log and out_log:
if not in_time:
in_time = in_log.time
out_time = out_log.time
total_hours += time_diff_in_hours(in_log.time, out_log.time) total_hours += time_diff_in_hours(in_log.time, out_log.time)
in_log = out_log = None in_log = out_log = None
if not in_log: if not in_log:
@ -150,8 +163,9 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
elif not out_log: elif not out_log:
out_log = log if log.log_type == 'OUT' else None out_log = log if log.log_type == 'OUT' else None
if in_log and out_log: if in_log and out_log:
out_time = out_log.time
total_hours += time_diff_in_hours(in_log.time, out_log.time) total_hours += time_diff_in_hours(in_log.time, out_log.time)
return total_hours return total_hours, in_time, out_time
def time_diff_in_hours(start, end): def time_diff_in_hours(start, end):
return round((end-start).total_seconds() / 3600, 1) return round((end-start).total_seconds() / 3600, 1)

View File

@ -70,16 +70,16 @@ class TestEmployeeCheckin(unittest.TestCase):
logs_type_2 = [frappe._dict(x) for x in logs_type_2] logs_type_2 = [frappe._dict(x) for x in logs_type_2]
working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0]) working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0])
self.assertEqual(working_hours, 6.5) self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time))
working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1]) working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1])
self.assertEqual(working_hours, 4.5) self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time))
working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0]) working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0])
self.assertEqual(working_hours, 5) self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time))
working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1]) working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1])
self.assertEqual(working_hours, 4.5) self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time))
def make_n_checkins(employee, n, hours_to_reverse=1): def make_n_checkins(employee, n, hours_to_reverse=1):
logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))] logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))]

View File

@ -2,7 +2,21 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Employee Incentive', { frappe.ui.form.on('Employee Incentive', {
refresh: function(frm) { setup: function(frm) {
frm.set_query("employee", function() {
return {
filters: {
"status": "Active"
}
};
});
frm.set_query("salary_component", function() {
return {
filters: {
"type": "Earning"
}
};
});
} }
}); });

View File

@ -1,262 +1,90 @@
{ {
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "HR-EINV-.YY.-.MM.-.#####", "autoname": "HR-EINV-.YY.-.MM.-.#####",
"beta": 0,
"creation": "2018-04-13 16:13:43.404546", "creation": "2018-04-13 16:13:43.404546",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"employee",
"incentive_amount",
"payroll_date",
"salary_component",
"amended_from",
"column_break_5",
"employee_name",
"department",
"additional_salary"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "employee", "fieldname": "employee",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Employee", "label": "Employee",
"length": 0,
"no_copy": 0,
"options": "Employee", "options": "Employee",
"permlevel": 0, "reqd": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "incentive_amount", "fieldname": "incentive_amount",
"fieldtype": "Currency", "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": "Incentive Amount", "label": "Incentive Amount",
"length": 0, "reqd": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payroll_date", "fieldname": "payroll_date",
"fieldtype": "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": "Payroll Date", "label": "Payroll Date",
"length": 0, "reqd": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "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", "label": "Amended From",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Employee Incentive", "options": "Employee Incentive",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5", "fieldname": "column_break_5",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.employee_name", "fetch_from": "employee.employee_name",
"fieldname": "employee_name", "fieldname": "employee_name",
"fieldtype": "Data", "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", "label": "Employee Name",
"length": 0, "read_only": 1
"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,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department", "fetch_from": "employee.department",
"fieldname": "department", "fieldname": "department",
"fieldtype": "Link", "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", "label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department", "options": "Department",
"permlevel": 0, "read_only": 1
"precision": "", },
"print_hide": 0, {
"print_hide_if_no_value": 0, "fieldname": "additional_salary",
"read_only": 1, "fieldtype": "Link",
"remember_last_selected_value": 0, "label": "Additional Salary",
"report_hide": 0, "no_copy": 1,
"reqd": 0, "options": "Additional Salary",
"search_index": 0, "read_only": 1
"set_only_once": 0, },
"translatable": 0, {
"unique": 0 "fieldname": "salary_component",
"fieldtype": "Link",
"label": "Salary Component",
"options": "Salary Component",
"reqd": 1
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "modified": "2019-09-03 16:48:16.822252",
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:51.811149",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Incentive", "name": "Employee Incentive",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@ -266,65 +94,37 @@
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR Manager", "role": "HR Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Employee", "role": "Employee",
"set_user_permissions": 0, "share": 1
"share": 1,
"submit": 0,
"write": 0
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "HR User", "role": "HR User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "employee_name", "title_field": "employee_name",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

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