Compare commits

..

69 Commits

Author SHA1 Message Date
Sahil Khan
2a58b6c20b Merge branch 'hotfix' 2019-07-19 17:11:27 +05:30
Sahil Khan
196bf2104f bumped to version 11.1.48 2019-07-19 17:31:27 +05:50
Deepesh Garg
a33145e6d9 Merge pull request #18400 from frappe/kennethsequeira-patch-1
fix: Update image in README file
2019-07-19 09:53:39 +05:30
Deepesh Garg
6161d3362d Merge pull request #18409 from rohitwaghchaure/not_able_to_make_material_request_for_product_bundle_from_so_hotfix
fix: not able to make material request for bundle items from the sale…
2019-07-19 09:47:43 +05:30
Deepesh Garg
fac8ed0630 Merge pull request #18389 from deepeshgarg007/lead_email
fix: Fetch email when making Opportunity from Lead
2019-07-19 08:05:46 +05:30
Rohit Waghchaure
3279ad55f6 fix: not able to make material request for bundle items from the sales order 2019-07-19 07:55:28 +05:30
Mangesh-Khairnar
9bdb04d7ca Merge pull request #18380 from rohitwaghchaure/pos_fixed_address_and_taxes_and_charges_not_set_as_per_profile_hotfix
fix: address and taxes not set as per pos profile in the pos invoice
2019-07-18 19:34:44 +05:30
Kenneth Sequeira
89270ef2c0 Update image in README file
Updated the link of the ERPNext logo image in the read me file
2019-07-18 17:01:23 +05:30
rohitwaghchaure
c0ea006571 Merge pull request #18392 from rohitwaghchaure/fix_send_sms_not_working_for_quotation
fix: send sms not working for the quotation
2019-07-18 15:46:01 +05:30
Mangesh-Khairnar
de3b79cce8 fix: precision on comparing with the outstanding amount (#18374) 2019-07-18 13:51:05 +05:30
Rohit Waghchaure
f326d5c9a1 fix: send sms not working for the quotation 2019-07-18 13:50:37 +05:30
Anurag Mishra
e112ac2345 fix: handled value Error (#18362) 2019-07-18 12:43:05 +05:30
Suraj Shetty
ad1719bbe4 feat(customer): Add report to show item prices per Customer (#17929)
feat(customer): Add report to show item prices per Customer
2019-07-18 10:15:05 +05:30
deepeshgarg007
f2c464dda8 fix: Fetch email when making Opportunity from Lead 2019-07-18 09:05:57 +05:30
Suraj Shetty
2b64b38a6e Merge branch 'hotfix' into hotfix-customer-item-price-report 2019-07-18 08:49:36 +05:30
rohitwaghchaure
dcbafecf58 Merge pull request #18384 from rohitwaghchaure/offline_pos_syncing_issue_for_customer_hotfix
fix: offline pos syncing issue for customer
2019-07-17 21:38:15 +05:30
rohitwaghchaure
20229b915c Merge pull request #18386 from rohitwaghchaure/cost_center_not_able_to_access
fix: cost center not able to access
2019-07-17 21:37:03 +05:30
Rohit Waghchaure
7a1a0fb5fb fix: cost center not able to access 2019-07-17 21:20:05 +05:30
Rohit Waghchaure
fddef7bd5c fix: offline pos syncing issue for customer 2019-07-17 20:34:50 +05:30
Rohit Waghchaure
804854f051 fix: address and taxes not set as per pos profile in the pos invoice 2019-07-17 19:34:42 +05:30
rohitwaghchaure
bdb48f34ed Merge pull request #18370 from Anurag810/fix_precision
fix: precision for base grand total amount
2019-07-17 19:00:18 +05:30
Suraj Shetty
986b5115da Merge branch 'hotfix' into hotfix-customer-item-price-report 2019-07-17 17:29:27 +05:30
Anurag Mishra
b37f4a157c fix precision 2019-07-17 14:53:25 +05:30
rohitwaghchaure
f650b287ef fix: conversion issue (#18357) 2019-07-16 15:58:43 +05:30
Sahil Khan
1866cebd14 Merge branch 'hotfix' 2019-07-16 15:23:51 +05:30
Sahil Khan
bb5bf06e0c bumped to version 11.1.47 2019-07-16 15:43:50 +05:50
Deepesh Garg
ecf8e20462 fix: Fixes in get_balance_on function (#18323) 2019-07-16 09:40:47 +05:30
Deepesh Garg
5a0bc043aa Merge pull request #18321 from deepeshgarg007/quo_opp_address_fix_hotfix
fix: Address filtering fix in opportunity
2019-07-16 09:03:07 +05:30
Deepesh Garg
a2c9c9505c Merge pull request #18349 from rohitwaghchaure/added_payment_terms_in_accounts_payble_summary
feat: added payment terms filter in the accounts payable summary
2019-07-16 08:39:50 +05:30
Rohit Waghchaure
f6060fa5f2 feat: added payment terms filter in the accounts payable summary 2019-07-16 01:53:34 +05:30
rohitwaghchaure
b4cf29391b fix: taxes sequence if shipping rule is set in the sales invoice (#18330) 2019-07-15 18:58:06 +05:30
Anurag Mishra
a6a6f69e26 fix: Validate loan repayment amount in Loan Application (#18170)
* fix: validate loan repayment amount

* fix: test for Loan Application
2019-07-15 14:16:45 +05:30
Mangesh-Khairnar
db38c02aee fix(bin): update requested qty in bin (#18313) 2019-07-15 14:08:12 +05:30
deepeshgarg007
e107d430eb fix: Address filtering fix in opportunity 2019-07-15 13:58:03 +05:30
Mangesh-Khairnar
0c849ac0f3 fix: bank-reconcillation-statement, payment entry links (#18312) 2019-07-15 11:59:59 +05:30
rohitwaghchaure
6b86bb462b Merge pull request #17845 from fproldan/fix_by_voucher_order
fix: General Ledger Group by Voucher not ordering by posting_date
2019-07-14 17:24:35 +05:30
rohitwaghchaure
80cfc88abb Merge pull request #18111 from sunhoww/pos_loyalty
fix: POS loyalty issues
2019-07-14 17:11:05 +05:30
rohitwaghchaure
3c425584a7 Merge pull request #18296 from rohitwaghchaure/fixed_dr_cr_issue_account_tree_hotfix
fix: Dr / Cr label for balance of the ledgers in the account tree
2019-07-14 17:01:13 +05:30
Mangesh-Khairnar
af561becc6 Merge pull request #17956 from harounasow/17902
fix(payment entry): payment to shareholder #17902
2019-07-13 19:10:17 +05:30
Mangesh-Khairnar
0ef08618c9 Merge branch 'hotfix' into 17902 2019-07-13 12:01:27 +05:30
Deepesh Garg
373d90f802 Merge pull request #18292 from nabinhait/taxes-calc-only-if-items
fix: Calculate taxes and totals only if items added in transaction
2019-07-12 23:03:41 +05:30
Mangesh-Khairnar
ceaedd0f95 fix: handle shareholder option in payment entry 2019-07-12 16:28:03 +05:30
Rohit Waghchaure
fd7c4e9822 fix: dr / cr label for balance of the ledgers in the account tree 2019-07-12 15:49:51 +05:30
Deepesh Garg
26878d907e Merge pull request #18000 from govindsmenokee/patch-2
fix: lead reference changed to dynamic link #17987
2019-07-12 14:41:00 +05:30
Mangesh-Khairnar
ae3c21046b Merge branch 'hotfix' into 17902 2019-07-12 14:39:56 +05:30
Nabin Hait
f01baaa56f fix: Calculate taxes and totals only if items added in transaction 2019-07-12 14:27:19 +05:30
Aditya Hase
ac534c64bc fix: QuickBooks Migrator (#18273)
* fix: Remove unused imports

* style: Remove whitespace before :

* style: Insert whitespace after ,

* style: One import per line

* fix: Remove unused local variable

* style: Remove multiple whitespace after :

* fix: Company might not have any warehouses when on_update hook for QBM is being executed

This used to throw while renaming a Company that is either used with QuickBooks
Migrator or is set as a Default Company in Global Defaults.
2019-07-12 11:19:53 +05:30
Rohan
6e8e42ca7c Merge branch 'hotfix' into hotfix-customer-item-price-report 2019-07-12 00:07:21 +05:30
Nabin Hait
7d3d99a9b8 fix: UX fixes in loan (#18219)
* fix: UX fixes in loan

* Update loan.py
2019-07-11 17:17:49 +05:30
Don-Leopardo
63e83cc005 Merge branch 'hotfix' into fix_by_voucher_order 2019-07-10 10:47:52 -03:00
Sun Howwrongbum
505332e680 feat(pos): reset payments and loyalty points on redeem uncheck 2019-06-29 21:35:58 +05:30
Sun Howwrongbum
ada3330403 fix(pos): changing loyalty points not updating payment amounts 2019-06-29 21:18:53 +05:30
Sun Howwrongbum
e0143e081c fix(pos): loyalty details not rendering in cart area 2019-06-29 20:48:27 +05:30
Don-Leopardo
f03522ad9e Merge branch 'hotfix' into fix_by_voucher_order 2019-06-27 18:09:39 -03:00
Don-Leopardo
f80be2d1f4 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-25 08:39:58 -03:00
Don-Leopardo
1bd60ac343 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-24 11:30:19 -03:00
Himanshu
2fc2d9b8ad Merge branch 'hotfix' into patch-2 2019-06-20 00:34:47 +05:30
Govind S Menokee
839b653f6b fix: lead reference changed to dynamic link #17987 2019-06-19 21:04:35 +05:30
Rohan Bansal
61cfa275f8 fix(customer): Improve performance by reducing queries 2019-06-17 12:14:09 +05:30
harounasow
7319bcb577 Merge branch 'hotfix' into 17902 2019-06-17 00:00:30 +02:00
Harouna Sow
3750a06d4f Merge branch '17902' of https://github.com/harounasow/erpnext into 17902 2019-06-16 23:45:19 +02:00
Harouna Sow
c79c8dae19 fix(payment entry): payment to shareholder 2019-06-16 23:42:57 +02:00
frappe user
1e4e2c81e9 fix(payment entry): payment to shareholder 2019-06-16 23:17:48 +02:00
Rohan Bansal
334106a6d0 feat(customer): Add report to show item prices per Customer 2019-06-13 14:46:21 +05:30
Francisco Roldán
eba2455ad3 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-11 10:22:26 -03:00
Don-Leopardo
63267d3616 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-10 17:28:17 -03:00
Don-Leopardo
5ed80a5ba9 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-10 08:23:07 -03:00
Don-Leopardo
98e5692a40 Merge branch 'hotfix' into fix_by_voucher_order 2019-06-05 08:34:01 -03:00
NahuelOperto
bc293cf251 fix order in general ledger 2019-06-04 08:47:09 -03:00
39 changed files with 435 additions and 228 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -397,14 +397,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(fieldname))
if not customer_price_list:
self.set('selling_price_list', pos.get('selling_price_list'))

View File

@@ -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"),

View File

@@ -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"),

View File

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

View File

@@ -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)
@@ -58,8 +58,8 @@ def get_columns():
{
"fieldname": "payment_entry",
"label": _("Payment Entry"),
"fieldtype": "Dynamic Link",
"options": "payment_document",
"fieldtype": "Link",
"options": "Payment Entry",
"width": 220
},
{
@@ -100,7 +100,7 @@ def get_columns():
"label": _("Clearance Date"),
"fieldtype": "Date",
"width": 110
},
},
{
"fieldname": "account_currency",
"label": _("Currency"),
@@ -112,9 +112,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 +122,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 +156,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):

View File

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

View File

@@ -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,7 @@ 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 cost_center and (allow_cost_center_in_entry_of_bs_account or acc.report_type =='Profit and Loss'):
cc = frappe.get_doc("Cost Center", cost_center)
if cc.is_group:
cond.append(""" exists (
@@ -132,20 +135,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 acc.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 +728,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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
}
],

View File

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

View File

@@ -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");
}
}
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();
}
}

View File

@@ -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]);

View File

@@ -503,10 +503,12 @@ def make_material_request(source_name, target_doc=None):
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 = source.stock_qty - requested_item_qty.get(source.name, 0)
target.qty = qty - requested_item_qty.get(source.name, 0)
target.conversion_factor = 1
target.stock_qty = source.stock_qty - requested_item_qty.get(source.name, 0)
target.stock_qty = qty - requested_item_qty.get(source.name, 0)
doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {

View File

@@ -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();
}
}

View File

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

View File

@@ -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"
}
]
}

View File

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

View File

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

View File

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

View File

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