Merge branch 'develop' into youtube-analytics
This commit is contained in:
commit
54ab426c1c
@ -43,7 +43,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"label": "Bank Statement",
|
||||
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
"links": "[\n {\n \"label\": \"Bank\",\n \"name\": \"Bank\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Account\",\n \"name\": \"Bank Account\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Reconciliation\",\n \"name\": \"bank-reconciliation\",\n \"type\": \"page\"\n },\n {\n \"label\": \"Bank Clearance\",\n \"name\": \"Bank Clearance\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Transaction Entry\",\n \"name\": \"Bank Statement Transaction Entry\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Bank Statement Settings\",\n \"name\": \"Bank Statement Settings\",\n \"type\": \"doctype\"\n }\n]"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
@ -98,7 +98,7 @@
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Accounting",
|
||||
"modified": "2020-06-19 12:42:44.054598",
|
||||
"modified": "2020-09-03 10:37:07.865801",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
@ -158,4 +158,4 @@
|
||||
"type": "Dashboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -244,6 +244,8 @@ class Account(NestedSet):
|
||||
|
||||
super(Account, self).on_trash(True)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select name from tabAccount
|
||||
where is_group = 1 and docstatus != 2 and company = %s
|
||||
|
@ -225,7 +225,7 @@ def build_tree_from_json(chart_template, chart_data=None):
|
||||
|
||||
account['parent_account'] = parent
|
||||
account['expandable'] = True if identify_is_group(child) else False
|
||||
account['value'] = (child.get('account_number') + ' - ' + account_name) \
|
||||
account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
|
||||
if child.get('account_number') else account_name
|
||||
accounts.append(account)
|
||||
_import_accounts(child, account['value'])
|
||||
|
@ -225,7 +225,7 @@
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-22 20:13:26.043092",
|
||||
"modified": "2020-08-03 20:13:26.043092",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
@ -60,12 +60,12 @@ class BankClearance(Document):
|
||||
""".format(condition=condition), {"account": self.account, "from":self.from_date,
|
||||
"to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
|
||||
|
||||
pos_entries = []
|
||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||
if self.include_pos_transactions:
|
||||
pos_entries = frappe.db.sql("""
|
||||
pos_sales_invoices = frappe.db.sql("""
|
||||
select
|
||||
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
|
||||
si.posting_date, si.debit_to as against_account, sip.clearance_date,
|
||||
si.posting_date, si.customer as against_account, sip.clearance_date,
|
||||
account.account_currency, 0 as credit
|
||||
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
|
||||
where
|
||||
@ -75,7 +75,20 @@ class BankClearance(Document):
|
||||
si.posting_date ASC, si.name DESC
|
||||
""", {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1)
|
||||
|
||||
entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
|
||||
pos_purchase_invoices = frappe.db.sql("""
|
||||
select
|
||||
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
|
||||
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
|
||||
account.account_currency, 0 as debit
|
||||
from `tabPurchase Invoice` pi, `tabAccount` account
|
||||
where
|
||||
pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
|
||||
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
|
||||
order by
|
||||
pi.posting_date ASC, pi.name DESC
|
||||
""", {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
|
||||
|
||||
entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
|
||||
key=lambda k: k['posting_date'] or getdate(nowdate()))
|
||||
|
||||
self.set('payment_entries', [])
|
||||
|
@ -27,4 +27,4 @@ def get_vouchar_detials(column_list, doctype, docname):
|
||||
for col in column_list:
|
||||
sanitize_searchfield(col)
|
||||
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
|
||||
.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0]
|
||||
.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]
|
||||
|
@ -135,7 +135,7 @@ var create_import_button = function(frm) {
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
clearInterval(frm.page["interval"]);
|
||||
frm.page.set_indicator(__('Import Successfull'), 'blue');
|
||||
frm.page.set_indicator(__('Import Successful'), 'blue');
|
||||
create_reset_button(frm);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
from frappe.test_runner import make_test_objects
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
def test_create_test_data():
|
||||
frappe.set_user("Administrator")
|
||||
# create test item
|
||||
@ -95,7 +97,6 @@ def test_create_test_data():
|
||||
})
|
||||
coupon_code.insert()
|
||||
|
||||
|
||||
class TestCouponCode(unittest.TestCase):
|
||||
def setUp(self):
|
||||
test_create_test_data()
|
||||
|
@ -44,6 +44,19 @@ frappe.ui.form.on("Dunning", {
|
||||
);
|
||||
frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
|
||||
if(frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(__('Ledger'), function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": frm.doc.name,
|
||||
"from_date": frm.doc.posting_date,
|
||||
"to_date": frm.doc.posting_date,
|
||||
"company": frm.doc.company,
|
||||
"show_cancelled_entries": frm.doc.docstatus === 2
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
}, __('View'));
|
||||
}
|
||||
},
|
||||
overdue_days: function (frm) {
|
||||
frappe.db.get_value(
|
||||
@ -125,9 +138,9 @@ frappe.ui.form.on("Dunning", {
|
||||
},
|
||||
calculate_interest_and_amount: function (frm) {
|
||||
const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
|
||||
const interest_amount = interest_per_year / 365 * frm.doc.overdue_days || 0;
|
||||
const dunning_amount = interest_amount + frm.doc.dunning_fee;
|
||||
const grand_total = frm.doc.outstanding_amount + dunning_amount;
|
||||
const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount'));
|
||||
const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount'));
|
||||
const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total'));
|
||||
frm.set_value("interest_amount", interest_amount);
|
||||
frm.set_value("dunning_amount", dunning_amount);
|
||||
frm.set_value("grand_total", grand_total);
|
||||
|
@ -29,10 +29,10 @@
|
||||
"company_address_display",
|
||||
"section_break_6",
|
||||
"dunning_type",
|
||||
"interest_amount",
|
||||
"dunning_fee",
|
||||
"column_break_8",
|
||||
"rate_of_interest",
|
||||
"dunning_fee",
|
||||
"interest_amount",
|
||||
"section_break_12",
|
||||
"dunning_amount",
|
||||
"grand_total",
|
||||
@ -215,7 +215,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "dunning_type.interest_rate",
|
||||
"fetch_from": "dunning_type.rate_of_interest",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "rate_of_interest",
|
||||
"fieldtype": "Float",
|
||||
@ -315,7 +315,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-21 18:20:23.512151",
|
||||
"modified": "2020-08-03 18:55:43.683053",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Dunning",
|
||||
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
from six import string_types
|
||||
from frappe.utils import getdate, get_datetime, rounded, flt
|
||||
from frappe.utils import getdate, get_datetime, rounded, flt, cint
|
||||
from erpnext.loan_management.doctype.loan_interest_accrual.loan_interest_accrual import days_in_year
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
@ -27,11 +27,11 @@ class Dunning(AccountsController):
|
||||
amounts = calculate_interest_and_amount(
|
||||
self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
||||
if self.interest_amount != amounts.get('interest_amount'):
|
||||
self.interest_amount = amounts.get('interest_amount')
|
||||
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
|
||||
if self.dunning_amount != amounts.get('dunning_amount'):
|
||||
self.dunning_amount = amounts.get('dunning_amount')
|
||||
self.dunning_amount = flt(amounts.get('dunning_amount'), self.precision('dunning_amount'))
|
||||
if self.grand_total != amounts.get('grand_total'):
|
||||
self.grand_total = amounts.get('grand_total')
|
||||
self.grand_total = flt(amounts.get('grand_total'), self.precision('grand_total'))
|
||||
|
||||
def on_submit(self):
|
||||
self.make_gl_entries()
|
||||
@ -47,10 +47,13 @@ class Dunning(AccountsController):
|
||||
gl_entries = []
|
||||
invoice_fields = ["project", "cost_center", "debit_to", "party_account_currency", "conversion_rate", "cost_center"]
|
||||
inv = frappe.db.get_value("Sales Invoice", self.sales_invoice, invoice_fields, as_dict=1)
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
invoice_fields.extend(accounting_dimensions)
|
||||
|
||||
dunning_in_company_currency = flt(self.dunning_amount * inv.conversion_rate)
|
||||
default_cost_center = frappe.get_cached_value('Company', self.company, 'cost_center')
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": inv.debit_to,
|
||||
@ -90,10 +93,10 @@ def resolve_dunning(doc, state):
|
||||
|
||||
def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
||||
interest_amount = 0
|
||||
grand_total = 0
|
||||
if rate_of_interest:
|
||||
interest_per_year = rounded(flt(outstanding_amount) * flt(rate_of_interest))/100
|
||||
interest_amount = (
|
||||
interest_per_year / days_in_year(get_datetime(posting_date).year)) * int(overdue_days)
|
||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
||||
grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
|
||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
||||
return {
|
||||
|
17
erpnext/accounts/doctype/dunning/dunning_dashboard.py
Normal file
17
erpnext/accounts/doctype/dunning/dunning_dashboard.py
Normal file
@ -0,0 +1,17 @@
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
'fieldname': 'dunning',
|
||||
'non_standard_fieldnames': {
|
||||
'Journal Entry': 'reference_name',
|
||||
'Payment Entry': 'reference_name'
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Payment'),
|
||||
'items': ['Payment Entry', 'Journal Entry']
|
||||
}
|
||||
]
|
||||
}
|
@ -13,7 +13,7 @@ def get_data():
|
||||
},
|
||||
{
|
||||
'label': _('References'),
|
||||
'items': ['Period Closing Voucher', 'Request for Quotation', 'Tax Withholding Category']
|
||||
'items': ['Period Closing Voucher', 'Tax Withholding Category']
|
||||
},
|
||||
{
|
||||
'label': _('Target Details'),
|
||||
|
@ -638,20 +638,12 @@ $.extend(erpnext.journal_entry, {
|
||||
return { filters: filters };
|
||||
},
|
||||
|
||||
reverse_journal_entry: function(frm) {
|
||||
var me = frm.doc;
|
||||
for(var i=0; i<me.accounts.length; i++) {
|
||||
me.accounts[i].credit += me.accounts[i].debit;
|
||||
me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit;
|
||||
me.accounts[i].credit -= me.accounts[i].debit;
|
||||
me.accounts[i].credit_in_account_currency = me.accounts[i].credit;
|
||||
me.accounts[i].debit_in_account_currency = me.accounts[i].debit;
|
||||
me.accounts[i].reference_type = "Journal Entry";
|
||||
me.accounts[i].reference_name = me.name
|
||||
}
|
||||
frm.copy_doc();
|
||||
cur_frm.reload_doc();
|
||||
}
|
||||
reverse_journal_entry: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
$.extend(erpnext.journal_entry, {
|
||||
|
@ -841,13 +841,33 @@ def get_opening_accounts(company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark
|
||||
from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
|
||||
where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s
|
||||
and (jv_detail.reference_type is null or jv_detail.reference_type = '')
|
||||
and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield),
|
||||
(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len))
|
||||
if not frappe.db.has_column('Journal Entry', searchfield):
|
||||
return []
|
||||
|
||||
return frappe.db.sql("""
|
||||
SELECT jv.name, jv.posting_date, jv.user_remark
|
||||
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
|
||||
WHERE jv_detail.parent = jv.name
|
||||
AND jv_detail.account = %(account)s
|
||||
AND IFNULL(jv_detail.party, '') = %(party)s
|
||||
AND (
|
||||
jv_detail.reference_type IS NULL
|
||||
OR jv_detail.reference_type = ''
|
||||
)
|
||||
AND jv.docstatus = 1
|
||||
AND jv.`{0}` LIKE %(txt)s
|
||||
ORDER BY jv.name DESC
|
||||
LIMIT %(offset)s, %(limit)s
|
||||
""".format(searchfield), dict(
|
||||
account=filters.get("account"),
|
||||
party=cstr(filters.get("party")),
|
||||
txt="%{0}%".format(txt),
|
||||
offset=start,
|
||||
limit=page_len
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@ -1001,3 +1021,34 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
journal_entry.posting_date = nowdate()
|
||||
journal_entry.inter_company_journal_entry_reference = name
|
||||
return journal_entry.as_dict()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_reverse_journal_entry(source_name, target_doc=None, ignore_permissions=False):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def update_accounts(source, target, source_parent):
|
||||
target.reference_type = "Journal Entry"
|
||||
target.reference_name = source_parent.name
|
||||
|
||||
doclist = get_mapped_doc("Journal Entry", source_name, {
|
||||
"Journal Entry": {
|
||||
"doctype": "Journal Entry",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Journal Entry Account": {
|
||||
"doctype": "Journal Entry Account",
|
||||
"field_map": {
|
||||
"account_currency": "account_currency",
|
||||
"exchange_rate": "exchange_rate",
|
||||
"debit_in_account_currency": "credit_in_account_currency",
|
||||
"debit": "credit",
|
||||
"credit_in_account_currency": "debit_in_account_currency",
|
||||
"credit": "debit",
|
||||
},
|
||||
"postprocess": update_accounts,
|
||||
},
|
||||
}, target_doc, ignore_permissions=ignore_permissions)
|
||||
|
||||
return doclist
|
@ -167,6 +167,49 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
self.assertFalse(gle)
|
||||
|
||||
def test_reverse_journal_entry(self):
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
"Sales - _TC", 100, exchange_rate=50, save=False)
|
||||
|
||||
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||
jv.get("accounts")[1].exchange_rate = 1
|
||||
jv.submit()
|
||||
|
||||
rjv = make_reverse_journal_entry(jv.name)
|
||||
rjv.posting_date = nowdate()
|
||||
rjv.submit()
|
||||
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""", rjv.name, as_dict=1)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
expected_values = {
|
||||
"_Test Bank USD - _TC": {
|
||||
"account_currency": "USD",
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit": 5000,
|
||||
"credit_in_account_currency": 100,
|
||||
},
|
||||
"Sales - _TC": {
|
||||
"account_currency": "INR",
|
||||
"debit": 5000,
|
||||
"debit_in_account_currency": 5000,
|
||||
"credit": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
}
|
||||
}
|
||||
|
||||
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account][field], gle[field])
|
||||
|
||||
def test_disallow_change_in_account_currency_for_a_party(self):
|
||||
# create jv in USD
|
||||
jv = make_journal_entry("_Test Bank USD - _TC",
|
||||
|
@ -42,7 +42,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_query("bank_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
is_company_account: 1
|
||||
is_company_account: 1,
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1049,4 +1050,4 @@ frappe.ui.form.on('Payment Entry', {
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
@ -897,7 +897,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
total_amount = ref_doc.get("grand_total")
|
||||
exchange_rate = 1
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
if reference_doctype == "Dunning":
|
||||
elif reference_doctype == "Dunning":
|
||||
total_amount = ref_doc.get("dunning_amount")
|
||||
exchange_rate = 1
|
||||
outstanding_amount = ref_doc.get("dunning_amount")
|
||||
@ -1101,7 +1101,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
'outstanding_amount': doc.get('dunning_amount'),
|
||||
'allocated_amount': doc.get('dunning_amount')
|
||||
})
|
||||
else:
|
||||
else:
|
||||
pe.append("references", {
|
||||
'reference_doctype': dt,
|
||||
'reference_name': dn,
|
||||
|
@ -27,6 +27,7 @@ class PaymentOrder(Document):
|
||||
frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and mode_of_payment like %(txt)s
|
||||
@ -38,6 +39,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(""" select supplier from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and supplier like %(txt)s and
|
||||
|
@ -24,7 +24,7 @@ class POSClosingEntry(Document):
|
||||
if user:
|
||||
frappe.throw(_("POS Closing Entry {} against {} between selected period"
|
||||
.format(frappe.bold("already exists"), frappe.bold(self.user))), title=_("Invalid Period"))
|
||||
|
||||
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
@ -41,6 +41,7 @@ class POSClosingEntry(Document):
|
||||
{"data": self, "currency": currency})
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=['user'])
|
||||
return [c['user'] for c in cashiers_list]
|
||||
@ -48,12 +49,12 @@ def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
def get_pos_invoices(start, end, user):
|
||||
data = frappe.db.sql("""
|
||||
select
|
||||
select
|
||||
name, timestamp(posting_date, posting_time) as "timestamp"
|
||||
from
|
||||
from
|
||||
`tabPOS Invoice`
|
||||
where
|
||||
owner = %s and docstatus = 1 and
|
||||
where
|
||||
owner = %s and docstatus = 1 and
|
||||
(consolidated_invoice is NULL or consolidated_invoice = '')
|
||||
""", (user), as_dict=1)
|
||||
|
||||
@ -101,7 +102,7 @@ def make_closing_entry_from_opening(opening_entry):
|
||||
for t in d.taxes:
|
||||
existing_tax = [tx for tx in taxes if tx.account_head == t.account_head and tx.rate == t.rate]
|
||||
if existing_tax:
|
||||
existing_tax[0].amount += flt(t.tax_amount);
|
||||
existing_tax[0].amount += flt(t.tax_amount);
|
||||
else:
|
||||
taxes.append(frappe._dict({
|
||||
'account_head': t.account_head,
|
||||
|
@ -21,7 +21,7 @@ from six import iteritems
|
||||
class POSInvoice(SalesInvoice):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(POSInvoice, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def validate(self):
|
||||
if not cint(self.is_pos):
|
||||
frappe.throw(_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment")))
|
||||
@ -58,7 +58,7 @@ class POSInvoice(SalesInvoice):
|
||||
if self.redeem_loyalty_points and self.loyalty_points:
|
||||
self.apply_loyalty_points()
|
||||
self.set_status(update=True)
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
# run on cancel method of selling controller
|
||||
super(SalesInvoice, self).on_cancel()
|
||||
@ -68,10 +68,10 @@ class POSInvoice(SalesInvoice):
|
||||
against_psi_doc = frappe.get_doc("POS Invoice", self.return_against)
|
||||
against_psi_doc.delete_loyalty_point_entry()
|
||||
against_psi_doc.make_loyalty_point_entry()
|
||||
|
||||
|
||||
def validate_stock_availablility(self):
|
||||
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
|
||||
|
||||
|
||||
for d in self.get('items'):
|
||||
if d.serial_no:
|
||||
filters = {
|
||||
@ -89,11 +89,11 @@ class POSInvoice(SalesInvoice):
|
||||
for s in serial_nos:
|
||||
if s in reserved_serial_nos:
|
||||
invalid_serial_nos.append(s)
|
||||
|
||||
|
||||
if len(invalid_serial_nos):
|
||||
multiple_nos = 's' if len(invalid_serial_nos) > 1 else ''
|
||||
frappe.throw(_("Row #{}: Serial No{}. {} has already been transacted into another POS Invoice. \
|
||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
||||
Please select valid serial no.".format(d.idx, multiple_nos,
|
||||
frappe.bold(', '.join(invalid_serial_nos)))), title=_("Not Available"))
|
||||
else:
|
||||
if allow_negative_stock:
|
||||
@ -105,9 +105,9 @@ class POSInvoice(SalesInvoice):
|
||||
.format(d.idx, frappe.bold(d.item_code), frappe.bold(d.warehouse))), title=_("Not Available"))
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
frappe.msgprint(_('Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. \
|
||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
||||
Available quantity {}.'.format(d.idx, frappe.bold(d.item_code),
|
||||
frappe.bold(d.warehouse), frappe.bold(d.qty))), title=_("Not Available"))
|
||||
|
||||
|
||||
def validate_serialised_or_batched_item(self):
|
||||
for d in self.get("items"):
|
||||
serialized = d.get("has_serial_no")
|
||||
@ -125,7 +125,7 @@ class POSInvoice(SalesInvoice):
|
||||
if batched and no_batch_selected:
|
||||
frappe.throw(_('Row #{}: No batch selected against item: {}. Please select a batch or remove it to complete transaction.'
|
||||
.format(d.idx, frappe.bold(d.item_code))), title=_("Invalid Item"))
|
||||
|
||||
|
||||
def validate_return_items(self):
|
||||
if not self.get("is_return"): return
|
||||
|
||||
@ -158,7 +158,7 @@ class POSInvoice(SalesInvoice):
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be positive").format(entry.idx))
|
||||
if self.is_return and entry.amount > 0:
|
||||
frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx))
|
||||
|
||||
|
||||
def validate_pos_return(self):
|
||||
if self.is_pos and self.is_return:
|
||||
total_amount_in_payments = 0
|
||||
@ -167,12 +167,12 @@ class POSInvoice(SalesInvoice):
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
if total_amount_in_payments < invoice_total:
|
||||
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total)))
|
||||
|
||||
|
||||
def validate_loyalty_transaction(self):
|
||||
if self.redeem_loyalty_points and (not self.loyalty_redemption_account or not self.loyalty_redemption_cost_center):
|
||||
expense_account, cost_center = frappe.db.get_value('Loyalty Program', self.loyalty_program, ["expense_account", "cost_center"])
|
||||
if not self.loyalty_redemption_account:
|
||||
self.loyalty_redemption_account = expense_account
|
||||
self.loyalty_redemption_account = expense_account
|
||||
if not self.loyalty_redemption_cost_center:
|
||||
self.loyalty_redemption_cost_center = cost_center
|
||||
|
||||
@ -212,7 +212,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
if update:
|
||||
self.db_set('status', self.status, update_modified = update_modified)
|
||||
|
||||
|
||||
def set_pos_fields(self, for_validate=False):
|
||||
"""Set retail related fields from POS Profiles"""
|
||||
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
|
||||
@ -315,25 +315,25 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||
from `tabStock Ledger Entry`
|
||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and warehouse = %s
|
||||
order by posting_date desc, posting_time desc
|
||||
limit 1""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
|
||||
pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
and p.docstatus = 1
|
||||
and p_item.docstatus = 1
|
||||
and p_item.item_code = %s
|
||||
and p_item.warehouse = %s
|
||||
""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||
|
||||
|
||||
if sle_qty and pos_sales_qty and sle_qty > pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
@ -360,14 +360,14 @@ def make_merge_log(invoices):
|
||||
merge_log = frappe.new_doc("POS Invoice Merge Log")
|
||||
merge_log.posting_date = getdate(nowdate())
|
||||
for inv in invoices:
|
||||
inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),
|
||||
inv_data = frappe.db.get_values("POS Invoice", inv.get('name'),
|
||||
["customer", "posting_date", "grand_total"], as_dict=1)[0]
|
||||
merge_log.customer = inv_data.customer
|
||||
merge_log.append("pos_invoices", {
|
||||
'pos_invoice': inv.get('name'),
|
||||
'customer': inv_data.customer,
|
||||
'posting_date': inv_data.posting_date,
|
||||
'grand_total': inv_data.grand_total
|
||||
'grand_total': inv_data.grand_total
|
||||
})
|
||||
|
||||
if merge_log.get('pos_invoices'):
|
||||
|
@ -31,8 +31,7 @@ frappe.ui.form.on('POS Profile', {
|
||||
frm.set_query("print_format", function() {
|
||||
return {
|
||||
filters: [
|
||||
['Print Format', 'doc_type', '=', 'Sales Invoice'],
|
||||
['Print Format', 'print_format_type', '=', 'Jinja'],
|
||||
['Print Format', 'doc_type', '=', 'POS Invoice']
|
||||
]
|
||||
};
|
||||
});
|
||||
@ -45,10 +44,6 @@ frappe.ui.form.on('POS Profile', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("print_format", function() {
|
||||
return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} };
|
||||
});
|
||||
|
||||
frm.set_query('company_address', function(doc) {
|
||||
if(!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
|
@ -302,10 +302,10 @@
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Warehouse",
|
||||
"mandatory_depends_on": "update_stock",
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@ -350,4 +350,4 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +105,7 @@ def get_series():
|
||||
return frappe.get_meta("POS Invoice").get_field("naming_series").options or "s"
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
user = frappe.session['user']
|
||||
company = filters.get('company') or frappe.defaults.get_user_default('company')
|
||||
|
@ -8,6 +8,8 @@ import unittest
|
||||
from erpnext.stock.get_item_details import get_pos_profile
|
||||
from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
class TestPOSProfile(unittest.TestCase):
|
||||
def test_pos_profile(self):
|
||||
make_pos_profile()
|
||||
@ -88,7 +90,7 @@ def make_pos_profile(**args):
|
||||
"write_off_account": args.write_off_account or "_Test Write Off - _TC",
|
||||
"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
|
||||
})
|
||||
|
||||
|
||||
payments = [{
|
||||
'mode_of_payment': 'Cash',
|
||||
'default': 1
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
# For license information, please see license.txt
|
||||
|
||||
@ -208,7 +207,7 @@ def get_serial_no_for_item(args):
|
||||
|
||||
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_pricing_rules,
|
||||
get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
|
||||
get_applied_pricing_rules, get_pricing_rule_items, get_product_discount_rule)
|
||||
|
||||
if isinstance(doc, string_types):
|
||||
doc = json.loads(doc)
|
||||
@ -237,7 +236,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
|
||||
update_args_for_pricing_rule(args)
|
||||
|
||||
pricing_rules = (get_applied_pricing_rules(args)
|
||||
pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules'))
|
||||
if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
|
||||
|
||||
if pricing_rules:
|
||||
@ -365,8 +364,9 @@ def set_discount_amount(rate, item_details):
|
||||
item_details.rate = rate
|
||||
|
||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items
|
||||
for d in json.loads(pricing_rules):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
|
||||
get_pricing_rule_items)
|
||||
for d in get_applied_pricing_rules(pricing_rules):
|
||||
if not d or not frappe.db.exists("Pricing Rule", d): continue
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
@ -433,14 +433,14 @@ def make_pricing_rule(doctype, docname):
|
||||
return doc
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
|
||||
items = [filters.get('value')]
|
||||
if filters.get('apply_on') != 'Item Code':
|
||||
field = frappe.scrub(filters.get('apply_on'))
|
||||
items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})]
|
||||
|
||||
items = frappe.db.sql_list("""select name
|
||||
from `tabItem` where {0} = %s""".format(field), filters.get('value'))
|
||||
|
||||
return frappe.get_all('UOM Conversion Detail',
|
||||
filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))},
|
||||
fields = ["distinct uom"], as_list=1)
|
||||
return frappe.get_all('UOM Conversion Detail', filters={
|
||||
'parent': ('in', items),
|
||||
'uom': ("like", "{0}%".format(txt))
|
||||
}, fields = ["distinct uom"], as_list=1)
|
||||
|
@ -447,9 +447,14 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
|
||||
doc.set_missing_values()
|
||||
|
||||
def get_applied_pricing_rules(item_row):
|
||||
return (json.loads(item_row.get("pricing_rules"))
|
||||
if item_row.get("pricing_rules") else [])
|
||||
def get_applied_pricing_rules(pricing_rules):
|
||||
if pricing_rules:
|
||||
if pricing_rules.startswith('['):
|
||||
return json.loads(pricing_rules)
|
||||
else:
|
||||
return pricing_rules.split(',')
|
||||
|
||||
return []
|
||||
|
||||
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
free_item = pricing_rule.free_item
|
||||
|
@ -10,13 +10,15 @@ frappe.ui.form.on('Process Deferred Accounting', {
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
if (frm.doc.company) {
|
||||
type: function(frm) {
|
||||
if (frm.doc.company && frm.doc.type) {
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
'root_type': 'Liability',
|
||||
'root_type': frm.doc.type === 'Income' ? 'Liability' : 'Asset',
|
||||
'is_group': 0
|
||||
}
|
||||
};
|
||||
|
@ -60,6 +60,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.type",
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account",
|
||||
@ -73,9 +74,10 @@
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-06 18:18:09.852844",
|
||||
"modified": "2020-09-03 18:07:02.463754",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Deferred Accounting",
|
||||
|
@ -0,0 +1,89 @@
|
||||
<h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1>
|
||||
<h3 class="text-center">{{ _("Statement of Accounts") }}</h3>
|
||||
|
||||
<h5 class="text-center">
|
||||
{{ frappe.format(filters.from_date, 'Date')}}
|
||||
{{ _("to") }}
|
||||
{{ frappe.format(filters.to_date, 'Date')}}
|
||||
</h5>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">{{ _("Date") }}</th>
|
||||
<th style="width: 15%">{{ _("Ref") }}</th>
|
||||
<th style="width: 25%">{{ _("Party") }}</th>
|
||||
<th style="width: 15%">{{ _("Debit") }}</th>
|
||||
<th style="width: 15%">{{ _("Credit") }}</th>
|
||||
<th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
{% if(row.posting_date) %}
|
||||
<td>{{ frappe.format(row.posting_date, 'Date') }}</td>
|
||||
<td>{{ row.voucher_type }}
|
||||
<br>{{ row.voucher_no }}</td>
|
||||
<td>
|
||||
{% if not (filters.party or filters.account) %}
|
||||
{{ row.party or row.account }}
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
{{ _("Against") }}: {{ row.against }}
|
||||
<br>{{ _("Remarks") }}: {{ row.remarks }}
|
||||
{% if row.bill_no %}
|
||||
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
|
||||
</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br><br>
|
||||
{% if aging %}
|
||||
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3>
|
||||
<h5 class="text-center">
|
||||
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||
</h5>
|
||||
<br>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">30 Days</th>
|
||||
<th style="width: 15%">60 Days</th>
|
||||
<th style="width: 25%">90 Days</th>
|
||||
<th style="width: 15%">120 Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ aging.range1 }}</td>
|
||||
<td>{{ aging.range2 }}</td>
|
||||
<td>{{ aging.range3 }}</td>
|
||||
<td>{{ aging.range4 }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p>
|
@ -0,0 +1,132 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
view_properties: function(frm) {
|
||||
frappe.route_options = {doc_type: 'Customer'};
|
||||
frappe.set_route("Form", "Customize Form");
|
||||
},
|
||||
refresh: function(frm){
|
||||
if(!frm.doc.__islocal) {
|
||||
frm.add_custom_button('Send Emails',function(){
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
||||
args: {
|
||||
"document_name": frm.doc.name,
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r && r.message) {
|
||||
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
||||
}
|
||||
else{
|
||||
frappe.msgprint('No Records for these settings.')
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
frm.add_custom_button('Download',function(){
|
||||
var url = frappe.urllib.get_full_url(
|
||||
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
||||
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: 'GET',
|
||||
success: function(result) {
|
||||
if(jQuery.isEmptyObject(result)){
|
||||
frappe.msgprint('No Records for these settings.');
|
||||
}
|
||||
else{
|
||||
window.location = url;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
onload: function(frm) {
|
||||
frm.set_query('currency', function(){
|
||||
return {
|
||||
filters: {
|
||||
'enabled': 1
|
||||
}
|
||||
}
|
||||
});
|
||||
if(frm.doc.__islocal){
|
||||
frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
|
||||
frm.set_value('to_date', frappe.datetime.get_today());
|
||||
}
|
||||
},
|
||||
customer_collection: function(frm){
|
||||
frm.set_value('collection_name', '');
|
||||
if(frm.doc.customer_collection){
|
||||
frm.get_field('collection_name').set_label(frm.doc.customer_collection);
|
||||
}
|
||||
},
|
||||
frequency: function(frm){
|
||||
if(frm.doc.frequency != ''){
|
||||
frm.set_value('start_date', frappe.datetime.get_today());
|
||||
}
|
||||
else{
|
||||
frm.set_value('start_date', '');
|
||||
}
|
||||
},
|
||||
fetch_customers: function(frm){
|
||||
if(frm.doc.collection_name){
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.fetch_customers",
|
||||
args: {
|
||||
'customer_collection': frm.doc.customer_collection,
|
||||
'collection_name': frm.doc.collection_name,
|
||||
'primary_mandatory': frm.doc.primary_mandatory
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if(r.message.length){
|
||||
frm.clear_table('customers');
|
||||
for (const customer of r.message){
|
||||
var row = frm.add_child('customers');
|
||||
row.customer = customer.name;
|
||||
row.primary_email = customer.primary_email;
|
||||
row.billing_email = customer.billing_email;
|
||||
}
|
||||
frm.refresh_field('customers');
|
||||
}
|
||||
else{
|
||||
frappe.msgprint('No Customers found with selected options.');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
frappe.throw('Enter ' + frm.doc.customer_collection + ' name.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on('Process Statement Of Accounts Customer', {
|
||||
customer: function(frm, cdt, cdn){
|
||||
var row = locals[cdt][cdn];
|
||||
if (!row.customer){
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: 'erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.get_customer_emails',
|
||||
args: {
|
||||
'customer_name': row.customer,
|
||||
'primary_mandatory': frm.doc.primary_mandatory
|
||||
},
|
||||
callback: function(r){
|
||||
if(!r.exe){
|
||||
if(r.message.length){
|
||||
frappe.model.set_value(cdt, cdn, "primary_email", r.message[0])
|
||||
frappe.model.set_value(cdt, cdn, "billing_email", r.message[1])
|
||||
}
|
||||
else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
@ -0,0 +1,310 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_workflow": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2020-05-22 16:46:18.712954",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_11",
|
||||
"from_date",
|
||||
"company",
|
||||
"account",
|
||||
"group_by",
|
||||
"cost_center",
|
||||
"column_break_14",
|
||||
"to_date",
|
||||
"finance_book",
|
||||
"currency",
|
||||
"project",
|
||||
"section_break_3",
|
||||
"customer_collection",
|
||||
"collection_name",
|
||||
"fetch_customers",
|
||||
"column_break_6",
|
||||
"primary_mandatory",
|
||||
"column_break_17",
|
||||
"customers",
|
||||
"preferences",
|
||||
"orientation",
|
||||
"section_break_14",
|
||||
"include_ageing",
|
||||
"ageing_based_on",
|
||||
"section_break_1",
|
||||
"enable_auto_email",
|
||||
"section_break_18",
|
||||
"frequency",
|
||||
"filter_duration",
|
||||
"column_break_21",
|
||||
"start_date",
|
||||
"section_break_33",
|
||||
"subject",
|
||||
"column_break_28",
|
||||
"cc_to",
|
||||
"section_break_30",
|
||||
"body",
|
||||
"help_text"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "frequency",
|
||||
"fieldtype": "Select",
|
||||
"label": "Frequency",
|
||||
"options": "Weekly\nMonthly\nQuarterly"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_email == 0;",
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date",
|
||||
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_email == 0;",
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date",
|
||||
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Cost Center",
|
||||
"options": "PSOA Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Project",
|
||||
"options": "PSOA Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Customers"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_6",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "General Ledger Filters"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_collection",
|
||||
"fieldtype": "Select",
|
||||
"label": "Select Customers By",
|
||||
"options": "\nCustomer Group\nTerritory\nSales Partner\nSales Person"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.customer_collection !== ''",
|
||||
"fieldname": "collection_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Recipient",
|
||||
"options": "customer_collection"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Email Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "preferences",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Print Preferences"
|
||||
},
|
||||
{
|
||||
"fieldname": "orientation",
|
||||
"fieldtype": "Select",
|
||||
"label": "Orientation",
|
||||
"options": "Landscape\nPortrait"
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Start Date"
|
||||
},
|
||||
{
|
||||
"default": "Group by Voucher (Consolidated)",
|
||||
"fieldname": "group_by",
|
||||
"fieldtype": "Select",
|
||||
"label": "Group By",
|
||||
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "include_ageing",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Include Ageing Summary"
|
||||
},
|
||||
{
|
||||
"default": "Due Date",
|
||||
"depends_on": "eval:doc.include_ageing === 1",
|
||||
"fieldname": "ageing_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Ageing Based On",
|
||||
"options": "Due Date\nPosting Date"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_auto_email",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Enable Auto Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Column Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.enable_auto_email ==1",
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_21",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.customer_collection !== ''",
|
||||
"fieldname": "fetch_customers",
|
||||
"fieldtype": "Button",
|
||||
"label": "Fetch Customers",
|
||||
"options": "fetch_customers",
|
||||
"print_hide": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "primary_mandatory",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send To Primary Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "cc_to",
|
||||
"fieldtype": "Link",
|
||||
"label": "CC To",
|
||||
"options": "User"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "filter_duration",
|
||||
"fieldtype": "Int",
|
||||
"label": "Filter Duration (Months)"
|
||||
},
|
||||
{
|
||||
"fieldname": "customers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Customers",
|
||||
"options": "Process Statement Of Accounts Customer",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_28",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_30",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_33",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "help_text",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Help Text",
|
||||
"options": "<br>\n<h4>Note</h4>\n<ul>\n<li>\nYou can use <a href=\"https://jinja.palletsprojects.com/en/2.11.x/\" target=\"_blank\">Jinja tags</a> in <b>Subject</b> and <b>Body</b> fields for dynamic values.\n</li><li>\n All fields in this doctype are available under the <b>doc</b> object and all fields for the customer to whom the mail will go to is available under the <b>customer</b> object.\n</li></ul>\n<h4> Examples</h4>\n<!-- {% raw %} -->\n<ul>\n <li><b>Subject</b>:<br><br><pre><code>Statement Of Accounts for {{ customer.name }}</code></pre><br></li>\n <li><b>Body</b>: <br><br>\n<pre><code>Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.</code> </pre></li>\n</ul>\n<!-- {% endraw %} -->"
|
||||
},
|
||||
{
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"label": "Subject"
|
||||
},
|
||||
{
|
||||
"fieldname": "body",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Body"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-08-08 08:47:09.185728",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,271 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
|
||||
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
|
||||
from frappe.core.doctype.communication.email import make
|
||||
|
||||
from frappe.utils.print_format import report_to_pdf
|
||||
from frappe.utils.pdf import get_pdf
|
||||
from frappe.utils import today, add_days, add_months, getdate, format_date
|
||||
from frappe.utils.jinja import validate_template
|
||||
|
||||
import copy
|
||||
from datetime import timedelta
|
||||
from frappe.www.printview import get_print_style
|
||||
|
||||
class ProcessStatementOfAccounts(Document):
|
||||
def validate(self):
|
||||
if not self.subject:
|
||||
self.subject = 'Statement Of Accounts for {{ customer.name }}'
|
||||
if not self.body:
|
||||
self.body = 'Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.'
|
||||
|
||||
validate_template(self.subject)
|
||||
validate_template(self.body)
|
||||
|
||||
if not self.customers:
|
||||
frappe.throw(frappe._('Customers not selected.'))
|
||||
|
||||
if self.enable_auto_email:
|
||||
self.to_date = self.start_date
|
||||
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
|
||||
|
||||
|
||||
def get_report_pdf(doc, consolidated=True):
|
||||
statement_dict = {}
|
||||
aging = ''
|
||||
base_template_path = "frappe/www/printview.html"
|
||||
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||
|
||||
for entry in doc.customers:
|
||||
if doc.include_ageing:
|
||||
ageing_filters = frappe._dict({
|
||||
'company': doc.company,
|
||||
'report_date': doc.to_date,
|
||||
'ageing_based_on': doc.ageing_based_on,
|
||||
'range1': 30,
|
||||
'range2': 60,
|
||||
'range3': 90,
|
||||
'range4': 120,
|
||||
'customer': entry.customer
|
||||
})
|
||||
col1, aging = get_ageing(ageing_filters)
|
||||
aging[0]['ageing_based_on'] = doc.ageing_based_on
|
||||
|
||||
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
||||
|
||||
filters= frappe._dict({
|
||||
'from_date': doc.from_date,
|
||||
'to_date': doc.to_date,
|
||||
'company': doc.company,
|
||||
'finance_book': doc.finance_book if doc.finance_book else None,
|
||||
"account": doc.account if doc.account else None,
|
||||
'party_type': 'Customer',
|
||||
'party': [entry.customer],
|
||||
'group_by': doc.group_by,
|
||||
'currency': doc.currency,
|
||||
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
|
||||
'project': [p.project_name for p in doc.project],
|
||||
'show_opening_entries': 0,
|
||||
'include_default_book_entries': 0,
|
||||
'show_cancelled_entries': 1,
|
||||
'tax_id': tax_id if tax_id else None
|
||||
})
|
||||
col, res = get_soa(filters)
|
||||
|
||||
for x in [0, -2, -1]:
|
||||
res[x]['account'] = res[x]['account'].replace("'","")
|
||||
|
||||
if len(res) == 3:
|
||||
continue
|
||||
html = frappe.render_template(template_path, \
|
||||
{"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None})
|
||||
html = frappe.render_template(base_template_path, {"body": html, \
|
||||
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
||||
statement_dict[entry.customer] = html
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
elif consolidated:
|
||||
result = ''.join(list(statement_dict.values()))
|
||||
return get_pdf(result, {'orientation': doc.orientation})
|
||||
else:
|
||||
for customer, statement_html in statement_dict.items():
|
||||
statement_dict[customer]=get_pdf(statement_html, {'orientation': doc.orientation})
|
||||
return statement_dict
|
||||
|
||||
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
|
||||
fields_dict = {
|
||||
'Customer Group': 'customer_group',
|
||||
'Territory': 'territory',
|
||||
}
|
||||
collection = frappe.get_doc(customer_collection, collection_name)
|
||||
selected = [customer.name for customer in frappe.get_list(customer_collection, filters=[
|
||||
['lft', '>=', collection.lft],
|
||||
['rgt', '<=', collection.rgt]
|
||||
],
|
||||
fields=['name'],
|
||||
order_by='lft asc, rgt desc'
|
||||
)]
|
||||
return frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
filters=[[fields_dict[customer_collection], 'IN', selected]])
|
||||
|
||||
def get_customers_based_on_sales_person(sales_person):
|
||||
lft, rgt = frappe.db.get_value("Sales Person",
|
||||
sales_person, ["lft", "rgt"])
|
||||
records = frappe.db.sql("""
|
||||
select distinct parent, parenttype
|
||||
from `tabSales Team` steam
|
||||
where parenttype = 'Customer'
|
||||
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
|
||||
""", (lft, rgt), as_dict=1)
|
||||
sales_person_records = frappe._dict()
|
||||
for d in records:
|
||||
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
filters=[['name', 'in', list(sales_person_records['Customer'])]])
|
||||
return customers
|
||||
|
||||
def get_recipients_and_cc(customer, doc):
|
||||
recipients = []
|
||||
for clist in doc.customers:
|
||||
if clist.customer == customer:
|
||||
recipients.append(clist.billing_email)
|
||||
if doc.primary_mandatory and clist.primary_email:
|
||||
recipients.append(clist.primary_email)
|
||||
cc = []
|
||||
if doc.cc_to != '':
|
||||
try:
|
||||
cc=[frappe.get_value('User', doc.cc_to, 'email')]
|
||||
except:
|
||||
pass
|
||||
|
||||
return recipients, cc
|
||||
|
||||
def get_context(customer, doc):
|
||||
template_doc = copy.deepcopy(doc)
|
||||
del template_doc.customers
|
||||
template_doc.from_date = format_date(template_doc.from_date)
|
||||
template_doc.to_date = format_date(template_doc.to_date)
|
||||
return {
|
||||
'doc': template_doc,
|
||||
'customer': frappe.get_doc('Customer', customer),
|
||||
'frappe': frappe.utils
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
customer_list = []
|
||||
customers = []
|
||||
|
||||
if customer_collection == 'Sales Person':
|
||||
customers = get_customers_based_on_sales_person(collection_name)
|
||||
if not bool(customers):
|
||||
frappe.throw('No Customers found with selected options.')
|
||||
else:
|
||||
if customer_collection == 'Sales Partner':
|
||||
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||
filters=[['default_sales_partner', '=', collection_name]])
|
||||
else:
|
||||
customers = get_customers_based_on_territory_or_customer_group(customer_collection, collection_name)
|
||||
|
||||
for customer in customers:
|
||||
primary_email = customer.get('email_id') or ''
|
||||
billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False)
|
||||
|
||||
if billing_email == '' or (primary_email == '' and int(primary_mandatory)):
|
||||
continue
|
||||
|
||||
customer_list.append({
|
||||
'name': customer.name,
|
||||
'primary_email': primary_email,
|
||||
'billing_email': billing_email
|
||||
})
|
||||
return customer_list
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||
billing_email = frappe.db.sql("""
|
||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
|
||||
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
|
||||
c.is_billing_contact=1 \
|
||||
order by c.creation desc""")
|
||||
|
||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||
if billing_and_primary:
|
||||
frappe.throw('No billing email found for customer: '+ customer_name)
|
||||
else:
|
||||
return ''
|
||||
|
||||
if billing_and_primary:
|
||||
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
|
||||
if primary_email is None and int(primary_mandatory):
|
||||
frappe.throw('No primary email found for customer: '+ customer_name)
|
||||
return [primary_email or '', billing_email[0][0]]
|
||||
else:
|
||||
return billing_email[0][0] or ''
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_statements(document_name):
|
||||
doc = frappe.get_doc('Process Statement Of Accounts', document_name)
|
||||
report = get_report_pdf(doc)
|
||||
if report:
|
||||
frappe.local.response.filename = doc.name + '.pdf'
|
||||
frappe.local.response.filecontent = report
|
||||
frappe.local.response.type = "download"
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_emails(document_name, from_scheduler=False):
|
||||
doc = frappe.get_doc('Process Statement Of Accounts', document_name)
|
||||
report = get_report_pdf(doc, consolidated=False)
|
||||
|
||||
if report:
|
||||
for customer, report_pdf in report.items():
|
||||
attachments = [{
|
||||
'fname': customer + '.pdf',
|
||||
'fcontent': report_pdf
|
||||
}]
|
||||
|
||||
recipients, cc = get_recipients_and_cc(customer, doc)
|
||||
context = get_context(customer, doc)
|
||||
subject = frappe.render_template(doc.subject, context)
|
||||
message = frappe.render_template(doc.body, context)
|
||||
|
||||
frappe.enqueue(
|
||||
queue='short',
|
||||
method=frappe.sendmail,
|
||||
recipients=recipients,
|
||||
sender=frappe.session.user,
|
||||
cc=cc,
|
||||
subject=subject,
|
||||
message=message,
|
||||
now=True,
|
||||
reference_doctype='Process Statement Of Accounts',
|
||||
reference_name=document_name,
|
||||
attachments=attachments
|
||||
)
|
||||
|
||||
if doc.enable_auto_email and from_scheduler:
|
||||
new_to_date = getdate(today())
|
||||
if doc.frequency == 'Weekly':
|
||||
new_to_date = add_days(new_to_date, 7)
|
||||
else:
|
||||
new_to_date = add_months(new_to_date, 1 if doc.frequency == 'Monthly' else 3)
|
||||
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
|
||||
doc.add_comment('Comment', 'Emails sent on: ' + frappe.utils.format_datetime(frappe.utils.now()))
|
||||
doc.db_set('to_date', new_to_date, commit=True)
|
||||
doc.db_set('from_date', new_from_date, commit=True)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_auto_email():
|
||||
selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
|
||||
for entry in selected:
|
||||
send_emails(entry.name, from_scheduler=True)
|
||||
return True
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestProcessStatementOfAccounts(unittest.TestCase):
|
||||
pass
|
@ -0,0 +1,47 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_workflow": 1,
|
||||
"creation": "2020-08-03 16:35:21.852178",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"customer",
|
||||
"billing_email",
|
||||
"primary_email"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Customer",
|
||||
"options": "Customer",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "primary_email",
|
||||
"fieldtype": "Read Only",
|
||||
"in_list_view": 1,
|
||||
"label": "Primary Contact Email"
|
||||
},
|
||||
{
|
||||
"fieldname": "billing_email",
|
||||
"fieldtype": "Read Only",
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Email"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 22:55:38.875601",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts Customer",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ProcessStatementOfAccountsCustomer(Document):
|
||||
pass
|
@ -0,0 +1,30 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-08-03 16:56:45.744905",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"cost_center_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "cost_center_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 16:56:45.744905",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "PSOA Cost Center",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PSOACostCenter(Document):
|
||||
pass
|
0
erpnext/accounts/doctype/psoa_project/__init__.py
Normal file
0
erpnext/accounts/doctype/psoa_project/__init__.py
Normal file
30
erpnext/accounts/doctype/psoa_project/psoa_project.json
Normal file
30
erpnext/accounts/doctype/psoa_project/psoa_project.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-08-03 16:52:14.731978",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"project_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "project_name",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 16:53:39.219736",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "PSOA Project",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
10
erpnext/accounts/doctype/psoa_project/psoa_project.py
Normal file
10
erpnext/accounts/doctype/psoa_project/psoa_project.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class PSOAProject(Document):
|
||||
pass
|
@ -180,7 +180,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "ACC-PINV-.YYYY.-",
|
||||
"options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
@ -969,8 +969,10 @@
|
||||
{
|
||||
"fieldname": "clearance_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 1,
|
||||
"label": "Clearance Date"
|
||||
"label": "Clearance Date",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_br_payments",
|
||||
@ -1332,7 +1334,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-24 09:46:40.405463",
|
||||
"modified": "2020-08-03 23:20:04.466153",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
@ -1394,4 +1396,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-05-22 12:43:10",
|
||||
"doctype": "DocType",
|
||||
@ -82,6 +81,7 @@
|
||||
"item_tax_rate",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
"purchase_invoice_item",
|
||||
"col_break6",
|
||||
"purchase_order",
|
||||
"po_detail",
|
||||
@ -769,12 +769,21 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "col_break7",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "purchase_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Purchase Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-04-22 10:37:35.103176",
|
||||
"modified": "2020-08-20 11:48:01.398356",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_workflow": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-24 19:29:05",
|
||||
"doctype": "DocType",
|
||||
@ -217,7 +216,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "ACC-SINV-.YYYY.-",
|
||||
"options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"set_only_once": 1
|
||||
@ -448,7 +447,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "po_no",
|
||||
"fieldtype": "Small Text",
|
||||
"fieldtype": "Data",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Customer's Purchase Order",
|
||||
@ -1947,7 +1946,7 @@
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-18 05:07:16.725974",
|
||||
"modified": "2020-08-27 01:56:28.532140",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -1619,22 +1619,23 @@ def update_multi_mode_option(doc, pos_profile):
|
||||
|
||||
for pos_payment_method in pos_profile.get('payments'):
|
||||
pos_payment_method = pos_payment_method.as_dict()
|
||||
|
||||
|
||||
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
if payment_mode:
|
||||
payment_mode[0].default = pos_payment_method.default
|
||||
append_payment(payment_mode[0])
|
||||
|
||||
def get_all_mode_of_payments(doc):
|
||||
return frappe.db.sql("""
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
|
||||
{'company': doc.company}, as_dict=1)
|
||||
|
||||
def get_mode_of_payment_info(mode_of_payment, company):
|
||||
return frappe.db.sql("""
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
select mpa.default_account, mpa.parent, mp.type as type
|
||||
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
|
||||
where mpa.parent = mp.name and mpa.company = %s and mp.enabled = 1 and mp.name = %s""",
|
||||
(company, mode_of_payment), as_dict=1)
|
||||
|
||||
|
@ -13,12 +13,13 @@ def get_data():
|
||||
'Auto Repeat': 'reference_document',
|
||||
},
|
||||
'internal_links': {
|
||||
'Sales Order': ['items', 'sales_order']
|
||||
'Sales Order': ['items', 'sales_order'],
|
||||
'Delivery Note': ['items', 'delivery_note']
|
||||
},
|
||||
'transactions': [
|
||||
{
|
||||
'label': _('Payment'),
|
||||
'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting']
|
||||
'items': ['Payment Entry', 'Payment Request', 'Journal Entry', 'Invoice Discounting', 'Dunning']
|
||||
},
|
||||
{
|
||||
'label': _('Reference'),
|
||||
|
@ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"rate": 14,
|
||||
'included_in_print_rate': 1
|
||||
})
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Item Quantity",
|
||||
"account_head": "_Test Account Education Cess - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "CESS",
|
||||
"rate": 5,
|
||||
'included_in_print_rate': 1
|
||||
})
|
||||
si.insert()
|
||||
|
||||
# with inclusive tax
|
||||
self.assertEqual(si.net_total, 4385.96)
|
||||
self.assertEqual(si.items[0].net_amount, 3947.368421052631)
|
||||
self.assertEqual(si.net_total, 3947.37)
|
||||
self.assertEqual(si.grand_total, 5000)
|
||||
|
||||
si.reload()
|
||||
@ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
|
||||
# with inclusive tax and additional discount
|
||||
self.assertEqual(si.net_total, 4285.96)
|
||||
self.assertEqual(si.grand_total, 4885.99)
|
||||
self.assertEqual(si.net_total, 3847.37)
|
||||
self.assertEqual(si.grand_total, 4886)
|
||||
|
||||
si.reload()
|
||||
|
||||
@ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
|
||||
# with inclusive tax and additional discount
|
||||
self.assertEqual(si.net_total, 4298.25)
|
||||
self.assertEqual(si.net_total, 3859.65)
|
||||
self.assertEqual(si.grand_total, 4900.00)
|
||||
|
||||
def test_sales_invoice_discount_amount(self):
|
||||
|
@ -1,5 +1,4 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "hash",
|
||||
"creation": "2013-06-04 11:02:19",
|
||||
"doctype": "DocType",
|
||||
@ -87,6 +86,7 @@
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
"sales_invoice_item",
|
||||
"column_break_74",
|
||||
"delivery_note",
|
||||
"dn_detail",
|
||||
@ -790,12 +790,22 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
}
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "sales_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Sales Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-18 12:24:41.749986",
|
||||
"modified": "2020-08-20 11:24:41.749986",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
@ -64,6 +64,7 @@
|
||||
"fieldname": "clearance_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Clearance Date",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -78,7 +79,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-05 16:51:20.091441",
|
||||
"modified": "2020-08-03 12:45:39.986598",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Payment",
|
||||
|
@ -8,7 +8,7 @@ def get_data():
|
||||
'fieldname': 'taxes_and_charges',
|
||||
'non_standard_fieldnames': {
|
||||
'Tax Rule': 'sales_tax_template',
|
||||
'Subscription': 'tax_template',
|
||||
'Subscription': 'sales_tax_template',
|
||||
'Restaurant': 'default_tax_template'
|
||||
},
|
||||
'transactions': [
|
||||
|
@ -3,6 +3,22 @@
|
||||
|
||||
frappe.ui.form.on('Shipping Rule', {
|
||||
refresh: function(frm) {
|
||||
frm.set_query("cost_center", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
frm.trigger('toggle_reqd');
|
||||
},
|
||||
calculate_based_on: function(frm) {
|
||||
@ -12,4 +28,4 @@ frappe.ui.form.on('Shipping Rule', {
|
||||
frm.toggle_reqd("shipping_amount", frm.doc.calculate_based_on === 'Fixed');
|
||||
frm.toggle_reqd("conditions", frm.doc.calculate_based_on !== 'Fixed');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -7,8 +7,8 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from erpnext.accounts.doctype.subscription.subscription import get_prorata_factor
|
||||
from frappe.utils.data import nowdate, add_days, add_to_date, add_months, date_diff, flt, get_date_str
|
||||
|
||||
from frappe.utils.data import (nowdate, add_days, add_to_date, add_months, date_diff, flt, get_date_str,
|
||||
get_first_day, get_last_day)
|
||||
|
||||
def create_plan():
|
||||
if not frappe.db.exists('Subscription Plan', '_Test Plan Name'):
|
||||
@ -68,14 +68,14 @@ class TestSubscription(unittest.TestCase):
|
||||
subscription.party_type = 'Customer'
|
||||
subscription.party = '_Test Customer'
|
||||
subscription.trial_period_start = nowdate()
|
||||
subscription.trial_period_end = add_days(nowdate(), 30)
|
||||
subscription.trial_period_end = add_months(nowdate(), 1)
|
||||
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
|
||||
subscription.save()
|
||||
|
||||
self.assertEqual(subscription.trial_period_start, nowdate())
|
||||
self.assertEqual(subscription.trial_period_end, add_days(nowdate(), 30))
|
||||
self.assertEqual(subscription.trial_period_end, add_months(nowdate(), 1))
|
||||
self.assertEqual(add_days(subscription.trial_period_end, 1), get_date_str(subscription.current_invoice_start))
|
||||
self.assertEqual(add_days(subscription.current_invoice_start, 30), get_date_str(subscription.current_invoice_end))
|
||||
self.assertEqual(add_to_date(subscription.current_invoice_start, months=1, days=-1), get_date_str(subscription.current_invoice_end))
|
||||
self.assertEqual(subscription.invoices, [])
|
||||
self.assertEqual(subscription.status, 'Trialling')
|
||||
|
||||
|
@ -1,134 +1,66 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:title",
|
||||
"beta": 0,
|
||||
"creation": "2018-11-22 23:38:39.668804",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "title",
|
||||
"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": "Title",
|
||||
"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": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-01-15 17:14:28.951793",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-30 19:41:25.783852",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Category",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
"track_changes": 1
|
||||
}
|
@ -45,8 +45,8 @@ def validate_accounting_period(gl_map):
|
||||
}, as_dict=1)
|
||||
|
||||
if accounting_periods:
|
||||
frappe.throw(_("You can't create accounting entries in the closed accounting period {0}")
|
||||
.format(accounting_periods[0].name), ClosedAccountingPeriod)
|
||||
frappe.throw(_("You cannot create or cancel any accounting entries with in the closed Accounting Period {0}")
|
||||
.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
|
||||
|
||||
def process_gl_map(gl_map, merge_entries=True):
|
||||
if merge_entries:
|
||||
@ -301,8 +301,9 @@ def make_reverse_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
})
|
||||
|
||||
if gl_entries:
|
||||
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
|
||||
validate_accounting_period(gl_entries)
|
||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||
set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
|
||||
|
||||
for entry in gl_entries:
|
||||
entry['name'] = None
|
||||
@ -342,7 +343,7 @@ def set_as_cancel(voucher_type, voucher_no):
|
||||
"""
|
||||
Set is_cancelled=1 in all original gl entries for the voucher
|
||||
"""
|
||||
frappe.db.sql("""update `tabGL Entry` set is_cancelled = 1,
|
||||
frappe.db.sql("""UPDATE `tabGL Entry` SET is_cancelled = 1,
|
||||
modified=%s, modified_by=%s
|
||||
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
|
||||
(now(), frappe.session.user, voucher_type, voucher_no))
|
||||
|
@ -290,6 +290,7 @@ def get_matching_transactions_payments(description_matching):
|
||||
return []
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
|
||||
if not account:
|
||||
@ -319,6 +320,7 @@ def payment_entry_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
account = frappe.db.get_value("Bank Account", filters.get("bank_account"), "account")
|
||||
|
||||
@ -355,6 +357,7 @@ def journal_entry_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def sales_invoices_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
|
@ -611,7 +611,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur
|
||||
cond = "posting_date <= '{0}'".format(posting_date)
|
||||
|
||||
if company:
|
||||
cond += "and company = '{0}'".format(company)
|
||||
cond += "and company = {0}".format(frappe.db.escape(company))
|
||||
|
||||
data = frappe.db.sql(""" SELECT party, sum({0}) as amount
|
||||
FROM `tabGL Entry`
|
||||
|
@ -643,8 +643,10 @@ class ReceivablePayableReport(object):
|
||||
account_type = "Receivable" if self.party_type == "Customer" else "Payable"
|
||||
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)))
|
||||
values += accounts
|
||||
|
||||
if accounts:
|
||||
conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
|
||||
values += accounts
|
||||
|
||||
def add_customer_filters(self, conditions, values):
|
||||
if self.filters.get("customer_group"):
|
||||
|
@ -71,7 +71,22 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
fieldtype: "Check",
|
||||
default: 0,
|
||||
},
|
||||
]
|
||||
],
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.fieldname.includes('variance')) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
}
|
||||
else if (data[column.fieldname] > 0) {
|
||||
value = "<span style='color:green'>" + value + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
|
@ -378,7 +378,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
|
||||
if filters and filters.get('presentation_currency') != d.default_currency:
|
||||
currency_info['company'] = d.name
|
||||
currency_info['company_currency'] = d.default_currency
|
||||
convert_to_presentation_currency(gl_entries, currency_info)
|
||||
convert_to_presentation_currency(gl_entries, currency_info, filters.get('company'))
|
||||
|
||||
for entry in gl_entries:
|
||||
key = entry.account_number or entry.account_name
|
||||
|
@ -173,7 +173,7 @@ class PartyLedgerSummaryReport(object):
|
||||
from `tabGL Entry` gle
|
||||
{join}
|
||||
where
|
||||
gle.docstatus < 2 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||
gle.docstatus < 2 and gle.is_cancelled = 0 and gle.party_type=%(party_type)s and ifnull(gle.party, '') != ''
|
||||
and gle.posting_date <= %(to_date)s {conditions}
|
||||
order by gle.posting_date
|
||||
""".format(join=join, join_field=join_field, conditions=conditions), self.filters, as_dict=True)
|
||||
@ -248,7 +248,7 @@ class PartyLedgerSummaryReport(object):
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
docstatus < 2
|
||||
docstatus < 2 and is_cancelled = 0
|
||||
and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
||||
|
@ -14,7 +14,7 @@ import frappe, erpnext
|
||||
from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from frappe import _
|
||||
from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr)
|
||||
from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr, cint)
|
||||
|
||||
from six import itervalues
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions, get_dimension_with_children
|
||||
@ -46,7 +46,7 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
|
||||
start_date = year_start_date
|
||||
months = get_months(year_start_date, year_end_date)
|
||||
|
||||
for i in range(math.ceil(months / months_to_add)):
|
||||
for i in range(cint(math.ceil(months / months_to_add))):
|
||||
period = frappe._dict({
|
||||
"from_date": start_date
|
||||
})
|
||||
@ -423,7 +423,7 @@ def set_gl_entries_by_account(
|
||||
distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
|
||||
|
||||
if filters and filters.get('presentation_currency'):
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters))
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))
|
||||
|
||||
for entry in gl_entries:
|
||||
gl_entries_by_account.setdefault(entry.account, []).append(entry)
|
||||
|
@ -146,6 +146,12 @@ frappe.query_reports["General Ledger"] = {
|
||||
return frappe.db.get_link_options('Project', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "include_dimensions",
|
||||
"label": __("Consider Accounting Dimensions"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "show_opening_entries",
|
||||
"label": __("Show Opening Entries"),
|
||||
|
@ -43,8 +43,11 @@ def execute(filters=None):
|
||||
|
||||
|
||||
def validate_filters(filters, account_details):
|
||||
if not filters.get('company'):
|
||||
frappe.throw(_('{0} is mandatory').format(_('Company')))
|
||||
if not filters.get("company"):
|
||||
frappe.throw(_("{0} is mandatory").format(_("Company")))
|
||||
|
||||
if not filters.get("from_date") and not filters.get("to_date"):
|
||||
frappe.throw(_("{0} and {1} are mandatory").format(frappe.bold(_("From Date")), frappe.bold(_("To Date"))))
|
||||
|
||||
if filters.get("account") and not account_details.get(filters.account):
|
||||
frappe.throw(_("Account {0} does not exists").format(filters.account))
|
||||
@ -106,15 +109,20 @@ def set_account_currency(filters):
|
||||
return filters
|
||||
|
||||
def get_result(filters, account_details):
|
||||
gl_entries = get_gl_entries(filters)
|
||||
accounting_dimensions = []
|
||||
if filters.get("include_dimensions"):
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
|
||||
data = get_data_with_opening_closing(filters, account_details, gl_entries)
|
||||
gl_entries = get_gl_entries(filters, accounting_dimensions)
|
||||
|
||||
data = get_data_with_opening_closing(filters, account_details,
|
||||
accounting_dimensions, gl_entries)
|
||||
|
||||
result = get_result_as_list(data, filters)
|
||||
|
||||
return result
|
||||
|
||||
def get_gl_entries(filters):
|
||||
def get_gl_entries(filters, accounting_dimensions):
|
||||
currency_map = get_currency(filters)
|
||||
select_fields = """, debit, credit, debit_in_account_currency,
|
||||
credit_in_account_currency """
|
||||
@ -128,11 +136,15 @@ def get_gl_entries(filters):
|
||||
filters['company_fb'] = frappe.db.get_value("Company",
|
||||
filters.get("company"), 'default_finance_book')
|
||||
|
||||
dimension_fields = ""
|
||||
if accounting_dimensions:
|
||||
dimension_fields = ', '.join(accounting_dimensions) + ','
|
||||
|
||||
distributed_cost_center_query = ""
|
||||
if filters and filters.get('cost_center'):
|
||||
select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit, credit*(DCC_allocation.percentage_allocation/100) as credit, debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
|
||||
credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
|
||||
|
||||
|
||||
distributed_cost_center_query = """
|
||||
UNION ALL
|
||||
SELECT name as gl_entry,
|
||||
@ -141,12 +153,12 @@ def get_gl_entries(filters):
|
||||
party_type,
|
||||
party,
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
against_voucher_type,
|
||||
against_voucher,
|
||||
account_currency,
|
||||
remarks, against,
|
||||
remarks, against,
|
||||
is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
|
||||
FROM `tabGL Entry`,
|
||||
(
|
||||
@ -160,13 +172,14 @@ def get_gl_entries(filters):
|
||||
{conditions}
|
||||
AND posting_date <= %(to_date)s
|
||||
AND cost_center = DCC_allocation.parent
|
||||
""".format(select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
|
||||
""".format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name as gl_entry, posting_date, account, party_type, party,
|
||||
voucher_type, voucher_no, cost_center, project,
|
||||
voucher_type, voucher_no, {dimension_fields}
|
||||
cost_center, project,
|
||||
against_voucher_type, against_voucher, account_currency,
|
||||
remarks, against, is_opening, creation {select_fields}
|
||||
from `tabGL Entry`
|
||||
@ -174,13 +187,13 @@ def get_gl_entries(filters):
|
||||
{distributed_cost_center_query}
|
||||
{order_by_statement}
|
||||
""".format(
|
||||
select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
|
||||
dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
|
||||
order_by_statement=order_by_statement
|
||||
),
|
||||
filters, as_dict=1)
|
||||
|
||||
if filters.get('presentation_currency'):
|
||||
return convert_to_presentation_currency(gl_entries, currency_map)
|
||||
return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))
|
||||
else:
|
||||
return gl_entries
|
||||
|
||||
@ -247,12 +260,12 @@ def get_conditions(filters):
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
|
||||
def get_data_with_opening_closing(filters, account_details, gl_entries):
|
||||
def get_data_with_opening_closing(filters, account_details, accounting_dimensions, gl_entries):
|
||||
data = []
|
||||
|
||||
gle_map = initialize_gle_map(gl_entries, filters)
|
||||
|
||||
totals, entries = get_accountwise_gle(filters, gl_entries, gle_map)
|
||||
totals, entries = get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map)
|
||||
|
||||
# Opening for filtered account
|
||||
data.append(totals.opening)
|
||||
@ -318,7 +331,7 @@ def initialize_gle_map(gl_entries, filters):
|
||||
return gle_map
|
||||
|
||||
|
||||
def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
||||
totals = get_totals_dict()
|
||||
entries = []
|
||||
consolidated_gle = OrderedDict()
|
||||
@ -350,8 +363,11 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
if filters.get("group_by") != _('Group by Voucher (Consolidated)'):
|
||||
gle_map[gle.get(group_by)].entries.append(gle)
|
||||
elif filters.get("group_by") == _('Group by Voucher (Consolidated)'):
|
||||
key = (gle.get("voucher_type"), gle.get("voucher_no"),
|
||||
gle.get("account"), gle.get("cost_center"))
|
||||
keylist = [gle.get("voucher_type"), gle.get("voucher_no"), gle.get("account")]
|
||||
for dim in accounting_dimensions:
|
||||
keylist.append(gle.get(dim))
|
||||
keylist.append(gle.get("cost_center"))
|
||||
key = tuple(keylist)
|
||||
if key not in consolidated_gle:
|
||||
consolidated_gle.setdefault(key, gle)
|
||||
else:
|
||||
@ -478,7 +494,19 @@ def get_columns(filters):
|
||||
"options": "Project",
|
||||
"fieldname": "project",
|
||||
"width": 100
|
||||
},
|
||||
}
|
||||
])
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
for dim in get_accounting_dimensions(as_list = False):
|
||||
columns.append({
|
||||
"label": _(dim.label),
|
||||
"options": dim.label,
|
||||
"fieldname": dim.fieldname,
|
||||
"width": 100
|
||||
})
|
||||
|
||||
columns.extend([
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"options": "Cost Center",
|
||||
|
@ -1,24 +1,23 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-02-25 17:03:34",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:12:22.464240",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Gross Profit",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Sales Invoice",
|
||||
"report_name": "Gross Profit",
|
||||
"report_type": "Script Report",
|
||||
"add_total_row": 1,
|
||||
"creation": "2013-02-25 17:03:34",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2020-08-13 11:26:39.112352",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Gross Profit",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Sales Invoice",
|
||||
"report_name": "Gross Profit",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
}
|
||||
|
@ -6,10 +6,6 @@ from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_dat
|
||||
from frappe.utils import cint, get_datetime_str, formatdate, flt
|
||||
|
||||
__exchange_rates = {}
|
||||
P_OR_L_ACCOUNTS = list(
|
||||
sum(frappe.get_list('Account', fields=['name'], or_filters=[{'root_type': 'Income'}, {'root_type': 'Expense'}], as_list=True), ())
|
||||
)
|
||||
|
||||
|
||||
def get_currency(filters):
|
||||
"""
|
||||
@ -73,18 +69,7 @@ def get_rate_as_at(date, from_currency, to_currency):
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
def is_p_or_l_account(account_name):
|
||||
"""
|
||||
Check if the given `account name` is an `Account` with `root_type` of either 'Income'
|
||||
or 'Expense'.
|
||||
:param account_name:
|
||||
:return: Boolean
|
||||
"""
|
||||
return account_name in P_OR_L_ACCOUNTS
|
||||
|
||||
|
||||
def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
def convert_to_presentation_currency(gl_entries, currency_info, company):
|
||||
"""
|
||||
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
||||
in `currency_info`.
|
||||
@ -96,6 +81,9 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
presentation_currency = currency_info['presentation_currency']
|
||||
company_currency = currency_info['company_currency']
|
||||
|
||||
pl_accounts = [d.name for d in frappe.get_list('Account',
|
||||
filters={'report_type': 'Profit and Loss', 'company': company})]
|
||||
|
||||
for entry in gl_entries:
|
||||
account = entry['account']
|
||||
debit = flt(entry['debit'])
|
||||
@ -107,7 +95,7 @@ def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
if account_currency != presentation_currency:
|
||||
value = debit or credit
|
||||
|
||||
date = currency_info['report_date'] if not is_p_or_l_account(account) else entry['posting_date']
|
||||
date = entry['posting_date'] if account in pl_accounts else currency_info['report_date']
|
||||
converted_value = convert(value, presentation_currency, company_currency, date)
|
||||
|
||||
if entry.get('debit'):
|
||||
|
@ -106,6 +106,7 @@ def update_maintenance_log(asset_maintenance, item_code, item_name, task):
|
||||
maintenance_log.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_team_members(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.get_values('Maintenance Team Member', { 'parent': filters.get("maintenance_team") })
|
||||
|
||||
|
@ -11,7 +11,7 @@ from erpnext.assets.doctype.asset_maintenance.asset_maintenance import calculate
|
||||
|
||||
class AssetMaintenanceLog(Document):
|
||||
def validate(self):
|
||||
if getdate(self.due_date) < getdate(nowdate()):
|
||||
if getdate(self.due_date) < getdate(nowdate()) and self.maintenance_status not in ["Completed", "Cancelled"]:
|
||||
self.maintenance_status = "Overdue"
|
||||
|
||||
if self.maintenance_status == "Completed" and not self.completion_date:
|
||||
@ -41,6 +41,7 @@ class AssetMaintenanceLog(Document):
|
||||
asset_maintenance_doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_maintenance_tasks(doctype, txt, searchfield, start, page_len, filters):
|
||||
asset_maintenance_tasks = frappe.db.get_values('Asset Maintenance Task', {'parent':filters.get("asset_maintenance")}, 'maintenance_task')
|
||||
return asset_maintenance_tasks
|
||||
|
@ -1,14 +1,15 @@
|
||||
frappe.listview_settings['Asset Maintenance Log'] = {
|
||||
add_fields: ["maintenance_status"],
|
||||
has_indicator_for_draft: 1,
|
||||
get_indicator: function(doc) {
|
||||
if(doc.maintenance_status=="Pending") {
|
||||
return [__("Pending"), "orange"];
|
||||
} else if(doc.maintenance_status=="Completed") {
|
||||
return [__("Completed"), "green"];
|
||||
} else if(doc.maintenance_status=="Cancelled") {
|
||||
return [__("Cancelled"), "red"];
|
||||
} else if(doc.maintenance_status=="Overdue") {
|
||||
return [__("Overdue"), "red"];
|
||||
if (doc.maintenance_status=="Planned") {
|
||||
return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status=="Completed") {
|
||||
return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status=="Cancelled") {
|
||||
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status=="Overdue") {
|
||||
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ from frappe import _
|
||||
from frappe.utils import flt, getdate, cint, date_diff, formatdate
|
||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
|
||||
|
||||
class AssetValueAdjustment(Document):
|
||||
def validate(self):
|
||||
@ -53,17 +54,33 @@ class AssetValueAdjustment(Document):
|
||||
je.company = self.company
|
||||
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
|
||||
|
||||
je.append("accounts", {
|
||||
credit_entry = {
|
||||
"account": accumulated_depreciation_account,
|
||||
"credit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center
|
||||
})
|
||||
}
|
||||
|
||||
je.append("accounts", {
|
||||
debit_entry = {
|
||||
"account": depreciation_expense_account,
|
||||
"debit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center
|
||||
})
|
||||
}
|
||||
|
||||
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
|
||||
|
||||
for dimension in accounting_dimensions:
|
||||
if dimension.get('mandatory_for_bs'):
|
||||
credit_entry.update({
|
||||
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
|
||||
})
|
||||
|
||||
if dimension.get('mandatory_for_pl'):
|
||||
debit_entry.update({
|
||||
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
|
||||
})
|
||||
|
||||
je.append("accounts", credit_entry)
|
||||
je.append("accounts", debit_entry)
|
||||
|
||||
je.flags.ignore_permissions = True
|
||||
je.submit()
|
||||
|
@ -94,7 +94,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
|
||||
this.frm.add_custom_button(__('Update Items'), () => {
|
||||
erpnext.utils.update_child_items({
|
||||
frm: frm,
|
||||
frm: this.frm,
|
||||
child_docname: "items",
|
||||
child_doctype: "Purchase Order Detail",
|
||||
cannot_add_row: false,
|
||||
|
@ -207,6 +207,7 @@ def get_list_context(context=None):
|
||||
return list_context
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select `tabContact`.name from `tabContact`, `tabDynamic Link`
|
||||
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
|
||||
|
@ -11,6 +11,8 @@ from erpnext.stock.doctype.item.test_item import make_item
|
||||
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import make_supplier_quotation
|
||||
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import create_supplier_quotation
|
||||
from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity
|
||||
from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq
|
||||
|
||||
class TestRequestforQuotation(unittest.TestCase):
|
||||
def test_quote_status(self):
|
||||
@ -110,6 +112,23 @@ class TestRequestforQuotation(unittest.TestCase):
|
||||
self.assertEqual(supplier_quotation.items[0].qty, 5)
|
||||
self.assertEqual(supplier_quotation.items[0].stock_qty, 10)
|
||||
|
||||
def test_make_rfq_from_opportunity(self):
|
||||
opportunity = make_opportunity(with_items=1)
|
||||
supplier_data = get_supplier_data()
|
||||
rfq = make_rfq(opportunity.name)
|
||||
|
||||
self.assertEqual(len(rfq.get("items")), len(opportunity.get("items")))
|
||||
rfq.message_for_supplier = 'Please supply the specified items at the best possible rates.'
|
||||
|
||||
for item in rfq.items:
|
||||
item.warehouse = "_Test Warehouse - _TC"
|
||||
|
||||
for data in supplier_data:
|
||||
rfq.append('suppliers', data)
|
||||
|
||||
rfq.status = 'Draft'
|
||||
rfq.submit()
|
||||
|
||||
def make_request_for_quotation(**args):
|
||||
"""
|
||||
:param supplier_data: List containing supplier data
|
||||
|
@ -12,7 +12,22 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
reqd: 1,
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": "80",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"width": "80",
|
||||
"reqd": 1,
|
||||
"default": frappe.datetime.get_today()
|
||||
},
|
||||
{
|
||||
default: "",
|
||||
options: "Item",
|
||||
label: __("Item"),
|
||||
@ -45,13 +60,12 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldtype: "MultiSelectList",
|
||||
label: __("Supplier Quotation"),
|
||||
options: "Supplier Quotation",
|
||||
fieldname: "supplier_quotation",
|
||||
default: "",
|
||||
get_query: () => {
|
||||
return { filters: { "docstatus": ["<", 2] } }
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Supplier Quotation', txt, {'docstatus': ["<", 2]});
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -63,9 +77,30 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
get_query: () => {
|
||||
return { filters: { "docstatus": ["<", 2] } }
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
label: __("Include Expired"),
|
||||
fieldname: "include_expired",
|
||||
default: 0
|
||||
}
|
||||
],
|
||||
|
||||
formatter: (value, row, column, data, default_formatter) => {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if(column.fieldname === "valid_till" && data.valid_till){
|
||||
if(frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 1){
|
||||
value = `<div style="color:red">${value}</div>`;
|
||||
}
|
||||
else if (frappe.datetime.get_diff(data.valid_till, frappe.datetime.nowdate()) <= 7){
|
||||
value = `<div style="color:darkorange">${value}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
onload: (report) => {
|
||||
// Create a button for setting the default supplier
|
||||
report.page.add_inner_button(__("Select Default Supplier"), () => {
|
||||
|
@ -16,44 +16,49 @@ def execute(filters=None):
|
||||
supplier_quotation_data = get_data(filters, conditions)
|
||||
columns = get_columns()
|
||||
|
||||
data, chart_data = prepare_data(supplier_quotation_data)
|
||||
data, chart_data = prepare_data(supplier_quotation_data, filters)
|
||||
message = get_message()
|
||||
|
||||
return columns, data, None, chart_data
|
||||
return columns, data, message, chart_data
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
if filters.get("item_code"):
|
||||
conditions += " AND sqi.item_code = %(item_code)s"
|
||||
|
||||
if filters.get("supplier_quotation"):
|
||||
conditions += " AND sqi.parent = %(supplier_quotation)s"
|
||||
conditions += " AND sqi.parent in %(supplier_quotation)s"
|
||||
|
||||
if filters.get("request_for_quotation"):
|
||||
conditions += " AND sqi.request_for_quotation = %(request_for_quotation)s"
|
||||
|
||||
if filters.get("supplier"):
|
||||
conditions += " AND sq.supplier in %(supplier)s"
|
||||
|
||||
if not filters.get("include_expired"):
|
||||
conditions += " AND sq.status != 'Expired'"
|
||||
|
||||
return conditions
|
||||
|
||||
def get_data(filters, conditions):
|
||||
if not filters.get("item_code"):
|
||||
return []
|
||||
|
||||
supplier_quotation_data = frappe.db.sql("""SELECT
|
||||
sqi.parent, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
||||
sq.supplier
|
||||
sqi.parent, sqi.item_code, sqi.qty, sqi.rate, sqi.uom, sqi.request_for_quotation,
|
||||
sqi.lead_time_days, sq.supplier, sq.valid_till
|
||||
FROM
|
||||
`tabSupplier Quotation Item` sqi,
|
||||
`tabSupplier Quotation` sq
|
||||
WHERE
|
||||
sqi.item_code = %(item_code)s
|
||||
AND sqi.parent = sq.name
|
||||
sqi.parent = sq.name
|
||||
AND sqi.docstatus < 2
|
||||
AND sq.company = %(company)s
|
||||
AND sq.status != 'Expired'
|
||||
{0}""".format(conditions), filters, as_dict=1)
|
||||
AND sq.transaction_date between %(from_date)s and %(to_date)s
|
||||
{0}
|
||||
order by sq.transaction_date, sqi.item_code""".format(conditions), filters, as_dict=1)
|
||||
|
||||
return supplier_quotation_data
|
||||
|
||||
def prepare_data(supplier_quotation_data):
|
||||
out, suppliers, qty_list = [], [], []
|
||||
def prepare_data(supplier_quotation_data, filters):
|
||||
out, suppliers, qty_list, chart_data = [], [], [], []
|
||||
supplier_wise_map = defaultdict(list)
|
||||
supplier_qty_price_map = {}
|
||||
|
||||
@ -70,20 +75,24 @@ def prepare_data(supplier_quotation_data):
|
||||
exchange_rate = 1
|
||||
|
||||
row = {
|
||||
"item_code": data.get('item_code'),
|
||||
"quotation": data.get("parent"),
|
||||
"qty": data.get("qty"),
|
||||
"price": flt(data.get("rate") * exchange_rate, float_precision),
|
||||
"uom": data.get("uom"),
|
||||
"request_for_quotation": data.get("request_for_quotation"),
|
||||
"valid_till": data.get('valid_till'),
|
||||
"lead_time_days": data.get('lead_time_days')
|
||||
}
|
||||
|
||||
# map for report view of form {'supplier1':[{},{},...]}
|
||||
supplier_wise_map[supplier].append(row)
|
||||
|
||||
# map for chart preparation of the form {'supplier1': {'qty': 'price'}}
|
||||
if not supplier in supplier_qty_price_map:
|
||||
supplier_qty_price_map[supplier] = {}
|
||||
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
|
||||
if filters.get("item_code"):
|
||||
if not supplier in supplier_qty_price_map:
|
||||
supplier_qty_price_map[supplier] = {}
|
||||
supplier_qty_price_map[supplier][row["qty"]] = row["price"]
|
||||
|
||||
suppliers.append(supplier)
|
||||
qty_list.append(data.get("qty"))
|
||||
@ -97,7 +106,8 @@ def prepare_data(supplier_quotation_data):
|
||||
for entry in supplier_wise_map[supplier]:
|
||||
out.append(entry)
|
||||
|
||||
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
|
||||
if filters.get("item_code"):
|
||||
chart_data = prepare_chart_data(suppliers, qty_list, supplier_qty_price_map)
|
||||
|
||||
return out, chart_data
|
||||
|
||||
@ -117,9 +127,10 @@ def prepare_chart_data(suppliers, qty_list, supplier_qty_price_map):
|
||||
data_points_map[qty].append(None)
|
||||
|
||||
dataset = []
|
||||
currency_symbol = frappe.db.get_value("Currency", frappe.db.get_default("currency"), "symbol")
|
||||
for qty in qty_list:
|
||||
datapoints = {
|
||||
"name": _("Price for Qty ") + str(qty),
|
||||
"name": currency_symbol + " (Qty " + str(qty) + " )",
|
||||
"values": data_points_map[qty]
|
||||
}
|
||||
dataset.append(datapoints)
|
||||
@ -140,14 +151,21 @@ def get_columns():
|
||||
"label": _("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"label": _("Item"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "quotation",
|
||||
"label": _("Supplier Quotation"),
|
||||
"fieldname": "uom",
|
||||
"label": _("UOM"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Quotation",
|
||||
"width": 200
|
||||
"options": "UOM",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"fieldname": "qty",
|
||||
@ -163,19 +181,43 @@ def get_columns():
|
||||
"width": 110
|
||||
},
|
||||
{
|
||||
"fieldname": "uom",
|
||||
"label": _("UOM"),
|
||||
"fieldname": "quotation",
|
||||
"label": _("Supplier Quotation"),
|
||||
"fieldtype": "Link",
|
||||
"options": "UOM",
|
||||
"width": 90
|
||||
"options": "Supplier Quotation",
|
||||
"width": 200
|
||||
},
|
||||
{
|
||||
"fieldname": "valid_till",
|
||||
"label": _("Valid Till"),
|
||||
"fieldtype": "Date",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "lead_time_days",
|
||||
"label": _("Lead Time (Days)"),
|
||||
"fieldtype": "Int",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "request_for_quotation",
|
||||
"label": _("Request for Quotation"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Request for Quotation",
|
||||
"width": 200
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
|
||||
return columns
|
||||
return columns
|
||||
|
||||
def get_message():
|
||||
return """<span class="indicator">
|
||||
Valid till :
|
||||
</span>
|
||||
<span class="indicator orange">
|
||||
Expires in a week or less
|
||||
</span>
|
||||
|
||||
<span class="indicator red">
|
||||
Expires today / Already Expired
|
||||
</span>"""
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:id",
|
||||
"creation": "2019-06-05 12:07:02.634534",
|
||||
"doctype": "DocType",
|
||||
@ -14,6 +15,7 @@
|
||||
"contact",
|
||||
"contact_name",
|
||||
"column_break_10",
|
||||
"customer",
|
||||
"lead",
|
||||
"lead_name",
|
||||
"section_break_5",
|
||||
@ -28,7 +30,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_5",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Call Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "id",
|
||||
@ -125,10 +128,19 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Lead Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"label": "Customer",
|
||||
"options": "Customer",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"modified": "2019-08-06 05:46:53.144683",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-25 17:08:34.085731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Communication",
|
||||
"name": "Call Log",
|
||||
|
@ -16,6 +16,9 @@ class CallLog(Document):
|
||||
self.contact = get_contact_with_phone_number(number)
|
||||
self.lead = get_lead_with_phone_number(number)
|
||||
|
||||
contact = frappe.get_doc("Contact", self.contact)
|
||||
self.customer = contact.get_link_for("Customer")
|
||||
|
||||
def after_insert(self):
|
||||
self.trigger_call_popup()
|
||||
|
||||
|
@ -325,7 +325,7 @@ class AccountsController(TransactionBase):
|
||||
apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data'))
|
||||
|
||||
elif pricing_rule_args.get("validate_applied_rule"):
|
||||
for pricing_rule in get_applied_pricing_rules(item):
|
||||
for pricing_rule in get_applied_pricing_rules(item.get('pricing_rules')):
|
||||
pricing_rule_doc = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
for field in ['discount_percentage', 'discount_amount', 'rate']:
|
||||
if item.get(field) < pricing_rule_doc.get(field):
|
||||
@ -479,7 +479,11 @@ class AccountsController(TransactionBase):
|
||||
if d.against_order:
|
||||
allocated_amount = flt(d.amount)
|
||||
else:
|
||||
amount = self.rounded_total or self.grand_total
|
||||
if self.get('party_account_currency') == self.company_currency:
|
||||
amount = self.get('base_rounded_total') or self.base_grand_total
|
||||
else:
|
||||
amount = self.get('rounded_total') or self.grand_total
|
||||
|
||||
allocated_amount = min(amount - advance_allocated, d.amount)
|
||||
advance_allocated += flt(allocated_amount)
|
||||
|
||||
@ -802,10 +806,22 @@ class AccountsController(TransactionBase):
|
||||
self.payment_terms_template = ''
|
||||
return
|
||||
|
||||
party_account_currency = self.get('party_account_currency')
|
||||
if not party_account_currency:
|
||||
party_type, party = self.get_party()
|
||||
|
||||
if party_type and party:
|
||||
party_account_currency = get_party_account_currency(party_type, party, self.company)
|
||||
|
||||
posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
|
||||
date = self.get("due_date")
|
||||
due_date = date or posting_date
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
|
||||
if party_account_currency == self.company_currency:
|
||||
grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
else:
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
|
||||
@ -850,13 +866,25 @@ class AccountsController(TransactionBase):
|
||||
def validate_payment_schedule_amount(self):
|
||||
if self.doctype == 'Sales Invoice' and self.is_pos: return
|
||||
|
||||
party_account_currency = self.get('party_account_currency')
|
||||
if not party_account_currency:
|
||||
party_type, party = self.get_party()
|
||||
|
||||
if party_type and party:
|
||||
party_account_currency = get_party_account_currency(party_type, party, self.company)
|
||||
|
||||
if self.get("payment_schedule"):
|
||||
total = 0
|
||||
for d in self.get("payment_schedule"):
|
||||
total += flt(d.payment_amount)
|
||||
total = flt(total, self.precision("grand_total"))
|
||||
|
||||
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
|
||||
if party_account_currency == self.company_currency:
|
||||
total = flt(total, self.precision("base_grand_total"))
|
||||
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
|
||||
else:
|
||||
total = flt(total, self.precision("grand_total"))
|
||||
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
|
||||
|
||||
if self.get("total_advance"):
|
||||
grand_total -= self.get("total_advance")
|
||||
|
||||
@ -957,7 +985,7 @@ def validate_inclusive_tax(tax, doc):
|
||||
# all rows about the reffered tax should be inclusive
|
||||
_on_previous_row_error("1 - %d" % (tax.row_id,))
|
||||
elif tax.get("category") == "Valuation":
|
||||
frappe.throw(_("Valuation type charges can not marked as Inclusive"))
|
||||
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
|
||||
|
||||
|
||||
def set_balance_in_account_currency(gl_dict, account_currency=None, conversion_rate=None, company_currency=None):
|
||||
|
@ -276,6 +276,9 @@ class BuyingController(StockController):
|
||||
qty_to_be_received_map = get_qty_to_be_received(purchase_orders)
|
||||
|
||||
for item in self.get('items'):
|
||||
if not item.purchase_order:
|
||||
continue
|
||||
|
||||
# reset raw_material cost
|
||||
item.rm_supp_cost = 0
|
||||
|
||||
@ -288,6 +291,12 @@ class BuyingController(StockController):
|
||||
|
||||
fg_yet_to_be_received = qty_to_be_received_map.get(item_key)
|
||||
|
||||
if not fg_yet_to_be_received:
|
||||
frappe.throw(_("Row #{0}: Item {1} is already fully received in Purchase Order {2}")
|
||||
.format(item.idx, frappe.bold(item.item_code),
|
||||
frappe.utils.get_link_to_form("Purchase Order", item.purchase_order)),
|
||||
title=_("Limit Crossed"))
|
||||
|
||||
transferred_batch_qty_map = get_transferred_batch_qty_map(item.purchase_order, item.item_code)
|
||||
backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code)
|
||||
|
||||
@ -559,9 +568,19 @@ class BuyingController(StockController):
|
||||
"serial_no": cstr(d.serial_no).strip()
|
||||
})
|
||||
if self.is_return:
|
||||
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": self.return_against,
|
||||
"item_code": d.item_code}, "incoming_rate")
|
||||
filters = {
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.return_against,
|
||||
"item_code": d.item_code
|
||||
}
|
||||
|
||||
if (self.doctype == "Purchase Invoice" and self.update_stock
|
||||
and d.get("purchase_invoice_item")):
|
||||
filters["voucher_detail_no"] = d.purchase_invoice_item
|
||||
elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
|
||||
filters["voucher_detail_no"] = d.purchase_receipt_item
|
||||
|
||||
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
|
||||
|
||||
sle.update({
|
||||
"outgoing_rate": original_incoming_rate
|
||||
|
@ -12,6 +12,7 @@ from frappe.utils import unique
|
||||
|
||||
# searches for active employees
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
fields = get_fields("Employee", ["name", "employee_name"])
|
||||
@ -42,6 +43,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
# searches for leads which are not converted
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
|
||||
|
||||
@ -72,6 +74,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
# searches for customer
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
|
||||
@ -110,8 +113,10 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
# searches for supplier
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
|
||||
|
||||
if supp_master_name == "Supplier Name":
|
||||
fields = ["name", "supplier_group"]
|
||||
else:
|
||||
@ -142,32 +147,49 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
company_currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount
|
||||
where tabAccount.docstatus!=2
|
||||
and account_type in (%s)
|
||||
and is_group = 0
|
||||
and company = %s
|
||||
and account_currency = %s
|
||||
and `%s` LIKE %s
|
||||
order by idx desc, name
|
||||
limit %s, %s""" %
|
||||
(", ".join(['%s']*len(filters.get("account_type"))), "%s", "%s", searchfield, "%s", "%s", "%s"),
|
||||
tuple(filters.get("account_type") + [filters.get("company"), company_currency, "%%%s%%" % txt,
|
||||
start, page_len]))
|
||||
def get_accounts(with_account_type_filter):
|
||||
account_type_condition = ''
|
||||
if with_account_type_filter:
|
||||
account_type_condition = "AND account_type in %(account_types)s"
|
||||
|
||||
accounts = frappe.db.sql("""
|
||||
SELECT name, parent_account
|
||||
FROM `tabAccount`
|
||||
WHERE `tabAccount`.docstatus!=2
|
||||
{account_type_condition}
|
||||
AND is_group = 0
|
||||
AND company = %(company)s
|
||||
AND account_currency = %(currency)s
|
||||
AND `{searchfield}` LIKE %(txt)s
|
||||
ORDER BY idx DESC, name
|
||||
LIMIT %(offset)s, %(limit)s
|
||||
""".format(account_type_condition=account_type_condition, searchfield=searchfield),
|
||||
dict(
|
||||
account_types=filters.get("account_type"),
|
||||
company=filters.get("company"),
|
||||
currency=company_currency,
|
||||
txt="%{}%".format(txt),
|
||||
offset=start,
|
||||
limit=page_len
|
||||
)
|
||||
)
|
||||
|
||||
return accounts
|
||||
|
||||
tax_accounts = get_accounts(True)
|
||||
|
||||
if not tax_accounts:
|
||||
tax_accounts = frappe.db.sql("""select name, parent_account from tabAccount
|
||||
where tabAccount.docstatus!=2 and is_group = 0
|
||||
and company = %s and account_currency = %s and `%s` LIKE %s limit %s, %s""" #nosec
|
||||
% ("%s", "%s", searchfield, "%s", "%s", "%s"),
|
||||
(filters.get("company"), company_currency, "%%%s%%" % txt, start, page_len))
|
||||
tax_accounts = get_accounts(False)
|
||||
|
||||
return tax_accounts
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
conditions = []
|
||||
|
||||
@ -215,7 +237,6 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
idx desc,
|
||||
name, item_name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
key=searchfield,
|
||||
columns=columns,
|
||||
scond=searchfields,
|
||||
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||
@ -231,6 +252,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
fields = get_fields("BOM", ["name", "item"])
|
||||
@ -258,6 +280,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ''
|
||||
if filters.get('customer'):
|
||||
@ -285,6 +308,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
||||
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
|
||||
|
||||
@ -315,6 +339,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ""
|
||||
if filters.get("posting_date"):
|
||||
@ -373,6 +398,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
filter_list = []
|
||||
|
||||
@ -395,8 +421,8 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = ["name", "parent_account"],
|
||||
limit_start=start, limit_page_length=page_len, as_list=True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
||||
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
|
||||
@ -413,6 +439,7 @@ def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
|
||||
@ -439,6 +466,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
|
||||
@ -463,29 +491,24 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
# Should be used when item code is passed in filters.
|
||||
conditions, bin_conditions = [], []
|
||||
filter_dict = get_doctype_wise_filters(filters)
|
||||
|
||||
sub_query = """ select round(`tabBin`.actual_qty, 2) from `tabBin`
|
||||
where `tabBin`.warehouse = `tabWarehouse`.name
|
||||
{bin_conditions} """.format(
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),
|
||||
bin_conditions, ignore_permissions=True))
|
||||
|
||||
query = """select `tabWarehouse`.name,
|
||||
CONCAT_WS(" : ", "Actual Qty", ifnull( ({sub_query}), 0) ) as actual_qty
|
||||
from `tabWarehouse`
|
||||
CONCAT_WS(" : ", "Actual Qty", ifnull(round(`tabBin`.actual_qty, 2), 0 )) actual_qty
|
||||
from `tabWarehouse` left join `tabBin`
|
||||
on `tabBin`.warehouse = `tabWarehouse`.name {bin_conditions}
|
||||
where
|
||||
`tabWarehouse`.`{key}` like {txt}
|
||||
`tabWarehouse`.`{key}` like {txt}
|
||||
{fcond} {mcond}
|
||||
order by
|
||||
`tabWarehouse`.name desc
|
||||
order by ifnull(`tabBin`.actual_qty, 0) desc
|
||||
limit
|
||||
{start}, {page_len}
|
||||
""".format(
|
||||
sub_query=sub_query,
|
||||
bin_conditions=get_filters_cond(doctype, filter_dict.get("Bin"),bin_conditions, ignore_permissions=True),
|
||||
key=searchfield,
|
||||
fcond=get_filters_cond(doctype, filter_dict.get("Warehouse"), conditions),
|
||||
mcond=get_match_cond(doctype),
|
||||
@ -506,6 +529,7 @@ def get_doctype_wise_filters(filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """select batch_id from `tabBatch`
|
||||
where disabled = 0
|
||||
@ -519,6 +543,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
item_filters = [
|
||||
['manufacturer', 'like', '%' + txt + '%'],
|
||||
@ -537,6 +562,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
select pr.name
|
||||
@ -551,6 +577,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
select pi.name
|
||||
@ -565,6 +592,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
|
||||
@ -579,9 +607,12 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not taxes:
|
||||
return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
|
||||
else:
|
||||
valid_from = filters.get('valid_from')
|
||||
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
|
||||
|
||||
args = {
|
||||
'item_code': filters.get('item_code'),
|
||||
'posting_date': filters.get('valid_from'),
|
||||
'posting_date': valid_from,
|
||||
'tax_category': filters.get('tax_category'),
|
||||
'company': filters.get('company')
|
||||
}
|
||||
|
@ -281,6 +281,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.rejected_warehouse = source_doc.rejected_warehouse
|
||||
target_doc.po_detail = source_doc.po_detail
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
target_doc.purchase_invoice_item = source_doc.name
|
||||
|
||||
elif doctype == "Delivery Note":
|
||||
target_doc.against_sales_order = source_doc.against_sales_order
|
||||
target_doc.against_sales_invoice = source_doc.against_sales_invoice
|
||||
@ -296,6 +298,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
||||
target_doc.so_detail = source_doc.so_detail
|
||||
target_doc.dn_detail = source_doc.dn_detail
|
||||
target_doc.expense_account = source_doc.expense_account
|
||||
target_doc.sales_invoice_item = source_doc.name
|
||||
if default_warehouse_for_sales_return:
|
||||
target_doc.warehouse = default_warehouse_for_sales_return
|
||||
|
||||
|
@ -217,7 +217,9 @@ class SellingController(StockController):
|
||||
'target_warehouse': p.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'delivery_note_item': d.get("dn_detail")
|
||||
}))
|
||||
else:
|
||||
il.append(frappe._dict({
|
||||
@ -233,7 +235,9 @@ class SellingController(StockController):
|
||||
'target_warehouse': d.target_warehouse,
|
||||
'company': self.company,
|
||||
'voucher_type': self.doctype,
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate
|
||||
'allow_zero_valuation': d.allow_zero_valuation_rate,
|
||||
'sales_invoice_item': d.get("sales_invoice_item"),
|
||||
'delivery_note_item': d.get("dn_detail")
|
||||
}))
|
||||
return il
|
||||
|
||||
@ -302,7 +306,11 @@ class SellingController(StockController):
|
||||
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
|
||||
return_rate = 0
|
||||
if cint(self.is_return) and self.return_against and self.docstatus==1:
|
||||
return_rate = self.get_incoming_rate_for_return(d.item_code, self.return_against)
|
||||
against_document_no = (d.get("sales_invoice_item")
|
||||
if self.doctype == "Sales Invoice" else d.get("delivery_note_item"))
|
||||
|
||||
return_rate = self.get_incoming_rate_for_return(d.item_code,
|
||||
self.return_against, against_document_no)
|
||||
|
||||
# On cancellation or if return entry submission, make stock ledger entry for
|
||||
# target warehouse first, to update serial no values properly
|
||||
|
@ -301,14 +301,19 @@ class StockController(AccountsController):
|
||||
|
||||
return serialized_items
|
||||
|
||||
def get_incoming_rate_for_return(self, item_code, against_document):
|
||||
def get_incoming_rate_for_return(self, item_code, against_document, against_document_no=None):
|
||||
incoming_rate = 0.0
|
||||
cond = ''
|
||||
if against_document and item_code:
|
||||
if against_document_no:
|
||||
cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no))
|
||||
|
||||
incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where voucher_type = %s and voucher_no = %s
|
||||
and item_code = %s limit 1""",
|
||||
and item_code = %s {0} limit 1""".format(cond),
|
||||
(self.doctype, against_document, item_code))
|
||||
|
||||
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
|
||||
|
||||
return incoming_rate
|
||||
|
@ -9,6 +9,7 @@ from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
|
||||
from erpnext.controllers.accounts_controller import validate_conversion_rate, \
|
||||
validate_taxes_and_charges, validate_inclusive_tax
|
||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import get_applied_pricing_rules
|
||||
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
@ -161,8 +162,9 @@ class calculate_taxes_and_totals(object):
|
||||
for item in self.doc.get("items"):
|
||||
item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
|
||||
cumulated_tax_fraction = 0
|
||||
total_inclusive_tax_amount_per_qty = 0
|
||||
for i, tax in enumerate(self.doc.get("taxes")):
|
||||
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
tax.tax_fraction_for_current_item, inclusive_tax_amount_per_qty = self.get_current_tax_fraction(tax, item_tax_map)
|
||||
|
||||
if i==0:
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||
@ -172,9 +174,12 @@ class calculate_taxes_and_totals(object):
|
||||
+ tax.tax_fraction_for_current_item
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty)
|
||||
|
||||
if cumulated_tax_fraction and not self.discount_amount_applied and item.qty:
|
||||
item.net_amount = flt(item.amount / (1 + cumulated_tax_fraction))
|
||||
if not self.discount_amount_applied and item.qty and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty):
|
||||
amount = flt(item.amount) - total_inclusive_tax_amount_per_qty
|
||||
|
||||
item.net_amount = flt(amount / (1 + cumulated_tax_fraction))
|
||||
item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate"))
|
||||
item.discount_percentage = flt(item.discount_percentage,
|
||||
item.precision("discount_percentage"))
|
||||
@ -190,6 +195,7 @@ class calculate_taxes_and_totals(object):
|
||||
from tax inclusive amount
|
||||
"""
|
||||
current_tax_fraction = 0
|
||||
inclusive_tax_amount_per_qty = 0
|
||||
|
||||
if cint(tax.included_in_print_rate):
|
||||
tax_rate = self._get_tax_rate(tax, item_tax_map)
|
||||
@ -205,9 +211,14 @@ class calculate_taxes_and_totals(object):
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||
|
||||
if getattr(tax, "add_deduct_tax", None):
|
||||
current_tax_fraction *= -1.0 if (tax.add_deduct_tax == "Deduct") else 1.0
|
||||
return current_tax_fraction
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
inclusive_tax_amount_per_qty = flt(tax_rate)
|
||||
|
||||
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
|
||||
current_tax_fraction *= -1.0
|
||||
inclusive_tax_amount_per_qty *= -1.0
|
||||
|
||||
return current_tax_fraction, inclusive_tax_amount_per_qty
|
||||
|
||||
def _get_tax_rate(self, tax, item_tax_map):
|
||||
if tax.account_head in item_tax_map:
|
||||
@ -321,7 +332,7 @@ class calculate_taxes_and_totals(object):
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.stock_qty
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
|
||||
@ -472,7 +483,7 @@ class calculate_taxes_and_totals(object):
|
||||
actual_taxes_dict = {}
|
||||
|
||||
for tax in self.doc.get("taxes"):
|
||||
if tax.charge_type == "Actual":
|
||||
if tax.charge_type in ["Actual", "On Item Quantity"]:
|
||||
tax_amount = self.get_tax_amount_if_for_valuation_or_deduction(tax.tax_amount, tax)
|
||||
actual_taxes_dict.setdefault(tax.idx, tax_amount)
|
||||
elif tax.row_id in actual_taxes_dict:
|
||||
@ -597,7 +608,7 @@ class calculate_taxes_and_totals(object):
|
||||
base_rate_with_margin = 0.0
|
||||
if item.price_list_rate:
|
||||
if item.pricing_rules and not self.doc.ignore_pricing_rule:
|
||||
for d in json.loads(item.pricing_rules):
|
||||
for d in get_applied_pricing_rules(item.pricing_rules):
|
||||
pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
|
||||
|
||||
if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\
|
||||
|
@ -16,6 +16,7 @@
|
||||
"opportunity_from",
|
||||
"party_name",
|
||||
"customer_name",
|
||||
"source",
|
||||
"column_break0",
|
||||
"title",
|
||||
"opportunity_type",
|
||||
@ -49,10 +50,9 @@
|
||||
"contact_email",
|
||||
"contact_mobile",
|
||||
"more_info",
|
||||
"source",
|
||||
"company",
|
||||
"campaign",
|
||||
"column_break1",
|
||||
"company",
|
||||
"transaction_date",
|
||||
"amended_from",
|
||||
"lost_reasons"
|
||||
@ -344,7 +344,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "more_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Source",
|
||||
"label": "More Information",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-file-text"
|
||||
},
|
||||
@ -411,7 +411,7 @@
|
||||
"fieldname": "lost_reasons",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Lost Reasons",
|
||||
"options": "Lost Reason Detail",
|
||||
"options": "Opportunity Lost Reason Detail",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@ -424,7 +424,7 @@
|
||||
"icon": "fa fa-info-sign",
|
||||
"idx": 195,
|
||||
"links": [],
|
||||
"modified": "2020-07-14 16:49:15.888503",
|
||||
"modified": "2020-08-11 17:34:35.066961",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity",
|
||||
|
@ -119,11 +119,19 @@ class Opportunity(TransactionBase):
|
||||
and q.status not in ('Lost', 'Closed')""", self.name)
|
||||
|
||||
def has_ordered_quotation(self):
|
||||
return frappe.db.sql("""
|
||||
select q.name
|
||||
from `tabQuotation` q, `tabQuotation Item` qi
|
||||
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
|
||||
and q.status = 'Ordered'""", self.name)
|
||||
if not self.with_items:
|
||||
return frappe.get_all('Quotation',
|
||||
{
|
||||
'opportunity': self.name,
|
||||
'status': 'Ordered',
|
||||
'docstatus': 1
|
||||
}, 'name')
|
||||
else:
|
||||
return frappe.db.sql("""
|
||||
select q.name
|
||||
from `tabQuotation` q, `tabQuotation Item` qi
|
||||
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
|
||||
and q.status = 'Ordered'""", self.name)
|
||||
|
||||
def has_lost_quotation(self):
|
||||
lost_quotation = frappe.db.sql("""
|
||||
@ -259,6 +267,9 @@ def make_quotation(source_name, target_doc=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_request_for_quotation(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.conversion_factor = 1.0
|
||||
|
||||
doclist = get_mapped_doc("Opportunity", source_name, {
|
||||
"Opportunity": {
|
||||
"doctype": "Request for Quotation"
|
||||
@ -269,7 +280,8 @@ def make_request_for_quotation(source_name, target_doc=None):
|
||||
["name", "opportunity_item"],
|
||||
["parent", "opportunity"],
|
||||
["uom", "uom"]
|
||||
]
|
||||
],
|
||||
"postprocess": update_item
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
@ -317,7 +329,7 @@ def auto_close_opportunity():
|
||||
doc.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_opportunity_from_communication(communication, ignore_communication_links=False):
|
||||
def make_opportunity_from_communication(communication, company, ignore_communication_links=False):
|
||||
from erpnext.crm.doctype.lead.lead import make_lead_from_communication
|
||||
doc = frappe.get_doc("Communication", communication)
|
||||
|
||||
@ -329,8 +341,9 @@ def make_opportunity_from_communication(communication, ignore_communication_link
|
||||
|
||||
opportunity = frappe.get_doc({
|
||||
"doctype": "Opportunity",
|
||||
"company": company,
|
||||
"opportunity_from": opportunity_from,
|
||||
"lead": lead
|
||||
"party_name": lead
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links)
|
||||
|
@ -82,7 +82,8 @@ def make_opportunity(**args):
|
||||
if args.with_items:
|
||||
opp_doc.append('items', {
|
||||
"item_code": args.item_code or "_Test Item",
|
||||
"qty": args.qty or 1
|
||||
"qty": args.qty or 1,
|
||||
"uom": "_Test UOM"
|
||||
})
|
||||
|
||||
opp_doc.insert()
|
||||
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-07-16 16:11:39.830389",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"lost_reason"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "lost_reason",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Opportunity Lost Reason",
|
||||
"options": "Opportunity Lost Reason"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-07-26 17:58:26.313242",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Opportunity Lost Reason Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class OpportunityLostReasonDetail(Document):
|
||||
pass
|
@ -30,14 +30,14 @@ frappe.ui.form.on('Social Media Post', {
|
||||
let color = frm.doc.twitter_post_id ? "green" : "red";
|
||||
let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">Twitter : ${status} </span></span>
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
if (frm.doc.linkedin){
|
||||
let color = frm.doc.linkedin_post_id ? "green" : "red";
|
||||
let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
|
||||
html += `<div class="col-xs-6">
|
||||
<span class="indicator whitespace-nowrap ${color}"><span class="hidden-xs">LinkedIn : ${status} </span></span>
|
||||
<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
|
||||
</div>` ;
|
||||
}
|
||||
html = `<div class="row">${html}</div>`;
|
||||
|
52
erpnext/crm/report/lead_details/lead_details.js
Normal file
52
erpnext/crm/report/lead_details/lead_details.js
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lead Details"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"status",
|
||||
"label": __("Status"),
|
||||
"fieldtype": "Select",
|
||||
options: [
|
||||
{ "value": "Lead", "label": __("Lead") },
|
||||
{ "value": "Open", "label": __("Open") },
|
||||
{ "value": "Replied", "label": __("Replied") },
|
||||
{ "value": "Opportunity", "label": __("Opportunity") },
|
||||
{ "value": "Quotation", "label": __("Quotation") },
|
||||
{ "value": "Lost Quotation", "label": __("Lost Quotation") },
|
||||
{ "value": "Interested", "label": __("Interested") },
|
||||
{ "value": "Converted", "label": __("Converted") },
|
||||
{ "value": "Do Not Contact", "label": __("Do Not Contact") },
|
||||
],
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
}
|
||||
]
|
||||
};
|
@ -7,16 +7,15 @@
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2020-01-22 16:51:56.591110",
|
||||
"modified": "2020-07-26 23:59:49.897577",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead Details",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "SELECT\n `tabLead`.name as \"Lead Id:Link/Lead:120\",\n `tabLead`.lead_name as \"Lead Name::120\",\n\t`tabLead`.company_name as \"Company Name::120\",\n\t`tabLead`.status as \"Status::120\",\n\tconcat_ws(', ', \n\t\ttrim(',' from `tabAddress`.address_line1), \n\t\ttrim(',' from tabAddress.address_line2)\n\t) as 'Address::180',\n\t`tabAddress`.state as \"State::100\",\n\t`tabAddress`.pincode as \"Pincode::70\",\n\t`tabAddress`.country as \"Country::100\",\n\t`tabLead`.phone as \"Phone::100\",\n\t`tabLead`.mobile_no as \"Mobile No::100\",\n\t`tabLead`.email_id as \"Email Id::120\",\n\t`tabLead`.lead_owner as \"Lead Owner::120\",\n\t`tabLead`.source as \"Source::120\",\n\t`tabLead`.territory as \"Territory::120\",\n\t`tabLead`.notes as \"Notes::360\",\n `tabLead`.owner as \"Owner:Link/User:120\"\nFROM\n\t`tabLead`\n\tleft join `tabDynamic Link` on (\n\t\t`tabDynamic Link`.link_name=`tabLead`.name \n\t\tand `tabDynamic Link`.parenttype = 'Address'\n\t)\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.name=`tabDynamic Link`.parent\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc",
|
||||
"ref_doctype": "Lead",
|
||||
"report_name": "Lead Details",
|
||||
"report_type": "Query Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
|
158
erpnext/crm/report/lead_details/lead_details.py
Normal file
158
erpnext/crm/report/lead_details/lead_details.py
Normal file
@ -0,0 +1,158 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = get_columns(), get_data(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{
|
||||
"label": _("Lead"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Lead",
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"label": _("Lead Name"),
|
||||
"fieldname": "lead_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname":"status",
|
||||
"label": _("Status"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname":"lead_owner",
|
||||
"label": _("Lead Owner"),
|
||||
"fieldtype": "Link",
|
||||
"options": "User",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"label": _("Source"),
|
||||
"fieldname": "source",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Email"),
|
||||
"fieldname": "email_id",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Mobile"),
|
||||
"fieldname": "mobile_no",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Phone"),
|
||||
"fieldname": "phone",
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Owner"),
|
||||
"fieldname": "owner",
|
||||
"fieldtype": "Link",
|
||||
"options": "user",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"label": _("Company"),
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname":"address",
|
||||
"label": _("Address"),
|
||||
"fieldtype": "Data",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"fieldname":"state",
|
||||
"label": _("State"),
|
||||
"fieldtype": "Data",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname":"pincode",
|
||||
"label": _("Postal Code"),
|
||||
"fieldtype": "Data",
|
||||
"width": 90
|
||||
},
|
||||
{
|
||||
"fieldname":"country",
|
||||
"label": _("Country"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Country",
|
||||
"width": 100
|
||||
},
|
||||
|
||||
]
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
`tabLead`.name,
|
||||
`tabLead`.lead_name,
|
||||
`tabLead`.status,
|
||||
`tabLead`.lead_owner,
|
||||
`tabLead`.territory,
|
||||
`tabLead`.source,
|
||||
`tabLead`.email_id,
|
||||
`tabLead`.mobile_no,
|
||||
`tabLead`.phone,
|
||||
`tabLead`.owner,
|
||||
`tabLead`.company,
|
||||
concat_ws(', ',
|
||||
trim(',' from `tabAddress`.address_line1),
|
||||
trim(',' from tabAddress.address_line2)
|
||||
) AS address,
|
||||
`tabAddress`.state,
|
||||
`tabAddress`.pincode,
|
||||
`tabAddress`.country
|
||||
FROM
|
||||
`tabLead` left join `tabDynamic Link` on (
|
||||
`tabLead`.name = `tabDynamic Link`.link_name and
|
||||
`tabDynamic Link`.parenttype = 'Address')
|
||||
left join `tabAddress` on (
|
||||
`tabAddress`.name=`tabDynamic Link`.parent)
|
||||
WHERE
|
||||
company = %(company)s
|
||||
AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
|
||||
{conditions}
|
||||
ORDER BY
|
||||
`tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1)
|
||||
|
||||
def get_conditions(filters) :
|
||||
conditions = []
|
||||
|
||||
if filters.get("territory"):
|
||||
conditions.append(" and `tabLead`.territory=%(territory)s")
|
||||
|
||||
if filters.get("status"):
|
||||
conditions.append(" and `tabLead`.status=%(status)s")
|
||||
|
||||
return " ".join(conditions) if conditions else ""
|
||||
|
67
erpnext/crm/report/lost_opportunity/lost_opportunity.js
Normal file
67
erpnext/crm/report/lost_opportunity/lost_opportunity.js
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Lost Opportunity"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -12),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"lost_reason",
|
||||
"label": __("Lost Reason"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Opportunity Lost Reason"
|
||||
},
|
||||
{
|
||||
"fieldname":"territory",
|
||||
"label": __("Territory"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory"
|
||||
},
|
||||
{
|
||||
"fieldname":"opportunity_from",
|
||||
"label": __("Opportunity From"),
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", ["Customer", "Lead"]],
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"party_name",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "opportunity_from"
|
||||
},
|
||||
{
|
||||
"fieldname":"contact_by",
|
||||
"label": __("Next Contact By"),
|
||||
"fieldtype": "Link",
|
||||
"options": "User"
|
||||
},
|
||||
]
|
||||
};
|
@ -1,13 +1,14 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"creation": "2018-12-31 16:30:57.188837",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}",
|
||||
"modified": "2019-06-26 16:33:08.083618",
|
||||
"modified": "2020-07-29 15:49:02.848845",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lost Opportunity",
|
||||
@ -15,7 +16,7 @@
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Opportunity",
|
||||
"report_name": "Lost Opportunity",
|
||||
"report_type": "Report Builder",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Sales User"
|
||||
|
131
erpnext/crm/report/lost_opportunity/lost_opportunity.py
Normal file
131
erpnext/crm/report/lost_opportunity/lost_opportunity.py
Normal file
@ -0,0 +1,131 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe import _
|
||||
import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
columns, data = get_columns(), get_data(filters)
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
columns = [
|
||||
{
|
||||
"label": _("Opportunity"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Opportunity",
|
||||
"width": 170,
|
||||
},
|
||||
{
|
||||
"label": _("Opportunity From"),
|
||||
"fieldname": "opportunity_from",
|
||||
"fieldtype": "Link",
|
||||
"options": "DocType",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"label": _("Party"),
|
||||
"fieldname":"party_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "opportunity_from",
|
||||
"width": 160
|
||||
},
|
||||
{
|
||||
"label": _("Customer/Lead Name"),
|
||||
"fieldname":"customer_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Opportunity Type"),
|
||||
"fieldname": "opportunity_type",
|
||||
"fieldtype": "Data",
|
||||
"width": 130
|
||||
},
|
||||
{
|
||||
"label": _("Lost Reasons"),
|
||||
"fieldname": "lost_reason",
|
||||
"fieldtype": "Data",
|
||||
"width": 220
|
||||
},
|
||||
{
|
||||
"label": _("Sales Stage"),
|
||||
"fieldname": "sales_stage",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Stage",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 150
|
||||
},
|
||||
{
|
||||
"label": _("Next Contact By"),
|
||||
"fieldname": "contact_by",
|
||||
"fieldtype": "Link",
|
||||
"options": "User",
|
||||
"width": 150
|
||||
}
|
||||
]
|
||||
return columns
|
||||
|
||||
def get_data(filters):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
`tabOpportunity`.name,
|
||||
`tabOpportunity`.opportunity_from,
|
||||
`tabOpportunity`.party_name,
|
||||
`tabOpportunity`.customer_name,
|
||||
`tabOpportunity`.opportunity_type,
|
||||
`tabOpportunity`.contact_by,
|
||||
GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason,
|
||||
`tabOpportunity`.sales_stage,
|
||||
`tabOpportunity`.territory
|
||||
FROM
|
||||
`tabOpportunity`
|
||||
{join}
|
||||
WHERE
|
||||
`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
|
||||
AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s
|
||||
{conditions}
|
||||
GROUP BY
|
||||
`tabOpportunity`.name
|
||||
ORDER BY
|
||||
`tabOpportunity`.creation asc """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1)
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = []
|
||||
|
||||
if filters.get("territory"):
|
||||
conditions.append(" and `tabOpportunity`.territory=%(territory)s")
|
||||
|
||||
if filters.get("opportunity_from"):
|
||||
conditions.append(" and `tabOpportunity`.opportunity_from=%(opportunity_from)s")
|
||||
|
||||
if filters.get("party_name"):
|
||||
conditions.append(" and `tabOpportunity`.party_name=%(party_name)s")
|
||||
|
||||
if filters.get("contact_by"):
|
||||
conditions.append(" and `tabOpportunity`.contact_by=%(contact_by)s")
|
||||
|
||||
return " ".join(conditions) if conditions else ""
|
||||
|
||||
def get_join(filters):
|
||||
join = """LEFT JOIN `tabOpportunity Lost Reason Detail`
|
||||
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
||||
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name"""
|
||||
|
||||
if filters.get("lost_reason"):
|
||||
join = """JOIN `tabOpportunity Lost Reason Detail`
|
||||
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
|
||||
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
|
||||
`tabOpportunity Lost Reason Detail`.lost_reason = '{0}'
|
||||
""".format(filters.get("lost_reason"))
|
||||
|
||||
return join
|
@ -0,0 +1,31 @@
|
||||
{
|
||||
"based_on": "",
|
||||
"chart_name": "Course wise Enrollment",
|
||||
"chart_type": "Group By",
|
||||
"creation": "2020-07-23 18:24:38.214220",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"document_type": "Course Enrollment",
|
||||
"dynamic_filters_json": "[]",
|
||||
"filters_json": "[[\"Course Enrollment\",\"enrollment_date\",\"Timespan\",\"this year\",false]]",
|
||||
"group_by_based_on": "course",
|
||||
"group_by_type": "Count",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"last_synced_on": "2020-07-27 17:50:32.490587",
|
||||
"modified": "2020-07-27 17:54:09.829206",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Course wise Enrollment",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"source": "",
|
||||
"time_interval": "Yearly",
|
||||
"timeseries": 0,
|
||||
"timespan": "Last Year",
|
||||
"type": "Percentage",
|
||||
"use_report_chart": 0,
|
||||
"value_based_on": "",
|
||||
"y_axis": []
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user