Merge branch 'staging-fixes' into staging

This commit is contained in:
Ameya Shenoy 2018-10-04 11:42:56 +00:00
commit c6eac7c60b
No known key found for this signature in database
GPG Key ID: AC016A555657D0A3
103 changed files with 11239 additions and 9550 deletions

View File

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

View File

@ -35,8 +35,8 @@ def validate_service_stop_date(doc):
def convert_deferred_expense_to_expense(start_date=None, end_date=None):
# check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select parent from `tabPurchase Invoice Item` where service_start_date<=%s and service_end_date>=%s
and enable_deferred_expense = 1 and docstatus = 1
select distinct parent from `tabPurchase Invoice Item` where service_start_date<=%s and service_end_date>=%s
and enable_deferred_expense = 1 and docstatus = 1 and ifnull(amount, 0) > 0
''', (end_date or today(), start_date or add_months(today(), -1)))
# For each invoice, book deferred expense
@ -47,8 +47,8 @@ def convert_deferred_expense_to_expense(start_date=None, end_date=None):
def convert_deferred_revenue_to_income(start_date=None, end_date=None):
# check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list('''
select parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s
and enable_deferred_revenue = 1 and docstatus = 1
select distinct parent from `tabSales Invoice Item` where service_start_date<=%s and service_end_date>=%s
and enable_deferred_revenue = 1 and docstatus = 1 and ifnull(amount, 0) > 0
''', (end_date or today(), start_date or add_months(today(), -1)))
# For each invoice, book deferred revenue

View File

@ -58,7 +58,6 @@ class CostCenter(NestedSet):
# Validate properties before merging
super(CostCenter, self).before_rename(olddn, new_cost_center, merge, "is_group")
if not merge:
from erpnext.accounts.doctype.account.account import get_name_with_number
new_cost_center = get_name_with_number(new_cost_center, self.cost_center_number)
return new_cost_center
@ -89,3 +88,8 @@ class CostCenter(NestedSet):
def on_doctype_update():
frappe.db.add_index("Cost Center", ["lft", "rgt"])
def get_name_with_number(new_account, account_number):
if account_number and not new_account[0].isdigit():
new_account = account_number + " - " + new_account
return new_account

View File

@ -23,7 +23,6 @@ frappe.ui.form.on('Payment Entry', {
}
}
});
frm.set_query("party_type", function() {
return{
"filters": {
@ -31,7 +30,17 @@ frappe.ui.form.on('Payment Entry', {
}
}
});
frm.set_query("contact_person", function() {
if (frm.doc.party) {
return {
query: 'frappe.contacts.doctype.contact.contact.contact_query',
filters: {
link_doctype: frm.doc.party_type,
link_name: frm.doc.party
}
};
}
});
frm.set_query("paid_to", function() {
var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type) ?
["Bank", "Cash"] : [frappe.boot.party_account_types[frm.doc.party_type]];
@ -114,6 +123,11 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_dynamic_labels(frm);
},
contact_person: function(frm) {
frm.set_value("contact_email", "");
erpnext.utils.get_contact_details(frm);
},
hide_unhide_fields: function(frm) {
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
@ -146,7 +160,7 @@ frappe.ui.form.on('Payment Entry', {
frm.toggle_display("set_exchange_gain_loss",
(frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount &&
((frm.doc.paid_from_account_currency != company_currency ||
frm.doc.paid_to_account_currency != company_currency) &&
frm.doc.paid_to_account_currency != company_currency) &&
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)));
frm.refresh_fields();
@ -208,7 +222,7 @@ frappe.ui.form.on('Payment Entry', {
});
} else {
if(frm.doc.party) {
frm.events.party(frm);
frm.events.party(frm);
}
if(frm.doc.mode_of_payment) {
@ -230,13 +244,16 @@ frappe.ui.form.on('Payment Entry', {
},
party: function(frm) {
if (frm.doc.contact_email || frm.doc.contact_person) {
frm.set_value("contact_email", "");
frm.set_value("contact_person", "");
}
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party) {
if(!frm.doc.posting_date) {
frappe.msgprint(__("Please select Posting Date before selecting Party"))
frm.set_value("party", "");
return ;
}
frm.set_party_account_based_on_party = true;
return frappe.call({
@ -302,7 +319,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
}
frm.set_value("received_amount", frm.doc.paid_amount);
} else {
frm.events.received_amount(frm);
}
@ -350,7 +367,7 @@ frappe.ui.form.on('Payment Entry', {
]);
}
}
});
});
}
},
@ -402,7 +419,7 @@ frappe.ui.form.on('Payment Entry', {
}
})
},
posting_date: function(frm) {
frm.events.paid_from_account_currency(frm);
},
@ -415,7 +432,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
frm.events.set_unallocated_amount(frm);
}
@ -425,17 +442,17 @@ frappe.ui.form.on('Payment Entry', {
target_exchange_rate: function(frm) {
frm.set_paid_amount_based_on_received_amount = true;
if (frm.doc.received_amount) {
frm.set_value("base_received_amount",
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate));
if(!frm.doc.source_exchange_rate &&
if(!frm.doc.source_exchange_rate &&
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
}
frm.events.set_unallocated_amount(frm);
}
frm.set_paid_amount_based_on_received_amount = false;
@ -468,14 +485,14 @@ frappe.ui.form.on('Payment Entry', {
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount);
else
frm.events.set_unallocated_amount(frm);
frm.set_paid_amount_based_on_received_amount = false;
},
reset_received_amount: function(frm) {
if(!frm.set_paid_amount_based_on_received_amount &&
(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency)) {
frm.set_value("received_amount", frm.doc.paid_amount);
if(frm.doc.source_exchange_rate) {
@ -483,7 +500,7 @@ frappe.ui.form.on('Payment Entry', {
}
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
}
if(frm.doc.payment_type == "Receive")
frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount);
else
@ -549,7 +566,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
) {
if(total_positive_outstanding > total_negative_outstanding)
frm.set_value("paid_amount",
@ -694,7 +711,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_value("unallocated_amount", unallocated_amount);
frm.trigger("set_difference_amount");
},
set_difference_amount: function(frm) {
var difference_amount = 0;
var base_unallocated_amount = flt(frm.doc.unallocated_amount) *
@ -753,7 +770,7 @@ frappe.ui.form.on('Payment Entry', {
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx]));
return false;
}
if(frm.doc.party_type=="Employee" &&
!in_list(["Expense Claim", "Journal Entry"], row.reference_doctype)
) {

View File

@ -376,6 +376,40 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "party",
"fieldname": "contact_person",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact",
"length": 0,
"no_copy": 0,
"options": "Contact",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -441,6 +475,40 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "contact_person",
"fieldname": "contact_email",
"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": "Email",
"length": 0,
"no_copy": 0,
"options": "Email",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -1972,7 +2040,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-09-11 15:44:28.647566",
"modified": "2018-09-25 14:38:48.312629",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@ -299,12 +299,12 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Receive" \
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
self.unallocated_amount = (self.base_received_amount + total_deductions -
self.unallocated_amount = (self.base_received_amount + total_deductions -
self.base_total_allocated_amount) / self.source_exchange_rate
elif self.payment_type == "Pay" \
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
self.base_total_allocated_amount)) / self.target_exchange_rate
def set_difference_amount(self):
@ -790,7 +790,6 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
@ -877,7 +876,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
pe.contact_person = doc.get("contact_person")
pe.contact_email = doc.get("contact_email")
pe.ensure_supplier_is_not_blocked()
pe.paid_from = party_account if payment_type=="Receive" else bank.account

View File

@ -116,6 +116,18 @@ frappe.ui.form.on('Pricing Rule', {
};
},
onload: function(frm) {
if(frm.doc.__islocal && !frm.doc.applicable_for && (frm.doc.customer || frm.doc.supplier)) {
if(frm.doc.customer) {
frm.doc.applicable_for = "Customer";
frm.doc.selling = 1
} else {
frm.doc.applicable_for = "Supplier";
frm.doc.buying = 1
}
}
},
refresh: function(frm) {
var help_content =
`<table class="table table-bordered" style="background-color: #f9f9f9;">

View File

@ -384,3 +384,13 @@ def set_transaction_type(args):
args.transaction_type = "selling"
else:
args.transaction_type = "buying"
@frappe.whitelist()
def make_pricing_rule(doctype, docname):
doc = frappe.new_doc("Pricing Rule")
doc.applicable_for = doctype
doc.set(frappe.scrub(doctype), docname)
doc.selling = 1 if doctype == "Customer" else 0
doc.buying = 1 if doctype == "Supplier" else 0
return doc

View File

@ -77,8 +77,9 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
}
if (doc.outstanding_amount > 0 && !cint(doc.is_return)) {
cur_frm.add_custom_button(__('Payment Request'),
this.make_payment_request, __("Make"));
cur_frm.add_custom_button(__('Payment Request'), function() {
me.make_payment_request()
}, __("Make"));
}
if(doc.docstatus===0) {

View File

@ -403,16 +403,20 @@ class PurchaseInvoice(BuyingController):
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
warehouse_account = get_warehouse_account_map()
voucher_wise_stock_value = {}
if self.update_stock:
for d in frappe.get_all('Stock Ledger Entry',
fields = ["voucher_detail_no", "stock_value_difference"], filters={'voucher_no': self.name}):
voucher_wise_stock_value.setdefault(d.voucher_detail_no, d.stock_value_difference)
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
if self.update_stock and self.auto_accounting_for_stock and item.item_code in stock_items:
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
# warehouse account
warehouse_debit_amount = flt(flt(item.valuation_rate, val_rate_db_precision)
* flt(item.qty) * flt(item.conversion_factor), item.precision("base_net_amount"))
warehouse_debit_amount = self.make_stock_adjustment_entry(gl_entries,
item, voucher_wise_stock_value, account_currency)
gl_entries.append(
self.get_gl_dict({
@ -552,6 +556,36 @@ class PurchaseInvoice(BuyingController):
return gl_entries
def make_stock_adjustment_entry(self, gl_entries, item, voucher_wise_stock_value, account_currency):
net_amt_precision = item.precision("base_net_amount")
val_rate_db_precision = 6 if cint(item.precision("valuation_rate")) <= 6 else 9
warehouse_debit_amount = flt(flt(item.valuation_rate, val_rate_db_precision)
* flt(item.qty) * flt(item.conversion_factor), net_amt_precision)
# Stock ledger value is not matching with the warehouse amount
if (self.update_stock and voucher_wise_stock_value.get(item.name) and
warehouse_debit_amount != flt(voucher_wise_stock_value.get(item.name), net_amt_precision)):
cost_of_goods_sold_account = self.get_company_default("default_expense_account")
stock_amount = flt(voucher_wise_stock_value.get(item.name), net_amt_precision)
stock_adjustment_amt = warehouse_debit_amount - stock_amount
gl_entries.append(
self.get_gl_dict({
"account": cost_of_goods_sold_account,
"against": item.expense_account,
"debit": stock_adjustment_amt,
"remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency)
)
warehouse_debit_amount = stock_amount
return warehouse_debit_amount
def make_tax_gl_entries(self, gl_entries):
# tax table gl entries
valuation_tax = {}

View File

@ -1742,7 +1742,7 @@
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@ -2544,7 +2544,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-09-04 10:11:28.246395",
"modified": "2018-10-04 09:05:43.166721",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@ -35,6 +35,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
},
refresh: function(doc, dt, dn) {
const me = this;
this._super();
if(cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
@ -82,9 +83,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
}
}
if(doc.outstanding_amount>0 && !cint(doc.is_return)) {
cur_frm.add_custom_button(__('Payment Request'),
this.make_payment_request, __("Make"));
if (doc.outstanding_amount>0 && !cint(doc.is_return)) {
cur_frm.add_custom_button(__('Payment Request'), function() {
me.make_payment_request();
}, __("Make"));
}
if(!doc.auto_repeat) {
@ -102,7 +104,6 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
}
this.set_default_print_format();
var me = this;
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
frappe.model.with_doc("Customer", me.frm.doc.customer, function() {
var customer = frappe.model.get_doc("Customer", me.frm.doc.customer);

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe, erpnext
import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate
from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after
@ -54,9 +54,18 @@ class SalesInvoice(SellingController):
def set_indicator(self):
"""Set indicator for portal"""
if self.outstanding_amount > 0:
if cint(self.is_return) == 1:
self.indicator_title = _("Return")
self.indicator_color = "darkgrey"
elif self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()):
self.indicator_color = "orange"
self.indicator_title = _("Unpaid")
elif self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()):
self.indicator_color = "red"
self.indicator_title = _("Overdue")
elif self.outstanding_amount < 0:
self.indicator_title = _("Credit Note Issued")
self.indicator_color = "darkgrey"
else:
self.indicator_color = "green"
self.indicator_title = _("Paid")

View File

@ -3,55 +3,82 @@
from __future__ import unicode_literals
import frappe, erpnext
from frappe.utils import flt
from frappe import _
def execute(filters=None):
columns, data = get_columns(), get_data(filters)
return columns, data
def get_data(filters):
data = frappe.db.sql("""
select
a.name as asset, a.asset_category, a.status,
ds.depreciation_method, a.purchase_date, a.gross_purchase_amount,
ds.schedule_date as depreciation_date, ds.depreciation_amount,
ds.accumulated_depreciation_amount,
(a.gross_purchase_amount - ds.accumulated_depreciation_amount) as amount_after_depreciation,
ds.journal_entry as depreciation_entry
from
`tabAsset` a, `tabDepreciation Schedule` ds
where
a.name = ds.parent
and a.docstatus=1
and ifnull(ds.journal_entry, '') != ''
and ds.schedule_date between %(from_date)s and %(to_date)s
and a.company = %(company)s
{conditions}
order by
a.name asc, ds.schedule_date asc
""".format(conditions=get_filter_conditions(filters)), filters, as_dict=1)
data = []
depreciation_accounts = frappe.db.sql_list(""" select name from tabAccount
where ifnull(account_type, '') = 'Depreciation' """)
filters_data = [["company", "=", filters.get('company')],
["posting_date", ">=", filters.get('from_date')],
["posting_date", "<=", filters.get('to_date')],
["against_voucher_type", "=", "Asset"],
["account", "in", depreciation_accounts]]
if filters.get("asset"):
filters_data.append(["against_voucher", "=", filters.get("asset")])
if filters.get("asset_category"):
assets = frappe.db.sql_list("""select name from tabAsset
where asset_category = %s and docstatus=1""", filters.get("asset_category"))
filters_data.append(["against_voucher", "in", assets])
company_finance_book = erpnext.get_default_finance_book(filters.get("company"))
if (not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book)):
filters_data.append(["finance_book", "in", ['', filters.get('finance_book')]])
elif filters.get("finance_book"):
filters_data.append(["finance_book", "=", filters.get('finance_book')])
gl_entries = frappe.get_all('GL Entry',
filters= filters_data,
fields = ["against_voucher", "debit_in_account_currency as debit", "voucher_no", "posting_date"],
order_by= "against_voucher, posting_date")
if not gl_entries:
return data
assets = [d.against_voucher for d in gl_entries]
assets_details = get_assets_details(assets)
for d in gl_entries:
asset_data = assets_details.get(d.against_voucher)
if not asset_data.get("accumulated_depreciation_amount"):
asset_data.accumulated_depreciation_amount = d.debit
else:
asset_data.accumulated_depreciation_amount += d.debit
row = frappe._dict(asset_data)
row.update({
"depreciation_amount": d.debit,
"depreciation_date": d.posting_date,
"amount_after_depreciation": (flt(row.gross_purchase_amount) -
flt(row.accumulated_depreciation_amount)),
"depreciation_entry": d.voucher_no
})
data.append(row)
return data
def get_filter_conditions(filters):
conditions = ""
if filters.get("asset"):
conditions += " and a.name = %(asset)s"
if filters.get("asset_category"):
conditions += " and a.asset_category = %(asset_category)s"
company_finance_book = erpnext.get_default_finance_book(filters.get("company"))
def get_assets_details(assets):
assets_details = {}
if (not filters.get('finance_book') or (filters.get('finance_book') == company_finance_book)):
filters['finance_book'] = company_finance_book
conditions += " and ifnull(ds.finance_book, '') in (%(finance_book)s, '') "
elif filters.get("finance_book"):
conditions += " and ifnull(ds.finance_book, '') = %(finance_book)s"
fields = ["name as asset", "gross_purchase_amount",
"asset_category", "status", "depreciation_method", "purchase_date"]
for d in frappe.get_all("Asset", fields = fields, filters = {'name': ('in', assets)}):
assets_details.setdefault(d.asset, d)
return assets_details
return conditions
def get_columns():
return [
{

View File

@ -4,8 +4,16 @@
frappe.query_reports["Budget Variance Report"] = {
"filters": [
{
fieldname: "fiscal_year",
label: __("Fiscal Year"),
fieldname: "from_fiscal_year",
label: __("From Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: frappe.sys_defaults.fiscal_year,
reqd: 1
},
{
fieldname: "to_fiscal_year",
label: __("To Fiscal Year"),
fieldtype: "Link",
options: "Fiscal Year",
default: frappe.sys_defaults.fiscal_year,
@ -21,7 +29,7 @@ frappe.query_reports["Budget Variance Report"] = {
{ "value": "Half-Yearly", "label": __("Half-Yearly") },
{ "value": "Yearly", "label": __("Yearly") }
],
default: "Monthly",
default: "Yearly",
reqd: 1
},
{
@ -46,5 +54,11 @@ frappe.query_reports["Budget Variance Report"] = {
fieldtype: "Link",
options: "Cost Center"
},
{
fieldname:"show_cumulative",
label: __("Show Cumulative Amount"),
fieldtype: "Check",
default: 0,
},
]
}

View File

@ -9,7 +9,7 @@ from frappe.utils import formatdate
from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges
from six import iteritems
from pprint import pprint
def execute(filters=None):
if not filters: filters = {}
validate_filters(filters)
@ -19,7 +19,7 @@ def execute(filters=None):
else:
cost_centers = get_cost_centers(filters)
period_month_ranges = get_period_month_ranges(filters["period"], filters["fiscal_year"])
period_month_ranges = get_period_month_ranges(filters["period"], filters["from_fiscal_year"])
cam_map = get_cost_center_account_month_map(filters)
data = []
@ -29,18 +29,28 @@ def execute(filters=None):
for account, monthwise_data in iteritems(cost_center_items):
row = [cost_center, account]
totals = [0, 0, 0]
for relevant_months in period_month_ranges:
period_data = [0, 0, 0]
for month in relevant_months:
month_data = monthwise_data.get(month, {})
for i, fieldname in enumerate(["target", "actual", "variance"]):
value = flt(month_data.get(fieldname))
period_data[i] += value
totals[i] += value
period_data[2] = period_data[0] - period_data[1]
row += period_data
for year in get_fiscal_years(filters):
last_total = 0
for relevant_months in period_month_ranges:
period_data = [0, 0, 0]
for month in relevant_months:
if monthwise_data.get(year[0]):
month_data = monthwise_data.get(year[0]).get(month, {})
for i, fieldname in enumerate(["target", "actual", "variance"]):
value = flt(month_data.get(fieldname))
period_data[i] += value
totals[i] += value
period_data[0] += last_total
if(filters.get("show_cumulative")):
last_total = period_data[0] - period_data[1]
period_data[2] = period_data[0] - period_data[1]
row += period_data
totals[2] = totals[0] - totals[1]
row += totals
if filters["period"] != "Yearly" :
row += totals
data.append(row)
return columns, data
@ -50,21 +60,32 @@ def validate_filters(filters):
frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center"))
def get_columns(filters):
columns = [_(filters.get("budget_against")) + ":Link/%s:120"%(filters.get("budget_against")), _("Account") + ":Link/Account:120"]
columns = [_(filters.get("budget_against")) + ":Link/%s:80"%(filters.get("budget_against")), _("Account") + ":Link/Account:80"]
group_months = False if filters["period"] == "Monthly" else True
for from_date, to_date in get_period_date_ranges(filters["period"], filters["fiscal_year"]):
for label in [_("Target") + " (%s)", _("Actual") + " (%s)", _("Variance") + " (%s)"]:
if group_months:
label = label % (formatdate(from_date, format_string="MMM") + " - " + formatdate(to_date, format_string="MMM"))
fiscal_year = get_fiscal_years(filters)
for year in fiscal_year:
for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
if filters["period"] == "Yearly":
labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])]
for label in labels:
columns.append(label+":Float:80")
else:
label = label % formatdate(from_date, format_string="MMM")
for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]:
if group_months:
label = label % (formatdate(from_date, format_string="MMM") + "-" + formatdate(to_date, format_string="MMM"))
else:
label = label % formatdate(from_date, format_string="MMM")
columns.append(label+":Float:120")
columns.append(label+":Float:80")
return columns + [_("Total Target") + ":Float:120", _("Total Actual") + ":Float:120",
_("Total Variance") + ":Float:120"]
if filters["period"] != "Yearly" :
return columns + [_("Total Budget") + ":Float:80", _("Total Actual") + ":Float:80",
_("Total Variance") + ":Float:80"]
else:
return columns
def get_cost_centers(filters):
cond = "and 1=1"
@ -81,21 +102,23 @@ def get_cost_center_target_details(filters):
cond += " and b.cost_center='%s'" % frappe.db.escape(filters.get("cost_center"))
return frappe.db.sql("""
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
from `tabBudget` b, `tabBudget Account` ba
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year=%s
and b.budget_against = %s and b.company=%s {cond}
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s
and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year
""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
(filters.fiscal_year, filters.budget_against, filters.company), as_dict=True)
(filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company), as_dict=True)
#Get target distribution details of accounts of cost center
def get_target_distribution_details(filters):
target_details = {}
for d in frappe.db.sql("""select md.name, mdp.month, mdp.percentage_allocation
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
where mdp.parent=md.name and md.fiscal_year=%s""", (filters["fiscal_year"]), as_dict=1):
where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1):
target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation))
return target_details
#Get actual details from gl entry
@ -107,7 +130,7 @@ def get_actual_details(name, filters):
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"])
cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt)
ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,
ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year,
MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against
from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b
where
@ -115,11 +138,11 @@ def get_actual_details(name, filters):
and b.docstatus = 1
and ba.account=gl.account
and b.{budget_against} = gl.{budget_against}
and gl.fiscal_year=%s
and gl.fiscal_year between %s and %s
and b.{budget_against}=%s
and exists(select name from `tab{tab}` where name=gl.{budget_against} and {cond}) group by gl.name
""".format(tab = filters.budget_against, budget_against = budget_against, cond = cond),
(filters.fiscal_year, name), as_dict=1)
and exists(select name from `tab{tab}` where name=gl.{budget_against} and {cond}) group by gl.name order by gl.fiscal_year
""".format(tab = filters.budget_against, budget_against = budget_against, cond = cond,from_year=filters.from_fiscal_year,to_year=filters.to_fiscal_year),
(filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1)
cc_actual_details = {}
for d in ac_details:
@ -139,13 +162,12 @@ def get_cost_center_account_month_map(filters):
for month_id in range(1, 13):
month = datetime.date(2013, month_id, 1).strftime('%B')
cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {})\
cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\
.setdefault(month, frappe._dict({
"target": 0.0, "actual": 0.0
}))
tav_dict = cam_map[ccd.budget_against][ccd.account][month]
tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month]
month_percentage = tdd.get(ccd.monthly_distribution, {}).get(month, 0) \
if ccd.monthly_distribution else 100.0/12
@ -156,3 +178,11 @@ def get_cost_center_account_month_map(filters):
tav_dict.actual += flt(ad.debit) - flt(ad.credit)
return cam_map
def get_fiscal_years(filters):
fiscal_year = frappe.db.sql("""select name from `tabFiscal Year` where
name between %(from_fiscal_year)s and %(to_fiscal_year)s""",
{'from_fiscal_year': filters["from_fiscal_year"], 'to_fiscal_year': filters["to_fiscal_year"]})
return fiscal_year

View File

@ -10,7 +10,7 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement
def get_mapper_for(mappers, position):
mapper_list = filter(lambda x: x['position'] == position, mappers)
mapper_list = list(filter(lambda x: x['position'] == position, mappers))
return mapper_list[0] if mapper_list else []
@ -345,13 +345,13 @@ def execute(filters=None):
# compute net profit / loss
income = get_data(
filters.company, "Income", "Credit", period_list,
filters.company, "Income", "Credit", period_list, filters=filters,
accumulated_values=filters.accumulated_values, ignore_closing_entries=True,
ignore_accumulated_values_for_fy=True
)
expense = get_data(
filters.company, "Expense", "Debit", period_list,
filters.company, "Expense", "Debit", period_list, filters=filters,
accumulated_values=filters.accumulated_values, ignore_closing_entries=True,
ignore_accumulated_values_for_fy=True
)

View File

@ -238,6 +238,9 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies):
for d in reversed(accounts):
if d.parent_account:
account = d.parent_account.split('-')[0].strip()
if not accounts_by_name.get(account):
continue
for company in companies:
accounts_by_name[account][company] = \
accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0)
@ -268,8 +271,7 @@ def get_companies(filters):
return all_companies, companies
def get_subsidiary_companies(company):
lft, rgt = frappe.get_cached_value('Company',
company, ["lft", "rgt"])
lft, rgt = frappe.db.get_value('Company', company, ["lft", "rgt"])
return frappe.db.sql_list("""select name from `tabCompany`
where lft >= {0} and rgt <= {1} order by lft, rgt""".format(lft, rgt))

View File

@ -28,6 +28,24 @@ frappe.query_reports["Item-wise Sales Register"] = {
"label": __("Mode of Payment"),
"fieldtype": "Link",
"options": "Mode of Payment"
},
{
"fieldname":"warehouse",
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse"
},
{
"fieldname":"brand",
"label": __("Brand"),
"fieldtype": "Link",
"options": "Brand"
},
{
"fieldname":"item_group",
"label": __("Item Group"),
"fieldtype": "Link",
"options": "Item Group"
}
]
}

View File

@ -14,10 +14,10 @@ def execute(filters=None):
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {}
filters.update({"from_date": filters.get("date_range")[0], "to_date": filters.get("date_range")[1]})
filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]})
columns = get_columns(additional_table_columns)
company_currency = erpnext.get_company_currency(filters.company)
company_currency = erpnext.get_company_currency(filters.get('company'))
item_list = get_items(filters, additional_query_columns)
if item_list:
@ -108,6 +108,23 @@ def get_conditions(filters):
conditions += """ and exists(select name from `tabSales Invoice Payment`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("brand"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
if filters.get("item_group"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)"""
return conditions

View File

@ -52,6 +52,18 @@ frappe.query_reports["Sales Register"] = {
"label": __("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse"
},
{
"fieldname":"brand",
"label": __("Brand"),
"fieldtype": "Link",
"options": "Brand"
},
{
"fieldname":"item_group",
"label": __("Item Group"),
"fieldtype": "Link",
"options": "Item Group"
}
]
}

View File

@ -153,6 +153,16 @@ def get_conditions(filters):
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("brand"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
if filters.get("item_group"):
conditions += """ and exists(select name from `tabSales Invoice Item`
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)"""
return conditions
def get_invoices(filters, additional_query_columns):

View File

@ -94,6 +94,12 @@ frappe.ui.form.on('Asset', {
}, __("Make"));
}
if (!frm.doc.calculate_depreciation) {
frm.add_custom_button(__("Depreciation Entry"), function() {
frm.trigger("make_journal_entry");
}, __("Make"));
}
frm.page.set_inner_btn_group_as_primary(__("Make"));
frm.trigger("setup_chart");
}
@ -103,6 +109,21 @@ frappe.ui.form.on('Asset', {
}
},
make_journal_entry: function(frm) {
frappe.call({
method: "erpnext.assets.doctype.asset.asset.make_journal_entry",
args: {
asset_name: frm.doc.name
},
callback: function(r) {
if (r.message) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
}
})
},
setup_chart: function(frm) {
var x_intervals = [frm.doc.purchase_date];
var asset_values = [frm.doc.gross_purchase_amount];

View File

@ -511,3 +511,34 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
.format(account_name.replace('_', ' ').title(), asset_category, company))
return account
@frappe.whitelist()
def make_journal_entry(asset_name):
asset = frappe.get_doc("Asset", asset_name)
fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \
get_depreciation_accounts(asset)
depreciation_cost_center, depreciation_series = frappe.db.get_value("Company", asset.company,
["depreciation_cost_center", "series_for_depreciation_entry"])
depreciation_cost_center = asset.cost_center or depreciation_cost_center
je = frappe.new_doc("Journal Entry")
je.voucher_type = "Depreciation Entry"
je.naming_series = depreciation_series
je.company = asset.company
je.remark = "Depreciation Entry against asset {0}".format(asset_name)
je.append("accounts", {
"account": depreciation_expense_account,
"reference_type": "Asset",
"reference_name": asset.name,
"cost_center": depreciation_cost_center
})
je.append("accounts", {
"account": accumulated_depreciation_account,
"reference_type": "Asset",
"reference_name": asset.name
})
return je

View File

@ -9,8 +9,9 @@ from frappe.utils import flt, today, getdate, cint
def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled
if not frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically"):
if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")):
return
if not date:
date = today()
for asset in get_depreciable_assets(date):
@ -197,4 +198,4 @@ def get_disposal_account_and_cost_center(company):
if not depreciation_cost_center:
frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company))
return disposal_account, depreciation_cost_center
return disposal_account, depreciation_cost_center

View File

@ -49,6 +49,10 @@ frappe.ui.form.on("Supplier", {
erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name);
}, __("Make"));
frm.add_custom_button(__('Pricing Rule'), function () {
erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
}, __("Make"));
// indicators
erpnext.utils.set_party_dashboard_indicators(frm);
}

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,10 @@ def get_data():
{
'label': _('Orders'),
'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
},
{
'label': _('Pricing'),
'items': ['Pricing Rule']
}
]
}

View File

@ -235,6 +235,16 @@ def get_data():
"type": "doctype",
"name": "GST HSN Code",
},
{
"type": "report",
"name": "GSTR-1",
"is_query_report": True
},
{
"type": "report",
"name": "GSTR-2",
"is_query_report": True
},
{
"type": "report",
"name": "GST Sales Register",

View File

@ -661,7 +661,7 @@ class BuyingController(StockController):
if self.doctype == 'Purchase Invoice' and not self.get('update_stock'):
return
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s and docstatus = 0", self.name)
frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name)
def validate_schedule_date(self):

View File

@ -137,7 +137,7 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items):
.format(args.item_code), StockOverReturnError)
elif abs(current_stock_qty) > max_returnable_qty:
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
.format(args.idx, reference_qty, args.item_code), StockOverReturnError)
.format(args.idx, max_returnable_qty, args.item_code), StockOverReturnError)
def get_ref_item_dict(valid_items, ref_item_row):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,96 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:market_segment",
"beta": 0,
"creation": "2018-10-01 09:59:14.479509",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "market_segment",
"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": "Market Segment",
"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": "2018-10-01 09:59:14.479509",
"modified_by": "Administrator",
"module": "CRM",
"name": "Market Segment",
"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": "Sales Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 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
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 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 MarketSegment(Document):
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Market Segment", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Market Segment
() => frappe.tests.make('Market Segment', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

@ -107,6 +107,8 @@ erpnext.crm.Opportunity = frappe.ui.form.Controller.extend({
if(!this.frm.doc.company && frappe.defaults.get_user_default("Company"))
set_multiple(this.frm.doc.doctype, this.frm.doc.name,
{ company:frappe.defaults.get_user_default("Company") });
if(!this.frm.doc.currency)
set_multiple(this.frm.doc.doctype, this.frm.doc.name, { currency:frappe.defaults.get_user_default("Currency") });
this.setup_queries();
},

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -0,0 +1,96 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:stage_name",
"beta": 0,
"creation": "2018-10-01 09:28:16.399518",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "stage_name",
"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": "Stage Name",
"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": "2018-10-01 09:29:43.230378",
"modified_by": "Administrator",
"module": "CRM",
"name": "Sales Stage",
"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": "Sales Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 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
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 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 SalesStage(Document):
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Sales Stage", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Sales Stage
() => frappe.tests.make('Sales Stage', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

@ -1,18 +1,18 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-10-22 11:58:16",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:20:28.725080",
"modified": "2018-09-26 18:59:46.520731",
"modified_by": "Administrator",
"module": "Selling",
"module": "CRM",
"name": "Lead Details",
"owner": "Administrator",
"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 `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)\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",
"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)\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",

View File

@ -12,7 +12,7 @@ app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext"
develop_version = '11.x.x-develop'
staging_version = '11.0.3-beta.2'
staging_version = '11.0.3-beta.3'
error_report_email = "support@erpnext.com"
@ -25,6 +25,7 @@ web_include_css = "assets/css/erpnext-web.css"
doctype_js = {
"Communication": "public/js/communication.js",
"Event": "public/js/event.js"
}
welcome_email = "erpnext.setup.utils.welcome_email"

File diff suppressed because it is too large Load Diff

View File

@ -651,6 +651,39 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "salary_slip",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Salary Slip",
"length": 0,
"no_copy": 0,
"options": "Salary Slip",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -916,7 +949,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 3,
"modified": "2018-08-21 14:44:42.766422",
"modified": "2018-09-21 15:53:11.935416",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Application",

View File

@ -770,7 +770,7 @@
"collapsible": 1,
"collapsible_depends_on": "",
"columns": 0,
"depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1 && doc.is_additional_component != 1",
"depends_on": "eval:doc.is_flexible_benefit != 1 && doc.variable_based_on_taxable_salary != 1",
"fieldname": "condition_and_formula",
"fieldtype": "Section Break",
"hidden": 0,
@ -835,7 +835,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"default": "",
"fieldname": "amount_based_on_formula",
"fieldtype": "Check",
"hidden": 0,
@ -1003,7 +1003,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-02 16:55:44.467519",
"modified": "2018-09-20 16:44:58.876044",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Component",
@ -1056,5 +1056,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@ -385,7 +385,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"default": "0",
"depends_on": "eval:doc.parenttype=='Salary Structure'",
"fetch_from": "",
"fieldname": "amount_based_on_formula",
@ -692,7 +692,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-07-04 16:28:32.314907",
"modified": "2018-09-20 16:59:33.622652",
"modified_by": "Administrator",
"module": "HR",
"name": "Salary Detail",
@ -706,5 +706,6 @@
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0
"track_seen": 0,
"track_views": 0
}

View File

@ -122,16 +122,14 @@ frappe.ui.form.on('Salary Slip Timesheet', {
// Get leave details
//---------------------------------------------------------------------
var get_emp_and_leave_details = function(doc, dt, dn) {
if(!doc.start_date){
return frappe.call({
method: 'get_emp_and_leave_details',
doc: locals[dt][dn],
callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
}
});
}
return frappe.call({
method: 'get_emp_and_leave_details',
doc: locals[dt][dn],
callback: function(r, rt) {
cur_frm.refresh();
calculate_all(doc, dt, dn);
}
});
}
cur_frm.cscript.employee = function(doc,dt,dn){

View File

@ -384,8 +384,8 @@ class SalarySlip(TransactionBase):
and t2.is_lwp = 1
and t1.docstatus = 1
and t1.employee = %(employee)s
and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date
and CASE WHEN t2.include_holiday != 1 THEN %(dt)s not in ('{0}') and %(dt)s between from_date and to_date and ifnull(t1.salary_slip, '') = ''
WHEN t2.include_holiday THEN %(dt)s between from_date and to_date and ifnull(t1.salary_slip, '') = ''
END
""".format(holidays), {"employee": self.employee, "dt": dt})
if leave:

View File

@ -323,11 +323,12 @@ def make_salary_component(salary_components, test_tax):
def get_salary_component_account(sal_comp):
company = erpnext.get_default_company()
sal_comp = frappe.get_doc("Salary Component", sal_comp)
sal_comp.append("accounts", {
"company": company,
"default_account": create_account(company)
})
sal_comp.save()
if not sal_comp.get("accounts"):
sal_comp.append("accounts", {
"company": company,
"default_account": create_account(company)
})
sal_comp.save()
def create_account(company):
salary_account = frappe.db.get_value("Account", "Salary - " + frappe.get_cached_value('Company', company, 'abbr'))
@ -347,7 +348,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"abbr":'BS',
"condition": 'base > 10000',
"formula": 'base*.5',
"type": "Earning"
"type": "Earning",
"amount_based_on_formula": 1
},
{
"salary_component": 'HRA',
@ -360,7 +362,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"abbr":'SA',
"condition": 'H < 10000',
"formula": 'BS*.5',
"type": "Earning"
"type": "Earning",
"amount_based_on_formula": 1
},
{
"salary_component": "Leave Encashment",
@ -401,7 +404,8 @@ def make_earning_salary_component(setup=False, test_tax=False):
"abbr":'BS',
"condition": 'base < 10000',
"formula": 'base*.2',
"type": "Earning"
"type": "Earning",
"amount_based_on_formula": 1
})
return data
@ -412,13 +416,15 @@ def make_deduction_salary_component(setup=False, test_tax=False):
"abbr":'PT',
"condition": 'base > 10000',
"formula": 'base*.1',
"type": "Deduction"
"type": "Deduction",
"amount_based_on_formula": 1
},
{
"salary_component": 'TDS',
"abbr":'T',
"formula": 'base*.1',
"type": "Deduction"
"type": "Deduction",
"amount_based_on_formula": 1
}
]
if not test_tax:
@ -427,7 +433,8 @@ def make_deduction_salary_component(setup=False, test_tax=False):
"abbr":'T',
"condition": 'employment_type=="Intern"',
"formula": 'base*.1',
"type": "Deduction"
"type": "Deduction",
"amount_based_on_formula": 1
})
if setup or test_tax:
make_salary_component(data, test_tax)

View File

@ -18,14 +18,21 @@ class SalaryStructure(Document):
self.validate_max_benefits_with_flexi()
def set_missing_values(self):
fields = ["depends_on_lwp", "variable_based_on_taxable_salary", "is_tax_applicable", "is_flexible_benefit"]
overwritten_fields = ["depends_on_lwp", "variable_based_on_taxable_salary", "is_tax_applicable", "is_flexible_benefit"]
overwritten_fields_if_missing = ["amount_based_on_formula", "formula", "amount"]
for table in ["earnings", "deductions"]:
for d in self.get(table):
component_default_value = frappe.db.get_value("Salary Component", str(d.salary_component), fields, as_dict=1)
component_default_value = frappe.db.get_value("Salary Component", str(d.salary_component),
overwritten_fields + overwritten_fields_if_missing, as_dict=1)
if component_default_value:
for fieldname, value in iteritems(component_default_value):
for fieldname in overwritten_fields:
value = component_default_value.get(fieldname)
if d.get(fieldname) != value:
d[fieldname] = value
d.set(fieldname, value)
if not (d.get("amount") or d.get("formula")):
for fieldname in overwritten_fields_if_missing:
d.set(fieldname, component_default_value.get(fieldname))
def validate_amount(self):
if flt(self.net_pay) < 0 and self.salary_slip_based_on_timesheet:

View File

@ -5,7 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils import cstr, add_days, date_diff
from frappe.utils import cstr, add_days, date_diff, getdate
from frappe import _
from frappe.utils.csvutils import UnicodeWriter
from frappe.model.document import Document
@ -48,8 +48,9 @@ def add_data(w, args):
for employee in employees:
existing_attendance = {}
if existing_attendance_records \
and tuple([date, employee.name]) in existing_attendance_records:
existing_attendance = existing_attendance_records[tuple([date, employee.name])]
and tuple([getdate(date), employee.name]) in existing_attendance_records:
existing_attendance = existing_attendance_records[tuple([getdate(date), employee.name])]
row = [
existing_attendance and existing_attendance.name or "",
employee.name, employee.employee_name, date,
@ -114,6 +115,7 @@ def upload():
if not row: continue
row_idx = i + 5
d = frappe._dict(zip(columns, row))
d["doctype"] = "Attendance"
if d.name:
d["docstatus"] = frappe.db.get_value("Attendance", d.name, "docstatus")
@ -121,6 +123,8 @@ def upload():
try:
check_record(d)
ret.append(import_doc(d, "Attendance", 1, row_idx, submit=True))
except AttributeError:
pass
except Exception as e:
error = True
ret.append('Error for row (#%d) %s : %s' % (row_idx,

View File

@ -506,7 +506,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-07-12 16:16:54.237829",
"modified": "2018-10-04 16:16:54.237829",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Item",

View File

@ -566,3 +566,6 @@ erpnext.patches.v11_0.redesign_healthcare_billing_work_flow
erpnext.patches.v10_0.delete_hub_documents # 12-08-2018
erpnext.patches.v11_0.rename_healthcare_fields
erpnext.patches.v11_0.remove_land_unit_icon
erpnext.patches.v11_0.add_default_dispatch_notification_template
erpnext.patches.v11_0.add_market_segments
erpnext.patches.v11_0.add_sales_stages

View File

@ -0,0 +1,25 @@
import os
import frappe
from frappe import _
def execute():
frappe.reload_doc("email", "doctype", "email_template")
frappe.reload_doc("stock", "doctype", "delivery_settings")
if not frappe.db.exists("Email Template", _("Dispatch Notification")):
base_path = frappe.get_app_path("erpnext", "stock", "doctype")
response = frappe.read_file(os.path.join(base_path, "delivery_trip/dispatch_notification_template.html"))
frappe.get_doc({
"doctype": "Email Template",
"name": _("Dispatch Notification"),
"response": response,
"subject": _("Your order is out for delivery!"),
"owner": frappe.session.user,
}).insert(ignore_permissions=True)
delivery_settings = frappe.get_doc("Delivery Settings")
delivery_settings.dispatch_template = _("Dispatch Notification")
delivery_settings.save()

View File

@ -0,0 +1,11 @@
import frappe
from frappe import _
from erpnext.setup.setup_wizard.operations.install_fixtures import add_market_segments
def execute():
frappe.reload_doc('crm', 'doctype', 'market_segment')
frappe.local.lang = frappe.db.get_default("lang") or 'en'
add_market_segments()

View File

@ -0,0 +1,10 @@
import frappe
from frappe import _
from erpnext.setup.setup_wizard.operations.install_fixtures import add_sale_stages
def execute():
frappe.reload_doc('crm', 'doctype', 'sales_stage')
frappe.local.lang = frappe.db.get_default("lang") or 'en'
add_sale_stages()

View File

@ -1,80 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
enable-background="new 0 0 512 512"
viewBox="0 0 512 512"
height="512px"
width="512px"
y="0px"
x="0px"
id="Layer_1"
version="1.1"><metadata
id="metadata45"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title><cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" /></cc:Work><cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/"><cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" /><cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" /><cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" /><cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /><cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" /></cc:License></rdf:RDF></metadata><defs
id="defs43" /><g
id="g8"><path
id="path6"
d="M150.483,371.684V141.15c0-15.167,9.534-25.133,23.833-25.133h162.5c13.866,0,20.8,6.933,20.8,18.633v2.6 c0,12.133-6.934,18.633-20.8,18.633h-141.7v78.434h109.634c14.3,0,20.8,6.066,20.8,17.767v1.3c0,12.133-6.934,18.633-20.8,18.633 H195.117v84.934h144.3c13.867,0,20.367,6.066,20.367,17.767v2.167c0,12.566-6.5,19.5-20.367,19.5h-165.1 C160.017,396.384,150.483,386.851,150.483,371.684z"
fill="#FFFFFF" /></g><g
id="g10" /><g
id="g12" /><g
id="g14" /><g
id="g16" /><g
id="g18" /><g
id="g20" /><g
id="g22" /><g
id="g24" /><g
id="g26" /><g
id="g28" /><g
id="g30" /><g
id="g32" /><g
id="g34" /><g
id="g36" /><g
id="g38" /><g
style="stroke-width:0.64453173"
transform="matrix(5.8639898,0,0,5.8639898,-9653.0918,-3338.6848)"
id="g1446-4-5"><g
style="stroke-width:0.64453214"
transform="matrix(0.9999989,0,0,0.99999981,279.19167,438.7826)"
id="g1422-7-2"><g
style="stroke-width:0.64453214"
id="g1418-4-6"
transform="translate(-25.731002,-0.1322636)"><g
style="stroke-width:0.64453214"
transform="translate(-95.250002)"
id="g1416-4-9"><path
style="opacity:1;vector-effect:none;fill:#7574ff;fill-opacity:1;stroke:none;stroke-width:2.55798697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
d="m 1501.1846,130.70348 h 60.8542 c 7.329,0 13.2292,5.9002 13.2292,13.22916 v 60.85419 c 0,7.32896 -5.9002,13.22917 -13.2292,13.22917 h -60.8542 c -7.3289,0 -13.2292,-5.90021 -13.2292,-13.22917 v -60.85419 c 0,-7.32896 5.9003,-13.22916 13.2292,-13.22916 z"
id="path1414-3-4" /></g></g><g
id="g1420-0-4"
transform="matrix(1.0131472,0,0,1.0131472,-144.01349,-2.2905938)"
style="stroke-width:0.63616848" /></g><path
id="circle1424-7-6"
transform="matrix(0.26458333,0,0,0.26458333,1646.1645,569.35379)"
d="M 164.92578,228.00977 A 412.60453,412.60453 0 0 0 0,262.94922 V 280 c 0,27.70001 22.30022,50 50,50 h 230 c 27.69978,0 50,-22.29999 50,-50 V 262.70508 A 412.60453,412.60453 0 0 0 164.92578,228.00977 Z"
clip-path="none"
style="opacity:1;vector-effect:none;fill:#6564f9;fill-opacity:1;stroke:none;stroke-width:9.66797638;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal" /><g
style="fill:#4a15c6;fill-opacity:1;stroke-width:0.64453173"
transform="translate(2.8144523,0.84784259)"
id="g1434-8-0" /><g
style="stroke-width:0.64453173"
id="g1444-6-7"
transform="translate(3.8727857,-0.32524952)"><path
style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:9.66797638;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
d="m 3738.9473,951.78516 c -1.0388,0 -2.0511,0.10552 -3.0293,0.30468 -0.9783,0.19916 -1.9225,0.49258 -2.8203,0.8711 -0.449,0.18926 -0.8854,0.40047 -1.3106,0.63086 -0.8503,0.46076 -1.6504,1.00091 -2.3906,1.61132 -3.3312,2.74687 -5.4492,6.90766 -5.4492,11.58204 v 0.91406 133.17378 0.9122 c 0,8.31 6.69,15 15,15 h 104.9082 c 8.31,0 15,-6.69 15,-15 v -0.9122 c 0,-8.31 -6.69,-15 -15,-15 h -88.9942 v -37.3613 h 66.1699 c 8.31,0 15,-6.69 15,-15 v -0.9121 c 0,-8.31 -6.69,-15 -15,-15 h -66.1699 v -34.90038 h 88.9942 c 8.31,0 15,-6.69 15,-15 v -0.91406 c 0,-8.31 -6.69,-15 -15,-15 z"
transform="matrix(0.26458334,0,0,0.26458334,682.80626,339.68051)"
id="rect1436-8-4" /></g></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="88px" height="88px" viewBox="0 0 88 88" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 44.1 (41455) - http://www.bohemiancoding.com/sketch -->
<title>erpnext-logo</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="erpnext-logo" transform="translate(-2.000000, -2.000000)" fill-rule="nonzero">
<g id="g1422-7-2" transform="translate(0.025630, 0.428785)" fill="#5E64FF">
<g id="g1418-4-6" transform="translate(0.268998, 0.867736)">
<g id="g1416-4-9" transform="translate(0.749997, 0.000000)">
<path d="M14.1845844,0.703479866 L75.0387175,0.703479866 C82.3677094,0.703479866 88.2679029,6.60367875 88.2679029,13.9326374 L88.2679029,74.7868158 C88.2679029,82.1157744 82.3677094,88.0159833 75.0387175,88.0159833 L14.1845844,88.0159833 C6.85569246,88.0159833 0.955398949,82.1157744 0.955398949,74.7868158 L0.955398949,13.9326374 C0.955398949,6.60367875 6.85569246,0.703479866 14.1845844,0.703479866 L14.1845844,0.703479866 Z" id="path1414-3-4"></path>
</g>
</g>
</g>
<g id="g1444-6-7" transform="translate(27.708247, 23.320960)" fill="#FFFFFF">
<path d="M4.06942472,0.507006595 C3.79457554,0.507006595 3.52673783,0.534925429 3.26792241,0.587619847 C3.00908052,0.640314265 2.75926093,0.717948309 2.52171801,0.818098395 C2.40292009,0.868173438 2.28745592,0.924056085 2.17495509,0.985013441 C1.94997987,1.10692286 1.73828674,1.24983755 1.54244215,1.41134187 C0.661062132,2.13811791 0.100674618,3.23899362 0.100674618,4.4757567 L0.100674618,4.71760174 L0.100674618,39.9531653 L0.100674618,40.1945182 C0.100674618,42.3932057 1.87073716,44.1632683 4.06942472,44.1632683 L31.8263867,44.1632683 C34.0250742,44.1632683 35.7951368,42.3932057 35.7951368,40.1945182 L35.7951368,39.9531653 C35.7951368,37.7544777 34.0250742,35.9844152 31.8263867,35.9844152 L8.28000399,35.9844152 L8.28000399,26.0992376 L25.7874571,26.0992376 C27.9861447,26.0992376 29.7562072,24.3291751 29.7562072,22.1304875 L29.7562072,21.8891611 C29.7562072,19.6904735 27.9861447,17.920411 25.7874571,17.920411 L8.28000399,17.920411 L8.28000399,8.68635184 L31.8263867,8.68635184 C34.0250742,8.68635184 35.7951368,6.9162893 35.7951368,4.71760174 L35.7951368,4.4757567 C35.7951368,2.27706914 34.0250742,0.507006595 31.8263867,0.507006595 L4.06942472,0.507006595 Z" id="rect1436-8-4"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -211,8 +211,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
make_payment_request: function() {
const me = this;
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], me.frm.doc.doctype))
var me = this;
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
? "Inward" : "Outward";
frappe.call({

View File

@ -0,0 +1,35 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.provide("frappe.desk");
frappe.ui.form.on("Event", {
refresh: function(frm) {
frm.set_query('reference_doctype', "event_participants", function() {
return {
"filters": {
"name": ["in", ["Contact", "Lead", "Customer", "Supplier", "Employee", "Sales Partner"]]
}
};
});
frm.add_custom_button(__('Add Leads'), function() {
new frappe.desk.eventParticipants(frm, "Lead");
}, __("Add Participants"));
frm.add_custom_button(__('Add Customers'), function() {
new frappe.desk.eventParticipants(frm, "Customer");
}, __("Add Participants"));
frm.add_custom_button(__('Add Suppliers'), function() {
new frappe.desk.eventParticipants(frm, "Supplier");
}, __("Add Participants"));
frm.add_custom_button(__('Add Employees'), function() {
new frappe.desk.eventParticipants(frm, "Employee");
}, __("Add Participants"));
frm.add_custom_button(__('Add Sales Partners'), function() {
new frappe.desk.eventParticipants(frm, "Sales Partners");
}, __("Add Participants"));
}
});

View File

@ -18,18 +18,19 @@ export default {
},
methods: {
make_input() {
this.message_input = new frappe.ui.CommentArea({
this.message_input = frappe.ui.form.make_control({
parent: this.$refs['comment-input'],
on_submit: (message) => {
this.message_input.reset();
this.$emit('change', message);
},
only_input: true,
no_wrapper: true
});
},
submit_input() {
if (!this.message_input) return;
const value = this.message_input.val();
const value = this.message_input.get_value();
if (!value) return;
this.message_input.submit();
}

View File

@ -1,6 +1,44 @@
<template>
<div>
<div ref="review-area" class="timeline-head"></div>
<div class="timeline-head">
<div class="comment-input-wrapper">
<div class="comment-input-header">
<span class="text-muted">{{ __('Add your review') }}</span>
<div class="btn btn-default btn-xs pull-right"
@click="on_submit_review"
:disabled="!(user_review.rating && user_review.subject)"
>
{{ __('Submit Review') }}
</div>
</div>
<div class="comment-input-container">
<div class="rating-area text-muted">
<span>{{ __('Your rating:') }}</span>
<div
v-for="i in [1, 2, 3, 4, 5]"
:key="i"
:class="['fa fa-fw', user_review.rating < i ? 'fa-star-o' : 'fa-star']"
:data-index="i"
@click="set_rating(i)"
>
</div>
</div>
<div class="comment-input-body margin-top" v-show="user_review.rating">
<input
type="text"
placeholder="Subject"
class="form-control margin-bottom"
style="border-color: #ebeff2"
v-model="user_review.subject"
>
<div ref="review-content"></div>
<div>
<span class="text-muted text-small">{{ __('Ctrl+Enter to submit') }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="timeline-items">
<review-timeline-item v-for="review in reviews"
:key="review.user"
@ -22,6 +60,11 @@ export default {
props: ['hub_item_name'],
data() {
return {
user_review: {
rating: 0,
subject: '',
content: ''
},
reviews: []
}
},
@ -35,6 +78,10 @@ export default {
this.make_input();
},
methods: {
set_rating(i) {
this.user_review.rating = i;
},
when(datetime) {
return comment_when(datetime);
},
@ -48,21 +95,37 @@ export default {
},
make_input() {
this.review_area = new frappe.ui.ReviewArea({
parent: this.$refs['review-area'],
mentions: [],
on_submit: this.on_submit_review.bind(this)
this.review_content = frappe.ui.form.make_control({
parent: this.$refs['review-content'],
on_submit: this.on_submit_review.bind(this),
no_wrapper: true,
only_input: true,
render_input: true,
df: {
fieldtype: 'Comment',
fieldname: 'comment'
}
});
},
on_submit_review(values) {
this.review_area.reset();
on_submit_review() {
const review = Object.assign({}, this.user_review, {
content: this.review_content.get_value()
});
hub.call('add_item_review', {
hub_item_name: this.hub_item_name,
review: JSON.stringify(values)
review: JSON.stringify(review)
})
.then(this.push_review.bind(this));
this.reset_user_review();
},
reset_user_review() {
this.user_review.rating = 0;
this.user_review.subject = '';
this.review_content.set_value('');
},
push_review(review){
@ -70,4 +133,4 @@ export default {
}
}
}
</script>
</script>

View File

@ -173,6 +173,20 @@ $.extend(erpnext.utils, {
})
},
make_pricing_rule: function(doctype, docname) {
frappe.call({
method: "erpnext.accounts.doctype.pricing_rule.pricing_rule.make_pricing_rule",
args: {
doctype: doctype,
docname: docname
},
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
})
},
/**
* Checks if the first row of a given child table is empty
* @param child_table - Child table Doctype

View File

@ -106,6 +106,10 @@ frappe.ui.form.on("Customer", {
frappe.set_route('query-report', 'Accounts Receivable', {customer:frm.doc.name});
});
frm.add_custom_button(__('Pricing Rule'), function () {
erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name);
}, __("Make"));
// indicator
erpnext.utils.set_party_dashboard_indicators(frm);

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,10 @@ def get_data():
{
'label': _('Projects'),
'items': ['Project']
},
{
'label': _('Pricing'),
'items': ['Pricing Rule']
}
]
}

View File

@ -13,23 +13,34 @@ frappe.pages['sales-funnel'].on_page_load = function(wrapper) {
frappe.breadcrumbs.add("Selling");
}
erpnext.SalesFunnel = Class.extend({
init: function(wrapper) {
erpnext.SalesFunnel = class SalesFunnel {
constructor(wrapper) {
var me = this;
// 0 setTimeout hack - this gives time for canvas to get width and height
setTimeout(function() {
me.setup(wrapper);
me.get_data();
}, 0);
},
}
setup: function(wrapper) {
setup(wrapper) {
var me = this;
this.company_field = wrapper.page.add_field({"fieldtype": "Link", "fieldname": "company", "options": "Company",
"label": __("Company"), "reqd": 1, "default": frappe.defaults.get_user_default('company'),
change: function() {
me.company = this.value || frappe.defaults.get_user_default('company');
me.get_data();
}
}),
this.elements = {
layout: $(wrapper).find(".layout-main"),
from_date: wrapper.page.add_date(__("From Date")),
to_date: wrapper.page.add_date(__("To Date")),
chart: wrapper.page.add_select(__("Chart"), [{value: 'sales_funnel', label:__("Sales Funnel")},
{value: 'sales_pipeline', label:__("Sales Pipeline")},
{value: 'opp_by_lead_source', label:__("Opportunities by lead source")}]),
refresh_btn: wrapper.page.set_primary_action(__("Refresh"),
function() { me.get_data(); }, "fa fa-refresh"),
};
@ -41,16 +52,27 @@ erpnext.SalesFunnel = Class.extend({
this.elements.funnel_wrapper = $('<div class="funnel-wrapper text-center"></div>')
.appendTo(this.elements.layout);
this.company = frappe.defaults.get_user_default('company');
this.options = {
from_date: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
to_date: frappe.datetime.get_today()
to_date: frappe.datetime.get_today(),
chart: 'sales_funnel'
};
// set defaults and bind on change
$.each(this.options, function(k, v) {
me.elements[k].val(frappe.datetime.str_to_user(v));
if (['from_date', 'to_date'].includes(k)) {
me.elements[k].val(frappe.datetime.str_to_user(v));
} else {
me.elements[k].val(v);
}
me.elements[k].on("change", function() {
me.options[k] = frappe.datetime.user_to_str($(this).val());
if (['from_date', 'to_date'].includes(k)) {
me.options[k] = frappe.datetime.user_to_str($(this).val()) != 'Invalid date' ? frappe.datetime.user_to_str($(this).val()) : frappe.datetime.get_today();
} else {
me.options.chart = $(this).val();
}
me.get_data();
});
});
@ -64,29 +86,90 @@ erpnext.SalesFunnel = Class.extend({
$(window).resize(function() {
me.render();
});
},
}
get_data: function(btn) {
get_data(btn) {
var me = this;
frappe.call({
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
args: {
from_date: this.options.from_date,
to_date: this.options.to_date
},
btn: btn,
callback: function(r) {
if(!r.exc) {
me.options.data = r.message;
me.render();
if (me.options.chart == 'sales_funnel'){
frappe.call({
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_funnel_data",
args: {
from_date: this.options.from_date,
to_date: this.options.to_date,
company: this.company
},
btn: btn,
callback: function(r) {
if(!r.exc) {
me.options.data = r.message;
if (me.options.data=='empty') {
const $parent = me.elements.funnel_wrapper;
$parent.html(__('No data for this period'));
} else {
me.render_funnel();
}
}
}
}
});
},
});
} else if (me.options.chart == 'opp_by_lead_source'){
frappe.call({
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_opp_by_lead_source",
args: {
from_date: this.options.from_date,
to_date: this.options.to_date,
company: this.company
},
btn: btn,
callback: function(r) {
if(!r.exc) {
me.options.data = r.message;
if (me.options.data=='empty') {
const $parent = me.elements.funnel_wrapper;
$parent.html(__('No data for this period'));
} else {
me.render_opp_by_lead_source();
}
}
}
});
} else if (me.options.chart == 'sales_pipeline'){
frappe.call({
method: "erpnext.selling.page.sales_funnel.sales_funnel.get_pipeline_data",
args: {
from_date: this.options.from_date,
to_date: this.options.to_date,
company: this.company
},
btn: btn,
callback: function(r) {
if(!r.exc) {
me.options.data = r.message;
if (me.options.data=='empty') {
const $parent = me.elements.funnel_wrapper;
$parent.html(__('No data for this period'));
} else {
me.render_pipeline();
}
}
}
});
}
}
render: function() {
render() {
let me = this;
if (me.options.chart == 'sales_funnel'){
me.render_funnel();
} else if (me.options.chart == 'opp_by_lead_source'){
me.render_opp_by_lead_source();
} else if (me.options.chart == 'sales_pipeline'){
me.render_pipeline();
}
}
render_funnel() {
var me = this;
this.prepare();
this.prepare_funnel();
var context = this.elements.context,
x_start = 0.0,
@ -119,9 +202,9 @@ erpnext.SalesFunnel = Class.extend({
me.draw_legend(x_mid, y_mid, me.options.width, me.options.height, d.value + " - " + d.title);
});
},
}
prepare: function() {
prepare_funnel() {
var me = this;
this.elements.no_data.toggle(false);
@ -147,9 +230,9 @@ erpnext.SalesFunnel = Class.extend({
.attr("height", this.options.height);
this.elements.context = this.elements.canvas.get(0).getContext("2d");
},
}
draw_triangle: function(x_start, x_mid, x_end, y, height) {
draw_triangle(x_start, x_mid, x_end, y, height) {
var context = this.elements.context;
context.beginPath();
context.moveTo(x_start, y);
@ -158,9 +241,9 @@ erpnext.SalesFunnel = Class.extend({
context.lineTo(x_start, y);
context.closePath();
context.fill();
},
}
draw_legend: function(x_mid, y_mid, width, height, title) {
draw_legend(x_mid, y_mid, width, height, title) {
var context = this.elements.context;
if(y_mid == 0) {
@ -186,4 +269,44 @@ erpnext.SalesFunnel = Class.extend({
context.font = "1.1em sans-serif";
context.fillText(__(title), width + 20, y_mid);
}
});
render_opp_by_lead_source() {
let me = this;
let currency = frappe.defaults.get_default("currency");
let chart_data = me.options.data ? me.options.data : null;
const parent = me.elements.funnel_wrapper[0];
this.chart = new Chart(parent, {
title: __("Sales Opportunities by Source"),
height: 400,
data: chart_data,
type: 'bar',
barOptions: {
stacked: 1
},
tooltipOptions: {
formatTooltipY: d => format_currency(d, currency),
}
});
}
render_pipeline() {
let me = this;
let currency = frappe.defaults.get_default("currency");
let chart_data = me.options.data ? me.options.data : null;
const parent = me.elements.funnel_wrapper[0];
this.chart = new Chart(parent, {
title: __("Sales Pipeline by Stage"),
height: 400,
data: chart_data,
type: 'bar',
tooltipOptions: {
formatTooltipY: d => format_currency(d, currency),
},
colors: ['light-green', 'green']
});
}
};

View File

@ -1,16 +1,18 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe import _
from erpnext.accounts.report.utils import convert
import pandas as pd
@frappe.whitelist()
def get_funnel_data(from_date, to_date):
def get_funnel_data(from_date, to_date, company):
active_leads = frappe.db.sql("""select count(*) from `tabLead`
where (date(`modified`) between %s and %s)
and status != "Do Not Contact" """, (from_date, to_date))[0][0]
and status != "Do Not Contact" and company=%s""", (from_date, to_date, company))[0][0]
active_leads += frappe.db.sql("""select count(distinct contact.name) from `tabContact` contact
left join `tabDynamic Link` dl on (dl.parent=contact.name) where dl.link_doctype='Customer'
@ -18,14 +20,14 @@ def get_funnel_data(from_date, to_date):
opportunities = frappe.db.sql("""select count(*) from `tabOpportunity`
where (date(`creation`) between %s and %s)
and status != "Lost" """, (from_date, to_date))[0][0]
and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
quotations = frappe.db.sql("""select count(*) from `tabQuotation`
where docstatus = 1 and (date(`creation`) between %s and %s)
and status != "Lost" """, (from_date, to_date))[0][0]
and status != "Lost" and company=%s""", (from_date, to_date, company))[0][0]
sales_orders = frappe.db.sql("""select count(*) from `tabSales Order`
where docstatus = 1 and (date(`creation`) between %s and %s)""", (from_date, to_date))[0][0]
where docstatus = 1 and (date(`creation`) between %s and %s) and company=%s""", (from_date, to_date, company))[0][0]
return [
{ "title": _("Active Leads / Customers"), "value": active_leads, "color": "#B03B46" },
@ -33,3 +35,54 @@ def get_funnel_data(from_date, to_date):
{ "title": _("Quotations"), "value": quotations, "color": "#006685" },
{ "title": _("Sales Orders"), "value": sales_orders, "color": "#00AD65" }
]
@frappe.whitelist()
def get_opp_by_lead_source(from_date, to_date, company):
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability', 'source'])
if opportunities:
default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency')
cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities]
df = pd.DataFrame(cp_opportunities).groupby(['source', 'sales_stage'], as_index=False).agg({'compound_amount': 'sum'})
result = {}
result['labels'] = list(set(df.source.values))
result['datasets'] = []
for s in set(df.sales_stage.values):
result['datasets'].append({'name': s, 'values': [0]*len(result['labels']), 'chartType': 'bar'})
for row in df.itertuples():
source_index = result['labels'].index(row.source)
for dataset in result['datasets']:
if dataset['name'] == row.sales_stage:
dataset['values'][source_index] = row.compound_amount
return result
else:
return 'empty'
@frappe.whitelist()
def get_pipeline_data(from_date, to_date, company):
opportunities = frappe.get_all("Opportunity", filters=[['status', 'in', ['Open', 'Quotation', 'Replied']], ['company', '=', company], ['transaction_date', 'Between', [from_date, to_date]]], fields=['currency', 'sales_stage', 'opportunity_amount', 'probability'])
if opportunities:
default_currency = frappe.get_cached_value('Global Defaults', 'None', 'default_currency')
cp_opportunities = [dict(x, **{'compound_amount': (convert(x['opportunity_amount'], x['currency'], default_currency, to_date) * x['probability']/100)}) for x in opportunities]
df = pd.DataFrame(cp_opportunities).groupby(['sales_stage'], as_index=True).agg({'compound_amount': 'sum'}).to_dict()
result = {}
result['labels'] = df['compound_amount'].keys()
result['datasets'] = []
result['datasets'].append({'name': _("Total Amount"), 'values': df['compound_amount'].values(), 'chartType': 'bar'})
return result
else:
return 'empty'

View File

@ -29,9 +29,9 @@ def execute(filters=None):
if customer_naming_type == "Naming Series":
row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal,
d.bypass_credit_limit_check_at_sales_order]
d.bypass_credit_limit_check_at_sales_order, d.disabled]
else:
row = [d.name, credit_limit, outstanding_amt, bal, d.bypass_credit_limit_check_at_sales_order]
row = [d.name, credit_limit, outstanding_amt, bal, d.bypass_credit_limit_check_at_sales_order, d.disabled]
if credit_limit:
data.append(row)
@ -40,11 +40,13 @@ def execute(filters=None):
def get_columns(customer_naming_type):
columns = [
_("Name") + ":Link/Customer:120",
_("Customer") + ":Link/Customer:120",
_("Credit Limit") + ":Currency:120",
_("Outstanding Amt") + ":Currency:100",
_("Credit Balance") + ":Currency:120",
_("Bypass credit check at Sales Order ") + ":Check:240"
_("Bypass credit check at Sales Order ") + ":Check:240",
_("Is Disabled ") + ":Check:240"
]
if customer_naming_type == "Naming Series":
@ -59,5 +61,5 @@ def get_details(filters):
conditions += " where name = %(customer)s"
return frappe.db.sql("""select name, customer_name,
bypass_credit_limit_check_at_sales_order from `tabCustomer` %s
bypass_credit_limit_check_at_sales_order,disabled from `tabCustomer` %s
""" % conditions, filters, as_dict=1)

View File

@ -17,7 +17,7 @@ def execute(filters=None):
for d in entries:
if d.stock_qty > 0 or filters.get('show_return_entries', 0):
data.append([
d.name, d.customer, d.territory, d.posting_date, d.item_code,
d.name, d.customer, d.territory, item_details.get(d.item_code, {}).get("website_warehouse"), d.posting_date, d.item_code,
item_details.get(d.item_code, {}).get("item_group"), item_details.get(d.item_code, {}).get("brand"),
d.stock_qty, d.base_net_amount, d.sales_person, d.allocated_percentage, d.contribution_amt
])
@ -33,7 +33,8 @@ def get_columns(filters):
msgprint(_("Please select the document type first"), raise_exception=1)
return [filters["doc_type"] + ":Link/" + filters["doc_type"] + ":140",
_("Customer") + ":Link/Customer:140", _("Territory") + ":Link/Territory:100", _("Posting Date") + ":Date:100",
_("Customer") + ":Link/Customer:140", _("Territory") + ":Link/Territory:100", _("Warehouse") + ":Link/Warehouse:100",
_("Posting Date") + ":Date:100",
_("Item Code") + ":Link/Item:120", _("Item Group") + ":Link/Item Group:120",
_("Brand") + ":Link/Brand:120", _("Qty") + ":Float:100", _("Amount") + ":Currency:120",
_("Sales Person") + ":Link/Sales Person:140", _("Contribution %") + "::110",
@ -115,7 +116,7 @@ def get_items(filters):
def get_item_details():
item_details = {}
for d in frappe.db.sql("""select name, item_group, brand from `tabItem`""", as_dict=1):
for d in frappe.db.sql("""select name, item_group, brand, website_warehouse from `tabItem`""", as_dict=1):
item_details.setdefault(d.name, d)
return item_details

View File

@ -264,7 +264,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
{
row.incentives = flt(
row.allocated_amount * row.commission_rate / 100.0,
precision("incentives", sales_person));
precision("incentives", row));
}
},

View File

@ -166,13 +166,12 @@ class NamingSeries(Document):
def parse_naming_series(self):
parts = self.prefix.split('.')
# If series contain date format like INV.YYYY.MM.#####
if len(parts) > 2:
del parts[-1] # Removed ### from the series
prefix = parse_naming_series(parts)
else:
prefix = parts[0]
# Remove ### from the end of series
if parts[-1] == "#" * len(parts[-1]):
del parts[-1]
prefix = parse_naming_series(parts)
return prefix
def set_by_naming_series(doctype, fieldname, naming_series, hide_name_field=True):

View File

@ -55,6 +55,10 @@ def set_default_settings(args):
buying_settings.allow_multiple_items = 1
buying_settings.save()
delivery_settings = frappe.get_doc("Delivery Settings")
delivery_settings.dispatch_template = _("Dispatch Notification")
delivery_settings.save()
hr_settings = frappe.get_doc("HR Settings")
hr_settings.emp_created_by = "Naming Series"
hr_settings.leave_approval_notification_template = _("Leave Approval Notification")

View File

@ -231,7 +231,7 @@ def install(country=None):
# Share Management
{"doctype": "Share Type", "title": _("Equity")},
{"doctype": "Share Type", "title": _("Preference")},
{"doctype": "Share Type", "title": _("Preference")}
]
from erpnext.setup.setup_wizard.data.industry_type import get_industry_types
@ -250,6 +250,12 @@ def install(country=None):
records += [{'doctype': 'Email Template', 'name': _("Leave Status Notification"), 'response': response,\
'subject': _("Leave Status Notification"), 'owner': frappe.session.user}]
base_path = frappe.get_app_path("erpnext", "stock", "doctype")
response = frappe.read_file(os.path.join(base_path, "delivery_trip/dispatch_notification_template.html"))
records += [{'doctype': 'Email Template', 'name': _("Dispatch Notification"), 'response': response,\
'subject': _("Your order is out for delivery!"), 'owner': frappe.session.user}]
# Records for the Supplier Scorecard
from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import make_default_records
make_default_records()
@ -292,6 +298,30 @@ def add_uom_data():
"value": d.get("value")
}).insert(ignore_permissions=True)
def add_market_segments():
records = [
# Market Segments
{"doctype": "Market Segment", "market_segment": _("Lower Income")},
{"doctype": "Market Segment", "market_segment": _("Middle Income")},
{"doctype": "Market Segment", "market_segment": _("Upper Income")}
]
make_fixture_records(records)
def add_sale_stages():
# Sale Stages
records = [
{"doctype": "Sales Stage", "stage_name": _("Prospecting")},
{"doctype": "Sales Stage", "stage_name": _("Qualification")},
{"doctype": "Sales Stage", "stage_name": _("Needs Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Value Proposition")},
{"doctype": "Sales Stage", "stage_name": _("Identifying Decision Makers")},
{"doctype": "Sales Stage", "stage_name": _("Perception Analysis")},
{"doctype": "Sales Stage", "stage_name": _("Proposal/Price Quote")},
{"doctype": "Sales Stage", "stage_name": _("Negotiation/Review")}
]
make_fixture_records(records)
def make_fixture_records(records):
from frappe.modules import scrub
for r in records:
@ -332,4 +362,4 @@ def install_post_company_fixtures(company=None):
{'doctype': 'Department', 'department_name': _('Legal'), 'parent_department': _('All Departments'), 'company': company},
]
make_fixture_records(records)
make_fixture_records(records)

View File

@ -104,6 +104,8 @@ def setup_complete(args=None):
def stage_fixtures(args):
install_fixtures.install(args.get("country"))
install_fixtures.add_market_segments()
install_fixtures.add_sale_stages()
def setup_company(args):
defaults_setup.create_price_lists(args)

View File

@ -460,6 +460,7 @@ def make_delivery_trip(source_name, target_doc=None):
target_doc.customer_address = source_parent.shipping_address
target_doc.contact = source_parent.contact_person
target_doc.customer_contact = source_parent.contact_display
target_doc.grand_total = source_parent.grand_total
# Append unique Delivery Notes in Delivery Trip
delivery_notes.append(target_doc.delivery_note)

View File

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

View File

@ -0,0 +1,194 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-09-04 23:01:34.458550",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sb_dispatch",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dispatch Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "dispatch_template",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dispatch Notification Template",
"length": 0,
"no_copy": 0,
"options": "Email Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "send_with_attachment",
"description": "Leave blank to use the standard Delivery Note format",
"fieldname": "dispatch_attachment",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dispatch Notification Attachment",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "send_with_attachment",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Send with Attachment",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2018-09-05 00:16:23.569855",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Settings",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 0,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 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
}

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, 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 DeliverySettings(Document):
pass

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Delivery Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Delivery Settings
() => frappe.tests.make('Delivery Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

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

View File

@ -18,7 +18,7 @@
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"columns": 2,
"fieldname": "customer",
"fieldtype": "Link",
"hidden": 0,
@ -78,6 +78,38 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "lock",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Lock",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -148,7 +180,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldname": "order_information_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -157,6 +189,135 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Order Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "delivery_note",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Delivery Note",
"length": 0,
"no_copy": 1,
"options": "Delivery Note",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb_order",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "grand_total",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Grand Total",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_7",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -186,7 +347,7 @@
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact Name",
"length": 0,
@ -205,6 +366,38 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_sent_to",
"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": "Email sent to",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -284,6 +477,7 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Dispatch Information",
"length": 0,
"no_copy": 0,
"permlevel": 0,
@ -362,196 +556,6 @@
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_12",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "delivery_note",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Delivery Note",
"length": 0,
"no_copy": 1,
"options": "Delivery Note",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_14",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "notified_by_email",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Notified by Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_18",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_sent_to",
"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": "Email sent to",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
@ -564,7 +568,7 @@
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-08-21 22:25:53.276548",
"modified": "2018-09-05 00:51:55.275009",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Stop",

View File

@ -2,8 +2,8 @@
// For license information, please see license.txt
frappe.ui.form.on('Delivery Trip', {
setup: function(frm) {
frm.set_query("driver", function() {
setup: function (frm) {
frm.set_query("driver", function () {
return {
filters: {
"status": "Active"
@ -11,7 +11,7 @@ frappe.ui.form.on('Delivery Trip', {
};
});
frm.set_query("address", "delivery_stops", function(doc, cdt, cdn) {
frm.set_query("address", "delivery_stops", function (doc, cdt, cdn) {
var row = locals[cdt][cdn];
if (row.customer) {
return {
@ -24,7 +24,7 @@ frappe.ui.form.on('Delivery Trip', {
}
})
frm.set_query("contact", "delivery_stops", function(doc, cdt, cdn) {
frm.set_query("contact", "delivery_stops", function (doc, cdt, cdn) {
var row = locals[cdt][cdn];
if (row.customer) {
return {
@ -45,7 +45,7 @@ frappe.ui.form.on('Delivery Trip', {
});
}
if (frm.doc.docstatus===0) {
if (frm.doc.docstatus === 0) {
frm.add_custom_button(__('Delivery Note'), () => {
erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_delivery_trip",
@ -93,12 +93,8 @@ frappe.ui.form.on('Delivery Trip', {
},
notify_customers: function (frm) {
var owner_email = frm.doc.owner == "Administrator"
? frappe.user_info("Administrator").email
: frm.doc.owner;
$.each(frm.doc.delivery_stops || [], function (i, delivery_stop) {
if (!delivery_stop.delivery_notes) {
if (!delivery_stop.delivery_note) {
frappe.msgprint({
"message": __("No Delivery Note selected for Customer {}", [delivery_stop.customer]),
"title": __("Warning"),
@ -107,34 +103,37 @@ frappe.ui.form.on('Delivery Trip', {
});
}
});
frappe.confirm(__("Do you want to notify all the customers by email?"), function () {
frappe.call({
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.notify_customers",
args: {
"docname": frm.doc.name,
"date": frm.doc.date,
"driver": frm.doc.driver,
"vehicle": frm.doc.vehicle,
"sender_email": owner_email,
"sender_name": frappe.user.full_name(owner_email),
"delivery_notification": frm.doc.delivery_notification
}
});
frm.doc.email_notification_sent = true;
frm.refresh_field('email_notification_sent');
frappe.db.get_value("Delivery Settings", { name: "Delivery Settings" }, "dispatch_template", (r) => {
if (!r.dispatch_template) {
frappe.throw(__("Missing email template for dispatch. Please set one in Delivery Settings."));
} else {
frappe.confirm(__("Do you want to notify all the customers by email?"), function () {
frappe.call({
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.notify_customers",
args: {
"delivery_trip": frm.doc.name
},
callback: function (r) {
if (!r.exc) {
frm.doc.email_notification_sent = true;
frm.refresh_field('email_notification_sent');
}
}
});
});
}
});
}
});
frappe.ui.form.on('Delivery Stop', {
customer: function (frm, cdt, cdn) {
var row = locals[cdt][cdn];
if(row.customer) {
if (row.customer) {
frappe.call({
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.get_contact_and_address",
args: {"name": row.customer},
args: { "name": row.customer },
callback: function (r) {
if (r.message) {
if (r.message["shipping_address"]) {
@ -158,12 +157,13 @@ frappe.ui.form.on('Delivery Stop', {
});
}
},
address: function (frm, cdt, cdn) {
var row = locals[cdt][cdn];
if(row.address) {
if (row.address) {
frappe.call({
method: "frappe.contacts.doctype.address.address.get_address_display",
args: {"address_dict": row.address},
args: { "address_dict": row.address },
callback: function (r) {
if (r.message) {
frappe.model.set_value(cdt, cdn, "customer_address", r.message);
@ -177,10 +177,10 @@ frappe.ui.form.on('Delivery Stop', {
contact: function (frm, cdt, cdn) {
var row = locals[cdt][cdn];
if(row.contact) {
if (row.contact) {
frappe.call({
method: "erpnext.stock.doctype.delivery_trip.delivery_trip.get_contact_display",
args: {"contact": row.contact},
args: { "contact": row.contact },
callback: function (r) {
if (r.message) {
frappe.model.set_value(cdt, cdn, "customer_contact", r.message);

View File

@ -114,7 +114,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
@ -437,7 +437,7 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
@ -507,7 +507,7 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_13",
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
@ -531,40 +531,6 @@
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "delivery_notification",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Delivery Notification",
"length": 0,
"no_copy": 0,
"options": "Email Template",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
@ -608,7 +574,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-30 02:31:49.400138",
"modified": "2018-09-05 01:20:34.165834",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Trip",

View File

@ -10,8 +10,7 @@ import frappe
from frappe import _
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.document import Document
from frappe.utils import cstr, get_datetime, getdate, get_link_to_form
from frappe.utils.user import get_user_fullname
from frappe.utils import get_datetime, get_link_to_form, cstr
class DeliveryTrip(Document):
@ -169,59 +168,54 @@ def get_arrival_times(name):
@frappe.whitelist()
def notify_customers(docname, date, driver, vehicle, sender_email, delivery_notification):
sender_name = get_user_fullname(sender_email)
attachments = []
def notify_customers(delivery_trip):
delivery_trip = frappe.get_doc("Delivery Trip", delivery_trip)
parent_doc = frappe.get_doc('Delivery Trip', docname)
args = parent_doc.as_dict()
context = delivery_trip.as_dict()
context.update({
"departure_time": cstr(context.get("departure_time")),
"estimated_arrival": cstr(context.get("estimated_arrival"))
})
for delivery_stop in parent_doc.delivery_stops:
contact_info = frappe.db.get_value("Contact", delivery_stop.contact,
["first_name", "last_name", "email_id", "gender"], as_dict=1)
if delivery_trip.driver:
context.update(frappe.db.get_value("Driver", delivery_trip.driver, "cell_number", as_dict=1))
args.update(delivery_stop.as_dict())
args.update(contact_info)
email_recipients = []
if delivery_stop.delivery_note:
default_print_format = frappe.get_meta('Delivery Note').default_print_format
attachments = frappe.attach_print('Delivery Note',
delivery_stop.delivery_note,
file_name="Delivery Note",
print_format=default_print_format or "Standard")
for stop in delivery_trip.delivery_stops:
contact_info = frappe.db.get_value("Contact", stop.contact,
["first_name", "last_name", "email_id", "gender"], as_dict=1)
if not delivery_stop.notified_by_email and contact_info.email_id:
driver_info = frappe.db.get_value("Driver", driver, ["full_name", "cell_number"], as_dict=1)
sender_designation = frappe.db.get_value("Employee", sender_email, ["designation"])
if contact_info and contact_info.email_id:
context.update(stop.as_dict())
context.update(contact_info)
estimated_arrival = cstr(delivery_stop.estimated_arrival)[:-3]
email_template = frappe.get_doc("Email Template", delivery_notification)
message = frappe.render_template(email_template.response, args)
dispatch_template_name = frappe.db.get_single_value("Delivery Settings", "dispatch_template")
dispatch_template = frappe.get_doc("Email Template", dispatch_template_name)
frappe.sendmail(
recipients=contact_info.email_id,
sender=sender_email,
message=message,
attachments=attachments,
subject=_(email_template.subject).format(getdate(date).strftime('%d.%m.%y'),
estimated_arrival))
frappe.sendmail(recipients=contact_info.email_id,
subject=dispatch_template.subject,
message=frappe.render_template(dispatch_template.response, context),
attachments=get_attachments(stop))
frappe.db.set_value("Delivery Stop", delivery_stop.name, "notified_by_email", 1)
frappe.db.set_value("Delivery Stop", delivery_stop.name,
"email_sent_to", contact_info.email_id)
frappe.msgprint(_("Email sent to {0}").format(contact_info.email_id))
stop.db_set("email_sent_to", contact_info.email_id)
email_recipients.append(contact_info.email_id)
def round_timedelta(td, period):
"""Round timedelta"""
period_seconds = period.total_seconds()
half_period_seconds = period_seconds / 2
remainder = td.total_seconds() % period_seconds
if remainder >= half_period_seconds:
return datetime.timedelta(seconds=td.total_seconds() + (period_seconds - remainder))
if email_recipients:
frappe.msgprint(_("Email sent to {0}").format(", ".join(email_recipients)))
delivery_trip.db_set("email_notification_sent", True)
else:
return datetime.timedelta(seconds=td.total_seconds() - remainder)
frappe.msgprint(_("No contacts with email IDs found."))
def format_address(address):
"""Customer Address format """
address = frappe.get_doc('Address', address)
return '{}, {}, {}, {}'.format(address.address_line1, address.city, address.pincode, address.country)
def get_attachments(delivery_stop):
if not (frappe.db.get_single_value("Delivery Settings", "send_with_attachment") and delivery_stop.delivery_note):
return []
dispatch_attachment = frappe.db.get_single_value("Delivery Settings", "dispatch_attachment")
attachments = frappe.attach_print("Delivery Note",
delivery_stop.delivery_note,
file_name="Delivery Note",
print_format=dispatch_attachment)
return [attachments]

View File

@ -0,0 +1,50 @@
<h1>Dispatch Notification</h1>
<h3>Details:</h3>
<table class="table table-bordered small" style="max-width: 500px;">
<tbody>
<tr>
<td>Customer's Name</td>
<td>{{ customer }}</td>
</tr>
<tr>
<td>Contact's Name</td>
<td>{{ first_name }} {{ last_name }}</td>
</tr>
<tr>
<td>Address Name</td>
<td>{{ address }}</td>
</tr>
<tr>
<td>Address Details</td>
<td>{{ customer_address }}</td>
</tr>
<tr>
<td>Order Number</td>
<td>{{ delivery_note }}</td>
</tr>
<tr>
<td>Order Total</td>
<td>{{ grand_total }}</td>
</tr>
<tr>
<td>Departure Time</td>
<td>{{ departure_time }}</td>
</tr>
<tr>
<td>Estimated Arrival</td>
<td>{{ estimated_arrival }}</td>
</tr>
<tr>
<td>Driver's Name</td>
<td>{{ driver_name }}</td>
</tr>
<tr>
<td>Driver's Number</td>
<td>{{ cell_number }}</td>
</tr>
<tr>
<td>Vehicle Number</td>
<td>{{ vehicle }}</td>
</tr>
</tbody>
</table>

View File

@ -3,60 +3,71 @@
# See license.txt
from __future__ import unicode_literals
import frappe
import erpnext
import unittest
from frappe.utils import nowdate, add_days
import erpnext
import frappe
from erpnext.stock.doctype.delivery_trip.delivery_trip import get_contact_and_address, notify_customers
from erpnext.tests.utils import create_test_contact_and_address
from erpnext.stock.doctype.delivery_trip.delivery_trip import notify_customers, get_contact_and_address
from frappe.utils import add_days, now_datetime, nowdate
class TestDeliveryTrip(unittest.TestCase):
def setUp(self):
create_driver()
create_vehicle()
create_delivery_notfication()
create_delivery_notification()
create_test_contact_and_address()
def test_delivery_trip(self):
contact = get_contact_and_address("_Test Customer")
if not frappe.db.exists("Delivery Trip", "TOUR-00000"):
delivery_trip = frappe.new_doc("Delivery Trip")
delivery_trip.company = erpnext.get_default_company()
delivery_trip.date = add_days(nowdate(), 5)
delivery_trip.driver = "DRIVER-00001"
delivery_trip.vehicle = "JB 007"
delivery_trip.append("delivery_stops", {
"customer": "_Test Customer",
"address": contact.shipping_address.parent,
"contact": contact.contact_person.parent
delivery_trip = frappe.get_doc({
"doctype": "Delivery Trip",
"company": erpnext.get_default_company(),
"date": add_days(nowdate(), 5),
"departure_time": add_days(now_datetime(), 5),
"driver": frappe.db.get_value('Driver', {"full_name": "Newton Scmander"}),
"vehicle": "JB 007",
"delivery_stops": [{
"customer": "_Test Customer",
"address": contact.shipping_address.parent,
"contact": contact.contact_person.parent
}]
})
delivery_trip.delivery_notification = 'Delivery Notification'
delivery_trip.insert()
sender_email = frappe.db.get_value("User", frappe.session.user, "email")
notify_customers(docname=delivery_trip.name, date=delivery_trip.date, driver=delivery_trip.driver,
vehicle=delivery_trip.vehicle,
sender_email=sender_email, delivery_notification=delivery_trip.delivery_notification)
self.assertEqual(delivery_trip.get("delivery_stops")[0].notified_by_email, 0)
notify_customers(delivery_trip=delivery_trip.name)
delivery_trip.load_from_db()
self.assertEqual(delivery_trip.email_notification_sent, 1)
def create_driver():
if not frappe.db.exists("Driver", "Newton Scmander"):
driver = frappe.new_doc("Driver")
driver.full_name = "Newton Scmander"
driver.cell_number = "98343424242"
driver.license_number = "B809"
driver = frappe.get_doc({
"doctype": "Driver",
"full_name": "Newton Scmander",
"cell_number": "98343424242",
"license_number": "B809"
})
driver.insert()
def create_delivery_notfication():
def create_delivery_notification():
if not frappe.db.exists("Email Template", "Delivery Notification"):
frappe.get_doc({
dispatch_template = frappe.get_doc({
'doctype': 'Email Template',
'name': 'Delivery Notification',
'response': 'Test Delivery Trip',
'subject': 'Test Subject',
'owner': frappe.session.user
}).insert()
})
dispatch_template.insert()
delivery_settings = frappe.get_single("Delivery Settings")
delivery_settings.dispatch_template = 'Delivery Notification'
def create_vehicle():
if not frappe.db.exists("Vehicle", "JB 007"):

View File

@ -7,7 +7,8 @@ def get_data():
'Purchase Invoice': 'purchase_receipt',
'Asset': 'purchase_receipt',
'Landed Cost Voucher': 'receipt_document',
'Auto Repeat': 'reference_document'
'Auto Repeat': 'reference_document',
'Purchase Receipt': 'return_against'
},
'internal_links': {
'Purchase Order': ['items', 'purchase_order'],
@ -25,11 +26,11 @@ def get_data():
},
{
'label': _('Returns'),
'items': ['Stock Entry']
'items': ['Purchase Receipt']
},
{
'label': _('Subscription'),
'items': ['Auto Repeat']
},
]
}
}

View File

@ -379,7 +379,7 @@ class StockEntry(StockController):
def calculate_rate_and_amount(self, force=False,
update_finished_item_rate=True, raise_error_if_no_rate=True):
self.set_basic_rate(force, update_finished_item_rate)
self.set_basic_rate(force, update_finished_item_rate, raise_error_if_no_rate)
self.distribute_additional_costs()
self.update_valuation_rate()
self.set_total_incoming_outgoing_value()

View File

@ -1,17 +1,17 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"add_total_row": 1,
"creation": "2013-06-04 11:03:47",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-02-24 20:19:13.150769",
"modified": "2018-09-20 18:20:49.229153",
"modified_by": "Administrator",
"module": "Stock",
"name": "Batch-Wise Balance History",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Stock Ledger Entry",
"report_name": "Batch-Wise Balance History",
"report_type": "Script Report",

View File

@ -3,6 +3,15 @@
frappe.query_reports["Item Prices"] = {
"filters": [
{
"fieldname": "items",
"label": __("Items Filter"),
"fieldtype": "Select",
"options": "Enabled Items only\nDisabled Items only\nAll Items",
"default": "Enabled Items only",
"on_change": function(query_report) {
query_report.trigger_refresh();
}
}
]
}

View File

@ -10,7 +10,8 @@ def execute(filters=None):
if not filters: filters = {}
columns = get_columns(filters)
item_map = get_item_details()
conditions = get_condition(filters)
item_map = get_item_details(conditions)
pl = get_price_list()
last_purchase_rate = get_last_purchase_rate()
bom_rate = get_item_bom_rate()
@ -41,14 +42,14 @@ def get_columns(filters):
return columns
def get_item_details():
def get_item_details(conditions):
"""returns all items details"""
item_map = {}
for i in frappe.db.sql("""select name, item_group, item_name, description,
brand, stock_uom from tabItem
order by item_code, item_group""", as_dict=1):
brand, stock_uom from tabItem %s
order by item_code, item_group""" % (conditions), as_dict=1):
item_map.setdefault(i.name, i)
return item_map
@ -133,3 +134,15 @@ def get_valuation_rate():
item_val_rate_map.setdefault(d.item_code, d.val_rate)
return item_val_rate_map
def get_condition(filters):
"""Get Filter Items"""
if filters.get("items") == "Enabled Items only":
conditions = " where disabled=0 "
elif filters.get("items") == "Disabled Items only":
conditions = " where disabled=1 "
else:
conditions = ""
return conditions

View File

@ -3,8 +3,8 @@
<a class="product-link" href="{{ route|abs_url }}">
<div class="col-sm-4 col-xs-4 product-image-wrapper">
<div class="product-image-img">
{{ product_image_square(thumbnail or website_image) }}
<div class="product-text" itemprop="name">{{ item_name }}</div>
{{ product_image_square(thumbnail or website_image or image) }}
<div class="product-text" itemprop="name">{{ item_name or name }}</div>
{% if price_sales_uom %}
<div>{{ price_sales_uom }}</div>
<div style='font-size: small; margin-bottom: 10px;'>({{ price_stock_uom }} / {{ stock_uom }})</div>

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