Merge branch 'develop' into youtube-analytics

This commit is contained in:
Marica 2020-09-07 18:54:11 +05:30 committed by GitHub
commit 54ab426c1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
326 changed files with 6775 additions and 4565 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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', [])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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']
}
]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 "&nbsp;" }}</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>

View File

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

View File

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

View File

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

View File

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

View File

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

View 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 ProcessStatementOfAccountsCustomer(Document):
pass

View File

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

View 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 PSOACostCenter(Document):
pass

View 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
}

View 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

View File

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

View File

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

View File

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

View File

@ -1621,6 +1621,7 @@ def update_multi_mode_option(doc, pos_profile):
pos_payment_method = pos_payment_method.as_dict()
payment_mode = get_mode_of_payment_info(pos_payment_method.mode_of_payment, doc.company)
if payment_mode:
payment_mode[0].default = pos_payment_method.default
append_payment(payment_mode[0])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -643,6 +643,8 @@ 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})]
if accounts:
conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
values += accounts

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,6 +136,10 @@ 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,
@ -141,7 +153,7 @@ def get_gl_entries(filters):
party_type,
party,
voucher_type,
voucher_no,
voucher_no, {dimension_fields}
cost_center, project,
against_voucher_type,
against_voucher,
@ -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",

View File

@ -1,13 +1,12 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"add_total_row": 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": "2020-08-13 11:26:39.112352",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",

View File

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

View File

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

View File

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

View File

@ -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"];
if (doc.maintenance_status=="Planned") {
return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
} else if (doc.maintenance_status=="Completed") {
return [__("Completed"), "green"];
return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
} else if (doc.maintenance_status=="Cancelled") {
return [__("Cancelled"), "red"];
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
} else if (doc.maintenance_status=="Overdue") {
return [__("Overdue"), "red"];
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
}
}
};

View File

@ -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,18 +54,34 @@ 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()

View File

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

View File

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

View File

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

View File

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

View File

@ -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,17 +75,21 @@ 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 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"]
@ -97,6 +106,7 @@ def prepare_data(supplier_quotation_data):
for entry in supplier_wise_map[supplier]:
out.append(entry)
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
def get_message():
return """<span class="indicator">
Valid till : &nbsp;&nbsp;
</span>
<span class="indicator orange">
Expires in a week or less
</span>
&nbsp;&nbsp;
<span class="indicator red">
Expires today / Already Expired
</span>"""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -119,6 +119,14 @@ class Opportunity(TransactionBase):
and q.status not in ('Lost', 'Closed')""", self.name)
def has_ordered_quotation(self):
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
@ -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)

View File

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

View File

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

View 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 OpportunityLostReasonDetail(Document):
pass

View File

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

View 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",
}
]
};

View File

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

View 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 ""

View 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"
},
]
};

View File

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

View 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

View File

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

View File

@ -0,0 +1,31 @@
{
"based_on": "",
"chart_name": "Course wise Student Count",
"chart_type": "Group By",
"creation": "2020-07-27 17:24:39.136163",
"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:24:56.184236",
"modified": "2020-07-27 17:25:46.232846",
"modified_by": "Administrator",
"module": "Education",
"name": "Course wise Student Count",
"number_of_groups": 0,
"owner": "Administrator",
"source": "",
"time_interval": "Yearly",
"timeseries": 0,
"timespan": "Last Year",
"type": "Donut",
"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