Merge branch 'master' of github.com:webnotes/erpnext into cleanup

This commit is contained in:
Rushabh Mehta 2013-11-08 16:28:12 +05:30
commit 515636f24c
14 changed files with 322 additions and 157 deletions

View File

@ -3,150 +3,158 @@
from __future__ import unicode_literals
import webnotes
from webnotes import msgprint, _
from webnotes.utils import getdate, nowdate, flt, cstr
from webnotes import _
from webnotes.utils import getdate, nowdate, flt
class AccountsReceivableReport(object):
def __init__(self, filters=None):
self.filters = webnotes._dict(filters or {})
self.filters.report_date = getdate(self.filters.report_date or nowdate())
self.age_as_on = getdate(nowdate()) \
if self.filters.report_date > getdate(nowdate()) \
else self.filters.report_date
def run(self):
return self.get_columns(), self.get_data()
def get_columns(self):
return [
"Posting Date:Date:80", "Account:Link/Account:150", "Customer::150",
"Voucher Type::110", "Voucher No::120", "Remarks::150",
"Due Date:Date:80", "Territory:Link/Territory:80",
"Invoiced Amount:Currency:100", "Payment Received:Currency:100",
"Outstanding Amount:Currency:100", "Age:Int:50", "0-30:Currency:100",
"30-60:Currency:100", "60-90:Currency:100", "90-Above:Currency:100"
]
def get_data(self):
data = []
future_vouchers = self.get_entries_after(self.filters.report_date)
for gle in self.get_entries_till(self.filters.report_date):
if self.is_receivable(gle, future_vouchers):
outstanding_amount = self.get_outstanding_amount(gle, self.filters.report_date)
if abs(outstanding_amount) > 0.01:
due_date = self.get_due_date(gle)
invoiced_amount = gle.debit if (gle.debit > 0) else 0
payment_received = invoiced_amount - outstanding_amount
row = [gle.posting_date, gle.account, self.get_customer(gle.account),
gle.voucher_type, gle.voucher_no, gle.remarks, due_date,
self.get_territory(gle.account), invoiced_amount, payment_received,
outstanding_amount]
entry_date = due_date if self.filters.ageing_based_on=="Due Date" \
else gle.posting_date
row += get_ageing_data(self.age_as_on, entry_date, outstanding_amount)
data.append(row)
return data
def get_entries_after(self, report_date):
# returns a distinct list
return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries()
if getdate(e.posting_date) > report_date]))
def get_entries_till(self, report_date):
# returns a generator
return (e for e in self.get_gl_entries()
if getdate(e.posting_date) <= report_date)
def is_receivable(self, gle, future_vouchers):
return ((not gle.against_voucher) or (gle.against_voucher==gle.voucher_no) or
((gle.against_voucher_type, gle.against_voucher) in future_vouchers))
def get_outstanding_amount(self, gle, report_date):
payment_received = 0.0
for e in self.get_gl_entries_for(gle.account, gle.voucher_type, gle.voucher_no):
if getdate(e.posting_date) <= report_date and e.name!=gle.name:
payment_received += (flt(e.credit) - flt(e.debit))
return flt(gle.debit) - flt(gle.credit) - payment_received
def get_customer(self, account):
return self.get_account_map().get(account).get("customer_name") or ""
def get_territory(self, account):
return self.get_account_map().get(account).get("territory") or ""
def get_account_map(self):
if not hasattr(self, "account_map"):
self.account_map = dict(((r.name, r) for r in webnotes.conn.sql("""select
account.name, customer.customer_name, customer.territory
from `tabAccount` account, `tabCustomer` customer
where account.master_type="Customer"
and customer.name=account.master_name""", as_dict=True)))
return self.account_map
def get_due_date(self, gle):
if not hasattr(self, "invoice_due_date_map"):
# TODO can be restricted to posting date
self.invoice_due_date_map = dict(webnotes.conn.sql("""select name, due_date
from `tabSales Invoice` where docstatus=1"""))
return gle.voucher_type == "Sales Invoice" \
and self.invoice_due_date_map.get(gle.voucher_no) or ""
def get_gl_entries(self):
if not hasattr(self, "gl_entries"):
conditions, values = self.prepare_conditions()
self.gl_entries = webnotes.conn.sql("""select * from `tabGL Entry`
where docstatus < 2 {} order by posting_date, account""".format(conditions),
values, as_dict=True)
return self.gl_entries
def prepare_conditions(self):
conditions = [""]
values = {}
if self.filters.company:
conditions.append("company=%(company)s")
values["company"] = self.filters.company
if self.filters.account:
conditions.append("account=%(account)s")
values["account"] = self.filters.account
else:
account_map = self.get_account_map()
if not account_map:
webnotes.throw(_("No Customer Accounts found."))
else:
accounts_list = ['"{}"'.format(ac) for ac in account_map]
conditions.append("account in ({})".format(", ".join(accounts_list)))
return " and ".join(conditions), values
def get_gl_entries_for(self, account, against_voucher_type, against_voucher):
if not hasattr(self, "gl_entries_map"):
self.gl_entries_map = {}
for gle in self.get_gl_entries():
if gle.against_voucher_type and gle.against_voucher:
self.gl_entries_map.setdefault(gle.account, {})\
.setdefault(gle.against_voucher_type, {})\
.setdefault(gle.against_voucher, [])\
.append(gle)
return self.gl_entries_map.get(account, {})\
.get(against_voucher_type, {})\
.get(against_voucher, [])
def execute(filters=None):
if not filters: filters = {}
columns = get_columns()
entries = get_gl_entries(filters)
account_customer = dict(webnotes.conn.sql("""select account.name, customer.customer_name
from `tabAccount` account, `tabCustomer` customer
where account.master_type="Customer" and customer.name=account.master_name"""))
return AccountsReceivableReport(filters).run()
entries_after_report_date = [[gle.voucher_type, gle.voucher_no]
for gle in get_gl_entries(filters, upto_report_date=False)]
account_territory_map = get_account_territory_map()
si_due_date_map = get_si_due_date_map()
def get_ageing_data(age_as_on, entry_date, oustanding_amount):
# [0-30, 30-60, 60-90, 90-above]
outstanding_range = [0.0, 0.0, 0.0, 0.0]
if not (age_as_on and entry_date):
return [0] + outstanding_range
# Age of the invoice on this date
age_on = getdate(filters.get("report_date")) > getdate(nowdate()) \
and nowdate() or filters.get("report_date")
data = []
for gle in entries:
if cstr(gle.against_voucher) == gle.voucher_no or not gle.against_voucher \
or [gle.against_voucher_type, gle.against_voucher] in entries_after_report_date:
due_date = (gle.voucher_type == "Sales Invoice") \
and si_due_date_map.get(gle.voucher_no) or ""
invoiced_amount = gle.debit > 0 and gle.debit or 0
outstanding_amount = get_outstanding_amount(gle,
filters.get("report_date") or nowdate())
if abs(flt(outstanding_amount)) > 0.01:
payment_amount = invoiced_amount - outstanding_amount
row = [gle.posting_date, gle.account, account_customer.get(gle.account, ""),
gle.voucher_type, gle.voucher_no,
gle.remarks, due_date, account_territory_map.get(gle.account),
invoiced_amount, payment_amount, outstanding_amount]
# Ageing
if filters.get("ageing_based_on") == "Due Date":
ageing_based_on_date = due_date
else:
ageing_based_on_date = gle.posting_date
row += get_ageing_data(ageing_based_on_date, age_on, outstanding_amount)
data.append(row)
return columns, data
age = (age_as_on - getdate(entry_date)).days or 0
index = None
for i, days in enumerate([30, 60, 90]):
if age <= days:
index = i
break
def get_columns():
return [
"Posting Date:Date:80", "Account:Link/Account:150", "Customer::150", "Voucher Type::110",
"Voucher No::120", "Remarks::150", "Due Date:Date:80", "Territory:Link/Territory:80",
"Invoiced Amount:Currency:100", "Payment Received:Currency:100",
"Outstanding Amount:Currency:100", "Age:Int:50", "0-30:Currency:100",
"30-60:Currency:100", "60-90:Currency:100", "90-Above:Currency:100"
]
if index is None: index = 3
outstanding_range[index] = oustanding_amount
def get_gl_entries(filters, upto_report_date=True):
conditions, customer_accounts = get_conditions(filters, upto_report_date)
return webnotes.conn.sql("""select * from `tabGL Entry`
where docstatus < 2 %s order by posting_date, account""" %
(conditions), tuple(customer_accounts), as_dict=1)
def get_conditions(filters, upto_report_date=True):
conditions = ""
if filters.get("company"):
conditions += " and company='%s'" % filters["company"]
customer_accounts = []
if filters.get("account"):
customer_accounts = [filters["account"]]
else:
customer_accounts = webnotes.conn.sql_list("""select name from `tabAccount`
where ifnull(master_type, '') = 'Customer' and docstatus < 2 %s""" %
conditions, filters)
if customer_accounts:
conditions += " and account in (%s)" % (", ".join(['%s']*len(customer_accounts)))
else:
msgprint(_("No Customer Accounts found. Customer Accounts are identified based on \
'Master Type' value in account record."), raise_exception=1)
if filters.get("report_date"):
if upto_report_date:
conditions += " and posting_date<='%s'" % filters["report_date"]
else:
conditions += " and posting_date>'%s'" % filters["report_date"]
return conditions, customer_accounts
def get_account_territory_map():
account_territory_map = {}
for each in webnotes.conn.sql("""select t2.name, t1.territory from `tabCustomer` t1,
`tabAccount` t2 where t1.name = t2.master_name"""):
account_territory_map[each[0]] = each[1]
return account_territory_map
def get_si_due_date_map():
""" get due_date from sales invoice """
si_due_date_map = {}
for t in webnotes.conn.sql("""select name, due_date from `tabSales Invoice`"""):
si_due_date_map[t[0]] = t[1]
return si_due_date_map
def get_outstanding_amount(gle, report_date):
payment_amount = webnotes.conn.sql("""
select sum(ifnull(credit, 0)) - sum(ifnull(debit, 0))
from `tabGL Entry`
where account = %s and posting_date <= %s and against_voucher_type = %s
and against_voucher = %s and name != %s""",
(gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0]
return flt(gle.debit) - flt(gle.credit) - flt(payment_amount)
def get_payment_amount(gle, report_date, entries_after_report_date):
payment_amount = 0
if flt(gle.credit) > 0 and (not gle.against_voucher or
[gle.against_voucher_type, gle.against_voucher] in entries_after_report_date):
payment_amount = gle.credit
elif flt(gle.debit) > 0:
payment_amount = webnotes.conn.sql("""
select sum(ifnull(credit, 0)) - sum(ifnull(debit, 0)) from `tabGL Entry`
where account = %s and posting_date <= %s and against_voucher_type = %s
and against_voucher = %s and name != %s""",
(gle.account, report_date, gle.voucher_type, gle.voucher_no, gle.name))[0][0]
return flt(payment_amount)
def get_ageing_data(ageing_based_on_date, age_on, outstanding_amount):
val1 = val2 = val3 = val4 = diff = 0
diff = age_on and ageing_based_on_date \
and (getdate(age_on) - getdate(ageing_based_on_date)).days or 0
if diff <= 30:
val1 = outstanding_amount
elif 30 < diff <= 60:
val2 = outstanding_amount
elif 60 < diff <= 90:
val3 = outstanding_amount
elif diff > 90:
val4 = outstanding_amount
return [diff, val1, val2, val3, val4]
return [age] + outstanding_range

View File

@ -143,6 +143,7 @@ Contents
1. [Style](docs.user.website.style.html)
1. [Blog](docs.user.website.blog.html)
1. [Shopping Cart](docs.user.website.shopping_cart.html)
1. [Add Products](docs.user.website.add_products_to_website.html)
1. [Tools](docs.user.tools.html)
1. [To Do](docs.user.tools.todo.html)
1. [Calendar](docs.user.tools.calendar.html)

View File

@ -15,19 +15,57 @@ To use the Production Planning Tool, go to:
> Manufacturing > Production Planning Tool
![Production Planning Tool](img/production-planning-tool.png)
![Material Requisition Planning](img/mrp.png)
<br>
#### Step 1: Select and get Sales Order
Select sales orders for MRP using filters (Time, Item, and Customer)
![Production Planning Tool](img/mrp-1.png)
Click on Get Sales Order to generate a list.
![Production Planning Tool](img/mrp-1.1.png)
<br>
#### Step 2: Get Item from Sales Orders.
You can add/remove or change quantity of these Items.
![Production Planning Tool](img/mrp-2.png)
<br>
#### Step 3: Create Production Orders
![Production Planning Tool](img/mrp-3.png)
<br>
#### Step 4: Create Material Request
Create Material Request for Items with projected shortfall.
![Production Planning Tool](img/mrp-4.png)
<br>
The Production Planning Tool is used in two stages:
- Selection of Open Sales Orders for the period based on “Expected Delivery Date”.
- Selection of Items from those Sales Orders.
- Click on “Raise Production Orders”
The tool will update if you have already created Production Orders for this Item against this Sales Order (“Planned Quantity”).
The tool will update if you have already created Production Orders for a particular Item against its Sales Order (“Planned Quantity”).
You can always edit the Item list and increase / reduce quantities to plan your production.
> Note: How do you change a Production Plan? The output of the Production Planning Tool is the Production Order. Once your orders are created, you can change them by amending the Production Orders.

View File

@ -3,7 +3,7 @@
"_label": "Sales Order"
}
---
The Sales Order confirms your sales and triggers purchase (**Purchase Request**) shipment (**Delivery Note**), billing (**Sales Invoice**) and manufacturing (**Production Plan**)
The Sales Order confirms your sales and triggers purchase (**Material Request**) shipment (**Delivery Note**), billing (**Sales Invoice**) and manufacturing (**Production Plan**)
A Sales Order is usually a binding Contract with your Customer.
@ -41,7 +41,7 @@ The “Packing List” table will be automatically updated when you “Save” t
#### Reservation and Warehouses
If your Sales Order contains Items for which inventory is tracked (Is Stock Item is “Yes”). ERPNext will ask you for “Reservation Warehouse”. If you have set a default Warehouse for the Item, it will automatically set this Warehouse here.
If your Sales Order contains Items for which inventory is tracked (Is Stock Item is “Yes”) then, ERPNext will ask you for “Reservation Warehouse”. If you have set a default Warehouse for the Item, it will automatically set this Warehouse here.
This “reserved” quantity will help you project what is the quantity you need to purchase based on all your commitments.

View File

@ -17,6 +17,11 @@ If some of your Items require different tax rates as compared to others, mention
- **Inclusive and Exclusive Tax**: ERPNext allows you to enter Item rates which are tax inclusive.
![Inclusive Tax](img/inclusive-tax.png)
- **Exception to the rule**: Item tax settings are required only if a particular Item has a different tax rate than the rate defined in the standard tax Account
- **Item tax is overwrite-able**: You can overwrite or change the item tax rate by going to the Item master in the Item tax table.

View File

@ -7,15 +7,42 @@ Goods sold being returned is quite a common practice in Business. They could be
> Stock > Stock Entry > New Stock Entry
#### Step 1: Select Purpose as "Sales Return"
![Sales Return](img/sales-return.png)
#### Step 2: Enter the Delivery Note No. or Sales Invoice No.
<br>
![Sales Return](img/sales-return-1.png)
<br>
#### Step 3: Enter Item Details
![Sales Return](img/sales-return-2.png)
<br>
#### Step 4: Mention Contact Information of the Customer.
![Sales Return](img/sales-return-3.png)
<br>
- For Sales Return click on Stock Entry
- Select Sales Return under Purpose
- Mention the Delivery Note number and the Sales Invoice number.
- Mention the Delivery Note number or the Sales Invoice number.
- Mention Contact Information of the Customer.
- Save the file.
- Save the file and Submit the Stock Entry.
#### Credit Note
Once the Stock Entry is submitted, you can click on Make Credit Note button and a new Journal Voucher will be created prefilled with the customer's account and Items' income account.
![Sales Return](img/sales-return-4.png)

View File

@ -0,0 +1,60 @@
---
{
"_label": "Add Products to Website"
}
---
### Add Products to the Website
To list your Item on the Website, fill the Item details and save the file. Once the file is saved, a plus (+) button will appear next to the Image icon. Click on the plus button and add your Item image. The html code will be generated automatically.
##### Step 1: Save Image
![Webimage](img/item-webimage.png)
<br>
##### Step 2: Check the 'Show in Website' box.
Under the Website section, please check the box that says 'show in Website'. Once the box is checked, the page will display other fields for entering information.
![Webimage](img/item-webimage-1.png)
<br>
##### Step 3: Enter Website Details
![Webimage](img/item-webimage-2.png)
The page name will be generated automatically. Mention the Item-Group under which the Item will be displayed.
#### Item Groups
Mention the Item Group under this column. If you wish to list your Item under the broad category products, name your Item Group as Products. In case you have various varieties of Item and want to classify them under different names, make Item Groups with those names and check the box that says 'show in Website'. For Example, if you wish to create a category called 'Bags', create a Item Group named Bags.
![Item Group](img/itemgroup-webimage-bags.png)
Once the Item Group is created go to the Website Settings page under Website. Enter the Label, Url, and Parent Label.
![Item Group](img/itemgroup-website-settings.png)
<br>
#### Webpage labels
![Webpage](img/webpage-labels.png)
Add more Items under a particular Item Group.
To add more Items under a certain Label, mention the Item Group on the Item Page. The Items will be added automatically on the Webpage, under the Item Group Label. For Example, To add Item-Kiddies Bag and Butterfly Print Bag, check the 'Show in Website'box. The Items will be placed under the Label Bags on the Webpage.
![Item Group](img/itemgroup-websettings.png)
<br>
Item Group Display
![Item Group Display](img/webpage-itemgroup-display.png)

View File

@ -5,7 +5,8 @@
"docs.user.website.setup",
"docs.user.website.web_page",
"docs.user.website.style",
"docs.user.website.blog"
"docs.user.website.blog",
"docs.user.website.add_products_to_website"
]
}
---

View File

@ -0,0 +1,24 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import webnotes, os, shutil
def execute():
# changed cache key for plugin code files
for doctype in webnotes.conn.sql_list("""select name from `tabDocType`"""):
webnotes.cache().delete_value("_server_script:"+doctype)
# move custom script reports to plugins folder
for name in webnotes.conn.sql_list("""select name from `tabReport`
where report_type="Script Report" and is_standard="No" """):
bean = webnotes.bean("Report", name)
bean.save()
module = webnotes.conn.get_value("DocType", bean.doc.ref_doctype, "module")
path = webnotes.modules.get_doc_path(module, "Report", name)
for extn in ["py", "js"]:
file_path = os.path.join(path, name + "." + extn)
plugins_file_path = webnotes.plugins.get_path(module, "Report", name, extn=extn)
if not os.path.exists(plugins_file_path) and os.path.exists(file_path):
shutil.copyfile(file_path, plugins_file_path)

View File

@ -237,4 +237,5 @@ patch_list = [
"patches.october_2013.p07_rename_for_territory",
"patches.june_2013.p07_taxes_price_list_for_territory",
"patches.october_2013.p08_cleanup_after_item_price_module_change",
"patches.october_2013.p10_plugins_refactor",
]

View File

@ -2,7 +2,7 @@
{
"creation": "2013-10-22 11:58:16",
"docstatus": 0,
"modified": "2013-10-22 12:08:18",
"modified": "2013-11-06 15:01:09",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -10,7 +10,7 @@
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"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\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\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\"\nFROM\n\t`tabLead`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.lead=`tabLead`.name\n\t)\nWHERE\n\t`tabLead`.docstatus<2\nORDER BY\n\t`tabLead`.name asc",
"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\ttabAddress.state, tabAddress.pincode, tabAddress.country\n\t) as 'Address::180',\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/Profile:120\"\nFROM\n\t`tabLead`\n\tleft join `tabAddress` on (\n\t\t`tabAddress`.lead=`tabLead`.name\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

@ -84,7 +84,7 @@ class DocType:
for pr in purchase_receipts:
pr_bean = webnotes.bean("Purchase Receipt", pr)
pr_bean.run_method("update_ordered_qty", is_cancelled="Yes")
pr_bean.run_method("update_ordered_qty")
webnotes.conn.sql("""delete from `tabStock Ledger Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s""", pr)

View File

@ -18,7 +18,7 @@ company = "Wind Power LLC"
company_abbr = "WP"
country = "United States"
currency = "USD"
time_zone = "America/New York"
time_zone = "America/New_York"
start_date = '2013-01-01'
bank_name = "Citibank"
runs_for = None

View File

@ -106,8 +106,8 @@ def make_demo_login_page():
def make_demo_on_login_script():
import shutil
from core.doctype.custom_script.custom_script import get_custom_server_script_path
custom_script_path = get_custom_server_script_path("Control Panel")
import webnotes.plugins
custom_script_path = webnotes.plugins.get_path("Core", "DocType", "Control Panel")
webnotes.create_folder(os.path.dirname(custom_script_path))
shutil.copyfile(os.path.join(os.path.dirname(__file__), "demo_control_panel.py"), custom_script_path)