Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
926150bccb | ||
|
|
e46d10676e | ||
|
|
631e8bb9dd | ||
|
|
13d6f4b291 | ||
|
|
bc72fdff7a | ||
|
|
e8148112fc | ||
|
|
a1dd861096 | ||
|
|
aeb0b008fa | ||
|
|
4c711ed62c | ||
|
|
7620742299 | ||
|
|
8a5bd02662 | ||
|
|
565936442d | ||
|
|
a8e1c90035 | ||
|
|
a953b68a17 | ||
|
|
c9c48d3736 | ||
|
|
d1d679843e | ||
|
|
c2b7084ee8 | ||
|
|
07bbe8568f | ||
|
|
2a58b6c20b | ||
|
|
196bf2104f | ||
|
|
a33145e6d9 | ||
|
|
6161d3362d | ||
|
|
fac8ed0630 | ||
|
|
3279ad55f6 | ||
|
|
9bdb04d7ca | ||
|
|
89270ef2c0 | ||
|
|
c0ea006571 | ||
|
|
de3b79cce8 | ||
|
|
f326d5c9a1 | ||
|
|
e112ac2345 | ||
|
|
ad1719bbe4 | ||
|
|
f2c464dda8 | ||
|
|
2b64b38a6e | ||
|
|
dcbafecf58 | ||
|
|
20229b915c | ||
|
|
7a1a0fb5fb | ||
|
|
fddef7bd5c | ||
|
|
804854f051 | ||
|
|
bdb48f34ed | ||
|
|
986b5115da | ||
|
|
b37f4a157c | ||
|
|
f650b287ef | ||
|
|
1866cebd14 | ||
|
|
bb5bf06e0c | ||
|
|
ecf8e20462 | ||
|
|
5a0bc043aa | ||
|
|
a2c9c9505c | ||
|
|
f6060fa5f2 | ||
|
|
b4cf29391b | ||
|
|
a6a6f69e26 | ||
|
|
db38c02aee | ||
|
|
e107d430eb | ||
|
|
0c849ac0f3 | ||
|
|
6b86bb462b | ||
|
|
80cfc88abb | ||
|
|
3c425584a7 | ||
|
|
af561becc6 | ||
|
|
0ef08618c9 | ||
|
|
373d90f802 | ||
|
|
ceaedd0f95 | ||
|
|
fd7c4e9822 | ||
|
|
26878d907e | ||
|
|
ae3c21046b | ||
|
|
f01baaa56f | ||
|
|
ac534c64bc | ||
|
|
6e8e42ca7c | ||
|
|
7d3d99a9b8 | ||
|
|
9a858a9824 | ||
|
|
5792d6037c | ||
|
|
75310637da | ||
|
|
6fa4697efb | ||
|
|
63e83cc005 | ||
|
|
68d7d6e223 | ||
|
|
7ee97b6128 | ||
|
|
49fadcbaf2 | ||
|
|
e6ab86e185 | ||
|
|
5c482e907e | ||
|
|
3ebc13f228 | ||
|
|
b5882aaa6c | ||
|
|
505332e680 | ||
|
|
ada3330403 | ||
|
|
e0143e081c | ||
|
|
f03522ad9e | ||
|
|
f80be2d1f4 | ||
|
|
1bd60ac343 | ||
|
|
2fc2d9b8ad | ||
|
|
839b653f6b | ||
|
|
61cfa275f8 | ||
|
|
7319bcb577 | ||
|
|
3750a06d4f | ||
|
|
c79c8dae19 | ||
|
|
1e4e2c81e9 | ||
|
|
334106a6d0 | ||
|
|
eba2455ad3 | ||
|
|
63267d3616 | ||
|
|
5ed80a5ba9 | ||
|
|
98e5692a40 | ||
|
|
bc293cf251 |
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/frappe/design/blob/master/logos/erpnext-logo.svg" height="128">
|
||||
<img src="https://github.com/frappe/design/blob/master/logos/logo-2019/erpnext-logo.png" height="128">
|
||||
<h2>ERPNext</h2>
|
||||
<p align="center">
|
||||
<p>ERP made simple</p>
|
||||
|
||||
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '11.1.45'
|
||||
__version__ = '11.1.49'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -121,7 +121,7 @@ frappe.treeview_settings["Account"] = {
|
||||
},
|
||||
onrender: function(node) {
|
||||
if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){
|
||||
var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr";
|
||||
var dr_or_cr = in_list(["Liability", "Income", "Equity"], node.data.root_type) ? "Cr" : "Dr";
|
||||
if (node.data && node.data.balance!==undefined) {
|
||||
$('<span class="balance-area pull-right text-muted small">'
|
||||
+ (node.data.balance_in_account_currency ?
|
||||
|
||||
@@ -48,7 +48,10 @@ class BankAccount(Document):
|
||||
# Encode characters as numbers
|
||||
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
|
||||
|
||||
to_check = int(''.join(encoded))
|
||||
try:
|
||||
to_check = int(''.join(encoded))
|
||||
except ValueError:
|
||||
frappe.throw(_('IBAN is not valid'))
|
||||
|
||||
if to_check % 97 != 1:
|
||||
frappe.throw(_('IBAN is not valid'))
|
||||
|
||||
@@ -21,9 +21,29 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
|
||||
|
||||
refresh: function(frm) {
|
||||
if(frm.doc.docstatus==1) {
|
||||
frm.add_custom_button(__('Make Journal Entry'), function() {
|
||||
return frm.events.make_jv(frm);
|
||||
});
|
||||
frappe.db.get_value("Journal Entry Account", {
|
||||
'reference_type': 'Exchange Rate Revaluation',
|
||||
'reference_name': frm.doc.name,
|
||||
'docstatus': 1
|
||||
}, "sum(debit) as sum", (r) =>{
|
||||
let total_amt = 0;
|
||||
frm.doc.accounts.forEach(d=> {
|
||||
total_amt = total_amt + d['new_balance_in_base_currency'];
|
||||
});
|
||||
if(total_amt === r.sum) {
|
||||
frm.add_custom_button(__("Journal Entry"), function(){
|
||||
frappe.route_options = {
|
||||
'reference_type': 'Exchange Rate Revaluation',
|
||||
'reference_name': frm.doc.name
|
||||
};
|
||||
frappe.set_route("List", "Journal Entry");
|
||||
}, __("View"));
|
||||
} else {
|
||||
frm.add_custom_button(__('Create Journal Entry'), function() {
|
||||
return frm.events.make_jv(frm);
|
||||
});
|
||||
}
|
||||
}, 'Journal Entry');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
|
||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program or lp_details.loyalty_program)
|
||||
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
|
||||
|
||||
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules],
|
||||
|
||||
@@ -558,7 +558,7 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
negative_outstanding_invoices = []
|
||||
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
|
||||
if args.get("party_type") in ("Supplier", "Customer") and not args.get("voucher_no"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(args.get("party_type"),
|
||||
args.get("party"), args.get("party_account"), party_account_currency, company_currency)
|
||||
|
||||
@@ -589,7 +589,7 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
# Get all SO / PO which are not fully billed or aginst which full advance not paid
|
||||
orders_to_be_billed = []
|
||||
if (args.get("party_type") != "Student"):
|
||||
if (args.get("party_type") in ("Supplier", "Customer")):
|
||||
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
|
||||
args.get("party"), party_account_currency, company_currency)
|
||||
|
||||
@@ -601,7 +601,7 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre
|
||||
voucher_type = 'Sales Order'
|
||||
elif party_type == "Supplier":
|
||||
voucher_type = 'Purchase Order'
|
||||
elif party_type == "Employee":
|
||||
else:
|
||||
voucher_type = None
|
||||
|
||||
# Add cost center condition
|
||||
|
||||
@@ -336,6 +336,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if not self.is_return:
|
||||
self.update_against_document_in_jv()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Receipt")
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
self.update_billing_status_in_pr()
|
||||
|
||||
@@ -776,6 +777,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
|
||||
unlink_ref_doc_from_payment_entries(self)
|
||||
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Receipt")
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
self.update_billing_status_in_pr()
|
||||
|
||||
|
||||
@@ -451,6 +451,10 @@ def make_customer_and_address(customers):
|
||||
|
||||
|
||||
def add_customer(data):
|
||||
customer = data.get('full_name') or data.get('customer')
|
||||
if frappe.db.exists("Customer", customer.strip()):
|
||||
return customer.strip()
|
||||
|
||||
customer_doc = frappe.new_doc('Customer')
|
||||
customer_doc.customer_name = data.get('full_name') or data.get('customer')
|
||||
customer_doc.customer_pos_id = data.get('customer_pos_id')
|
||||
|
||||
@@ -174,9 +174,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
method: "erpnext.selling.doctype.quotation.quotation.make_sales_invoice",
|
||||
source_doctype: "Quotation",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
customer: me.frm.doc.customer || undefined,
|
||||
},
|
||||
setters: [{
|
||||
fieldtype: 'Link',
|
||||
label: __('Customer'),
|
||||
options: 'Customer',
|
||||
fieldname: 'party_name',
|
||||
default: me.frm.doc.customer,
|
||||
}],
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
status: ["!=", "Lost"],
|
||||
|
||||
@@ -166,6 +166,7 @@ class SalesInvoice(SellingController):
|
||||
self.make_gl_entries()
|
||||
|
||||
if not self.is_return:
|
||||
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.check_credit_limit()
|
||||
|
||||
@@ -222,6 +223,7 @@ class SalesInvoice(SellingController):
|
||||
self.update_billing_status_in_dn()
|
||||
|
||||
if not self.is_return:
|
||||
self.update_billing_status_for_zero_amount_refdoc("Delivery Note")
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.update_serial_no(in_cancel=True)
|
||||
|
||||
@@ -397,14 +399,18 @@ class SalesInvoice(SellingController):
|
||||
if pos.get('account_for_change_amount'):
|
||||
self.account_for_change_amount = pos.get('account_for_change_amount')
|
||||
|
||||
for fieldname in ('territory', 'naming_series', 'currency', 'taxes_and_charges', 'letter_head', 'tc_name',
|
||||
'company', 'select_print_heading', 'cash_bank_account', 'company_address',
|
||||
'write_off_account', 'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||
for fieldname in ('territory', 'naming_series', 'currency', 'letter_head', 'tc_name',
|
||||
'company', 'select_print_heading', 'cash_bank_account', 'write_off_account',
|
||||
'write_off_cost_center', 'apply_discount_on', 'cost_center'):
|
||||
if (not for_validate) or (for_validate and not self.get(fieldname)):
|
||||
self.set(fieldname, pos.get(fieldname))
|
||||
|
||||
customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
|
||||
|
||||
for field in ['taxes_and_charges', 'company_address']:
|
||||
if pos.get(field):
|
||||
self.set(field, pos.get(field))
|
||||
|
||||
if not customer_price_list:
|
||||
self.set('selling_price_list', pos.get('selling_price_list'))
|
||||
|
||||
|
||||
@@ -73,6 +73,12 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms_template",
|
||||
"label": __("Payment Terms Template"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname":"supplier_group",
|
||||
"label": __("Supplier Group"),
|
||||
|
||||
@@ -63,6 +63,12 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier"
|
||||
},
|
||||
{
|
||||
"fieldname":"payment_terms_template",
|
||||
"label": __("Payment Terms Template"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname":"supplier_group",
|
||||
"label": __("Supplier Group"),
|
||||
|
||||
@@ -540,6 +540,10 @@ class ReceivablePayableReport(object):
|
||||
where supplier_group=%s)""")
|
||||
values.append(self.filters.get("supplier_group"))
|
||||
|
||||
if self.filters.get("payment_terms_template"):
|
||||
conditions.append("party in (select name from tabSupplier where payment_terms=%s)")
|
||||
values.append(self.filters.get("payment_terms_template"))
|
||||
|
||||
accounts = [d.name for d in frappe.get_all("Account",
|
||||
filters={"account_type": account_type, "company": self.filters.company})]
|
||||
conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
|
||||
|
||||
@@ -12,11 +12,11 @@ def execute(filters=None):
|
||||
columns = get_columns()
|
||||
|
||||
if not filters.get("account"): return columns, []
|
||||
|
||||
|
||||
account_currency = frappe.db.get_value("Account", filters.account, "account_currency")
|
||||
|
||||
data = get_entries(filters)
|
||||
|
||||
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
|
||||
|
||||
@@ -24,7 +24,7 @@ def execute(filters=None):
|
||||
for d in data:
|
||||
total_debit += flt(d.debit)
|
||||
total_credit += flt(d.credit)
|
||||
|
||||
|
||||
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
|
||||
|
||||
bank_bal = flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) \
|
||||
@@ -39,7 +39,7 @@ def execute(filters=None):
|
||||
"credit": total_credit,
|
||||
"account_currency": account_currency
|
||||
},
|
||||
get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
|
||||
get_balance_row(_("Cheques and Deposits incorrectly cleared"), amounts_not_reflected_in_system,
|
||||
account_currency),
|
||||
{},
|
||||
get_balance_row(_("Calculated Bank Statement balance"), bank_bal, account_currency)
|
||||
@@ -55,9 +55,16 @@ def get_columns():
|
||||
"fieldtype": "Date",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_document",
|
||||
"label": _("Payment Document Type"),
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 220
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_entry",
|
||||
"label": _("Payment Entry"),
|
||||
"label": _("Payment Document"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "payment_document",
|
||||
"width": 220
|
||||
@@ -100,7 +107,7 @@ def get_columns():
|
||||
"label": _("Clearance Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": 110
|
||||
},
|
||||
},
|
||||
{
|
||||
"fieldname": "account_currency",
|
||||
"label": _("Currency"),
|
||||
@@ -112,9 +119,9 @@ def get_columns():
|
||||
|
||||
def get_entries(filters):
|
||||
journal_entries = frappe.db.sql("""
|
||||
select "Journal Entry" as payment_document, jv.posting_date,
|
||||
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
|
||||
jvd.credit_in_account_currency as credit, jvd.against_account,
|
||||
select "Journal Entry" as payment_document, jv.posting_date,
|
||||
jv.name as payment_entry, jvd.debit_in_account_currency as debit,
|
||||
jvd.credit_in_account_currency as credit, jvd.against_account,
|
||||
jv.cheque_no as reference_no, jv.cheque_date as ref_date, jv.clearance_date, jvd.account_currency
|
||||
from
|
||||
`tabJournal Entry Account` jvd, `tabJournal Entry` jv
|
||||
@@ -122,13 +129,13 @@ def get_entries(filters):
|
||||
and jvd.account = %(account)s and jv.posting_date <= %(report_date)s
|
||||
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
|
||||
and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1)
|
||||
|
||||
|
||||
payment_entries = frappe.db.sql("""
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no, reference_date as ref_date,
|
||||
if(paid_to=%(account)s, received_amount, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no, reference_date as ref_date,
|
||||
if(paid_to=%(account)s, received_amount, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry`
|
||||
@@ -156,25 +163,25 @@ def get_entries(filters):
|
||||
|
||||
return sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
|
||||
key=lambda k: k['posting_date'] or getdate(nowdate()))
|
||||
|
||||
|
||||
def get_amounts_not_reflected_in_system(filters):
|
||||
je_amount = frappe.db.sql("""
|
||||
select sum(jvd.debit_in_account_currency - jvd.credit_in_account_currency)
|
||||
from `tabJournal Entry Account` jvd, `tabJournal Entry` jv
|
||||
where jvd.parent = jv.name and jv.docstatus=1 and jvd.account=%(account)s
|
||||
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
|
||||
and jv.posting_date > %(report_date)s and jv.clearance_date <= %(report_date)s
|
||||
and ifnull(jv.is_opening, 'No') = 'No' """, filters)
|
||||
|
||||
je_amount = flt(je_amount[0][0]) if je_amount else 0.0
|
||||
|
||||
|
||||
pe_amount = frappe.db.sql("""
|
||||
select sum(if(paid_from=%(account)s, paid_amount, received_amount))
|
||||
from `tabPayment Entry`
|
||||
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
|
||||
where (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
|
||||
and posting_date > %(report_date)s and clearance_date <= %(report_date)s""", filters)
|
||||
|
||||
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
|
||||
|
||||
|
||||
return je_amount + pe_amount
|
||||
|
||||
def get_balance_row(label, amount, account_currency):
|
||||
|
||||
@@ -10,6 +10,7 @@ from frappe import _, _dict
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.report.financial_statements import get_cost_centers_with_children
|
||||
from six import iteritems
|
||||
from collections import OrderedDict
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
@@ -269,7 +270,7 @@ def group_by_field(group_by):
|
||||
return 'voucher_no'
|
||||
|
||||
def initialize_gle_map(gl_entries, filters):
|
||||
gle_map = frappe._dict()
|
||||
gle_map = OrderedDict()
|
||||
group_by = group_by_field(filters.get('group_by'))
|
||||
|
||||
for gle in gl_entries:
|
||||
|
||||
@@ -93,4 +93,6 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss):
|
||||
else:
|
||||
chart["type"] = "line"
|
||||
|
||||
chart["fieldtype"] = "Currency"
|
||||
|
||||
return chart
|
||||
@@ -104,6 +104,9 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
||||
# get balance of all entries that exist
|
||||
date = nowdate()
|
||||
|
||||
if account:
|
||||
acc = frappe.get_doc("Account", account)
|
||||
|
||||
try:
|
||||
year_start_date = get_fiscal_year(date, verbose=0)[1]
|
||||
except FiscalYearError:
|
||||
@@ -118,7 +121,12 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
||||
|
||||
allow_cost_center_in_entry_of_bs_account = get_allow_cost_center_in_entry_of_bs_account()
|
||||
|
||||
if cost_center and allow_cost_center_in_entry_of_bs_account:
|
||||
if account:
|
||||
report_type = acc.report_type
|
||||
else:
|
||||
report_type = ""
|
||||
|
||||
if cost_center and (allow_cost_center_in_entry_of_bs_account or report_type =='Profit and Loss'):
|
||||
cc = frappe.get_doc("Cost Center", cost_center)
|
||||
if cc.is_group:
|
||||
cond.append(""" exists (
|
||||
@@ -132,20 +140,13 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
|
||||
|
||||
if account:
|
||||
|
||||
acc = frappe.get_doc("Account", account)
|
||||
|
||||
if not frappe.flags.ignore_account_permission:
|
||||
acc.check_permission("read")
|
||||
|
||||
|
||||
if not allow_cost_center_in_entry_of_bs_account and acc.report_type == 'Profit and Loss':
|
||||
if report_type == 'Profit and Loss':
|
||||
# for pl accounts, get balance within a fiscal year
|
||||
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
|
||||
% year_start_date)
|
||||
elif allow_cost_center_in_entry_of_bs_account:
|
||||
# for all accounts, get balance within a fiscal year if maintain cost center in balance account is checked
|
||||
cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
|
||||
% year_start_date)
|
||||
# different filter for group and ledger - improved performance
|
||||
if acc.is_group:
|
||||
cond.append("""exists (
|
||||
@@ -732,7 +733,7 @@ def get_children(doctype, parent, company, is_root=False):
|
||||
filters.append(['company', '=', company])
|
||||
|
||||
else:
|
||||
fields += ['account_currency'] if doctype == 'Account' else []
|
||||
fields += ['root_type', 'account_currency'] if doctype == 'Account' else []
|
||||
fields += [parent_fieldname + ' as parent']
|
||||
|
||||
acc = frappe.get_list(doctype, fields=fields, filters=filters)
|
||||
|
||||
@@ -303,14 +303,17 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
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({
|
||||
method: "get_depreciation_rate",
|
||||
doc: frm.doc,
|
||||
args: row,
|
||||
callback: function(r) {
|
||||
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) {
|
||||
const row = locals[cdt][cdn];
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe, erpnext, math, json
|
||||
from frappe import _
|
||||
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 erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.assets.doctype.asset.depreciation \
|
||||
@@ -101,97 +101,88 @@ class Asset(AccountsController):
|
||||
|
||||
def set_depreciation_rate(self):
|
||||
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):
|
||||
depreciation_method = [d.depreciation_method for d in self.finance_books]
|
||||
|
||||
if 'Manual' not in depreciation_method:
|
||||
if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
|
||||
self.schedules = []
|
||||
|
||||
if not self.get("schedules") and self.available_for_use_date:
|
||||
total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')])
|
||||
if self.get("schedules") or not self.available_for_use_date:
|
||||
return
|
||||
|
||||
for d in self.get('finance_books'):
|
||||
self.validate_asset_finance_books(d)
|
||||
for d in self.get('finance_books'):
|
||||
self.validate_asset_finance_books(d)
|
||||
|
||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
value_after_depreciation = (flt(self.gross_purchase_amount) -
|
||||
flt(self.opening_accumulated_depreciation))
|
||||
|
||||
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))
|
||||
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
|
||||
cint(self.number_of_depreciations_booked)
|
||||
|
||||
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
|
||||
has_pro_rata = self.check_is_pro_rata(d)
|
||||
|
||||
number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \
|
||||
cint(self.number_of_depreciations_booked)
|
||||
if has_pro_rata:
|
||||
number_of_pending_depreciations += 1
|
||||
|
||||
from_date = self.available_for_use_date
|
||||
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):
|
||||
skip_row = False
|
||||
for n in range(number_of_pending_depreciations):
|
||||
# If depreciation is already completed (for double declining balance)
|
||||
if skip_row: continue
|
||||
|
||||
number_of_pending_depreciations += 1
|
||||
for n in range(number_of_pending_depreciations):
|
||||
if n == list(range(number_of_pending_depreciations))[-1]:
|
||||
schedule_date = add_months(self.available_for_use_date, n * 12)
|
||||
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)
|
||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
||||
d.total_number_of_depreciations, d)
|
||||
|
||||
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)
|
||||
if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
|
||||
schedule_date = add_months(d.depreciation_start_date,
|
||||
n * cint(d.frequency_of_depreciation))
|
||||
|
||||
else:
|
||||
schedule_date = add_months(d.depreciation_start_date, n * 12)
|
||||
depreciation_amount = \
|
||||
self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d)
|
||||
# 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))
|
||||
|
||||
if value_after_depreciation != 0:
|
||||
value_after_depreciation -= flt(depreciation_amount)
|
||||
depreciation_amount, days = get_pro_rata_amt(d,
|
||||
depreciation_amount, schedule_date, to_date)
|
||||
|
||||
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))
|
||||
schedule_date = add_days(schedule_date, days)
|
||||
|
||||
if d.depreciation_method in ("Straight Line", "Manual"):
|
||||
days = date_diff(schedule_date, from_date)
|
||||
if n == 0: days += 1
|
||||
if not depreciation_amount: continue
|
||||
value_after_depreciation -= flt(depreciation_amount,
|
||||
self.precision("gross_purchase_amount"))
|
||||
|
||||
depreciation_amount = days * rate_per_day
|
||||
from_date = schedule_date
|
||||
else:
|
||||
depreciation_amount = self.get_depreciation_amount(value_after_depreciation,
|
||||
d.total_number_of_depreciations, d)
|
||||
# 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:
|
||||
value_after_depreciation -= flt(depreciation_amount)
|
||||
if depreciation_amount > 0:
|
||||
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
|
||||
})
|
||||
|
||||
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
|
||||
})
|
||||
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):
|
||||
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
|
||||
@@ -261,31 +252,14 @@ class Asset(AccountsController):
|
||||
return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation)
|
||||
|
||||
def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row):
|
||||
if row.depreciation_method in ["Straight Line", "Manual"]:
|
||||
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
|
||||
precision = self.precision("gross_purchase_amount")
|
||||
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
depreciation_amount = (flt(row.value_after_depreciation) -
|
||||
flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) -
|
||||
cint(self.number_of_depreciations_booked)) * prorata_temporis
|
||||
cint(self.number_of_depreciations_booked))
|
||||
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
|
||||
|
||||
@@ -301,9 +275,12 @@ class Asset(AccountsController):
|
||||
flt(accumulated_depreciation_after_full_schedule),
|
||||
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}")
|
||||
.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):
|
||||
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
|
||||
@@ -388,7 +365,8 @@ class Asset(AccountsController):
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"posting_date": self.available_for_use_date,
|
||||
"credit": self.purchase_receipt_amount,
|
||||
"credit_in_account_currency": self.purchase_receipt_amount
|
||||
"credit_in_account_currency": self.purchase_receipt_amount,
|
||||
"cost_center": self.cost_center
|
||||
}))
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
@@ -397,7 +375,8 @@ class Asset(AccountsController):
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"posting_date": self.available_for_use_date,
|
||||
"debit": self.purchase_receipt_amount,
|
||||
"debit_in_account_currency": self.purchase_receipt_amount
|
||||
"debit_in_account_currency": self.purchase_receipt_amount,
|
||||
"cost_center": self.cost_center
|
||||
}))
|
||||
|
||||
if gl_entries:
|
||||
@@ -410,15 +389,7 @@ class Asset(AccountsController):
|
||||
if isinstance(args, string_types):
|
||||
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
|
||||
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':
|
||||
return 200.0 / args.get("total_number_of_depreciations")
|
||||
@@ -598,3 +569,15 @@ def make_journal_entry(asset_name):
|
||||
|
||||
def is_cwip_accounting_disabled():
|
||||
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)
|
||||
@@ -88,23 +88,23 @@ class TestAsset(unittest.TestCase):
|
||||
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 = '2020-06-06'
|
||||
asset.purchase_date = '2020-06-06'
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.save()
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
expected_schedules = [
|
||||
["2020-06-06", 147.54, 147.54],
|
||||
["2021-04-06", 44852.46, 45000.0],
|
||||
["2022-02-06", 45000.0, 90000.00]
|
||||
["2030-12-31", 30000.00, 30000.00],
|
||||
["2031-12-31", 30000.00, 60000.00],
|
||||
["2032-12-31", 30000.00, 90000.00]
|
||||
]
|
||||
|
||||
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.number_of_depreciations_booked = 1
|
||||
asset.opening_accumulated_depreciation = 40000
|
||||
asset.available_for_use_date = "2030-06-06"
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
expected_schedules = [
|
||||
["2020-06-06", 164.47, 40164.47],
|
||||
["2021-04-06", 49835.53, 90000.00]
|
||||
["2030-12-31", 14246.58, 54246.58],
|
||||
["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]
|
||||
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 = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.available_for_use_date = '2020-06-06'
|
||||
asset.purchase_date = '2020-06-06'
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Double Declining Balance",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": '2030-12-31'
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
["2020-06-06", 66666.67, 66666.67],
|
||||
["2021-04-06", 22222.22, 88888.89],
|
||||
["2022-02-06", 1111.11, 90000.0]
|
||||
['2030-12-31', 66667.00, 66667.00],
|
||||
['2031-12-31', 22222.11, 88889.11],
|
||||
['2032-12-31', 1110.89, 90000.0]
|
||||
]
|
||||
|
||||
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.number_of_depreciations_booked = 1
|
||||
asset.opening_accumulated_depreciation = 50000
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2029-11-30'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"next_depreciation_date": "2020-12-31",
|
||||
"depreciation_method": "Double Declining Balance",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
asset.insert()
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
asset.save()
|
||||
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
["2020-06-06", 33333.33, 83333.33],
|
||||
["2021-04-06", 6666.67, 90000.0]
|
||||
["2030-12-31", 33333.50, 83333.50],
|
||||
["2031-12-31", 6666.50, 90000.0]
|
||||
]
|
||||
|
||||
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 = frappe.get_doc('Asset', asset_name)
|
||||
asset.calculate_depreciation = 1
|
||||
asset.purchase_date = '2020-01-30'
|
||||
asset.purchase_date = '2030-01-30'
|
||||
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", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-12-31"
|
||||
"frequency_of_depreciation": 12,
|
||||
"depreciation_start_date": "2030-12-31"
|
||||
})
|
||||
|
||||
asset.insert()
|
||||
asset.save()
|
||||
|
||||
expected_schedules = [
|
||||
["2020-12-31", 28000.0, 28000.0],
|
||||
["2021-12-31", 30000.0, 58000.0],
|
||||
["2022-12-31", 30000.0, 88000.0],
|
||||
["2023-01-30", 2000.0, 90000.0]
|
||||
["2030-12-31", 27534.25, 27534.25],
|
||||
["2031-12-31", 30000.0, 57534.25],
|
||||
["2032-12-31", 30000.0, 87534.25],
|
||||
["2033-01-30", 2465.75, 90000.0]
|
||||
]
|
||||
|
||||
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")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 32129.24),
|
||||
("_Test Depreciations - _TC", 32129.24, 0.0)
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 30000.0),
|
||||
("_Test Depreciations - _TC", 30000.0, 0.0)
|
||||
)
|
||||
|
||||
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(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",
|
||||
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-06-06'
|
||||
asset.available_for_use_date = '2030-01-01'
|
||||
asset.purchase_date = '2030-01-01'
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 1000,
|
||||
"depreciation_method": "Written Down Value",
|
||||
@@ -298,9 +296,41 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
|
||||
|
||||
expected_schedules = [
|
||||
["2030-12-31", 4000.0, 4000.0],
|
||||
["2031-12-31", 2000.0, 6000.0],
|
||||
["2032-12-31", 1000.0, 7000.0],
|
||||
["2030-12-31", 4000.00, 4000.00],
|
||||
["2031-12-31", 2000.00, 6000.00],
|
||||
["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)]
|
||||
@@ -346,18 +376,19 @@ class TestAsset(unittest.TestCase):
|
||||
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 = '2020-06-06'
|
||||
asset.purchase_date = '2020-06-06'
|
||||
asset.available_for_use_date = nowdate()
|
||||
asset.purchase_date = nowdate()
|
||||
asset.append("finance_books", {
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": "2020-06-06"
|
||||
"depreciation_start_date": nowdate()
|
||||
})
|
||||
asset.insert()
|
||||
asset.submit()
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
post_depreciation_entries(date=add_months(nowdate(), 10))
|
||||
|
||||
scrap_asset(asset.name)
|
||||
|
||||
@@ -366,9 +397,9 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertTrue(asset.journal_entry_for_scrap)
|
||||
|
||||
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 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`
|
||||
@@ -412,9 +443,9 @@ class TestAsset(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
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 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)
|
||||
)
|
||||
|
||||
|
||||
@@ -46,75 +46,6 @@
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "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_in_quick_entry": 0,
|
||||
@@ -159,7 +90,7 @@
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-03-08 10:44:41.924547",
|
||||
"modified": "2019-05-26 18:31:19.930563",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Settings",
|
||||
|
||||
@@ -825,7 +825,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
if total != grand_total:
|
||||
if total != flt(grand_total, self.precision("grand_total")):
|
||||
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
||||
|
||||
def is_rounded_total_disabled(self):
|
||||
|
||||
@@ -38,7 +38,6 @@ status_map = {
|
||||
["To Bill", "eval:self.per_delivered == 100 and self.per_billed < 100 and self.docstatus == 1"],
|
||||
["To Deliver", "eval:self.per_delivered < 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.per_delivered == 100 and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Completed", "eval:self.order_type == 'Maintenance' and self.per_billed == 100 and self.docstatus == 1"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
],
|
||||
@@ -94,7 +93,8 @@ status_map = {
|
||||
["Partially Ordered", "eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1"],
|
||||
["Ordered", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'"],
|
||||
["Transferred", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Transfer'"],
|
||||
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"]
|
||||
["Issued", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Material Issue'"],
|
||||
["Manufactured", "eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'"]
|
||||
],
|
||||
"Bank Transaction": [
|
||||
["Unreconciled", "eval:self.docstatus == 1 and self.unallocated_amount>0"],
|
||||
|
||||
@@ -15,6 +15,9 @@ class calculate_taxes_and_totals(object):
|
||||
self.calculate()
|
||||
|
||||
def calculate(self):
|
||||
if not len(self.doc.get("items")):
|
||||
return
|
||||
|
||||
self.discount_amount_applied = False
|
||||
self._calculate()
|
||||
|
||||
@@ -326,7 +329,7 @@ class calculate_taxes_and_totals(object):
|
||||
|
||||
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||
|
||||
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate) \
|
||||
self.doc.base_grand_total = flt(self.doc.grand_total * self.doc.conversion_rate, self.doc.precision("base_grand_total")) \
|
||||
if (self.doc.taxes_and_charges_added or self.doc.taxes_and_charges_deducted) \
|
||||
else self.doc.base_net_total
|
||||
|
||||
|
||||
@@ -8,14 +8,8 @@ erpnext.LeadController = frappe.ui.form.Controller.extend({
|
||||
setup: function () {
|
||||
|
||||
this.frm.make_methods = {
|
||||
'Quotation': () => erpnext.utils.create_new_doc('Quotation', {
|
||||
'quotation_to': this.frm.doc.doctype,
|
||||
'party_name': this.frm.doc.name
|
||||
}),
|
||||
'Opportunity': () => erpnext.utils.create_new_doc('Opportunity', {
|
||||
'opportunity_from': this.frm.doc.doctype,
|
||||
'party_name': this.frm.doc.name
|
||||
})
|
||||
'Quotation': this.make_quotation,
|
||||
'Opportunity': this.create_opportunity
|
||||
}
|
||||
|
||||
this.frm.fields_dict.customer.get_query = function (doc, cdt, cdn) {
|
||||
|
||||
@@ -18,6 +18,10 @@ frappe.ui.form.on("Opportunity", {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (frm.doc.opportunity_from && frm.doc.party_name){
|
||||
frm.trigger('set_contact_link');
|
||||
}
|
||||
},
|
||||
|
||||
onload_post_render: function(frm) {
|
||||
|
||||
@@ -7,7 +7,9 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from requests_oauthlib import OAuth2Session
|
||||
import json, requests
|
||||
import json
|
||||
import requests
|
||||
import traceback
|
||||
from erpnext import encode_company_abbr
|
||||
|
||||
# QuickBooks requires a redirect URL, User will be redirect to this URL
|
||||
@@ -32,7 +34,6 @@ def callback(*args, **kwargs):
|
||||
class QuickBooksMigrator(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuickBooksMigrator, self).__init__(*args, **kwargs)
|
||||
from pprint import pprint
|
||||
self.oauth = OAuth2Session(
|
||||
client_id=self.client_id,
|
||||
redirect_uri=self.redirect_url,
|
||||
@@ -46,7 +47,9 @@ class QuickBooksMigrator(Document):
|
||||
if self.company:
|
||||
# We need a Cost Center corresponding to the selected erpnext Company
|
||||
self.default_cost_center = frappe.db.get_value('Company', self.company, 'cost_center')
|
||||
self.default_warehouse = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})[0]["name"]
|
||||
company_warehouses = frappe.get_all('Warehouse', filters={"company": self.company, "is_group": 0})
|
||||
if company_warehouses:
|
||||
self.default_warehouse = company_warehouses[0].name
|
||||
if self.authorization_endpoint:
|
||||
self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
|
||||
|
||||
@@ -218,7 +221,7 @@ class QuickBooksMigrator(Document):
|
||||
|
||||
def _fetch_general_ledger(self):
|
||||
try:
|
||||
query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint ,self.quickbooks_company_id)
|
||||
query_uri = "{}/company/{}/reports/GeneralLedger".format(self.api_endpoint, self.quickbooks_company_id)
|
||||
response = self._get(query_uri,
|
||||
params={
|
||||
"columns": ",".join(["tx_date", "txn_type", "credit_amt", "debt_amt"]),
|
||||
@@ -493,17 +496,17 @@ class QuickBooksMigrator(Document):
|
||||
"account_currency": customer["CurrencyRef"]["value"],
|
||||
"company": self.company,
|
||||
})[0]["name"]
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
receivable_account = None
|
||||
erpcustomer = frappe.get_doc({
|
||||
"doctype": "Customer",
|
||||
"quickbooks_id": customer["Id"],
|
||||
"customer_name" : encode_company_abbr(customer["DisplayName"], self.company),
|
||||
"customer_type" : "Individual",
|
||||
"customer_group" : "Commercial",
|
||||
"customer_name": encode_company_abbr(customer["DisplayName"], self.company),
|
||||
"customer_type": "Individual",
|
||||
"customer_group": "Commercial",
|
||||
"default_currency": customer["CurrencyRef"]["value"],
|
||||
"accounts": [{"company": self.company, "account": receivable_account}],
|
||||
"territory" : "All Territories",
|
||||
"territory": "All Territories",
|
||||
"company": self.company,
|
||||
}).insert()
|
||||
if "BillAddr" in customer:
|
||||
@@ -521,7 +524,7 @@ class QuickBooksMigrator(Document):
|
||||
item_dict = {
|
||||
"doctype": "Item",
|
||||
"quickbooks_id": item["Id"],
|
||||
"item_code" : encode_company_abbr(item["Name"], self.company),
|
||||
"item_code": encode_company_abbr(item["Name"], self.company),
|
||||
"stock_uom": "Unit",
|
||||
"is_stock_item": 0,
|
||||
"item_group": "All Item Groups",
|
||||
@@ -549,14 +552,14 @@ class QuickBooksMigrator(Document):
|
||||
erpsupplier = frappe.get_doc({
|
||||
"doctype": "Supplier",
|
||||
"quickbooks_id": vendor["Id"],
|
||||
"supplier_name" : encode_company_abbr(vendor["DisplayName"], self.company),
|
||||
"supplier_group" : "All Supplier Groups",
|
||||
"supplier_name": encode_company_abbr(vendor["DisplayName"], self.company),
|
||||
"supplier_group": "All Supplier Groups",
|
||||
"company": self.company,
|
||||
}).insert()
|
||||
if "BillAddr" in vendor:
|
||||
self._create_address(erpsupplier, "Supplier", vendor["BillAddr"], "Billing")
|
||||
if "ShipAddr" in vendor:
|
||||
self._create_address(erpsupplier, "Supplier",vendor["ShipAddr"], "Shipping")
|
||||
self._create_address(erpsupplier, "Supplier", vendor["ShipAddr"], "Shipping")
|
||||
except Exception as e:
|
||||
self._log_error(e)
|
||||
|
||||
@@ -829,7 +832,7 @@ class QuickBooksMigrator(Document):
|
||||
"currency": invoice["CurrencyRef"]["value"],
|
||||
"conversion_rate": invoice.get("ExchangeRate", 1),
|
||||
"posting_date": invoice["TxnDate"],
|
||||
"due_date": invoice.get("DueDate", invoice["TxnDate"]),
|
||||
"due_date": invoice.get("DueDate", invoice["TxnDate"]),
|
||||
"credit_to": credit_to_account,
|
||||
"supplier": frappe.get_all("Supplier",
|
||||
filters={
|
||||
@@ -1200,7 +1203,7 @@ class QuickBooksMigrator(Document):
|
||||
|
||||
|
||||
def _create_address(self, entity, doctype, address, address_type):
|
||||
try :
|
||||
try:
|
||||
if not frappe.db.exists({"doctype": "Address", "quickbooks_id": address["Id"]}):
|
||||
frappe.get_doc({
|
||||
"doctype": "Address",
|
||||
@@ -1252,8 +1255,6 @@ class QuickBooksMigrator(Document):
|
||||
|
||||
|
||||
def _log_error(self, execption, data=""):
|
||||
import json, traceback
|
||||
traceback.print_exc()
|
||||
frappe.log_error(title="QuickBooks Migration Error",
|
||||
message="\n".join([
|
||||
"Data",
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, math
|
||||
from frappe import _
|
||||
from frappe.utils import flt, rounded
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.hr.doctype.employee_loan.employee_loan import get_monthly_repayment_amount, check_repayment_method
|
||||
|
||||
class EmployeeLoanApplication(Document):
|
||||
def validate(self):
|
||||
check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
|
||||
self.validate_loan_amount()
|
||||
self.get_repayment_details()
|
||||
|
||||
def validate_loan_amount(self):
|
||||
maximum_loan_limit = frappe.db.get_value('Loan Type', self.loan_type, 'maximum_loan_amount')
|
||||
if maximum_loan_limit and self.loan_amount > maximum_loan_limit:
|
||||
frappe.throw(_("Loan Amount cannot exceed Maximum Loan Amount of {0}").format(maximum_loan_limit))
|
||||
|
||||
def get_repayment_details(self):
|
||||
if self.repayment_method == "Repay Over Number of Periods":
|
||||
self.repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
|
||||
|
||||
if self.repayment_method == "Repay Fixed Amount per Period":
|
||||
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
|
||||
if monthly_interest_rate:
|
||||
monthly_interest_amount = self.loan_amount * monthly_interest_rate
|
||||
if monthly_interest_amount >= self.repayment_amount:
|
||||
frappe.throw(_("Repayment amount {} should be greater than monthly interest amount {}").
|
||||
format(self.repayment_amount, monthly_interest_amount))
|
||||
|
||||
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
|
||||
math.log(self.repayment_amount - (monthly_interest_amount))) /
|
||||
(math.log(1 + monthly_interest_rate)))
|
||||
else:
|
||||
self.repayment_periods = self.loan_amount / self.repayment_amount
|
||||
|
||||
self.calculate_payable_amount()
|
||||
|
||||
def calculate_payable_amount(self):
|
||||
balance_amount = self.loan_amount
|
||||
self.total_payable_amount = 0
|
||||
self.total_payable_interest = 0
|
||||
|
||||
while(balance_amount > 0):
|
||||
interest_amount = rounded(balance_amount * flt(self.rate_of_interest) / (12*100))
|
||||
balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
|
||||
|
||||
self.total_payable_interest += interest_amount
|
||||
|
||||
self.total_payable_amount = self.loan_amount + self.total_payable_interest
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_employee_loan(source_name, target_doc = None):
|
||||
doclist = get_mapped_doc("Employee Loan Application", source_name, {
|
||||
"Employee Loan Application": {
|
||||
"doctype": "Employee Loan",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doclist
|
||||
@@ -39,31 +39,19 @@ frappe.ui.form.on('Loan', {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.status == "Sanctioned") {
|
||||
frm.add_custom_button(__('Make Disbursement Entry'), function() {
|
||||
frm.trigger("make_jv");
|
||||
})
|
||||
}
|
||||
if (frm.doc.repayment_schedule) {
|
||||
let total_amount_paid = 0;
|
||||
$.each(frm.doc.repayment_schedule || [], function(i, row) {
|
||||
if (row.paid) {
|
||||
total_amount_paid += row.total_payment;
|
||||
}
|
||||
});
|
||||
frm.set_value("total_amount_paid", total_amount_paid);
|
||||
; }
|
||||
if (frm.doc.docstatus == 1 && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
|
||||
frm.add_custom_button(__('Make Repayment Entry'), function() {
|
||||
frm.trigger("make_repayment_entry");
|
||||
})
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (frm.doc.status == "Sanctioned") {
|
||||
frm.add_custom_button(__('Create Disbursement Entry'), function() {
|
||||
frm.trigger("make_jv");
|
||||
}).addClass("btn-primary");
|
||||
} else if (frm.doc.status == "Disbursed" && frm.doc.repayment_start_date && (frm.doc.applicant_type == 'Member' || frm.doc.repay_from_salary == 0)) {
|
||||
frm.add_custom_button(__('Create Repayment Entry'), function() {
|
||||
frm.trigger("make_repayment_entry");
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
}
|
||||
frm.trigger("toggle_fields");
|
||||
},
|
||||
status: function (frm) {
|
||||
frm.toggle_reqd("disbursement_date", frm.doc.status == 'Disbursed')
|
||||
frm.toggle_reqd("repayment_start_date", frm.doc.status == 'Disbursed')
|
||||
},
|
||||
|
||||
make_jv: function (frm) {
|
||||
frappe.call({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
@@ -20,6 +21,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "applicant_type",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@@ -53,6 +55,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "applicant",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
@@ -86,6 +89,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "applicant_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
@@ -118,6 +122,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "loan_application",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -151,6 +156,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "loan_type",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -184,6 +190,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
@@ -215,7 +222,8 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"default": "Today",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
@@ -248,6 +256,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -282,6 +291,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Sanctioned",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@@ -299,7 +309,7 @@
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -316,6 +326,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.applicant_type==\"Employee\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "repay_from_salary",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
@@ -348,6 +359,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -380,6 +392,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "loan_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
@@ -415,6 +428,7 @@
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fetch_from": "loan_type.rate_of_interest",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "rate_of_interest",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 0,
|
||||
@@ -448,6 +462,8 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.status==\"Disbursed\"",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "disbursement_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
@@ -480,6 +496,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "repayment_start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
@@ -499,7 +516,7 @@
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
@@ -512,6 +529,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
@@ -544,6 +562,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Repay Over Number of Periods",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "repayment_method",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
@@ -579,6 +598,7 @@
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "repayment_periods",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
@@ -613,6 +633,7 @@
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "monthly_repayment_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
@@ -646,6 +667,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "account_info",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -678,6 +700,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -711,6 +734,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -744,6 +768,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
@@ -775,6 +800,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "loan_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -808,6 +834,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "interest_income_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -841,6 +868,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -873,6 +901,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "repayment_schedule",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
@@ -906,6 +935,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "section_break_17",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -939,6 +969,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "total_payment",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
@@ -972,6 +1003,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
@@ -1004,6 +1036,7 @@
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "total_interest_payable",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
@@ -1037,6 +1070,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "total_amount_paid",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
@@ -1070,6 +1104,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
@@ -1106,7 +1141,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:53.267145",
|
||||
"modified": "2019-07-10 13:04:20.953694",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Loan",
|
||||
@@ -1149,7 +1184,6 @@
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"user_permission_doctypes": "[\"Employee\"]",
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
|
||||
@@ -6,29 +6,33 @@ from __future__ import unicode_literals
|
||||
import frappe, math, json
|
||||
import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import flt, rounded, add_months, nowdate
|
||||
from frappe.utils import flt, rounded, add_months, nowdate, getdate
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
|
||||
class Loan(AccountsController):
|
||||
def validate(self):
|
||||
check_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
|
||||
validate_repayment_method(self.repayment_method, self.loan_amount, self.monthly_repayment_amount, self.repayment_periods)
|
||||
self.set_missing_fields()
|
||||
self.make_repayment_schedule()
|
||||
self.set_repayment_period()
|
||||
self.calculate_totals()
|
||||
|
||||
def set_missing_fields(self):
|
||||
if not self.company:
|
||||
self.company = erpnext.get_default_company()
|
||||
|
||||
if not self.posting_date:
|
||||
self.posting_date = nowdate()
|
||||
|
||||
if self.loan_type and not self.rate_of_interest:
|
||||
self.rate_of_interest = frappe.db.get_value("Loan Type", self.loan_type, "rate_of_interest")
|
||||
|
||||
if self.repayment_method == "Repay Over Number of Periods":
|
||||
self.monthly_repayment_amount = get_monthly_repayment_amount(self.repayment_method, self.loan_amount, self.rate_of_interest, self.repayment_periods)
|
||||
|
||||
if self.status == "Repaid/Closed":
|
||||
self.total_amount_paid = self.total_payment
|
||||
if self.status == 'Disbursed' and self.repayment_start_date < self.disbursement_date:
|
||||
frappe.throw(_("Repayment Start Date cannot be before Disbursement Date."))
|
||||
|
||||
if self.status == "Disbursed":
|
||||
self.make_repayment_schedule()
|
||||
self.set_repayment_period()
|
||||
self.calculate_totals()
|
||||
|
||||
def make_jv_entry(self):
|
||||
self.check_permission('write')
|
||||
@@ -105,20 +109,31 @@ def update_total_amount_paid(doc):
|
||||
frappe.db.set_value("Loan", doc.name, "total_amount_paid", total_amount_paid)
|
||||
|
||||
def update_disbursement_status(doc):
|
||||
disbursement = frappe.db.sql("""select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
|
||||
from `tabGL Entry` where account = %s and against_voucher_type = 'Loan' and against_voucher = %s""",
|
||||
(doc.payment_account, doc.name), as_dict=1)[0]
|
||||
if disbursement.disbursed_amount == doc.loan_amount:
|
||||
frappe.db.set_value("Loan", doc.name , "status", "Disbursed")
|
||||
if disbursement.disbursed_amount == 0:
|
||||
frappe.db.set_value("Loan", doc.name , "status", "Sanctioned")
|
||||
if disbursement.disbursed_amount > doc.loan_amount:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
|
||||
if disbursement.disbursed_amount > 0:
|
||||
frappe.db.set_value("Loan", doc.name , "disbursement_date", disbursement.posting_date)
|
||||
frappe.db.set_value("Loan", doc.name , "repayment_start_date", disbursement.posting_date)
|
||||
disbursement = frappe.db.sql("""
|
||||
select posting_date, ifnull(sum(credit_in_account_currency), 0) as disbursed_amount
|
||||
from `tabGL Entry`
|
||||
where account = %s and against_voucher_type = 'Loan' and against_voucher = %s
|
||||
""", (doc.payment_account, doc.name), as_dict=1)[0]
|
||||
|
||||
def check_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
|
||||
disbursement_date = None
|
||||
if not disbursement or disbursement.disbursed_amount == 0:
|
||||
status = "Sanctioned"
|
||||
elif disbursement.disbursed_amount == doc.loan_amount:
|
||||
disbursement_date = disbursement.posting_date
|
||||
status = "Disbursed"
|
||||
elif disbursement.disbursed_amount > doc.loan_amount:
|
||||
frappe.throw(_("Disbursed Amount cannot be greater than Loan Amount {0}").format(doc.loan_amount))
|
||||
|
||||
if status == 'Disbursed' and getdate(disbursement_date) > getdate(frappe.db.get_value("Loan", doc.name, "repayment_start_date")):
|
||||
frappe.throw(_("Disbursement Date cannot be after Loan Repayment Start Date"))
|
||||
|
||||
frappe.db.sql("""
|
||||
update `tabLoan`
|
||||
set status = %s, disbursement_date = %s
|
||||
where name = %s
|
||||
""", (status, disbursement_date, doc.name))
|
||||
|
||||
def validate_repayment_method(repayment_method, loan_amount, monthly_repayment_amount, repayment_periods):
|
||||
if repayment_method == "Repay Over Number of Periods" and not repayment_periods:
|
||||
frappe.throw(_("Please enter Repayment Periods"))
|
||||
|
||||
@@ -222,4 +237,4 @@ def make_jv_entry(loan, company, loan_account, applicant_type, applicant, loan_a
|
||||
"reference_name": loan,
|
||||
})
|
||||
journal_entry.set("accounts", account_amt_list)
|
||||
return journal_entry.as_dict()
|
||||
return journal_entry.as_dict()
|
||||
|
||||
@@ -23,9 +23,8 @@ frappe.ui.form.on('Loan Application', {
|
||||
},
|
||||
add_toolbar_buttons: function(frm) {
|
||||
if (frm.doc.status == "Approved") {
|
||||
frm.add_custom_button(__('Loan'), function() {
|
||||
frm.add_custom_button(__('Create Loan'), function() {
|
||||
frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.hr.doctype.loan_application.loan_application.make_loan",
|
||||
args: {
|
||||
"source_name": frm.doc.name
|
||||
@@ -37,7 +36,7 @@ frappe.ui.form.on('Loan Application', {
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
}).addClass("btn-primary");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9,11 +9,11 @@ from frappe.utils import flt, rounded
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, check_repayment_method
|
||||
from erpnext.hr.doctype.loan.loan import get_monthly_repayment_amount, validate_repayment_method
|
||||
|
||||
class LoanApplication(Document):
|
||||
def validate(self):
|
||||
check_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
|
||||
validate_repayment_method(self.repayment_method, self.loan_amount, self.repayment_amount, self.repayment_periods)
|
||||
self.validate_loan_amount()
|
||||
self.get_repayment_details()
|
||||
|
||||
@@ -29,14 +29,17 @@ class LoanApplication(Document):
|
||||
if self.repayment_method == "Repay Fixed Amount per Period":
|
||||
monthly_interest_rate = flt(self.rate_of_interest) / (12 *100)
|
||||
if monthly_interest_rate:
|
||||
self.repayment_periods = math.ceil((math.log(self.repayment_amount) -
|
||||
math.log(self.repayment_amount - (self.loan_amount*monthly_interest_rate))) /
|
||||
(math.log(1 + monthly_interest_rate)))
|
||||
min_repayment_amount = self.loan_amount*monthly_interest_rate
|
||||
if self.repayment_amount - min_repayment_amount < 0:
|
||||
frappe.throw(_("Repayment Amount must be greater than " \
|
||||
+ str(flt(min_repayment_amount, 2))))
|
||||
self.repayment_periods = math.ceil(math.log(self.repayment_amount) -
|
||||
math.log(self.repayment_amount - min_repayment_amount) /(math.log(1 + monthly_interest_rate)))
|
||||
else:
|
||||
self.repayment_periods = self.loan_amount / self.repayment_amount
|
||||
|
||||
self.calculate_payable_amount()
|
||||
|
||||
|
||||
def calculate_payable_amount(self):
|
||||
balance_amount = self.loan_amount
|
||||
self.total_payable_amount = 0
|
||||
@@ -47,9 +50,9 @@ class LoanApplication(Document):
|
||||
balance_amount = rounded(balance_amount + interest_amount - self.repayment_amount)
|
||||
|
||||
self.total_payable_interest += interest_amount
|
||||
|
||||
|
||||
self.total_payable_amount = self.loan_amount + self.total_payable_interest
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_loan(source_name, target_doc = None):
|
||||
doclist = get_mapped_doc("Loan Application", source_name, {
|
||||
|
||||
@@ -31,21 +31,22 @@ class TestLoanApplication(unittest.TestCase):
|
||||
"rate_of_interest": 9.2,
|
||||
"loan_amount": 250000,
|
||||
"repayment_method": "Repay Over Number of Periods",
|
||||
"repayment_periods": 24
|
||||
"repayment_periods": 18
|
||||
})
|
||||
loan_application.insert()
|
||||
|
||||
|
||||
|
||||
def test_loan_totals(self):
|
||||
loan_application = frappe.get_doc("Loan Application", {"applicant":self.applicant})
|
||||
self.assertEquals(loan_application.repayment_amount, 11445)
|
||||
self.assertEquals(loan_application.total_payable_interest, 24657)
|
||||
self.assertEquals(loan_application.total_payable_amount, 274657)
|
||||
|
||||
loan_application.repayment_method = "Repay Fixed Amount per Period"
|
||||
loan_application.repayment_amount = 15000
|
||||
self.assertEqual(loan_application.total_payable_interest, 18599)
|
||||
self.assertEqual(loan_application.total_payable_amount, 268599)
|
||||
self.assertEqual(loan_application.repayment_amount, 14923)
|
||||
|
||||
loan_application.repayment_periods = 24
|
||||
loan_application.save()
|
||||
loan_application.reload()
|
||||
|
||||
self.assertEqual(loan_application.repayment_periods, 18)
|
||||
self.assertEqual(loan_application.total_payable_interest, 18506)
|
||||
self.assertEqual(loan_application.total_payable_amount, 268506)
|
||||
self.assertEqual(loan_application.total_payable_interest, 24657)
|
||||
self.assertEqual(loan_application.total_payable_amount, 274657)
|
||||
self.assertEqual(loan_application.repayment_amount, 11445)
|
||||
@@ -516,10 +516,14 @@ class BOM(WebsiteGenerator):
|
||||
return erpnext.get_company_currency(self.company)
|
||||
|
||||
def add_to_cur_exploded_items(self, args):
|
||||
if self.cur_exploded_items.get(args.item_code):
|
||||
self.cur_exploded_items[args.item_code]["stock_qty"] += args.stock_qty
|
||||
key = (args.item_code)
|
||||
if args.operation:
|
||||
key = (args.item_code, args.operation)
|
||||
|
||||
if key in self.cur_exploded_items:
|
||||
self.cur_exploded_items[key]["stock_qty"] += args.stock_qty
|
||||
else:
|
||||
self.cur_exploded_items[args.item_code] = args
|
||||
self.cur_exploded_items[key] = args
|
||||
|
||||
def get_child_exploded_items(self, bom_no, stock_qty):
|
||||
""" Add all items from Flat BOM of child BOM"""
|
||||
@@ -609,7 +613,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
|
||||
and bom.name = %(bom)s
|
||||
and item.is_stock_item in (1, {is_stock_item})
|
||||
{where_conditions}
|
||||
group by item_code, stock_uom
|
||||
group by item_code, stock_uom {groupby_columns}
|
||||
order by idx"""
|
||||
|
||||
is_stock_item = 0 if include_non_stock_items else 1
|
||||
@@ -619,23 +623,29 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
|
||||
is_stock_item=is_stock_item,
|
||||
qty_field="stock_qty",
|
||||
select_columns = """, bom_item.source_warehouse, bom_item.operation, bom_item.include_item_in_manufacturing,
|
||||
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""")
|
||||
(Select idx from `tabBOM Item` where item_code = bom_item.item_code and parent = %(parent)s limit 1) as idx""",
|
||||
groupby_columns = """, bom_item.operation""")
|
||||
|
||||
items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
|
||||
elif fetch_scrap_items:
|
||||
query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item, qty_field="stock_qty")
|
||||
query = query.format(table="BOM Scrap Item", where_conditions="", select_columns=", bom_item.idx", is_stock_item=is_stock_item, qty_field="stock_qty", groupby_columns="")
|
||||
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
|
||||
else:
|
||||
query = query.format(table="BOM Item", where_conditions="", is_stock_item=is_stock_item,
|
||||
qty_field="stock_qty" if fetch_qty_in_stock_uom else "qty",
|
||||
select_columns = ", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing")
|
||||
select_columns = ", bom_item.uom, bom_item.conversion_factor, bom_item.source_warehouse, bom_item.idx, bom_item.operation, bom_item.include_item_in_manufacturing",
|
||||
groupby_columns = """, bom_item.operation""")
|
||||
items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
|
||||
|
||||
for item in items:
|
||||
if item.item_code in item_dict:
|
||||
item_dict[item.item_code]["qty"] += flt(item.qty)
|
||||
key = (item.item_code)
|
||||
if item.operation:
|
||||
key = (item.item_code, item.operation)
|
||||
|
||||
if key in item_dict:
|
||||
item_dict[key]["qty"] += flt(item.qty)
|
||||
else:
|
||||
item_dict[item.item_code] = item
|
||||
item_dict[key] = item
|
||||
|
||||
for item, item_details in item_dict.items():
|
||||
for d in [["Account", "expense_account", "default_expense_account"],
|
||||
|
||||
@@ -506,6 +506,7 @@ erpnext.patches.v10_0.update_hub_connector_domain
|
||||
erpnext.patches.v10_0.set_student_party_type
|
||||
erpnext.patches.v10_0.update_project_in_sle
|
||||
erpnext.patches.v10_0.fix_reserved_qty_for_sub_contract
|
||||
erpnext.patches.v10_0.repost_requested_qty_for_non_stock_uom_items
|
||||
erpnext.patches.v11_0.merge_land_unit_with_location
|
||||
erpnext.patches.v11_0.add_index_on_nestedset_doctypes
|
||||
erpnext.patches.v11_0.remove_modules_setup_page
|
||||
@@ -603,4 +604,5 @@ erpnext.patches.v11_1.update_bank_transaction_status
|
||||
erpnext.patches.v11_1.renamed_delayed_item_report
|
||||
erpnext.patches.v11_1.set_missing_opportunity_from
|
||||
erpnext.patches.v11_1.set_quotation_status
|
||||
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
|
||||
erpnext.patches.v11_1.update_default_supplier_in_item_defaults
|
||||
erpnext.patches.v11_1.set_status_for_material_request_type_manufacture
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright (c) 2019, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
|
||||
|
||||
count=0
|
||||
for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse
|
||||
from `tabMaterial Request Item` where docstatus = 1 and stock_uom<>uom"""):
|
||||
try:
|
||||
count += 1
|
||||
update_bin_qty(item_code, warehouse, {
|
||||
"indented_qty": get_indented_qty(item_code, warehouse),
|
||||
})
|
||||
if count % 200 == 0:
|
||||
frappe.db.commit()
|
||||
except:
|
||||
frappe.db.rollback()
|
||||
@@ -0,0 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.db.sql("""
|
||||
update `tabMaterial Request`
|
||||
set status='Manufactured'
|
||||
where docstatus=1 and material_request_type='Manufacture' and per_ordered=100 and status != 'Stopped'
|
||||
""")
|
||||
@@ -1290,11 +1290,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.frm.set_value("taxes", r.message);
|
||||
if(me.frm.doc.shipping_rule && me.frm.doc.taxes) {
|
||||
for (let tax of r.message) {
|
||||
me.frm.add_child("taxes", tax);
|
||||
}
|
||||
|
||||
if(me.frm.doc.shipping_rule) {
|
||||
me.frm.script_manager.trigger("shipping_rule");
|
||||
refresh_field("taxes");
|
||||
} else {
|
||||
me.frm.set_value("taxes", r.message);
|
||||
me.calculate_taxes_and_totals();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ erpnext.SMSManager = function SMSManager(doc) {
|
||||
'Purchase Receipt' : 'Items has been received against purchase receipt: ' + doc.name
|
||||
}
|
||||
|
||||
if (in_list(['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype))
|
||||
if (in_list(['Sales Order', 'Delivery Note', 'Sales Invoice'], doc.doctype))
|
||||
this.show(doc.contact_person, 'Customer', doc.customer, '', default_msg[doc.doctype]);
|
||||
else if (in_list(['Purchase Order', 'Purchase Receipt'], doc.doctype))
|
||||
this.show(doc.contact_person, 'Supplier', doc.supplier, '', default_msg[doc.doctype]);
|
||||
@@ -28,6 +28,8 @@ erpnext.SMSManager = function SMSManager(doc) {
|
||||
this.show('', '', '', doc.mobile_no, default_msg[doc.doctype]);
|
||||
else if (doc.doctype == 'Opportunity')
|
||||
this.show('', '', '', doc.contact_no, default_msg[doc.doctype]);
|
||||
else if (doc.doctype == 'Quotation')
|
||||
this.show(doc.contact_person, doc.quotation_to, doc.party_name, '', default_msg[doc.doctype]);
|
||||
else if (doc.doctype == 'Material Request')
|
||||
this.show('', '', '', '', default_msg[doc.doctype]);
|
||||
|
||||
|
||||
@@ -587,7 +587,6 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
if(!r.exc) {
|
||||
var doc = frappe.model.sync(r.message);
|
||||
cur_frm.dirty();
|
||||
erpnext.utils.clear_duplicates();
|
||||
cur_frm.refresh();
|
||||
}
|
||||
}
|
||||
@@ -618,28 +617,6 @@ erpnext.utils.map_current_doc = function(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.utils.clear_duplicates = function() {
|
||||
if(!cur_frm.doc.items) return;
|
||||
const unique_items = new Map();
|
||||
/*
|
||||
Create a Map of items with
|
||||
item_code => [qty, warehouse, batch_no]
|
||||
*/
|
||||
let items = [];
|
||||
|
||||
for (let item of cur_frm.doc.items) {
|
||||
if (!(unique_items.has(item.item_code) && unique_items.get(item.item_code)[0] === item.qty &&
|
||||
unique_items.get(item.item_code)[1] === item.warehouse && unique_items.get(item.item_code)[2] === item.batch_no &&
|
||||
unique_items.get(item.item_code)[3] === item.delivery_date && unique_items.get(item.item_code)[4] === item.required_date &&
|
||||
unique_items.get(item.item_code)[5] === item.rate)) {
|
||||
|
||||
unique_items.set(item.item_code, [item.qty, item.warehouse, item.batch_no, item.delivery_date, item.required_date, item.rate]);
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
cur_frm.doc.items = items;
|
||||
}
|
||||
|
||||
frappe.form.link_formatters['Item'] = function(value, doc) {
|
||||
if(doc && doc.item_name && doc.item_name !== value) {
|
||||
return value? value + ': ' + doc.item_name: doc.item_name;
|
||||
|
||||
@@ -486,13 +486,29 @@ def close_or_unclose_sales_orders(names, status):
|
||||
|
||||
frappe.local.message_log = []
|
||||
|
||||
def get_requested_item_qty(sales_order):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select sales_order_item, sum(stock_qty)
|
||||
from `tabMaterial Request Item`
|
||||
where docstatus = 1
|
||||
and sales_order = %s
|
||||
group by sales_order_item
|
||||
""", sales_order))
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_material_request(source_name, target_doc=None):
|
||||
requested_item_qty = get_requested_item_qty(source_name)
|
||||
|
||||
def postprocess(source, doc):
|
||||
doc.material_request_type = "Purchase"
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
# qty is for packed items, because packed items don't have stock_qty field
|
||||
qty = source.get("stock_qty") or source.get("qty")
|
||||
target.project = source_parent.project
|
||||
target.qty = qty - requested_item_qty.get(source.name, 0)
|
||||
target.conversion_factor = 1
|
||||
target.stock_qty = qty - requested_item_qty.get(source.name, 0)
|
||||
|
||||
doc = get_mapped_doc("Sales Order", source_name, {
|
||||
"Sales Order": {
|
||||
@@ -517,7 +533,7 @@ def make_material_request(source_name, target_doc=None):
|
||||
"stock_uom": "uom",
|
||||
"stock_qty": "qty"
|
||||
},
|
||||
"condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code),
|
||||
"condition": lambda doc: not frappe.db.exists('Product Bundle', doc.item_code) and doc.stock_qty > requested_item_qty.get(doc.name, 0),
|
||||
"postprocess": update_item
|
||||
}
|
||||
}, target_doc, postprocess)
|
||||
|
||||
@@ -155,7 +155,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
var me = this;
|
||||
if (this.frm.doc.customer) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details",
|
||||
method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points",
|
||||
args: {
|
||||
"customer": me.frm.doc.customer,
|
||||
"expiry_date": me.frm.doc.posting_date,
|
||||
@@ -1694,7 +1694,13 @@ class Payment {
|
||||
fieldtype: 'Check',
|
||||
label: 'Redeem Loyalty Points',
|
||||
fieldname: 'redeem_loyalty_points',
|
||||
onchange: () => {
|
||||
onchange: async function () {
|
||||
if (!cint(me.dialog.get_value('redeem_loyalty_points'))) {
|
||||
await Promise.all([
|
||||
me.frm.set_value('loyalty_points', 0),
|
||||
me.dialog.set_value('loyalty_points', 0)
|
||||
]);
|
||||
}
|
||||
me.update_cur_frm_value("redeem_loyalty_points", () => {
|
||||
frappe.flags.redeem_loyalty_points = false;
|
||||
me.update_loyalty_points();
|
||||
@@ -1838,13 +1844,14 @@ class Payment {
|
||||
});
|
||||
}
|
||||
|
||||
update_loyalty_points() {
|
||||
if (this.dialog.get_value("redeem_loyalty_points")) {
|
||||
this.dialog.set_value("loyalty_points", this.frm.doc.loyalty_points);
|
||||
this.dialog.set_value("loyalty_amount", this.frm.doc.loyalty_amount);
|
||||
this.update_payment_amount();
|
||||
this.show_paid_amount();
|
||||
}
|
||||
async update_loyalty_points() {
|
||||
const { loyalty_points, loyalty_amount } = this.frm.doc;
|
||||
await Promise.all([
|
||||
this.dialog.set_value("loyalty_points", loyalty_points),
|
||||
this.dialog.set_value("loyalty_amount", loyalty_amount)
|
||||
]);
|
||||
this.update_payment_amount();
|
||||
this.show_paid_amount();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Customer-wise Item Price"] = {
|
||||
"filters": [
|
||||
{
|
||||
"label": __("Customer"),
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"label": __("Item"),
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"get_query": () => {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters: { 'is_sales_item': 1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2019-06-12 03:25:36.263179",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letter_head": "Delta9",
|
||||
"modified": "2019-06-12 03:25:36.263179",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer-wise Item Price",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Customer",
|
||||
"report_name": "Customer-wise Item Price",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Sales Manager"
|
||||
},
|
||||
{
|
||||
"role": "Sales Master Manager"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from erpnext import get_default_company
|
||||
from erpnext.accounts.party import get_party_details
|
||||
from erpnext.stock.get_item_details import get_price_list_rate_for
|
||||
from frappe import _
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
if not filters.get("customer"):
|
||||
frappe.throw(_("Please select a Customer"))
|
||||
|
||||
columns = get_columns(filters)
|
||||
data = get_data(filters)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns(filters=None):
|
||||
return [
|
||||
{
|
||||
"label": _("Item Code"),
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Item Name"),
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"label": _("Selling Rate"),
|
||||
"fieldname": "selling_rate",
|
||||
"fieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"label": _("Available Stock"),
|
||||
"fieldname": "available_stock",
|
||||
"fieldtype": "Float",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Price List"),
|
||||
"fieldname": "price_list",
|
||||
"fieldtype": "Link",
|
||||
"options": "Price List",
|
||||
"width": 120
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters=None):
|
||||
data = []
|
||||
customer_details = get_customer_details(filters)
|
||||
|
||||
items = get_selling_items(filters)
|
||||
item_stock_map = frappe.get_all("Bin", fields=["item_code", "sum(actual_qty) AS available"], group_by="item_code")
|
||||
item_stock_map = {item.item_code: item.available for item in item_stock_map}
|
||||
|
||||
for item in items:
|
||||
price_list_rate = get_price_list_rate_for(customer_details, item.item_code) or 0.0
|
||||
available_stock = item_stock_map.get(item.item_code)
|
||||
|
||||
data.append({
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"selling_rate": price_list_rate,
|
||||
"price_list": customer_details.get("price_list"),
|
||||
"available_stock": available_stock,
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_customer_details(filters):
|
||||
customer_details = get_party_details(party=filters.get("customer"), party_type="Customer")
|
||||
customer_details.update({
|
||||
"company": get_default_company(),
|
||||
"price_list": customer_details.get("selling_price_list")
|
||||
})
|
||||
|
||||
return customer_details
|
||||
|
||||
|
||||
def get_selling_items(filters):
|
||||
if filters.get("item"):
|
||||
item_filters = {"item_code": filters.get("item"), "is_sales_item": 1, "disabled": 0}
|
||||
else:
|
||||
item_filters = {"is_sales_item": 1, "disabled": 0}
|
||||
|
||||
items = frappe.get_all("Item", filters=item_filters, fields=["item_code", "item_name"], order_by="item_name")
|
||||
|
||||
return items
|
||||
@@ -115,6 +115,11 @@ def get_data():
|
||||
{"sales_order_item": ("!=",""), "docstatus": 1},
|
||||
["parent", "qty", "sales_order", "item_code"])
|
||||
|
||||
packed_items = get_packed_items([row.name for row in sales_order_entry])
|
||||
|
||||
item_with_product_bundle = get_item_with_product_bundle([row.item_code for row in sales_order_entry])
|
||||
item_with_product_bundle = [row.new_item_code for row in item_with_product_bundle]
|
||||
|
||||
materials_request_dict = {}
|
||||
|
||||
for record in mr_records:
|
||||
@@ -139,19 +144,57 @@ def get_data():
|
||||
|
||||
# check for pending sales order
|
||||
if cint(so.net_qty) > cint(materials_request.get('qty')):
|
||||
so_record = {
|
||||
"item_code": so.item_code,
|
||||
"item_name": so.item_name,
|
||||
"description": so.description,
|
||||
"sales_order_no": so.name,
|
||||
"date": so.transaction_date,
|
||||
"material_request": ','.join(materials_request.get('material_requests', [])),
|
||||
"customer": so.customer,
|
||||
"territory": so.territory,
|
||||
"so_qty": so.net_qty,
|
||||
"requested_qty": cint(materials_request.get('qty')),
|
||||
"pending_qty": so.net_qty - cint(materials_request.get('qty')),
|
||||
"company": so.company
|
||||
}
|
||||
pending_so.append(so_record)
|
||||
return pending_so
|
||||
|
||||
if so.item_code not in item_with_product_bundle:
|
||||
so_record = {
|
||||
"item_code": so.item_code,
|
||||
"item_name": so.item_name,
|
||||
"description": so.description,
|
||||
"sales_order_no": so.name,
|
||||
"date": so.transaction_date,
|
||||
"material_request": ','.join(materials_request.get('material_requests', [])),
|
||||
"customer": so.customer,
|
||||
"territory": so.territory,
|
||||
"so_qty": so.net_qty,
|
||||
"requested_qty": cint(materials_request.get('qty')),
|
||||
"pending_qty": so.net_qty - cint(materials_request.get('qty')),
|
||||
"company": so.company
|
||||
}
|
||||
pending_so.append(so_record)
|
||||
else:
|
||||
for item in packed_items:
|
||||
material_request_qty = materials_request.get('qty') if materials_request.get('qty') else 0
|
||||
so_record = {
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"description": item.description,
|
||||
"sales_order_no": so.name,
|
||||
"date": so.transaction_date,
|
||||
"material_request": ','.join(materials_request.get('material_requests', [])),
|
||||
"customer": so.customer,
|
||||
"territory": so.territory,
|
||||
"so_qty": item.qty,
|
||||
"requested_qty": cint(material_request_qty * item.qty),
|
||||
"pending_qty": (so.net_qty - cint(material_request_qty)) * item.qty,
|
||||
"company": so.company
|
||||
}
|
||||
pending_so.append(so_record)
|
||||
|
||||
|
||||
return pending_so
|
||||
|
||||
def get_item_with_product_bundle(item_list):
|
||||
|
||||
bundled_item = frappe.get_all("Product Bundle", filters = [
|
||||
("new_item_code", "IN", item_list)
|
||||
], fields = ["new_item_code"])
|
||||
|
||||
return bundled_item
|
||||
|
||||
def get_packed_items(sales_order_list):
|
||||
|
||||
packed_items = frappe.get_all("Packed Item", filters = [
|
||||
("parent", "IN", sales_order_list)
|
||||
], fields = ["*"])
|
||||
|
||||
return packed_items
|
||||
|
||||
@@ -60,7 +60,7 @@ def place_order():
|
||||
quotation.flags.ignore_permissions = True
|
||||
quotation.submit()
|
||||
|
||||
if quotation.lead:
|
||||
if quotation.quotation_to == 'Lead' and quotation.party_name:
|
||||
# company used to create customer accounts
|
||||
frappe.defaults.set_user_default("company", quotation.company)
|
||||
|
||||
|
||||
@@ -189,6 +189,9 @@ class Item(WebsiteGenerator):
|
||||
'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
|
||||
|
||||
def validate_website_image(self):
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
|
||||
"""Validate if the website image is a public file"""
|
||||
auto_set_website_image = False
|
||||
if not self.website_image and self.image:
|
||||
@@ -208,8 +211,7 @@ class Item(WebsiteGenerator):
|
||||
|
||||
if not file_doc:
|
||||
if not auto_set_website_image:
|
||||
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found")
|
||||
.format(self.website_image, self.name))
|
||||
frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
|
||||
|
||||
self.website_image = None
|
||||
|
||||
@@ -220,6 +222,9 @@ class Item(WebsiteGenerator):
|
||||
self.website_image = None
|
||||
|
||||
def make_thumbnail(self):
|
||||
if frappe.flags.in_import:
|
||||
return
|
||||
|
||||
"""Make a thumbnail of `website_image`"""
|
||||
import requests.exceptions
|
||||
|
||||
|
||||
@@ -31,13 +31,16 @@ class ItemPrice(Document):
|
||||
frappe.throw(_("Valid From Date must be lesser than Valid Upto Date."))
|
||||
|
||||
def update_price_list_details(self):
|
||||
self.buying, self.selling, self.currency = \
|
||||
frappe.db.get_value("Price List",
|
||||
{"name": self.price_list, "enabled": 1},
|
||||
["buying", "selling", "currency"])
|
||||
if self.price_list:
|
||||
self.buying, self.selling, self.currency = \
|
||||
frappe.db.get_value("Price List",
|
||||
{"name": self.price_list, "enabled": 1},
|
||||
["buying", "selling", "currency"])
|
||||
|
||||
def update_item_details(self):
|
||||
self.item_name, self.item_description = frappe.db.get_value("Item",self.item_code,["item_name", "description"])
|
||||
if self.item_code:
|
||||
self.item_name, self.item_description = frappe.db.get_value("Item",
|
||||
self.item_code,["item_name", "description"])
|
||||
|
||||
def check_duplicates(self):
|
||||
conditions = "where item_code=%(item_code)s and price_list=%(price_list)s and name != %(name)s"
|
||||
|
||||
@@ -14,6 +14,8 @@ frappe.listview_settings['Material Request'] = {
|
||||
return [__("Transfered"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Material Issue") {
|
||||
return [__("Issued"), "green", "per_ordered,=,100"];
|
||||
} else if (doc.material_request_type == "Manufacture") {
|
||||
return [__("Manufactured"), "green", "per_ordered,=,100"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,8 @@ class StockEntry(StockController):
|
||||
item.set(f, item_details.get(f))
|
||||
|
||||
if not item.transfer_qty and item.qty:
|
||||
item.transfer_qty = item.qty * item.conversion_factor
|
||||
item.transfer_qty = ( flt(item.qty, item.precision("qty"))
|
||||
* flt(item.conversion_factor, item.precision("conversion_factor")) )
|
||||
|
||||
if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
|
||||
and not item.serial_no
|
||||
|
||||
@@ -110,7 +110,7 @@ def get_reserved_qty(item_code, warehouse):
|
||||
return flt(reserved_qty[0][0]) if reserved_qty else 0
|
||||
|
||||
def get_indented_qty(item_code, warehouse):
|
||||
indented_qty = frappe.db.sql("""select sum(mr_item.qty - mr_item.ordered_qty)
|
||||
indented_qty = frappe.db.sql("""select sum((mr_item.qty - mr_item.ordered_qty) * mr_item.conversion_factor)
|
||||
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
|
||||
where mr_item.item_code=%s and mr_item.warehouse=%s
|
||||
and mr_item.qty > mr_item.ordered_qty and mr_item.parent=mr.name
|
||||
|
||||
Reference in New Issue
Block a user