Merge branch 'develop' into bom_convert_uom

This commit is contained in:
bcornwellmott 2017-06-16 08:29:00 -07:00 committed by GitHub
commit 6561b8ade9
32 changed files with 388 additions and 25 deletions

136
.eslintrc Normal file
View File

@ -0,0 +1,136 @@
{
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
"tab",
{ "SwitchCase": 1 }
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"off"
],
"semi": [
"warn",
"always"
],
"camelcase": [
"off"
],
"no-unused-vars": [
"warn"
],
"no-redeclare": [
"warn"
],
"no-console": [
"warn"
],
"no-extra-boolean-cast": [
"off"
],
"no-control-regex": [
"off"
],
"spaced-comment": [
"warn"
],
"no-trailing-spaces": [
"warn"
]
},
"root": true,
"globals": {
"frappe": true,
"erpnext": true,
"schools": true,
"$": true,
"jQuery": true,
"moment": true,
"hljs": true,
"Awesomplete": true,
"CalHeatMap": true,
"Sortable": true,
"Showdown": true,
"Taggle": true,
"Gantt": true,
"Slick": true,
"PhotoSwipe": true,
"PhotoSwipeUI_Default": true,
"fluxify": true,
"io": true,
"c3": true,
"__": true,
"_p": true,
"_f": true,
"repl": true,
"Class": true,
"locals": true,
"cint": true,
"cstr": true,
"cur_frm": true,
"cur_dialog": true,
"cur_page": true,
"cur_list": true,
"cur_tree": true,
"msg_dialog": true,
"is_null": true,
"in_list": true,
"has_common": true,
"has_words": true,
"validate_email": true,
"get_number_format": true,
"format_number": true,
"format_currency": true,
"round_based_on_smallest_currency_fraction": true,
"roundNumber": true,
"comment_when": true,
"replace_newlines": true,
"open_url_post": true,
"toTitle": true,
"lstrip": true,
"strip": true,
"strip_html": true,
"replace_all": true,
"flt": true,
"precision": true,
"md5": true,
"CREATE": true,
"AMEND": true,
"CANCEL": true,
"copy_dict": true,
"get_number_format_info": true,
"print_table": true,
"Layout": true,
"web_form_settings": true,
"$c": true,
"$a": true,
"$i": true,
"$bg": true,
"$y": true,
"$c_obj": true,
"$c_obj_csv": true,
"refresh_many": true,
"refresh_field": true,
"toggle_field": true,
"get_field_obj": true,
"get_query_params": true,
"unhide_field": true,
"hide_field": true,
"set_field_options": true,
"getCookie": true,
"getCookies": true,
"get_url_arg": true,
"get_server_fields": true,
"set_multiple": true
}
}

View File

@ -59,6 +59,7 @@ class PurchaseInvoice(BuyingController):
self.check_for_closed_status()
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.set_expense_account(for_validate=True)
self.set_against_expense_account()
self.validate_write_off_account()

View File

@ -57,7 +57,8 @@ class SalesInvoice(SellingController):
self.so_dn_required()
self.validate_proj_cust()
self.validate_with_previous_doc()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
self.check_close_sales_order("sales_order")
self.validate_debit_to_acc()
self.clear_unallocated_advances("Sales Invoice Advance", "advances")

View File

@ -43,7 +43,7 @@ class PurchaseOrder(BuyingController):
self.check_for_closed_status()
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", ["qty", "required_qty"])
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_with_previous_doc()
self.validate_for_subcontracting()

View File

@ -16,8 +16,11 @@ def call_command(cmd, context):
help='Run the demo for so many days. Default 100')
@click.option('--resume', default=False, is_flag=True,
help='Continue running the demo for given days')
@click.option('--reinstall', default=False, is_flag=True,
help='Reinstall site before demo')
@pass_context
def make_demo(context, site, domain='Manufacturing', days=100, resume=False):
def make_demo(context, site, domain='Manufacturing', days=100,
resume=False, reinstall=False):
"Reinstall site and setup demo"
from frappe.commands.site import _reinstall
from frappe.installer import install_app
@ -30,7 +33,8 @@ def make_demo(context, site, domain='Manufacturing', days=100, resume=False):
from erpnext.demo import demo
demo.simulate(days=days)
else:
_reinstall(site, yes=True)
if reinstall:
_reinstall(site, yes=True)
with frappe.init_site(site=site):
frappe.connect()
if not 'erpnext' in frappe.get_installed_apps():

View File

@ -73,10 +73,13 @@ class BuyingController(StockController):
def validate_stock_or_nonstock_items(self):
if self.meta.get_field("taxes") and not self.get_stock_items():
tax_for_valuation = [d.account_head for d in self.get("taxes")
tax_for_valuation = [d for d in self.get("taxes")
if d.category in ["Valuation", "Valuation and Total"]]
if tax_for_valuation:
frappe.throw(_("Tax Category can not be 'Valuation' or 'Valuation and Total' as all items are non-stock items"))
for d in tax_for_valuation:
d.category = 'Total'
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
def set_landed_cost_voucher_amount(self):
for d in self.get("items"):

View File

@ -116,7 +116,7 @@ def setup_user():
for u in json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'user.json')).read()):
user = frappe.new_doc("User")
user.update(u)
user.flags.no_welcome_mail
user.flags.no_welcome_mail = True
user.new_password = 'demo'
user.insert()

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,86 @@
# Email Inbox
Business involves many transactional emails exchanges with parties like Customers and Suppliers, and within a company. Email Inbox feature allows you pull all your business emails into your ERPNext account. Accessing all the business emails with other transactions details makes ERPNext a single platform for accessing complete business information in one place.
In ERPNext, you can configure Email Inbox for each System User. Following are the detailed steps to configure Email Inbox for a User.
#### Step 1: Create User
As mentioned above, you can configure Email Inbox for a System User only. Hence ensure that you have added yourself and your colleagues as a User and assigned them required permissions.
To add new User, go to:
`Setup > User > New User`
<img class="screenshot" alt="Email User" src="{{docs_base_url}}/assets/img/setup/email/email-user.png">
#### Step 2: Create Email Domain
To be able to send and receive emails into your ERPNext from other email service (like WebMail or Gmail), you should setup an Email Domain master. In this master, email gateway details like SMTP Address, Port No., IMAP/POP3 address details are captured. If you have ever configured a local email client (like Outlook), Email Domain master requires details to be fed in the similar way.
To add new Email Domain, go to:
`Setup > Emails > Email Domain > New`
<img class="screenshot" alt="Email Domain" src="{{docs_base_url}}/assets/img/setup/email/email-domain.png">
Once you have configured an Email Domain for your Email Service, it will be used for creating Email Accounts for all the Users in your ERPNext account.
<div class=well>If you use one of the following Email Service, then you need not create Email Domain in your ERPNext account. In ERPNext, Email Domain for the following Services is available out-of-the-box and you can directly proceed to creating Email Account.
<ul>
<li>Gmail</li>
<li>Yahoo</li>
<li>Sparkpost</li>
<li>SendGrid</li>
<li>Outlook.com</li>
<li>Yandex.mail</li>
<ul>
</div>
#### Step 3: Email Account
Create an Email Account based on the Email ID of the User. For each User who's email account is to be integrated with ERPNext, an Email Account master should be created for it.
If you are creating an Email Account for your colleague who's Email Password is unknown to you, then check field "Awaiting Password". As per this setting, a User (for whom Email Account is created) will get a prompt to enter email password when accessing his/her ERPNext Account.
<img class="screenshot" alt="Email Password" src="{{docs_base_url}}/assets/img/setup/email/email-password.png">
In the Email Account, select Email Domain only if you are using Email Service other than Email Services listed above. Else, you can just select Email Service, leave Email Domain blank and proceed forward.
<img class="screenshot" alt="Email Service" src="{{docs_base_url}}/assets/img/setup/email/email-service.png">
>If you are creating an Email Account for Email Inbox of a User, then leave Append To field as blank.
For more details on how to setup Email Account, [click here]({{docs_base_url}}/user/manual/en/setting-up/email/email-account.html").
#### Step 4: Linking Email Account in User master
Once an Email Account is created for an User, select that Email Account in the User. This will ensure that emails pulled from the said Email ID will accessible only to this User in your ERPNext account.
<img class="screenshot" alt="Email User Link" src="{{docs_base_url}}/assets/img/setup/email/email-user-link.png">
## Email Inbox
If you have correctly configured Email Inbox as instructed above, then on the login of a User, Email Inbox icon will be visible. This will navigate user to Email Inbox view within the ERPNext account. All the Emails received on that email will be fetch and listed in the Email Inbox view. User will be able to open emails and take various actions against it.
<img class="screenshot" alt="Email Inbox" src="{{docs_base_url}}/assets/img/setup/email/email-inbox.png">
#### Folders
In ERPNext, you can link multiple Email Accounts with the single User. To switch to Inbox of different email account and access other folders like Sent Emails, Spam, Trash, check Email Inbox option in the left bar.
<img class="screenshot" alt="Email Folders" src="{{docs_base_url}}/assets/img/setup/email/email-folders.png">
#### Actions
On the emails in your inbox, you can take various actions like Reply, Forward, Mark as Spam or Trash.
<img class="screenshot" alt="Email Actions" src="{{docs_base_url}}/assets/img/setup/email/email-actions.png">
#### Make Options
The Email Inbox within ERPNext also allow you to quickly create ERPNext transaction based on email received. From an Email itself, you can a Issue, Lead or Opportunity based on the context of the email.
<img class="screenshot" alt="Make from Email" src="{{docs_base_url}}/assets/img/setup/email/make-from-email.png">

View File

@ -1,4 +1,5 @@
email-account
email-inbox
email-alerts
email-digest
email-reports

View File

@ -14,6 +14,10 @@ def get_data():
'label': _('Payroll'),
'items': ['Salary Structure', 'Salary Slip', 'Timesheet']
},
{
'label': _('Training Events/Results'),
'items': ['Training Event', 'Training Result']
},
{
'label': _('Expense'),
'items': ['Expense Claim']

View File

@ -3,13 +3,15 @@
frappe.ui.form.on('Training Result', {
onload: function(frm) {
if (frm.doc.training_event) {
frm.trigger("training_event");
}
frm.trigger("training_event");
},
training_event: function(frm) {
if (frm.doc.training_event) {
frm.trigger("training_event");
},
training_event: function(frm) {
if (frm.doc.training_event && !frm.doc.docstatus && !frm.doc.employees) {
frappe.call({
method: "erpnext.hr.doctype.training_result.training_result.get_employees",
args: {

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "TRES.#####",
@ -13,6 +14,7 @@
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -23,6 +25,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Training Event",
@ -34,6 +37,7 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
@ -41,6 +45,7 @@
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -51,6 +56,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
@ -60,6 +66,7 @@
"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,
@ -67,6 +74,7 @@
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -77,6 +85,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Employees",
@ -88,13 +97,15 @@
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
@ -105,6 +116,7 @@
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
@ -115,6 +127,7 @@
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
@ -122,17 +135,17 @@
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"in_dialog": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2016-11-04 08:53:48.597031",
"modified": "2017-06-15 08:16:01.566531",
"modified_by": "Administrator",
"module": "HR",
"name": "Training Result",
@ -149,7 +162,6 @@
"export": 1,
"if_owner": 0,
"import": 0,
"is_custom": 0,
"permlevel": 0,
"print": 1,
"read": 1,
@ -164,8 +176,10 @@
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "training_event",
"track_changes": 0,
"track_seen": 0
}

View File

@ -404,3 +404,5 @@ erpnext.patches.v8_0.delete_bin_indexes
erpnext.patches.v8_0.move_account_head_from_account_to_warehouse_for_inventory
erpnext.patches.v8_0.change_in_words_varchar_length
erpnext.patches.v8_0.update_stock_qty_value_in_bom_item
erpnext.patches.v8_0.create_domain_docs #16-05-2017

View File

@ -0,0 +1,52 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
import erpnext
def execute():
"""Create domain documents"""
frappe.reload_doc("core", "doctype", "domain")
frappe.reload_doc("core", "doctype", "domain_settings")
frappe.reload_doc("core", "doctype", "has_domain")
for domain in ("Distribution", "Manufacturing", "Retail", "Services", "Education"):
if not frappe.db.exists({"doctype": "Domain", "domain": domain}):
create_domain(domain)
# set domain in domain settings based on company domain
domains = []
condition = ""
company = erpnext.get_default_company()
if company:
condition = " and name='{0}'".format(company)
domains = frappe.db.sql_list("select distinct domain from `tabCompany` where domain != 'Other' {0}".format(condition))
if not domains:
return
domain_settings = frappe.get_doc("Domain Settings", "Domain Settings")
checked_domains = [row.domain for row in domain_settings.active_domains]
for domain in domains:
# check and ignore if the domains is already checked in domain settings
if domain in checked_domains:
continue
if not frappe.db.get_value("Domain", domain):
# user added custom domain in companies domain field
create_domain(domain)
row = domain_settings.append("active_domains", dict(domain=domain))
domain_settings.save(ignore_permissions=True)
def create_domain(domain):
# create new domain
doc = frappe.new_doc("Domain")
doc.domain = domain
doc.db_update()

View File

@ -12,4 +12,4 @@ def execute():
set
account = (select name from `tabAccount`
where account_type = 'Stock' and
warehouse = `tabWarehouse`.name and is_group = 0)""")
warehouse = `tabWarehouse`.name and is_group = 0 limit 1)""")

View File

@ -47,7 +47,10 @@ class Project(Document):
self.append("tasks", task_map)
def get_tasks(self):
return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc")
if self.name is None:
return {}
else:
return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc")
def validate(self):
self.validate_dates()

View File

@ -10,7 +10,8 @@ frappe.ui.form.on("Sales Order", {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Invoice',
'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order'
'Purchase Order': 'Purchase Order',
'Project': 'Project'
}
frm.add_fetch('customer', 'tax_id', 'tax_id');
},
@ -120,6 +121,12 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
function() { me.make_maintenance_schedule() }, __("Make"));
}
// project
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1 && allow_delivery) {
this.frm.add_custom_button(__('Project'),
function() { me.make_project() }, __("Make"));
}
} else {
if (this.frm.has_perm("submit")) {
// un-close
@ -264,6 +271,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
})
},
make_project: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_project",
frm: this.frm
})
},
make_maintenance_visit: function() {
frappe.model.open_mapped_doc({
method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",

View File

@ -32,7 +32,7 @@ class SalesOrder(SellingController):
self.validate_mandatory()
self.validate_proj_cust()
self.validate_po()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
self.validate_for_items()
self.validate_warehouse()
@ -408,6 +408,34 @@ def make_material_request(source_name, target_doc=None):
return doc
@frappe.whitelist()
def make_project(source_name, target_doc=None):
def postprocess(source, doc):
doc.project_type = "External"
doc.project_name = source.name
doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": {
"doctype": "Project",
"validation": {
"docstatus": ["=", 1]
},
"field_map":{
"name" : "sales_order",
"delivery_date" : "expected_end_date",
"base_grand_total" : "estimated_costing",
}
},
"Sales Order Item": {
"doctype": "Project Task",
"field_map": {
"description": "title",
},
}
}, target_doc, postprocess)
return doc
@frappe.whitelist()
def make_delivery_note(source_name, target_doc=None):
def set_missing_values(source, target):

View File

@ -105,8 +105,9 @@ def setup_roles(data):
if data.allow_roles:
# remove all roles other than allowed roles
active_domains = frappe.get_active_domains()
data.allow_roles += ['Administrator', 'Guest', 'System Manager', 'All']
for role in frappe.get_all('Role'):
for role in frappe.get_all('Role', filters = {"restrict_to_domain": ("not in", active_domains)}):
if not (role.name in data.allow_roles):
remove_role(role.name)

View File

@ -13,6 +13,13 @@ default_lead_sources = ["Existing Customer", "Reference", "Advertisement",
def install(country=None):
records = [
# domains
{ 'doctype': 'Domain', 'domain': _('Distribution')},
{ 'doctype': 'Domain', 'domain': _('Manufacturing')},
{ 'doctype': 'Domain', 'domain': _('Retail')},
{ 'doctype': 'Domain', 'domain': _('Services')},
{ 'doctype': 'Domain', 'domain': _('Education')},
# address template
{'doctype':"Address Template", "country": country},
@ -35,7 +42,7 @@ def install(country=None):
{'doctype': 'Salary Component', 'salary_component': _('Basic'), 'description': _('Basic'), 'type': 'Earning'},
{'doctype': 'Salary Component', 'salary_component': _('Arrear'), 'description': _('Arrear'), 'type': 'Earning'},
{'doctype': 'Salary Component', 'salary_component': _('Leave Encashment'), 'description': _('Leave Encashment'), 'type': 'Earning'},
# expense claim type
{'doctype': 'Expense Claim Type', 'name': _('Calls'), 'expense_type': _('Calls')},
@ -197,7 +204,7 @@ def install(country=None):
# Assessment Group
{'doctype': 'Assessment Group', 'assessment_group_name': _('All Assessment Groups'),
'is_group': 1, 'parent_assessment_group': ''},
]
from erpnext.setup.setup_wizard.industry_type import get_industry_types

View File

@ -198,6 +198,10 @@ def set_defaults(args):
hr_settings.emp_created_by = "Naming Series"
hr_settings.save()
domain_settings = frappe.get_doc("Domain Settings")
domain_settings.append('active_domains', dict(domain=args.domain))
domain_settings.save()
def create_feed_and_todo():
"""update Activity feed and create todo for creation of item, customer, vendor"""
add_info_comment(**{

View File

@ -102,7 +102,7 @@ class DeliveryNote(SellingController):
self.check_close_sales_order("against_sales_order")
self.validate_for_items()
self.validate_warehouse()
self.validate_uom_is_integer("stock_uom", "qty")
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.validate_uom_is_integer("uom", "qty")
self.validate_with_previous_doc()

View File

@ -248,7 +248,7 @@ class Item(WebsiteGenerator):
self.set_attribute_context(context)
self.set_disabled_attributes(context)
context.parents = self.get_parents(context)
self.get_parents(context)
return context