Compare commits

..

74 Commits

Author SHA1 Message Date
Ameya Shenoy
c6eac7c60b Merge branch 'staging-fixes' into staging 2018-10-04 11:42:56 +00:00
Ameya Shenoy
5ae344593e bumped to version 11.0.3-beta.3 2018-10-04 11:42:56 +00:00
Ameya Shenoy
6dc4cdca3a Merge pull request #15585 from rohitwaghchaure/fix_asset_depreciation_ledger_report
Fix issue after merging hotfix into staging-fixes
2018-10-04 16:47:43 +05:30
Rohit Waghchaure
18678696db Fix issue after merging hotfix into staging-fixes 2018-10-04 16:33:43 +05:30
Ameya Shenoy
7d929c508f Merge branch 'master' into staging-fixes 2018-10-04 09:25:07 +00:00
Ameya Shenoy
65652071ff Merge branch 'hotfix' 2018-10-04 09:11:50 +00:00
Ameya Shenoy
79dc8ac9cc bumped to version 10.1.55 2018-10-04 09:11:50 +00:00
rohitwaghchaure
8b42bf57cd Merge pull request #15581 from rohitwaghchaure/work_order_item_has_no
[Fix] WorkOrderItem object has no attribute operation
2018-10-04 13:27:00 +05:30
Rohit Waghchaure
9f2859701e [Fix] WorkOrderItem object has no attribute operation 2018-10-04 13:25:49 +05:30
Zarrar
897d1baf74 Merge pull request #15577 from Zlash65/min-min
[Minor] Purchase Invoice - stop date editable after submit
2018-10-04 09:31:50 +05:30
Zlash65
6e3b1a58a1 service_stop_date should be editable after submit 2018-10-04 09:30:27 +05:30
rohitwaghchaure
f9930ff2a4 Merge pull request #15573 from rohitwaghchaure/cost_center_issue_while_saving
[Fix] Cost center issue
2018-10-03 18:47:28 +05:30
Shreya Shah
3204c94fdd Merge pull request #15570 from shreyashah115/fix-filter-error
[Bug] Item wise sales register filters
2018-10-03 17:38:41 +05:30
Shreya
cbb112f2db Fix filter error 2018-10-03 17:37:40 +05:30
rohitwaghchaure
15e7646edd Merge pull request #15569 from rohitwaghchaure/stock_adjustment_to_cost_of_goods_sold
Book cost of goods sold instead of stock adjustment
2018-10-03 17:36:29 +05:30
Rohit Waghchaure
8bf9f377dd [Fix] Cost center issue 2018-10-03 16:59:43 +05:30
Rohit Waghchaure
d1b87ba41c Book cost of goods sold instead of stock adjustment 2018-10-03 16:29:43 +05:30
rohitwaghchaure
d6cacbe565 Merge pull request #15567 from rohitwaghchaure/fix_payment_request
[Fix] Payment request not working
2018-10-03 16:03:48 +05:30
Rohit Waghchaure
210f4984f7 [Fix] Payment request not working 2018-10-03 15:55:51 +05:30
Charles-Henri Decultot
75fa6b3ee8 [Enhancement] Improvement to the sales pipeline (#15524)
* Additions to sales pipeline

* Codacy corrections

* Codacy corrections

* Codacy corrections

* Replace _ with dummy for unused variable

* Performance + dates corrections

* Itertuples modification

* Removing trailing whitespaces

* Sales stage doctype

* Divide sales stages fixtures in separate functions

* Remove duplicate fixtures

* Add newline after method

* Missing requirement
2018-10-03 10:41:40 +05:30
Charles-Henri Decultot
efd776da46 Events in timeline (#15485)
* Events in timeline

* Namespace correction

* Codacy correction

* Addition of sales partners in participants

* Correct json indent
2018-10-03 10:41:26 +05:30
rohitwaghchaure
a5576f5b21 [Fix] Stock difference between gl entry and stock ledger entry booked in stock adjustment (#15374) 2018-10-03 10:39:50 +05:30
deepeshgarg007
719701f2a4 Enhancement in Budget Variance and other reports (#15455)
* Enhanced Budget Variance report and added filters and columns in other reports

* Update budget_variance_report.js

* Changes in budget variance report

* Spacing in column names
2018-10-02 12:52:13 +05:30
Shreya Shah
7d115183e4 Improvise and fix indicators in Portal list (#15552) 2018-10-02 12:46:26 +05:30
Charles-Henri Decultot
3b11debcd2 Gocardless correction (#15554) 2018-10-02 12:46:15 +05:30
Charles-Henri Decultot
b2281cc1fd Lead to customer UX enhancement (#15525)
* Lead to customer UX enhancement

* Market segment

* Market Segment to DocType

* Specific function for fixtures

* Merge issue correction

* Doctype merge correction
2018-10-02 11:25:00 +05:30
Shreya Shah
5a2aced586 Merge pull request #15551 from Zlash65/minor-fix
[Minor] Delivery trip test fix
2018-10-02 11:12:51 +05:30
Rushabh Mehta
11e2119670 style(icon): revert to old icon 2018-10-02 11:06:55 +05:30
Zlash65
6a5e6ed2ff delivery trip notification to customer fix 2018-10-01 17:28:42 +05:30
Zlash65
9e67b8ec4d fix driver name selection 2018-10-01 17:27:12 +05:30
Zlash65
622bd10946 add date data [mandatory] 2018-10-01 15:37:31 +05:30
rohitwaghchaure
8976ad5ca1 Merge pull request #15523 from rohitwaghchaure/fix_attendance_tool_issue
[Fix] Attendance tool
2018-10-01 15:07:49 +05:30
Rohit Waghchaure
c7f8b82fff [Fix] Attendance tool 2018-10-01 12:30:58 +05:30
rohitwaghchaure
6b62b86bbf Merge pull request #15545 from rohitwaghchaure/salary_slip_not_creating_because_of_the_date_issue
[Fix] Salary slip
2018-10-01 12:03:26 +05:30
Rohit Waghchaure
8fbf856618 [Fix] Salary slip 2018-10-01 12:00:45 +05:30
Saif
90cf2ddc01 Make pricing rule from Supplier and Customer Doc (#15533)
* Make pricing rule from Supplier and Customer Doc

* Make sure the "+" button also works the same way as the "Make" button
2018-09-30 22:16:31 +05:30
Alchez
6ecb2556b7 Add Delivery Settings + improve 'Notify Customers' functionality (#15322)
* Add Delivery Settings + improve 'Notify Customers' functionality

* Minor improvements

* Add patch to create a default dispatch notification template

* Fix travis errors
2018-09-30 21:38:47 +05:30
Charles-Henri Decultot
4fd7a4e0d7 Lead notes (#15510)
* Notes field in Lead

* Add notes to lead details report
2018-09-30 21:20:55 +05:30
Zarrar
e4c9ac3961 [Minor] Cash Flow report - custom (#15522)
* python 2-3 fix for filter

* pass filters while fetching data to financial statement report
2018-09-30 21:19:01 +05:30
rohitwaghchaure
90f9f9d3d8 Code optimization for deferred revenue (#15527) 2018-09-30 21:12:50 +05:30
Faris Ansari
6575e52942 fix: Refactor Review Area according to new Text Editor (#15536)
- depends on https://github.com/frappe/frappe/pull/6159
2018-09-30 21:08:46 +05:30
rohitwaghchaure
1cf71d96b1 Merge pull request #15530 from rohitwaghchaure/consolidated_report_issue_v11
[Fix] Consolidated Financial Statement report
2018-09-28 17:06:57 +05:30
Rohit Waghchaure
ea0ef95539 [Fix] Consolidated Financial Statement report 2018-09-28 17:05:55 +05:30
Saif
c2dd9ab778 Fix the problem that naming series would remove trailing '-' from series prefix if ##### was not entered (#15514) 2018-09-27 15:48:04 +05:30
Shreya Shah
0fc4917d81 Merge pull request #15516 from shreyashah115/product-page
Fetch Item Group's name and image on website
2018-09-27 15:12:42 +05:30
Shreya
5c335550e3 Fetch item_group's name and image on website if children 2018-09-27 13:55:59 +05:30
Nabin Hait
7d6d678e8d purchase receipt return entry in dashboard 2018-09-26 19:04:11 +05:30
Nabin Hait
c22ba2ec26 fix(sales return): validation message fix 2018-09-26 18:56:45 +05:30
Nabin Hait
183972f58f fix(stock_entry): allow creating stock entry against work order even if item rate is zero 2018-09-26 18:34:07 +05:30
Nabin Hait
1e7c32b909 Payroll fixes and more (#15475)
* fix(payroll): multiple minor fixes related to salary structure

* Added GSTR-1 and GSTR-2 report in Accounts module page

* delete asset movement records on cancellation of Purchase Receipt

* Update consolidated_financial_statement.py

* minor fix

* minor fix

* add filters on item prices report (#15495)
2018-09-26 18:01:00 +05:30
deepeshgarg007
c3772f1ac4 minor fix (#15503) 2018-09-26 15:55:43 +05:30
Aditya Hase
e20b7cc47d fix(python3): super() backwards compatibility fix (#15498) 2018-09-26 15:39:02 +05:30
Shreya Shah
352e6494f3 Contact and email fields in Payment Entry (#15490)
* Add contact_person and contact_email fields to Payment Entry

* Minor fix

* Map contact details while making Payment Entry

* Update payment_entry.py
2018-09-26 15:38:34 +05:30
rohitwaghchaure
0cf0ebf08b [Refactored] Asset Depreciation Ledger report based on GL entries (#15415)
* [Refactored] Asset Depreciation Ledger report is based on GL entries

* Provision to make manual JV from the asset if Calculate Depreciation is disabled
2018-09-26 15:24:49 +05:30
Ameya Shenoy
a7ae06eadb Merge branch 'staging-fixes' into staging 2018-09-26 08:52:54 +00:00
Ameya Shenoy
90197dd341 bumped to version 11.0.3-beta.2 2018-09-26 08:52:54 +00:00
Ameya Shenoy
41d1982b90 Merge pull request #15501 from codingCoffee/qwe
fix(multiple): syntax and patch
2018-09-26 14:12:01 +05:30
Ameya Shenoy
12ec142a69 fix(multiple): syntax and patch
- syntax fix for using super class
- check "All Departments" in department name with translations
2018-09-26 14:05:48 +05:30
Ameya Shenoy
47b82789ae Merge branch 'staging-fixes' into staging 2018-09-26 07:32:44 +00:00
Ameya Shenoy
b8a5fb7392 bumped to version 11.0.3 2018-09-26 07:32:44 +00:00
Ameya Shenoy
9d3a97fd15 Merge branch 'master' into staging-fixes 2018-09-26 07:28:53 +00:00
Ameya Shenoy
0ff35a852a Merge branch 'hotfix' 2018-09-26 07:26:49 +00:00
Ameya Shenoy
e04431ea5c bumped to version 10.1.54 2018-09-26 07:26:49 +00:00
deepeshgarg007
86152eb5c8 Sales person Commission Report and more (#15431)
* Commission For Sales Person

* Changes Made

* Changes made in sales person dashboard and commission report

* Update sales_person_commission_summary.py
2018-09-25 19:08:16 +05:30
rohitwaghchaure
fafc277666 [Fix] BOM update tool, too many writes in one request. Please send smaller requests (#15432) 2018-09-25 18:59:20 +05:30
Aditya Hase
865cb88bdd fix(vscode-intellisense): Make VS Code Intellisense work with ERPNext (#15453)
__init__.py in apps/erpnext directory confuses vs code.

Causing all erpnext dotted paths to need an extra `erpnext.` for 
suggestion and completion to work properly.

This commit fixes this issue.
2018-09-25 18:51:17 +05:30
rohitwaghchaure
fe1e4a41e6 Validate negative stock serial number (#15492) 2018-09-25 18:36:32 +05:30
Faris Ansari
7a8c5b0c2c fix(setup wizard): Validate FY dates (#15473) 2018-09-25 18:34:33 +05:30
Aditya Hase
a38b77cbfd feature(chart-of-accounts): Rebuild HSM Tree after bulk insertion (#15457)
* feature(chart-of-accounts): Rebuild HSM Tree after bulk insertion

* Update chart_of_accounts.py
2018-09-25 18:10:50 +05:30
Zarrar
b113349f31 minor fix for auto-opt-in for customer (#15483) 2018-09-24 14:30:07 +05:30
Shreya Shah
0a95b31aea Merge pull request #15454 from frappe/coverage-badge
Add coverage badge to README.md
2018-09-24 12:25:06 +05:30
Aditya Hase
40880da280 fix(readme): Add coverage badge 2018-09-20 17:48:35 +05:30
Aditya Hase
6886c47251 fix(style): Center align badges as well 2018-09-20 17:46:53 +05:30
Aditya Hase
99c8fb19f7 fix(style): Put markdown for badges on separate lines 2018-09-20 17:43:04 +05:30
120 changed files with 11810 additions and 9647 deletions

View File

@@ -4,12 +4,16 @@
<p align="center">
<p>ERP made simple</p>
</p>
</div>
[![Build Status](https://travis-ci.com/frappe/erpnext.png)](https://travis-ci.com/frappe/erpnext) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/frappe/erpnext?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![Build Status](https://travis-ci.com/frappe/erpnext.png)](https://travis-ci.com/frappe/erpnext)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/frappe/erpnext?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
[![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
[https://erpnext.com](https://erpnext.com)
</div>
Includes: Accounting, Inventory, Manufacturing, CRM, Sales, Purchase, Project Management, HRMS. Requires MariaDB.
ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '10.1.53'
__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

@@ -12,6 +12,11 @@ class BalanceMismatchError(frappe.ValidationError): pass
class Account(NestedSet):
nsm_parent_field = 'parent_account'
def on_update(self):
if frappe.local.flags.ignore_on_update:
return
else:
super(Account, self).on_update()
def onload(self):
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",

View File

@@ -6,6 +6,7 @@ import frappe, os, json
from frappe.utils import cstr
from unidecode import unidecode
from six import iteritems
from frappe.utils.nestedset import rebuild_tree
def create_charts(company, chart_template=None, existing_company=None):
chart = get_chart(chart_template, existing_company)
@@ -53,7 +54,12 @@ def create_charts(company, chart_template=None, existing_company=None):
_import_accounts(child, account.name, root_type)
# Rebuild NestedSet HSM tree for Account Doctype
# after all accounts are already inserted.
frappe.local.flags.ignore_on_update = True
_import_accounts(chart, None, None, root_account=True)
rebuild_tree("Account", "parent_account")
frappe.local.flags.ignore_on_update = False
def add_suffix_if_duplicate(account_name, account_number, accounts):
if account_number:

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

@@ -572,7 +572,7 @@ def save_invoice(doc, name, name_list):
frappe.db.commit()
name_list.append(name)
except Exception:
frappe.log_error(frappe.get_traceback())
frappe.db.rollback()
frappe.log_error(frappe.get_traceback())
return name_list

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

@@ -16,16 +16,23 @@ class BOMUpdateTool(Document):
self.update_new_bom()
bom_list = self.get_parent_boms(self.new_bom)
updated_bom = []
for bom in bom_list:
bom_obj = frappe.get_doc("BOM", bom)
bom_obj.get_doc_before_save()
updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
if (getattr(bom_obj.meta, 'track_changes', False)
and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
bom_obj.save_version()
try:
bom_obj = frappe.get_doc("BOM", bom)
bom_obj.get_doc_before_save()
updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom)
bom_obj.calculate_cost()
bom_obj.update_parent_cost()
bom_obj.db_update()
if (getattr(bom_obj.meta, 'track_changes', False)
and bom_obj._doc_before_save and not bom_obj.flags.ignore_version):
bom_obj.save_version()
frappe.db.commit()
except Exception:
frappe.db.rollback()
frappe.log_error(frappe.get_traceback())
def validate_bom(self):
if cstr(self.current_bom) == cstr(self.new_bom):

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

@@ -18,7 +18,7 @@ def execute():
for department in departments:
# skip root node
if department.name == _("All Departments"):
if _(department.name) == _("All Departments"):
continue
# for each company, create a copy of the doc

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

@@ -214,8 +214,9 @@ def get_loyalty_programs(doc):
"ifnull(to_date, '2500-01-01')": [">=", today()]})
for loyalty_program in loyalty_programs:
customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)]
customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)]
customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
and (not loyalty_program.customer_territory or doc.territory in customer_territories):
lp_details.append(loyalty_program.name)

View File

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

View File

@@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
@@ -9,18 +10,24 @@
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "sales_person",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Sales Person",
"length": 0,
"no_copy": 0,
@@ -32,24 +39,31 @@
"print_hide_if_no_value": 0,
"print_width": "200px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "200px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "contact_no",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Contact No.",
"length": 0,
"no_copy": 0,
@@ -60,24 +74,31 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allocated_percentage",
"fieldtype": "Float",
"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": "Contribution (%)",
"length": 0,
"no_copy": 0,
@@ -88,24 +109,31 @@
"print_hide_if_no_value": 0,
"print_width": "100px",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "100px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"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": "Contribution to Net Total",
"length": 0,
"no_copy": 0,
@@ -117,24 +145,63 @@
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "120px"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "commission_rate",
"fieldtype": "Data",
"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": "Commission Rate",
"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": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "incentives",
"fieldtype": "Currency",
"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": "Incentives",
"length": 0,
"no_copy": 0,
@@ -145,24 +212,26 @@
"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": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:28:08.421297",
"modified": "2018-09-17 13:03:14.755974",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Team",
@@ -171,5 +240,8 @@
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"track_seen": 0
"show_name_in_global_search": 0,
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

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

@@ -0,0 +1,54 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Sales Person Commission Summary"] = {
"filters": [
{
fieldname: "sales_person",
label: __("Sales Person"),
fieldtype: "Link",
options: "Sales Person"
},
{
fieldname: "doc_type",
label: __("Document Type"),
fieldtype: "Select",
options: "Sales Order\nDelivery Note\nSales Invoice",
default: "Sales Order"
},
{
fieldname: "from_date",
label: __("From Date"),
fieldtype: "Date",
default: frappe.defaults.get_user_default("year_start_date"),
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.get_today()
},
{
fieldname:"company",
label: __("Company"),
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company")
},
{
fieldname:"customer",
label: __("Customer"),
fieldtype: "Link",
options: "Customer",
},
{
fieldname:"territory",
label: __("Territory"),
fieldtype: "Link",
options: "Territory",
},
]
}

View File

@@ -0,0 +1,26 @@
{
"add_total_row": 1,
"creation": "2018-09-11 17:49:27.256304",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-09-11 17:49:27.256304",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Person Commission Summary",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Sales Order",
"report_name": "Sales Person Commission Summary",
"report_type": "Script Report",
"roles": [
{
"role": "Sales Manager"
},
{
"role": "Maintenance User"
}
]
}

View File

@@ -0,0 +1,142 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
from frappe.utils import flt
def execute(filters=None):
if not filters: filters = {}
columns = get_columns(filters)
entries = get_entries(filters)
data = []
for d in entries:
data.append([
d.name, d.customer, d.territory, d.posting_date,
d.base_net_amount, d.sales_person, d.allocated_percentage, d.commission_rate, d.allocated_amount,d.incentives
])
if data:
total_row = [""]*len(data[0])
data.append(total_row)
return columns, data
def get_columns(filters):
if not filters.get("doc_type"):
msgprint(_("Please select the document type first"), raise_exception=1)
columns =[
{
"label": _(filters["doc_type"]),
"options": filters["doc_type"],
"fieldname": filters['doc_type'],
"fieldtype": "Link",
"width": 140
},
{
"label": _("Customer"),
"options": "Customer",
"fieldname": "customer",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Territory"),
"options": "Territory",
"fieldname": "territory",
"fieldtype": "Link",
"width": 100
},
{
"label": _("Posting Date"),
"fieldname": "posting_date",
"fieldtype": "Date",
"width": 100
},
{
"label": _("Amount"),
"fieldname": "amount",
"fieldtype": "Currency",
"width": 120
},
{
"label": _("Sales Person"),
"options": "Sales Person",
"fieldname": "sales_person",
"fieldtype": "Link",
"width": 140
},
{
"label": _("Contribution %"),
"fieldname": "contribution_percentage",
"fieldtype": "Data",
"width": 110
},
{
"label": _("Commission Rate %"),
"fieldname": "commission_rate",
"fieldtype": "Data",
"width": 100
},
{
"label": _("Contribution Amount"),
"fieldname": "contribution_amount",
"fieldtype": "Currency",
"width": 120
},
{
"label": _("Incentives"),
"fieldname": "incentives",
"fieldtype": "Currency",
"width": 120
}
]
return columns
def get_entries(filters):
date_field = filters["doc_type"] == "Sales Order" and "transaction_date" or "posting_date"
conditions, values = get_conditions(filters, date_field)
entries = frappe.db.sql("""
select
dt.name, dt.customer, dt.territory, dt.%s as posting_date,dt.base_net_total as base_net_amount,
st.commission_rate,st.sales_person, st.allocated_percentage, st.allocated_amount, st.incentives
from
`tab%s` dt, `tabSales Team` st
where
st.parent = dt.name and st.parenttype = %s
and dt.docstatus = 1 %s order by dt.name desc,st.sales_person
""" %(date_field, filters["doc_type"], '%s', conditions),
tuple([filters["doc_type"]] + values), as_dict=1)
return entries
def get_conditions(filters, date_field):
conditions = [""]
values = []
for field in ["company", "customer", "territory"]:
if filters.get(field):
conditions.append("dt.{0}=%s".format(field))
values.append(filters[field])
if filters.get("sales_person"):
conditions.append("st.sales_person = '{0}'".format(filters.get("sales_person")))
if filters.get("from_date"):
conditions.append("dt.{0}>=%s".format(date_field))
values.append(filters["from_date"])
if filters.get("to_date"):
conditions.append("dt.{0}<=%s".format(date_field))
values.append(filters["to_date"])
return " and ".join(conditions), values

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

@@ -12,6 +12,8 @@ frappe.provide("erpnext.selling");
erpnext.selling.SellingController = erpnext.TransactionController.extend({
setup: function() {
this._super();
this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate");
this.frm.add_fetch("sales_person", "commission_rate", "commission_rate");
},
onload: function() {
@@ -29,8 +31,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
setup_queries: function() {
var me = this;
this.frm.add_fetch("sales_partner", "commission_rate", "commission_rate");
$.each([["customer", "customer"],
["lead", "lead"]],
function(i, opts) {
@@ -171,17 +171,26 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
allocated_percentage: function(doc, cdt, cdn) {
var sales_person = frappe.get_doc(cdt, cdn);
if(sales_person.allocated_percentage) {
sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
precision("allocated_percentage", sales_person));
sales_person.allocated_amount = flt(this.frm.doc.base_net_total *
sales_person.allocated_percentage / 100.0,
precision("allocated_amount", sales_person));
refresh_field(["allocated_amount"], sales_person);
refresh_field(["allocated_percentage", "allocated_amount"], sales_person.name,
this.calculate_incentive(sales_person);
refresh_field(["allocated_percentage", "allocated_amount", "commission_rate","incentives"], sales_person.name,
sales_person.parentfield);
}
}
},
sales_person: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.calculate_incentive(row);
refresh_field("incentives",row.name,row.parentfield);
},
warehouse: function(doc, cdt, cdn) {
@@ -250,6 +259,15 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
});
},
calculate_incentive: function(row) {
if(row.allocated_amount)
{
row.incentives = flt(
row.allocated_amount * row.commission_rate / 100.0,
precision("incentives", row));
}
},
batch_no: function(doc, cdt, cdn) {
var me = this;
var item = frappe.get_doc(cdt, cdn);

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

@@ -1,6 +1,15 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Sales Person', {
refresh: function(frm) {
if(frm.doc.__onload && frm.doc.__onload.dashboard_info) {
var info = frm.doc.__onload.dashboard_info;
frm.dashboard.add_indicator(__('Total Contribution Amount: {0}',
[format_currency(info.allocated_amount, info.currency)]), 'blue');
}
}
});
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
cur_frm.cscript.set_root_readonly(doc);

View File

@@ -1,6 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:sales_person_name",
@@ -15,7 +15,8 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -42,11 +43,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -74,11 +76,12 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -108,11 +111,44 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "commission_rate",
"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": "Commission Rate",
"length": 0,
"no_copy": 0,
"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,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -141,47 +177,49 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "1",
"fieldname": "enabled",
"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": "Enabled",
"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,
"default": "1",
"fieldname": "enabled",
"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": "Enabled",
"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_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "cb0",
"fieldtype": "Column Break",
"hidden": 0,
@@ -202,11 +240,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -233,18 +272,19 @@
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"fetch_from": "employee.department",
"fieldname": "department",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@@ -252,25 +292,26 @@
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Department",
"label": "Department",
"length": 0,
"no_copy": 0,
"options": "Department",
"options": "Department",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -298,11 +339,12 @@
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -330,11 +372,12 @@
"reqd": 0,
"search_index": 1,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -362,11 +405,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -395,11 +439,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -428,11 +473,12 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@@ -462,11 +508,11 @@
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "icon-user",
@@ -477,7 +523,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-05-17 09:44:18.203325",
"modified": "2018-09-12 16:41:06.378899",
"modified_by": "Administrator",
"module": "Setup",
"name": "Sales Person",
@@ -541,12 +587,13 @@
"write": 1
}
],
"quick_entry": 0,
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"search_fields": "parent_sales_person",
"show_name_in_global_search": 1,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -6,9 +6,10 @@ import frappe
from frappe import _
from frappe.utils import flt
from frappe.utils.nestedset import NestedSet
from erpnext import get_default_currency
class SalesPerson(NestedSet):
nsm_parent_field = 'parent_sales_person';
nsm_parent_field = 'parent_sales_person'
def validate(self):
for d in self.get('targets') or []:
@@ -16,6 +17,24 @@ class SalesPerson(NestedSet):
frappe.throw(_("Either target qty or target amount is mandatory."))
self.validate_employee_id()
def onload(self):
self.load_dashboard_info()
def load_dashboard_info(self):
company_default_currency = get_default_currency()
allocated_amount = frappe.db.sql("""
select sum(allocated_amount)
from `tabSales Team`
where sales_person = %s and docstatus=1 and parenttype = 'Sales Order'
""",(self.sales_person_name))
info = {}
info["allocated_amount"] = flt(allocated_amount[0][0]) if allocated_amount else 0
info["currency"] = company_default_currency
self.set_onload('dashboard_info', info)
def on_update(self):
super(SalesPerson, self).on_update()
self.validate_one_root()
@@ -35,4 +54,48 @@ class SalesPerson(NestedSet):
frappe.throw(_("Another Sales Person {0} exists with the same Employee id").format(sales_person))
def on_doctype_update():
frappe.db.add_index("Sales Person", ["lft", "rgt"])
frappe.db.add_index("Sales Person", ["lft", "rgt"])
def get_timeline_data(doctype, name):
out = {}
out.update(dict(frappe.db.sql('''select
unix_timestamp(dt.transaction_date), count(st.parenttype)
from
`tabSales Order` dt, `tabSales Team` st
where
st.sales_person = %s and st.parent = dt.name and dt.transaction_date > date_sub(curdate(), interval 1 year)
group by dt.transaction_date ''', name)))
sales_invoice = dict(frappe.db.sql('''select
unix_timestamp(dt.posting_date), count(st.parenttype)
from
`tabSales Invoice` dt, `tabSales Team` st
where
st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
group by dt.posting_date ''', name))
for key in sales_invoice:
if out.get(key):
out[key] += sales_invoice[key]
else:
out[key] = sales_invoice[key]
delivery_note = dict(frappe.db.sql('''select
unix_timestamp(dt.posting_date), count(st.parenttype)
from
`tabDelivery Note` dt, `tabSales Team` st
where
st.sales_person = %s and st.parent = dt.name and dt.posting_date > date_sub(curdate(), interval 1 year)
group by dt.posting_date ''', name))
for key in delivery_note:
if out.get(key):
out[key] += delivery_note[key]
else:
out[key] = delivery_note[key]
return out

View File

@@ -0,0 +1,14 @@
from frappe import _
def get_data():
return {
'heatmap': True,
'heatmap_message': _('This is based on transactions against this Sales Person. See timeline below for details'),
'fieldname': 'sales_person',
'transactions': [
{
'label': _('Sales'),
'items': ['Sales Order', 'Delivery Note', 'Sales Invoice']
},
]
}

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

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