[fix] [minor] merge conflict fixed

This commit is contained in:
Akhilesh Darjee 2013-12-18 13:32:37 +05:30
commit 2d0e31b479
52 changed files with 639 additions and 1356 deletions

View File

@ -399,9 +399,10 @@ class DocType(SellingController):
if not self.doc.cash_bank_account and flt(self.doc.paid_amount): if not self.doc.cash_bank_account and flt(self.doc.paid_amount):
msgprint("Cash/Bank Account is mandatory for POS, for making payment entry") msgprint("Cash/Bank Account is mandatory for POS, for making payment entry")
raise Exception raise Exception
if (flt(self.doc.paid_amount) + flt(self.doc.write_off_amount) - round(flt(self.doc.grand_total), 2))>0.001: if flt(self.doc.paid_amount) + flt(self.doc.write_off_amount) \
msgprint("(Paid amount + Write Off Amount) can not be greater than Grand Total") - flt(self.doc.grand_total) > 1/(10**(self.precision("grand_total") + 1)):
raise Exception webnotes.throw(_("""(Paid amount + Write Off Amount) can not be \
greater than Grand Total"""))
def validate_item_code(self): def validate_item_code(self):

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-04-24 11:39:32", "creation": "2013-04-24 11:39:32",
"docstatus": 0, "docstatus": 0,
"modified": "2013-07-10 14:54:21", "modified": "2013-12-17 12:38:08",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -37,11 +37,20 @@
"options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total", "options": "\nActual\nOn Net Total\nOn Previous Row Amount\nOn Previous Row Total",
"reqd": 1 "reqd": 1
}, },
{
"doctype": "DocField",
"fieldname": "row_id",
"fieldtype": "Data",
"hidden": 0,
"label": "Enter Row",
"oldfieldname": "row_id",
"oldfieldtype": "Data"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "account_head", "fieldname": "account_head",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 0,
"label": "Account Head", "label": "Account Head",
"oldfieldname": "account_head", "oldfieldname": "account_head",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -54,7 +63,7 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "cost_center", "fieldname": "cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 0,
"label": "Cost Center", "label": "Cost Center",
"oldfieldname": "cost_center_other_charges", "oldfieldname": "cost_center_other_charges",
"oldfieldtype": "Link", "oldfieldtype": "Link",
@ -72,6 +81,24 @@
"reqd": 1, "reqd": 1,
"width": "300px" "width": "300px"
}, },
{
"allow_on_submit": 0,
"description": "If checked, the tax amount will be considered as already included in the Print Rate / Print Amount",
"doctype": "DocField",
"fieldname": "included_in_print_rate",
"fieldtype": "Check",
"label": "Is this Tax included in Basic Rate?",
"no_copy": 0,
"print_hide": 1,
"print_width": "150px",
"report_hide": 1,
"width": "150px"
},
{
"doctype": "DocField",
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "rate", "fieldname": "rate",
@ -80,7 +107,7 @@
"label": "Rate", "label": "Rate",
"oldfieldname": "rate", "oldfieldname": "rate",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"reqd": 0 "reqd": 1
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
@ -104,15 +131,6 @@
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1 "read_only": 1
}, },
{
"doctype": "DocField",
"fieldname": "row_id",
"fieldtype": "Data",
"hidden": 0,
"label": "Enter Row",
"oldfieldname": "row_id",
"oldfieldtype": "Data"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "item_wise_tax_detail", "fieldname": "item_wise_tax_detail",
@ -134,18 +152,5 @@
"oldfieldtype": "Data", "oldfieldtype": "Data",
"print_hide": 1, "print_hide": 1,
"search_index": 1 "search_index": 1
},
{
"allow_on_submit": 0,
"description": "If checked, the tax amount will be considered as already included in the Print Rate / Print Amount",
"doctype": "DocField",
"fieldname": "included_in_print_rate",
"fieldtype": "Check",
"label": "Is this Tax included in Basic Rate?",
"no_copy": 0,
"print_hide": 1,
"print_width": "150px",
"report_hide": 1,
"width": "150px"
} }
] ]

View File

@ -24,10 +24,6 @@ def process_gl_map(gl_map, merge_entries=True):
gl_map = merge_similar_entries(gl_map) gl_map = merge_similar_entries(gl_map)
for entry in gl_map: for entry in gl_map:
# round off upto 2 decimal
entry.debit = flt(entry.debit, 2)
entry.credit = flt(entry.credit, 2)
# toggle debit, credit if negative entry # toggle debit, credit if negative entry
if flt(entry.debit) < 0: if flt(entry.debit) < 0:
entry.credit = flt(entry.credit) - flt(entry.debit) entry.credit = flt(entry.credit) - flt(entry.debit)

View File

@ -157,7 +157,8 @@ wn.module_page["Accounts"] = [
items: [ items: [
{ {
"label":wn._("General Ledger"), "label":wn._("General Ledger"),
page: "general-ledger" doctype: "GL Entry",
route: "query-report/General Ledger"
}, },
{ {
"label":wn._("Trial Balance"), "label":wn._("Trial Balance"),

View File

@ -1 +0,0 @@
General Ledger report (for all transactions and accounts).

View File

@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@ -1,396 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['general-ledger'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('General Ledger'),
single_column: true
});
erpnext.general_ledger = new erpnext.GeneralLedger(wrapper);
wrapper.appframe.add_module_icon("Accounts")
}
erpnext.GeneralLedger = wn.views.GridReport.extend({
init: function(wrapper) {
this._super({
title: wn._("General Ledger"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Company", "Account", "GL Entry", "Cost Center"],
});
},
setup_columns: function() {
this.columns = [
{id: "posting_date", name: wn._("Posting Date"), field: "posting_date", width: 100,
formatter: this.date_formatter},
{id: "account", name: wn._("Account"), field: "account", width: 240,
link_formatter: {
filter_input: "account",
open_btn: true,
doctype: "'Account'"
}},
{id: "against_account", name: wn._("Against Account"), field: "against_account",
width: 240, hidden: !this.account},
{id: "debit", name: wn._("Debit"), field: "debit", width: 100,
formatter: this.currency_formatter},
{id: "credit", name: wn._("Credit"), field: "credit", width: 100,
formatter: this.currency_formatter},
{id: "voucher_type", name: wn._("Voucher Type"), field: "voucher_type", width: 120},
{id: "voucher_no", name: wn._("Voucher No"), field: "voucher_no", width: 160,
link_formatter: {
filter_input: "voucher_no",
open_btn: true,
doctype: "dataContext.voucher_type"
}},
{id: "remarks", name: wn._("Remarks"), field: "remarks", width: 200,
formatter: this.text_formatter},
];
},
filters: [
{fieldtype:"Select", label: wn._("Company"), link:"Company", default_value: wn._("Select Company..."),
filter: function(val, item, opts) {
return item.company == val || val == opts.default_value;
}},
{fieldtype:"Link", label: wn._("Account"), link:"Account",
filter: function(val, item, opts, me) {
if(!val) {
return true;
} else {
// true if GL Entry belongs to selected
// account ledger or group
return me.is_child_account(val, item.account);
}
}},
{fieldtype:"Data", label: wn._("Voucher No"),
filter: function(val, item, opts) {
if(!val) return true;
return (item.voucher_no && item.voucher_no.indexOf(val)!=-1);
}},
{fieldtype:"Date", label: wn._("From Date"), filter: function(val, item) {
return dateutil.str_to_obj(val) <= dateutil.str_to_obj(item.posting_date);
}},
{fieldtype:"Label", label: wn._("To")},
{fieldtype:"Date", label: wn._("To Date"), filter: function(val, item) {
return dateutil.str_to_obj(val) >= dateutil.str_to_obj(item.posting_date);
}},
{fieldtype: "Check", label: wn._("Group by Ledger")},
{fieldtype: "Check", label: wn._("Group by Voucher")},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
this._super();
var me = this;
this.accounts_by_company = this.make_accounts_by_company();
// filter accounts options by company
this.filter_inputs.company.on("change", function() {
me.setup_account_filter(this);
me.refresh();
});
this.trigger_refresh_on_change(["group_by_ledger", "group_by_voucher"]);
},
setup_account_filter: function(company_filter) {
var me = this;
var $account = me.filter_inputs.account;
var company = $(company_filter).val();
var default_company = this.filter_inputs.company.get(0).opts.default_value;
var opts = $account.get(0).opts;
opts.list = $.map(wn.report_dump.data["Account"], function(ac) {
return (company===default_company ||
me.accounts_by_company[company].indexOf(ac.name)!=-1) ?
ac.name : null;
});
this.set_autocomplete($account, opts.list);
},
init_filter_values: function() {
this._super();
this.toggle_group_by_checks();
this.filter_inputs.company.trigger("change");
},
apply_filters_from_route: function() {
this._super();
this.toggle_group_by_checks();
},
make_accounts_by_company: function() {
var accounts_by_company = {};
var me = this;
$.each(wn.report_dump.data["Account"], function(i, ac) {
if(!accounts_by_company[ac.company]) accounts_by_company[ac.company] = [];
accounts_by_company[ac.company].push(ac.name);
});
return accounts_by_company;
},
is_child_account: function(account, item_account) {
account = this.account_by_name[account];
item_account = this.account_by_name[item_account];
return ((item_account.lft >= account.lft) && (item_account.rgt <= account.rgt));
},
toggle_group_by_checks: function() {
this.make_account_by_name();
// this.filter_inputs.group_by_ledger
// .parent().toggle(!!(this.account_by_name[this.account]
// && this.account_by_name[this.account].group_or_ledger==="Group"));
//
// this.filter_inputs.group_by_voucher
// .parent().toggle(!!(this.account_by_name[this.account]
// && this.account_by_name[this.account].group_or_ledger==="Ledger"));
},
prepare_data: function() {
var me = this;
var data = wn.report_dump.data["GL Entry"];
var out = [];
this.toggle_group_by_checks();
var from_date = dateutil.str_to_obj(this.from_date);
var to_date = dateutil.str_to_obj(this.to_date);
if(to_date < from_date) {
msgprint(wn._("From Date must be before To Date"));
return;
}
// add Opening, Closing, Totals rows
// if filtered by account and / or voucher
var opening = this.make_summary_row("Opening", this.account);
var totals = this.make_summary_row("Totals", this.account);
var grouped_ledgers = {};
$.each(data, function(i, item) {
if(me.apply_filter(item, "company") &&
(me.account ? me.is_child_account(me.account, item.account)
: true) && (me.voucher_no ? item.voucher_no==me.voucher_no : true)) {
var date = dateutil.str_to_obj(item.posting_date);
// create grouping by ledger
if(!grouped_ledgers[item.account]) {
grouped_ledgers[item.account] = {
entries: [],
entries_group_by_voucher: {},
opening: me.make_summary_row("Opening", item.account),
totals: me.make_summary_row("Totals", item.account),
closing: me.make_summary_row("Closing (Opening + Totals)",
item.account)
};
}
if(!grouped_ledgers[item.account].entries_group_by_voucher[item.voucher_no]) {
grouped_ledgers[item.account].entries_group_by_voucher[item.voucher_no] = {
row: {},
totals: {"debit": 0, "credit": 0}
}
}
if(!me.voucher_no && (date < from_date || item.is_opening=="Yes")) {
opening.debit += item.debit;
opening.credit += item.credit;
grouped_ledgers[item.account].opening.debit += item.debit;
grouped_ledgers[item.account].opening.credit += item.credit;
} else if(date <= to_date) {
totals.debit += item.debit;
totals.credit += item.credit;
grouped_ledgers[item.account].totals.debit += item.debit;
grouped_ledgers[item.account].totals.credit += item.credit;
grouped_ledgers[item.account].entries_group_by_voucher[item.voucher_no]
.totals.debit += item.debit;
grouped_ledgers[item.account].entries_group_by_voucher[item.voucher_no]
.totals.credit += item.credit;
}
if(item.account) {
item.against_account = me.voucher_accounts[item.voucher_type + ":"
+ item.voucher_no][(item.debit > 0 ? "credits" : "debits")].join(", ");
}
if(me.apply_filters(item) && (me.voucher_no || item.is_opening=="No")) {
out.push(item);
grouped_ledgers[item.account].entries.push(item);
if(grouped_ledgers[item.account].entries_group_by_voucher[item.voucher_no].row){
grouped_ledgers[item.account].entries_group_by_voucher[item.voucher_no]
.row = $.extend({}, item);
}
}
}
});
var closing = this.make_summary_row("Closing (Opening + Totals)", this.account);
closing.debit = opening.debit + totals.debit;
closing.credit = opening.credit + totals.credit;
if(me.account) {
me.appframe.set_title(wn._("General Ledger: ") + me.account);
// group by ledgers
if(this.account_by_name[this.account].group_or_ledger==="Group"
&& this.group_by_ledger) {
out = this.group_data_by_ledger(grouped_ledgers);
}
if(this.account_by_name[this.account].group_or_ledger==="Ledger"
&& this.group_by_voucher) {
out = this.group_data_by_voucher(grouped_ledgers);
}
opening = me.get_balance(me.account_by_name[me.account].debit_or_credit, opening)
closing = me.get_balance(me.account_by_name[me.account].debit_or_credit, closing)
out = [opening].concat(out).concat([totals, closing]);
} else {
me.appframe.set_title(wn._("General Ledger"));
out = out.concat([totals]);
}
this.data = out;
},
group_data_by_ledger: function(grouped_ledgers) {
var me = this;
var out = []
$.each(Object.keys(grouped_ledgers).sort(), function(i, account) {
if(grouped_ledgers[account].entries.length) {
grouped_ledgers[account].closing.debit =
grouped_ledgers[account].opening.debit
+ grouped_ledgers[account].totals.debit;
grouped_ledgers[account].closing.credit =
grouped_ledgers[account].opening.credit
+ grouped_ledgers[account].totals.credit;
grouped_ledgers[account].opening =
me.get_balance(me.account_by_name[me.account].debit_or_credit,
grouped_ledgers[account].opening)
grouped_ledgers[account].closing =
me.get_balance(me.account_by_name[me.account].debit_or_credit,
grouped_ledgers[account].closing)
out = out.concat([grouped_ledgers[account].opening])
.concat(grouped_ledgers[account].entries)
.concat([grouped_ledgers[account].totals,
grouped_ledgers[account].closing,
{id: "_blank" + i, debit: "", credit: ""}]);
}
});
return [{id: "_blank_first", debit: "", credit: ""}].concat(out);
},
group_data_by_voucher: function(grouped_ledgers) {
var me = this;
var out = []
$.each(Object.keys(grouped_ledgers).sort(), function(i, account) {
if(grouped_ledgers[account].entries.length) {
$.each(Object.keys(grouped_ledgers[account].entries_group_by_voucher),
function(j, voucher) {
voucher_dict = grouped_ledgers[account].entries_group_by_voucher[voucher];
if(voucher_dict &&
(voucher_dict.totals.debit || voucher_dict.totals.credit)) {
voucher_dict.row.debit = voucher_dict.totals.debit;
voucher_dict.row.credit = voucher_dict.totals.credit;
voucher_dict.row.id = "entry_grouped_by_" + voucher
out = out.concat(voucher_dict.row);
}
});
}
});
return out;
},
get_balance: function(debit_or_credit, balance) {
if(debit_or_credit == "Debit") {
balance.debit -= balance.credit; balance.credit = 0;
} else {
balance.credit -= balance.debit; balance.debit = 0;
}
return balance
},
make_summary_row: function(label, item_account) {
return {
account: label,
debit: 0.0,
credit: 0.0,
id: ["", label, item_account].join("_").replace(" ", "_").toLowerCase(),
_show: true,
_style: "font-weight: bold"
}
},
make_account_by_name: function() {
this.account_by_name = this.make_name_map(wn.report_dump.data["Account"]);
this.make_voucher_accounts_map();
},
make_voucher_accounts_map: function() {
this.voucher_accounts = {};
var data = wn.report_dump.data["GL Entry"];
for(var i=0, j=data.length; i<j; i++) {
var gl = data[i];
if(!this.voucher_accounts[gl.voucher_type + ":" + gl.voucher_no])
this.voucher_accounts[gl.voucher_type + ":" + gl.voucher_no] = {
debits: [],
credits: []
}
var va = this.voucher_accounts[gl.voucher_type + ":" + gl.voucher_no];
if(gl.debit > 0) {
va.debits.push(gl.account);
} else {
va.credits.push(gl.account);
}
}
},
get_plot_data: function() {
var data = [];
var me = this;
if(!me.account || me.voucher_no) return false;
var debit_or_credit = me.account_by_name[me.account].debit_or_credit;
var balance = debit_or_credit=="Debit" ? me.data[0].debit : me.data[0].credit;
data.push({
label: me.account,
data: [[dateutil.str_to_obj(me.from_date).getTime(), balance]]
.concat($.map(me.data, function(col, idx) {
if (col.posting_date) {
var diff = (debit_or_credit == "Debit" ? 1 : -1) * (flt(col.debit) - flt(col.credit));
balance += diff;
return [[dateutil.str_to_obj(col.posting_date).getTime(), balance - diff],
[dateutil.str_to_obj(col.posting_date).getTime(), balance]]
}
return null;
})).concat([
// closing
[dateutil.str_to_obj(me.to_date).getTime(), balance]
]),
points: {show: true},
lines: {show: true, fill: true},
});
return data;
},
get_plot_options: function() {
return {
grid: { hoverable: true, clickable: true },
xaxis: { mode: "time",
min: dateutil.str_to_obj(this.from_date).getTime(),
max: dateutil.str_to_obj(this.to_date).getTime() },
series: { downsample: { threshold: 1000 } }
}
},
});

View File

@ -1,41 +0,0 @@
[
{
"creation": "2012-09-14 11:25:48",
"docstatus": 0,
"modified": "2013-07-11 14:42:21",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Accounts",
"name": "__common__",
"page_name": "general-ledger",
"standard": "Yes",
"title": "General Ledger"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "general-ledger",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "general-ledger"
},
{
"doctype": "Page Role",
"role": "Analytics"
},
{
"doctype": "Page Role",
"role": "Accounts Manager"
},
{
"doctype": "Page Role",
"role": "Accounts User"
}
]

View File

@ -0,0 +1,51 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.query_reports["General Ledger"] = {
"filters": [
{
"fieldname":"company",
"label": wn._("Company"),
"fieldtype": "Link",
"options": "Company",
"default": wn.defaults.get_user_default("company"),
"reqd": 1
},
{
"fieldname":"account",
"label": wn._("Account"),
"fieldtype": "Link",
"options": "Account"
},
{
"fieldname":"voucher_no",
"label": wn._("Voucher No"),
"fieldtype": "Data",
},
{
"fieldname":"group_by",
"label": wn._("Group by"),
"fieldtype": "Select",
"options": "\nGroup by Account\nGroup by Voucher"
},
{
"fieldtype": "Break",
},
{
"fieldname":"from_date",
"label": wn._("From Date"),
"fieldtype": "Date",
"default": wn.datetime.add_months(wn.datetime.get_today(), -1),
"reqd": 1,
"width": "60px"
},
{
"fieldname":"to_date",
"label": wn._("To Date"),
"fieldtype": "Date",
"default": wn.datetime.get_today(),
"reqd": 1,
"width": "60px"
}
]
}

View File

@ -0,0 +1,79 @@
# 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 webnotes
from webnotes.utils import flt
from webnotes import _
def execute(filters=None):
validate_filters(filters)
columns = get_columns()
if filters.get("group_by"):
data = get_grouped_gle(filters)
else:
data = get_gl_entries(filters)
if data:
data.append(get_total_row(data))
return columns, data
def validate_filters(filters):
if filters.get("account") and filters.get("group_by") == "Group by Account":
webnotes.throw(_("Can not filter based on Account, if grouped by Account"))
if filters.get("voucher_no") and filters.get("group_by") == "Group by Voucher":
webnotes.throw(_("Can not filter based on Voucher No, if grouped by Voucher"))
def get_columns():
return ["Posting Date:Date:100", "Account:Link/Account:200", "Debit:Currency:100",
"Credit:Currency:100", "Voucher Type::120", "Voucher No::160",
"Cost Center:Link/Cost Center:100", "Remarks::200"]
def get_gl_entries(filters):
return webnotes.conn.sql("""select
posting_date, account, debit, credit, voucher_type, voucher_no, cost_center, remarks
from `tabGL Entry`
where company=%(company)s
and posting_date between %(from_date)s and %(to_date)s
{conditions}
order by posting_date, account"""\
.format(conditions=get_conditions(filters)), filters, as_list=1)
def get_conditions(filters):
conditions = []
if filters.get("account"):
conditions.append("account=%(account)s")
if filters.get("voucher_no"):
conditions.append("voucher_no=%(voucher_no)s")
return "and {}".format(" and ".join(conditions)) if conditions else ""
def get_grouped_gle(filters):
gle_map = {}
gle = get_gl_entries(filters)
for d in gle:
gle_map.setdefault(d[1 if filters["group_by"]=="Group by Account" else 5], []).append(d)
data = []
for entries in gle_map.values():
subtotal_debit = subtotal_credit = 0.0
for entry in entries:
data.append(entry)
subtotal_debit += flt(entry[2])
subtotal_credit += flt(entry[3])
data.append(["", "Total", subtotal_debit, subtotal_credit, "", "", ""])
if data:
data.append(get_total_row(gle))
return data
def get_total_row(gle):
total_debit = total_credit = 0.0
for d in gle:
total_debit += flt(d[2])
total_credit += flt(d[3])
return ["", "Total Debit/Credit", total_debit, total_credit, "", "", ""]

View File

@ -0,0 +1,21 @@
[
{
"creation": "2013-12-06 13:22:23",
"docstatus": 0,
"modified": "2013-12-06 13:22:23",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"ref_doctype": "GL Entry",
"report_name": "General Ledger",
"report_type": "Script Report"
},
{
"doctype": "Report",
"name": "General Ledger"
}
]

View File

@ -1,6 +1,6 @@
{ {
"app_name": "ERPNext", "app_name": "ERPNext",
"app_version": "3.2.3", "app_version": "3.3.1",
"base_template": "app/portal/templates/base.html", "base_template": "app/portal/templates/base.html",
"modules": { "modules": {
"Accounts": { "Accounts": {
@ -74,5 +74,5 @@
"type": "module" "type": "module"
} }
}, },
"requires_framework_version": "==3.2.0" "requires_framework_version": "==3.3.1"
} }

View File

@ -205,8 +205,8 @@ class SellingController(StockController):
self.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount", self.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount",
"paid_amount"]) "paid_amount"])
total_amount_to_pay = self.doc.grand_total - self.doc.write_off_amount total_amount_to_pay = self.doc.grand_total - self.doc.write_off_amount
self.doc.outstanding_amount = flt(total_amount_to_pay - self.doc.total_advance - self.doc.paid_amount, self.doc.outstanding_amount = flt(total_amount_to_pay - self.doc.total_advance \
self.precision("outstanding_amount")) - self.doc.paid_amount, self.precision("outstanding_amount"))
def calculate_commission(self): def calculate_commission(self):
if self.meta.get_field("commission_rate"): if self.meta.get_field("commission_rate"):

View File

@ -0,0 +1,25 @@
# 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 webnotes
def execute():
webnotes.reload_doc("setup", "doctype", "email_digest")
from webnotes.profile import get_system_managers
system_managers = get_system_managers(only_name=True)
if not system_managers:
return
# scheduler errors digest
edigest = webnotes.new_bean("Email Digest")
edigest.doc.fields.update({
"name": "Scheduler Errors",
"company": webnotes.conn.get_default("company"),
"frequency": "Daily",
"enabled": 1,
"recipient_list": "\n".join(system_managers),
"scheduler_errors": 1
})
edigest.insert()

View File

@ -0,0 +1,11 @@
# 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 webnotes
def execute():
from webnotes.utils import extract_email_id
for name, recipients in webnotes.conn.sql("""select name, recipient_list from `tabEmail Digest`"""):
recipients = "\n".join([extract_email_id(r) for r in recipients.split("\n")])
webnotes.conn.set_value("Email Digest", name, "recipient_list", recipients)

View File

@ -0,0 +1,17 @@
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
def execute():
import webnotes, os, shutil
from webnotes.utils import get_base_path
webnotes.delete_doc('Page', 'stock-ledger')
webnotes.delete_doc('Page', 'stock-ageing')
webnotes.delete_doc('Page', 'stock-level')
webnotes.delete_doc('Page', 'general-ledger')
for d in [["stock", "stock_ledger"], ["stock", "stock_ageing"],
["stock", "stock_level"], ["accounts", "general_ledger"]]:
path = os.path.join(get_base_path(), "app", d[0], "page", d[1])
if os.path.exists(path):
shutil.rmtree(path)

View File

@ -256,7 +256,10 @@ patch_list = [
"patches.1311.p06_fix_report_columns", "patches.1311.p06_fix_report_columns",
"execute:webnotes.delete_doc('DocType', 'Documentation Tool')", "execute:webnotes.delete_doc('DocType', 'Documentation Tool')",
"execute:webnotes.delete_doc('Report', 'Stock Ledger') #2013-11-29", "execute:webnotes.delete_doc('Report', 'Stock Ledger') #2013-11-29",
"patches.1312.p01_delete_old_stock_reports",
"execute:webnotes.delete_doc('Report', 'Payment Collection With Ageing')", "execute:webnotes.delete_doc('Report', 'Payment Collection With Ageing')",
"execute:webnotes.delete_doc('Report', 'Payment Made With Ageing')", "execute:webnotes.delete_doc('Report', 'Payment Made With Ageing')",
"patches.1311.p07_scheduler_errors_digest",
"patches.1311.p08_email_digest_recipients",
"execute:webnotes.delete_doc('DocType', 'Warehouse Type')", "execute:webnotes.delete_doc('DocType', 'Warehouse Type')",
] ]

View File

@ -515,7 +515,6 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
} }
}); });
}; };
setup_field_label_map(["net_total", "other_charges_total", "grand_total", setup_field_label_map(["net_total", "other_charges_total", "grand_total",
"rounded_total", "in_words", "rounded_total", "in_words",
"outstanding_amount", "total_advance", "paid_amount", "write_off_amount"], "outstanding_amount", "total_advance", "paid_amount", "write_off_amount"],

View File

@ -16,8 +16,6 @@ import webnotes
from webnotes.utils import get_request_site_address, cstr from webnotes.utils import get_request_site_address, cstr
from webnotes import _ from webnotes import _
from backup_manager import ignore_list
@webnotes.whitelist() @webnotes.whitelist()
def get_dropbox_authorize_url(): def get_dropbox_authorize_url():
sess = get_dropbox_session() sess = get_dropbox_session()
@ -100,8 +98,6 @@ def backup_to_dropbox():
path = get_files_path() path = get_files_path()
for filename in os.listdir(path): for filename in os.listdir(path):
filename = cstr(filename) filename = cstr(filename)
if filename in ignore_list:
continue
found = False found = False
filepath = os.path.join(path, filename) filepath = os.path.join(path, filename)

View File

@ -87,7 +87,7 @@ $.extend(cur_frm.cscript, {
cur_frm.save(); cur_frm.save();
}, },
upload_backups_to_gdrive: function() { // upload_backups_to_gdrive: function() {
cur_frm.save(); // cur_frm.save();
}, // },
}); });

View File

@ -7,8 +7,6 @@ from __future__ import unicode_literals
import webnotes import webnotes
from webnotes import _ from webnotes import _
ignore_list = []
class DocType: class DocType:
def __init__(self, d, dl): def __init__(self, d, dl):
self.doc, self.doclist = d, dl self.doc, self.doclist = d, dl
@ -39,10 +37,6 @@ def take_backups_dropbox():
file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)] file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)]
error_message = ("\n".join(file_and_error) + "\n" + webnotes.getTraceback()) error_message = ("\n".join(file_and_error) + "\n" + webnotes.getTraceback())
webnotes.errprint(error_message) webnotes.errprint(error_message)
if not webnotes.conn:
webnotes.connect()
send_email(False, "Dropbox", error_message) send_email(False, "Dropbox", error_message)
#backup to gdrive #backup to gdrive
@ -62,6 +56,7 @@ def take_backups_gdrive():
send_email(False, "Google Drive", error_message) send_email(False, "Google Drive", error_message)
def send_email(success, service_name, error_status=None): def send_email(success, service_name, error_status=None):
from webnotes.utils.email_lib import sendmail
if success: if success:
subject = "Backup Upload Successful" subject = "Backup Upload Successful"
message ="""<h3>Backup Uploaded Successfully</h3><p>Hi there, this is just to inform you message ="""<h3>Backup Uploaded Successfully</h3><p>Hi there, this is just to inform you
@ -76,7 +71,8 @@ def send_email(success, service_name, error_status=None):
<p>Please contact your system manager for more information.</p> <p>Please contact your system manager for more information.</p>
""" % (service_name, error_status) """ % (service_name, error_status)
# email system managers if not webnotes.conn:
from webnotes.utils.email_lib import sendmail webnotes.connect()
sendmail(webnotes.conn.get_value("Backup Manager", None, "send_notifications_to").split(","),
subject=subject, msg=message) recipients = webnotes.conn.get_value("Backup Manager", None, "send_notifications_to").split(",")
sendmail(recipients, subject=subject, msg=message)

View File

@ -70,8 +70,10 @@ cur_frm.cscript.addremove_recipients = function(doc, dt, dn) {
check.checked = 1; check.checked = 1;
add_or_update = 'Update'; add_or_update = 'Update';
} }
var fullname = wn.user.full_name(v.name);
if(fullname !== v.name) v.name = fullname + " &lt;" + v.name + "&gt;";
if(v.enabled==0) { if(v.enabled==0) {
v.name = "<span style='color: red'>" + v.name + " (disabled user)</span>" v.name = repl("<span style='color: red'> %(name)s (disabled user)</span>", {name: v.name});
} }
var profile = $a($td(tab, i+1, 1), 'span', '', '', v.name); var profile = $a($td(tab, i+1, 1), 'span', '', '', v.name);
//profile.onclick = function() { check.checked = !check.checked; } //profile.onclick = function() { check.checked = !check.checked; }

View File

@ -19,16 +19,16 @@ content_sequence = [
["Selling", ["new_leads", "new_enquiries", "new_quotations", "new_sales_orders"]], ["Selling", ["new_leads", "new_enquiries", "new_quotations", "new_sales_orders"]],
["Stock", ["new_delivery_notes", "new_purchase_receipts", "new_stock_entries"]], ["Stock", ["new_delivery_notes", "new_purchase_receipts", "new_stock_entries"]],
["Support", ["new_communications", "new_support_tickets", "open_tickets"]], ["Support", ["new_communications", "new_support_tickets", "open_tickets"]],
["Projects", ["new_projects"]] ["Projects", ["new_projects"]],
["System", ["scheduler_errors"]],
] ]
user_specific_content = ["calendar_events", "todo_list"] user_specific_content = ["calendar_events", "todo_list"]
digest_template = """\ digest_template = """<style>p.ed-indent { margin-right: 17px; }</style>
<style>p.ed-indent { margin-right: 17px; }</style> <h2>%(name)s</h2>
<h2>%(digest)s</h2>
<p style='color: grey'>%(date)s</p>
<h4>%(company)s</h4> <h4>%(company)s</h4>
<p style='color: grey'>%(date)s</p>
<hr> <hr>
%(with_value)s %(with_value)s
%(no_value)s %(no_value)s
@ -53,10 +53,10 @@ class DocType(DocListController):
def get_profiles(self): def get_profiles(self):
"""get list of profiles""" """get list of profiles"""
import webnotes
profile_list = webnotes.conn.sql(""" profile_list = webnotes.conn.sql("""
select name, enabled from tabProfile select name, enabled from tabProfile
where docstatus=0 and name not in ('Administrator', 'Guest') where docstatus=0 and name not in ('Administrator', 'Guest')
and user_type = "System User"
order by enabled desc, name asc""", as_dict=1) order by enabled desc, name asc""", as_dict=1)
if self.doc.recipient_list: if self.doc.recipient_list:
@ -81,7 +81,9 @@ class DocType(DocListController):
msg_for_this_receipient = self.get_msg_html(self.get_user_specific_content(user_id) + \ msg_for_this_receipient = self.get_msg_html(self.get_user_specific_content(user_id) + \
common_msg) common_msg)
from webnotes.utils.email_lib import sendmail from webnotes.utils.email_lib import sendmail
sendmail(recipients=user_id, subject="[ERPNext] " + (self.doc.frequency + " Digest"), sendmail(recipients=user_id,
subject="[ERPNext] [{frequency} Digest] {name}".format(
frequency=self.doc.frequency, name=self.doc.name),
msg=msg_for_this_receipient) msg=msg_for_this_receipient)
def get_digest_msg(self): def get_digest_msg(self):
@ -123,7 +125,7 @@ class DocType(DocListController):
if with_value: if with_value:
with_value = "\n".join(with_value) with_value = "\n".join(with_value)
else: else:
with_value = "<p>There were no updates in the items selected for this digest.</p>" with_value = "<p>There were no updates in the items selected for this digest.</p><hr>"
# seperate out no value items # seperate out no value items
no_value = [o[1] for o in out if not o[0]] no_value = [o[1] for o in out if not o[0]]
@ -138,7 +140,8 @@ class DocType(DocListController):
"date": date, "date": date,
"company": self.doc.company, "company": self.doc.company,
"with_value": with_value, "with_value": with_value,
"no_value": no_value or "" "no_value": no_value or "",
"name": self.doc.name
} }
return msg return msg
@ -241,7 +244,7 @@ class DocType(DocListController):
return self.get_new_count("Lead", self.meta.get_label("new_leads")) return self.get_new_count("Lead", self.meta.get_label("new_leads"))
def get_new_enquiries(self): def get_new_enquiries(self):
return self.get_new_count("Opportunity", self.meta.get_label("new_enquiries")) return self.get_new_count("Opportunity", self.meta.get_label("new_enquiries"), docstatus=1)
def get_new_quotations(self): def get_new_quotations(self):
return self.get_new_sum("Quotation", self.meta.get_label("new_quotations"), "grand_total") return self.get_new_sum("Quotation", self.meta.get_label("new_quotations"), "grand_total")
@ -253,7 +256,8 @@ class DocType(DocListController):
return self.get_new_sum("Delivery Note", self.meta.get_label("new_delivery_notes"), "grand_total") return self.get_new_sum("Delivery Note", self.meta.get_label("new_delivery_notes"), "grand_total")
def get_new_purchase_requests(self): def get_new_purchase_requests(self):
return self.get_new_count("Material Request", self.meta.get_label("new_purchase_requests")) return self.get_new_count("Material Request",
self.meta.get_label("new_purchase_requests"), docstatus=1)
def get_new_supplier_quotations(self): def get_new_supplier_quotations(self):
return self.get_new_sum("Supplier Quotation", self.meta.get_label("new_supplier_quotations"), return self.get_new_sum("Supplier Quotation", self.meta.get_label("new_supplier_quotations"),
@ -271,13 +275,16 @@ class DocType(DocListController):
return self.get_new_sum("Stock Entry", self.meta.get_label("new_stock_entries"), "total_amount") return self.get_new_sum("Stock Entry", self.meta.get_label("new_stock_entries"), "total_amount")
def get_new_support_tickets(self): def get_new_support_tickets(self):
return self.get_new_count("Support Ticket", self.meta.get_label("new_support_tickets"), False) return self.get_new_count("Support Ticket", self.meta.get_label("new_support_tickets"),
filter_by_company=False)
def get_new_communications(self): def get_new_communications(self):
return self.get_new_count("Communication", self.meta.get_label("new_communications"), False) return self.get_new_count("Communication", self.meta.get_label("new_communications"),
filter_by_company=False)
def get_new_projects(self): def get_new_projects(self):
return self.get_new_count("Project", self.meta.get_label("new_projects"), False) return self.get_new_count("Project", self.meta.get_label("new_projects"),
filter_by_company=False)
def get_calendar_events(self, user_id): def get_calendar_events(self, user_id):
from core.doctype.event.event import get_events from core.doctype.event.event import get_events
@ -321,22 +328,22 @@ class DocType(DocListController):
else: else:
return 0, "<p>To Do</p>" return 0, "<p>To Do</p>"
def get_new_count(self, doctype, label, filter_by_company=True): def get_new_count(self, doctype, label, docstatus=0, filter_by_company=True):
if filter_by_company: if filter_by_company:
company = """and company="%s" """ % self.doc.company company = """and company="%s" """ % self.doc.company
else: else:
company = "" company = ""
count = webnotes.conn.sql("""select count(*) from `tab%s` count = webnotes.conn.sql("""select count(*) from `tab%s`
where docstatus < 2 %s and where docstatus=%s %s and
date(creation)>=%s and date(creation)<=%s""" % (doctype, company, "%s", "%s"), date(creation)>=%s and date(creation)<=%s""" %
(self.from_date, self.to_date)) (doctype, docstatus, company, "%s", "%s"), (self.from_date, self.to_date))
count = count and count[0][0] or 0 count = count and count[0][0] or 0
return count, self.get_html(label, None, count) return count, self.get_html(label, None, count)
def get_new_sum(self, doctype, label, sum_field): def get_new_sum(self, doctype, label, sum_field):
count_sum = webnotes.conn.sql("""select count(*), sum(ifnull(`%s`, 0)) count_sum = webnotes.conn.sql("""select count(*), sum(ifnull(`%s`, 0))
from `tab%s` where docstatus < 2 and company = %s and from `tab%s` where docstatus=1 and company = %s and
date(creation)>=%s and date(creation)<=%s""" % (sum_field, doctype, "%s", date(creation)>=%s and date(creation)<=%s""" % (sum_field, doctype, "%s",
"%s", "%s"), (self.doc.company, self.from_date, self.to_date)) "%s", "%s"), (self.doc.company, self.from_date, self.to_date))
count, total = count_sum and count_sum[0] or (0, 0) count, total = count_sum and count_sum[0] or (0, 0)
@ -449,6 +456,10 @@ class DocType(DocListController):
else: else:
return 0, "No Open Tickets!" return 0, "No Open Tickets!"
def get_scheduler_errors(self):
import webnotes.utils.scheduler
return webnotes.utils.scheduler.get_error_report(self.from_date, self.to_date)
def onload(self): def onload(self):
self.get_next_sending() self.get_next_sending()

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-02-21 14:15:31", "creation": "2013-02-21 14:15:31",
"docstatus": 0, "docstatus": 0,
"modified": "2013-07-05 14:36:13", "modified": "2013-12-16 12:37:43",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -100,11 +100,10 @@
"label": "Add/Remove Recipients" "label": "Add/Remove Recipients"
}, },
{ {
"description": "Check all the items below that you want to send in this digest.",
"doctype": "DocField", "doctype": "DocField",
"fieldname": "select_digest_content", "fieldname": "accounts",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Select Digest Content" "label": "Accounts"
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
@ -178,7 +177,7 @@
"doctype": "DocField", "doctype": "DocField",
"fieldname": "section_break_20", "fieldname": "section_break_20",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"options": "Simple" "label": "Buying & Selling"
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
@ -234,6 +233,12 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "New Sales Orders" "label": "New Sales Orders"
}, },
{
"doctype": "DocField",
"fieldname": "section_break_34",
"fieldtype": "Section Break",
"label": "Inventory & Support"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "stock_module", "fieldname": "stock_module",
@ -258,12 +263,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "New Stock Entries" "label": "New Stock Entries"
}, },
{
"doctype": "DocField",
"fieldname": "section_break_34",
"fieldtype": "Section Break",
"options": "Simple"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "support_module", "fieldname": "support_module",
@ -288,6 +287,12 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "New Communications" "label": "New Communications"
}, },
{
"doctype": "DocField",
"fieldname": "section_break_40",
"fieldtype": "Section Break",
"label": "Projects & System"
},
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "projects_module", "fieldname": "projects_module",
@ -302,7 +307,25 @@
}, },
{ {
"doctype": "DocField", "doctype": "DocField",
"fieldname": "utilities_module", "fieldname": "core_module",
"fieldtype": "Column Break",
"label": "System"
},
{
"doctype": "DocField",
"fieldname": "scheduler_errors",
"fieldtype": "Check",
"label": "Scheduler Failed Events"
},
{
"doctype": "DocField",
"fieldname": "user_specific",
"fieldtype": "Section Break",
"label": "User Specific"
},
{
"doctype": "DocField",
"fieldname": "general",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"label": "General" "label": "General"
}, },
@ -318,6 +341,12 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "To Do List" "label": "To Do List"
}, },
{
"doctype": "DocField",
"fieldname": "stub",
"fieldtype": "Column Break",
"label": "Stub"
},
{ {
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,

View File

@ -233,8 +233,9 @@ items = [
"route": "Report/Scheduler Log", "type": "Link", "icon": "icon-exclamation-sign" }, "route": "Report/Scheduler Log", "type": "Link", "icon": "icon-exclamation-sign" },
] ]
@webnotes.whitelist(allow_roles=["System Manager"]) @webnotes.whitelist()
def get(): def get():
webnotes.only_for("System Manager")
for item in items: for item in items:
if item.get("type")=="Section": if item.get("type")=="Section":
continue continue

View File

@ -175,7 +175,7 @@ def create_feed_and_todo():
def create_email_digest(): def create_email_digest():
from webnotes.profile import get_system_managers from webnotes.profile import get_system_managers
system_managers = get_system_managers() system_managers = get_system_managers(only_name=True)
if not system_managers: if not system_managers:
return return
@ -190,10 +190,23 @@ def create_email_digest():
}) })
for fieldname in edigest.meta.get_fieldnames({"fieldtype": "Check"}): for fieldname in edigest.meta.get_fieldnames({"fieldtype": "Check"}):
edigest.doc.fields[fieldname] = 1 if fieldname != "scheduler_errors":
edigest.doc.fields[fieldname] = 1
edigest.insert() edigest.insert()
# scheduler errors digest
edigest = webnotes.new_bean("Email Digest")
edigest.doc.fields.update({
"name": "Scheduler Errors",
"company": webnotes.conn.get_default("company"),
"frequency": "Daily",
"recipient_list": "\n".join(system_managers),
"scheduler_errors": 1,
"enabled": 1
})
edigest.insert()
def get_fy_details(fy_start_date, fy_end_date): def get_fy_details(fy_start_date, fy_end_date):
start_year = getdate(fy_start_date).year start_year = getdate(fy_start_date).year
if start_year == getdate(fy_end_date).year: if start_year == getdate(fy_end_date).year:

View File

@ -14,3 +14,4 @@ def on_method(bean, method):
if bean.doc.doctype=="Stock Entry" and method in ("on_submit", "on_cancel"): if bean.doc.doctype=="Stock Entry" and method in ("on_submit", "on_cancel"):
update_completed_qty(bean.controller, method) update_completed_qty(bean.controller, method)

View File

@ -34,10 +34,6 @@ def execute_daily():
from core.doctype.notification_count.notification_count import delete_notification_count_for from core.doctype.notification_count.notification_count import delete_notification_count_for
delete_notification_count_for("Event") delete_notification_count_for("Event")
# email digest
from setup.doctype.email_digest.email_digest import send
run_fn(send)
# run recurring invoices # run recurring invoices
from accounts.doctype.sales_invoice.sales_invoice import manage_recurring_invoices from accounts.doctype.sales_invoice.sales_invoice import manage_recurring_invoices
run_fn(manage_recurring_invoices) run_fn(manage_recurring_invoices)
@ -54,8 +50,9 @@ def execute_daily():
from stock.utils import reorder_item from stock.utils import reorder_item
run_fn(reorder_item) run_fn(reorder_item)
# scheduler error # email digest
scheduler.report_errors() from setup.doctype.email_digest.email_digest import send
run_fn(send)
def execute_weekly(): def execute_weekly():
from setup.doctype.backup_manager.backup_manager import take_backups_weekly from setup.doctype.backup_manager.backup_manager import take_backups_weekly

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import webnotes import webnotes
from webnotes.utils import cstr, flt, cint from webnotes.utils import cstr, flt
from webnotes.model.doc import addchild from webnotes.model.doc import addchild
from webnotes.model.bean import getlist from webnotes.model.bean import getlist
from webnotes import msgprint, _ from webnotes import msgprint, _
@ -116,40 +116,29 @@ class DocType(DocListController, WebsiteGenerator):
self.doc.is_pro_applicable = "No" self.doc.is_pro_applicable = "No"
if self.doc.is_pro_applicable == 'Yes' and self.doc.is_stock_item == 'No': if self.doc.is_pro_applicable == 'Yes' and self.doc.is_stock_item == 'No':
msgprint("As Production Order can be made for this Item, then Is Stock Item Should be 'Yes' as we maintain it's stock. Refer Manufacturing and Inventory section.", raise_exception=1) webnotes.throw(_("As Production Order can be made for this item, \
it must be a stock item."))
if self.doc.has_serial_no == 'Yes' and self.doc.is_stock_item == 'No': if self.doc.has_serial_no == 'Yes' and self.doc.is_stock_item == 'No':
msgprint("'Has Serial No' can not be 'Yes' for non-stock item", raise_exception=1) msgprint("'Has Serial No' can not be 'Yes' for non-stock item", raise_exception=1)
def check_for_active_boms(self): def check_for_active_boms(self):
def _check_for_active_boms(field_label): if self.doc.is_active != "Yes" or self.doc.is_purchase_item != "Yes":
if field_label in ['Is Active', 'Is Purchase Item']: bom_mat = webnotes.conn.sql("""select distinct t1.parent
bom_mat = webnotes.conn.sql("""select distinct t1.parent from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent
from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1
and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1 and t2.docstatus = 1 and t1.docstatus =1 """, self.doc.name)
and t2.docstatus = 1 and t1.docstatus =1 """, self.doc.name)
if bom_mat and bom_mat[0][0]:
msgprint(_(field_label) + _(" should be 'Yes'. As Item: ") + self.doc.name +
_(" is present in one or many Active BOMs"), raise_exception=1)
if ((field_label == 'Allow Production Order' if bom_mat and bom_mat[0][0]:
and self.doc.is_sub_contracted_item != 'Yes') webnotes.throw(_("Item must be active and purchase item, \
or (field_label == 'Is Sub Contracted Item' as it is present in one or many Active BOMs"))
and self.doc.is_manufactured_item != 'Yes')):
bom = webnotes.conn.sql("""select name from `tabBOM` where item = %s
and is_active = 1""", (self.doc.name,))
if bom and bom[0][0]:
msgprint(_(field_label) + _(" should be 'Yes'. As Item: ") + self.doc.name +
_(" is present in one or many Active BOMs"), raise_exception=1)
if not cint(self.doc.fields.get("__islocal")): if self.doc.is_manufactured_item != "Yes":
fl = {'is_manufactured_item' :'Allow Bill of Materials', bom = webnotes.conn.sql("""select name from `tabBOM` where item = %s
'is_sub_contracted_item':'Is Sub Contracted Item', and is_active = 1""", (self.doc.name,))
'is_purchase_item' :'Is Purchase Item', if bom and bom[0][0]:
'is_pro_applicable' :'Allow Production Order'} webnotes.throw(_("""Allow Bill of Materials should be 'Yes'. Because one or many \
for d in fl: active BOMs present for this item"""))
if cstr(self.doc.fields.get(d)) != 'Yes':
_check_for_active_boms(fl[d])
def fill_customer_code(self): def fill_customer_code(self):
""" Append all the customer codes and insert into "customer_code" field of item table """ """ Append all the customer codes and insert into "customer_code" field of item table """

View File

@ -2,7 +2,7 @@
{ {
"creation": "2013-05-24 19:29:10", "creation": "2013-05-24 19:29:10",
"docstatus": 0, "docstatus": 0,
"modified": "2013-11-02 19:41:45", "modified": "2013-12-18 10:38:39",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -326,7 +326,7 @@
"fieldname": "schedule_date", "fieldname": "schedule_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Required By", "label": "Required By",
"no_copy": 1, "no_copy": 0,
"oldfieldname": "schedule_date", "oldfieldname": "schedule_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"print_hide": 1, "print_hide": 1,

View File

@ -1 +0,0 @@
Average "age" of an Item in a particular Warehouse based on First-in-first-out (FIFO).

View File

@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@ -1,183 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['stock-ageing'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('Stock Ageing'),
single_column: true
});
new erpnext.StockAgeing(wrapper);
wrapper.appframe.add_module_icon("Stock")
}
wn.require("app/js/stock_grid_report.js");
erpnext.StockAgeing = erpnext.StockGridReport.extend({
init: function(wrapper) {
this._super({
title: wn._("Stock Ageing"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Item Group", "Brand", "Serial No"],
})
},
setup_columns: function() {
this.columns = [
{id: "name", name: wn._("Item"), field: "name", width: 300,
link_formatter: {
open_btn: true,
doctype: '"Item"'
}},
{id: "item_name", name: wn._("Item Name"), field: "item_name",
width: 100, formatter: this.text_formatter},
{id: "description", name: wn._("Description"), field: "description",
width: 200, formatter: this.text_formatter},
{id: "brand", name: wn._("Brand"), field: "brand", width: 100},
{id: "average_age", name: wn._("Average Age"), field: "average_age",
formatter: this.currency_formatter},
{id: "earliest", name: wn._("Earliest"), field: "earliest",
formatter: this.currency_formatter},
{id: "latest", name: wn._("Latest"), field: "latest",
formatter: this.currency_formatter},
{id: "stock_uom", name: "UOM", field: "stock_uom", width: 100},
];
},
filters: [
{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
default_value: "Select Warehouse..."},
{fieldtype:"Select", label: wn._("Brand"), link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val;
}, link_formatter: {filter_input: "brand"}},
{fieldtype:"Select", label: wn._("Plot By"),
options: ["Average Age", "Earliest", "Latest"]},
{fieldtype:"Date", label: wn._("To Date")},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
var me = this;
this._super();
this.trigger_refresh_on_change(["warehouse", "plot_by", "brand"]);
this.show_zero_check();
},
init_filter_values: function() {
this._super();
this.filter_inputs.to_date.val(dateutil.obj_to_user(new Date()));
},
prepare_data: function() {
var me = this;
if(!this.data) {
me._data = wn.report_dump.data["Item"];
me.item_by_name = me.make_name_map(me._data);
}
this.data = [].concat(this._data);
this.serialized_buying_rates = this.get_serialized_buying_rates();
$.each(this.data, function(i, d) {
me.reset_item_values(d);
});
this.prepare_balances();
// filter out brand
this.data = $.map(this.data, function(d) {
return me.apply_filter(d, "brand") ? d : null;
});
// filter out rows with zero values
this.data = $.map(this.data, function(d) {
return me.apply_zero_filter(null, d, null, me) ? d : null;
});
},
prepare_balances: function() {
var me = this;
var to_date = dateutil.str_to_obj(this.to_date);
var data = wn.report_dump.data["Stock Ledger Entry"];
this.item_warehouse = {};
for(var i=0, j=data.length; i<j; i++) {
var sl = data[i];
var posting_date = dateutil.str_to_obj(sl.posting_date);
if(me.is_default("warehouse") ? true : me.warehouse == sl.warehouse) {
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
// call diff to build fifo stack in item_warehouse
var diff = me.get_value_diff(wh, sl, true);
if(posting_date > to_date)
break;
}
}
$.each(me.data, function(i, item) {
var full_fifo_stack = [];
if(me.is_default("warehouse")) {
$.each(me.item_warehouse[item.name] || {}, function(i, wh) {
full_fifo_stack = full_fifo_stack.concat(wh.fifo_stack || [])
});
} else {
full_fifo_stack = me.get_item_warehouse(me.warehouse, item.name).fifo_stack || [];
}
var age_qty = total_qty = 0.0;
var min_age = max_age = null;
$.each(full_fifo_stack, function(i, batch) {
var batch_age = dateutil.get_diff(me.to_date, batch[2]);
age_qty += batch_age * batch[0];
total_qty += batch[0];
max_age = Math.max(max_age, batch_age);
if(min_age===null) min_age=batch_age;
else min_age = Math.min(min_age, batch_age);
});
item.average_age = total_qty.toFixed(2)==0.0 ? 0
: (age_qty / total_qty).toFixed(2);
item.earliest = max_age || 0.0;
item.latest = min_age || 0.0;
});
this.data = this.data.sort(function(a, b) {
var sort_by = me.plot_by.replace(" ", "_").toLowerCase();
return b[sort_by] - a[sort_by];
});
},
get_plot_data: function() {
var data = [];
var me = this;
data.push({
label: me.plot_by,
data: $.map(me.data, function(item, idx) {
return [[idx+1, item[me.plot_by.replace(" ", "_").toLowerCase() ]]]
}),
bars: {show: true},
});
return data.length ? data : false;
},
get_plot_options: function() {
var me = this;
return {
grid: { hoverable: true, clickable: true },
xaxis: {
ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }),
max: 15
},
series: { downsample: { threshold: 1000 } }
}
}
});

View File

@ -1,37 +0,0 @@
[
{
"creation": "2012-09-21 20:15:14",
"docstatus": 0,
"modified": "2013-07-11 14:44:08",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Stock",
"name": "__common__",
"page_name": "stock-ageing",
"standard": "Yes",
"title": "Stock Ageing"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "stock-ageing",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "stock-ageing"
},
{
"doctype": "Page Role",
"role": "Analytics"
},
{
"doctype": "Page Role",
"role": "Material Manager"
}
]

View File

@ -138,7 +138,7 @@ wn.module_page["Stock"] = [
items: [ items: [
{ {
"label":wn._("Stock Ledger"), "label":wn._("Stock Ledger"),
doctype: "Delivery Note", doctype: "Item",
route: "query-report/Stock Ledger" route: "query-report/Stock Ledger"
}, },
{ {
@ -146,12 +146,14 @@ wn.module_page["Stock"] = [
page: "stock-balance" page: "stock-balance"
}, },
{ {
"page":"stock-level", "label":wn._("Stock Projected Qty"),
"label": wn._("Stock Level") doctype: "Item",
route: "query-report/Stock Projected Qty"
}, },
{ {
"page":"stock-ageing", "label":wn._("Stock Ageing"),
"label": wn._("Stock Ageing") doctype: "Item",
route: "query-report/Stock Ageing"
}, },
] ]
}, },

View File

@ -1 +0,0 @@
Stock movement report based on Stock Ledger Entry.

View File

@ -1 +0,0 @@
from __future__ import unicode_literals

View File

@ -1,247 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['stock-ledger'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('Stock Ledger'),
single_column: true
});
new erpnext.StockLedger(wrapper);
wrapper.appframe.add_module_icon("Stock")
}
wn.require("app/js/stock_grid_report.js");
erpnext.StockLedger = erpnext.StockGridReport.extend({
init: function(wrapper) {
this._super({
title: wn._("Stock Ledger"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Item", "Item Group", "Warehouse", "Stock Ledger Entry", "Brand", "Serial No"],
})
},
setup_columns: function() {
this.hide_balance = (this.is_default("item_code") || this.voucher_no) ? true : false;
this.columns = [
{id: "posting_datetime", name: wn._("Posting Date"), field: "posting_datetime", width: 120,
formatter: this.date_formatter},
{id: "item_code", name: wn._("Item Code"), field: "item_code", width: 160,
link_formatter: {
filter_input: "item_code",
open_btn: true,
doctype: '"Item"',
}},
{id: "description", name: wn._("Description"), field: "description", width: 200,
formatter: this.text_formatter},
{id: "warehouse", name: wn._("Warehouse"), field: "warehouse", width: 100,
link_formatter: {filter_input: "warehouse"}},
{id: "brand", name: wn._("Brand"), field: "brand", width: 100},
{id: "stock_uom", name: wn._("UOM"), field: "stock_uom", width: 100},
{id: "qty", name: wn._("Qty"), field: "qty", width: 100,
formatter: this.currency_formatter},
{id: "balance", name: wn._("Balance Qty"), field: "balance", width: 100,
formatter: this.currency_formatter,
hidden: this.hide_balance},
{id: "balance_value", name: wn._("Balance Value"), field: "balance_value", width: 100,
formatter: this.currency_formatter, hidden: this.hide_balance},
{id: "voucher_type", name: wn._("Voucher Type"), field: "voucher_type", width: 120},
{id: "voucher_no", name: wn._("Voucher No"), field: "voucher_no", width: 160,
link_formatter: {
filter_input: "voucher_no",
open_btn: true,
doctype: "dataContext.voucher_type"
}},
];
},
filters: [
{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
default_value: "Select Warehouse...", filter: function(val, item, opts) {
return item.warehouse == val || val == opts.default_value;
}},
{fieldtype:"Link", label: wn._("Item Code"), link:"Item", default_value: "Select Item...",
filter: function(val, item, opts) {
return item.item_code == val || !val;
}},
{fieldtype:"Select", label: "Brand", link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val || item._show;
}, link_formatter: {filter_input: "brand"}},
{fieldtype:"Data", label: wn._("Voucher No"),
filter: function(val, item, opts) {
if(!val) return true;
return (item.voucher_no && item.voucher_no.indexOf(val)!=-1);
}},
{fieldtype:"Date", label: wn._("From Date"), filter: function(val, item) {
return dateutil.str_to_obj(val) <= dateutil.str_to_obj(item.posting_date);
}},
{fieldtype:"Label", label: wn._("To")},
{fieldtype:"Date", label: wn._("To Date"), filter: function(val, item) {
return dateutil.str_to_obj(val) >= dateutil.str_to_obj(item.posting_date);
}},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
var me = this;
this._super();
this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });
this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
},
toggle_enable_brand: function() {
if(!this.filter_inputs.item_code.val()) {
this.filter_inputs.brand.prop("disabled", false);
} else {
this.filter_inputs.brand
.val(this.filter_inputs.brand.get(0).opts.default_value)
.prop("disabled", true);
}
},
init_filter_values: function() {
this._super();
this.filter_inputs.warehouse.get(0).selectedIndex = 0;
},
prepare_data: function() {
var me = this;
if(!this.item_by_name)
this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
var data = wn.report_dump.data["Stock Ledger Entry"];
var out = [];
var opening = {
item_code: "On " + dateutil.str_to_user(this.from_date), qty: 0.0, balance: 0.0,
id:"_opening", _show: true, _style: "font-weight: bold", balance_value: 0.0
}
var total_in = {
item_code: "Total In", qty: 0.0, balance: 0.0, balance_value: 0.0,
id:"_total_in", _show: true, _style: "font-weight: bold"
}
var total_out = {
item_code: "Total Out", qty: 0.0, balance: 0.0, balance_value: 0.0,
id:"_total_out", _show: true, _style: "font-weight: bold"
}
// clear balance
$.each(wn.report_dump.data["Item"], function(i, item) {
item.balance = item.balance_value = 0.0;
});
// initialize warehouse-item map
this.item_warehouse = {};
this.serialized_buying_rates = this.get_serialized_buying_rates();
var from_datetime = dateutil.str_to_obj(me.from_date + " 00:00:00");
var to_datetime = dateutil.str_to_obj(me.to_date + " 23:59:59");
//
for(var i=0, j=data.length; i<j; i++) {
var sl = data[i];
var item = me.item_by_name[sl.item_code]
var wh = me.get_item_warehouse(sl.warehouse, sl.item_code);
sl.description = item.description;
sl.posting_datetime = sl.posting_date + " " + (sl.posting_time || "00:00:00");
sl.brand = item.brand;
var posting_datetime = dateutil.str_to_obj(sl.posting_datetime);
var is_fifo = item.valuation_method ? item.valuation_method=="FIFO"
: sys_defaults.valuation_method=="FIFO";
var value_diff = me.get_value_diff(wh, sl, is_fifo);
// opening, transactions, closing, total in, total out
var before_end = posting_datetime <= to_datetime;
if((!me.is_default("item_code") ? me.apply_filter(sl, "item_code") : true)
&& me.apply_filter(sl, "warehouse") && me.apply_filter(sl, "voucher_no")
&& me.apply_filter(sl, "brand")) {
if(posting_datetime < from_datetime) {
opening.balance += sl.qty;
opening.balance_value += value_diff;
} else if(before_end) {
if(sl.qty > 0) {
total_in.qty += sl.qty;
total_in.balance_value += value_diff;
} else {
total_out.qty += (-1 * sl.qty);
total_out.balance_value += value_diff;
}
}
}
if(!before_end) break;
// apply filters
if(me.apply_filters(sl)) {
out.push(sl);
}
// update balance
if((!me.is_default("warehouse") ? me.apply_filter(sl, "warehouse") : true)) {
sl.balance = me.item_by_name[sl.item_code].balance + sl.qty;
me.item_by_name[sl.item_code].balance = sl.balance;
sl.balance_value = me.item_by_name[sl.item_code].balance_value + value_diff;
me.item_by_name[sl.item_code].balance_value = sl.balance_value;
}
}
if(me.item_code && !me.voucher_no) {
var closing = {
item_code: "On " + dateutil.str_to_user(this.to_date),
balance: (out.length ? out[out.length-1].balance : 0), qty: 0,
balance_value: (out.length ? out[out.length-1].balance_value : 0),
id:"_closing", _show: true, _style: "font-weight: bold"
};
total_out.balance_value = -total_out.balance_value;
var out = [opening].concat(out).concat([total_in, total_out, closing]);
}
this.data = out;
},
get_plot_data: function() {
var data = [];
var me = this;
if(me.hide_balance) return false;
data.push({
label: me.item_code,
data: [[dateutil.str_to_obj(me.from_date).getTime(), me.data[0].balance]]
.concat($.map(me.data, function(col, idx) {
if (col.posting_datetime) {
return [[dateutil.str_to_obj(col.posting_datetime).getTime(), col.balance - col.qty],
[dateutil.str_to_obj(col.posting_datetime).getTime(), col.balance]]
}
return null;
})).concat([
// closing
[dateutil.str_to_obj(me.to_date).getTime(), me.data[me.data.length - 1].balance]
]),
points: {show: true},
lines: {show: true, fill: true},
});
return data;
},
get_plot_options: function() {
return {
grid: { hoverable: true, clickable: true },
xaxis: { mode: "time",
min: dateutil.str_to_obj(this.from_date).getTime(),
max: dateutil.str_to_obj(this.to_date).getTime(),
},
series: { downsample: { threshold: 1000 } }
}
},
get_tooltip_text: function(label, x, y) {
var d = new Date(x);
var date = dateutil.obj_to_user(d) + " " + d.getHours() + ":" + d.getMinutes();
var value = format_number(y);
return value.bold() + " on " + date;
}
});

View File

@ -1,41 +0,0 @@
[
{
"creation": "2012-09-21 20:15:14",
"docstatus": 0,
"modified": "2013-07-11 14:44:19",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Stock",
"name": "__common__",
"page_name": "stock-ledger",
"standard": "Yes",
"title": "Stock Ledger"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "stock-ledger",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "stock-ledger"
},
{
"doctype": "Page Role",
"role": "Analytics"
},
{
"doctype": "Page Role",
"role": "Material Manager"
},
{
"doctype": "Page Role",
"role": "Material User"
}
]

View File

@ -1 +0,0 @@
Stock levels (actual, planned, reserved, ordered) for Items on a particular date.

View File

@ -1,228 +0,0 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.pages['stock-level'].onload = function(wrapper) {
wn.ui.make_app_page({
parent: wrapper,
title: wn._('Stock Level'),
single_column: true
});
new erpnext.StockLevel(wrapper);
wrapper.appframe.add_module_icon("Stock")
;
}
wn.require("app/js/stock_grid_report.js");
erpnext.StockLevel = erpnext.StockGridReport.extend({
init: function(wrapper) {
var me = this;
this._super({
title: wn._("Stock Level"),
page: wrapper,
parent: $(wrapper).find('.layout-main'),
appframe: wrapper.appframe,
doctypes: ["Item", "Warehouse", "Stock Ledger Entry", "Production Order",
"Material Request Item", "Purchase Order Item", "Sales Order Item", "Brand", "Serial No"],
});
this.wrapper.bind("make", function() {
wn.utils.set_footnote(me, me.wrapper.get(0),
"<ul> \
<li style='font-weight: bold;'> \
Projected Qty = Actual Qty + Planned Qty + Requested Qty \
+ Ordered Qty - Reserved Qty </li> \
<ul> \
<li>"+wn._("Actual Qty: Quantity available in the warehouse.") +"</li>"+
"<li>"+wn._("Planned Qty: Quantity, for which, Production Order has been raised,")+
wn._("but is pending to be manufactured.")+ "</li> " +
"<li>"+wn._("Requested Qty: Quantity requested for purchase, but not ordered.") + "</li>" +
"<li>" + wn._("Ordered Qty: Quantity ordered for purchase, but not received.")+ "</li>" +
"<li>" + wn._("Reserved Qty: Quantity ordered for sale, but not delivered.") + "</li>" +
"</ul> \
</ul>");
});
},
setup_columns: function() {
this.columns = [
{id: "item_code", name: wn._("Item Code"), field: "item_code", width: 160,
link_formatter: {
filter_input: "item_code",
open_btn: true,
doctype: '"Item"',
}},
{id: "item_name", name: wn._("Item Name"), field: "item_name", width: 100,
formatter: this.text_formatter},
{id: "description", name: wn._("Description"), field: "description", width: 200,
formatter: this.text_formatter},
{id: "brand", name: wn._("Brand"), field: "brand", width: 100,
link_formatter: {filter_input: "brand"}},
{id: "warehouse", name: wn._("Warehouse"), field: "warehouse", width: 100,
link_formatter: {filter_input: "warehouse"}},
{id: "uom", name: wn._("UOM"), field: "uom", width: 60},
{id: "actual_qty", name: wn._("Actual Qty"),
field: "actual_qty", width: 80, formatter: this.currency_formatter},
{id: "planned_qty", name: wn._("Planned Qty"),
field: "planned_qty", width: 80, formatter: this.currency_formatter},
{id: "requested_qty", name: wn._("Requested Qty"),
field: "requested_qty", width: 80, formatter: this.currency_formatter},
{id: "ordered_qty", name: wn._("Ordered Qty"),
field: "ordered_qty", width: 80, formatter: this.currency_formatter},
{id: "reserved_qty", name: wn._("Reserved Qty"),
field: "reserved_qty", width: 80, formatter: this.currency_formatter},
{id: "projected_qty", name: wn._("Projected Qty"),
field: "projected_qty", width: 80, formatter: this.currency_formatter},
{id: "re_order_level", name: wn._("Re-Order Level"),
field: "re_order_level", width: 80, formatter: this.currency_formatter},
{id: "re_order_qty", name: wn._("Re-Order Qty"),
field: "re_order_qty", width: 80, formatter: this.currency_formatter},
];
},
filters: [
{fieldtype:"Link", label: wn._("Item Code"), link:"Item", default_value: "Select Item...",
filter: function(val, item, opts) {
return item.item_code == val || !val;
}},
{fieldtype:"Select", label: wn._("Warehouse"), link:"Warehouse",
default_value: "Select Warehouse...", filter: function(val, item, opts) {
return item.warehouse == val || val == opts.default_value;
}},
{fieldtype:"Select", label: wn._("Brand"), link:"Brand",
default_value: "Select Brand...", filter: function(val, item, opts) {
return val == opts.default_value || item.brand == val;
}},
{fieldtype:"Button", label: wn._("Refresh"), icon:"icon-refresh icon-white"},
{fieldtype:"Button", label: wn._("Reset Filters")}
],
setup_filters: function() {
var me = this;
this._super();
this.wrapper.bind("apply_filters_from_route", function() { me.toggle_enable_brand(); });
this.filter_inputs.item_code.change(function() { me.toggle_enable_brand(); });
this.trigger_refresh_on_change(["item_code", "warehouse", "brand"]);
},
toggle_enable_brand: function() {
if(!this.filter_inputs.item_code.val()) {
this.filter_inputs.brand.prop("disabled", false);
} else {
this.filter_inputs.brand
.val(this.filter_inputs.brand.get(0).opts.default_value)
.prop("disabled", true);
}
},
init_filter_values: function() {
this._super();
this.filter_inputs.warehouse.get(0).selectedIndex = 0;
},
prepare_data: function() {
var me = this;
if(!this._data) {
this._data = [];
this.item_warehouse_map = {};
this.item_by_name = this.make_name_map(wn.report_dump.data["Item"]);
this.calculate_quantities();
}
this.data = [].concat(this._data);
this.data = $.map(this.data, function(d) {
return me.apply_filters(d) ? d : null;
});
this.calculate_total();
},
calculate_quantities: function() {
var me = this;
$.each([
["Stock Ledger Entry", "actual_qty"],
["Production Order", "planned_qty"],
["Material Request Item", "requested_qty"],
["Purchase Order Item", "ordered_qty"],
["Sales Order Item", "reserved_qty"]],
function(i, v) {
$.each(wn.report_dump.data[v[0]], function(i, item) {
var row = me.get_row(item.item_code, item.warehouse);
row[v[1]] += flt(item.qty);
});
}
);
// sort by item, warehouse
this._data = $.map(Object.keys(this.item_warehouse_map).sort(), function(key) {
return me.item_warehouse_map[key];
});
// calculate projected qty
$.each(this._data, function(i, row) {
row.projected_qty = row.actual_qty + row.planned_qty + row.requested_qty
+ row.ordered_qty - row.reserved_qty;
});
// filter out rows with zero values
this._data = $.map(this._data, function(d) {
return me.apply_zero_filter(null, d, null, me) ? d : null;
});
},
get_row: function(item_code, warehouse) {
var key = item_code + ":" + warehouse;
if(!this.item_warehouse_map[key]) {
var item = this.item_by_name[item_code];
var row = {
item_code: item_code,
warehouse: warehouse,
description: item.description,
brand: item.brand,
item_name: item.item_name || item.name,
uom: item.stock_uom,
id: key,
}
this.reset_item_values(row);
row["re_order_level"] = item.re_order_level
row["re_order_qty"] = item.re_order_qty
this.item_warehouse_map[key] = row;
}
return this.item_warehouse_map[key];
},
calculate_total: function() {
var me = this;
// show total if a specific item is selected and warehouse is not filtered
if(this.is_default("warehouse") && !this.is_default("item_code")) {
var total = {
id: "_total",
item_code: "Total",
_style: "font-weight: bold",
_show: true
};
this.reset_item_values(total);
$.each(this.data, function(i, row) {
$.each(me.columns, function(i, col) {
if (col.formatter==me.currency_formatter) {
total[col.id] += row[col.id];
}
});
});
this.data = this.data.concat([total]);
}
}
})

View File

@ -1,37 +0,0 @@
[
{
"creation": "2012-12-31 10:52:14",
"docstatus": 0,
"modified": "2013-07-11 14:44:21",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Page",
"icon": "icon-table",
"module": "Stock",
"name": "__common__",
"page_name": "stock-level",
"standard": "Yes",
"title": "Stock Level"
},
{
"doctype": "Page Role",
"name": "__common__",
"parent": "stock-level",
"parentfield": "roles",
"parenttype": "Page"
},
{
"doctype": "Page",
"name": "stock-level"
},
{
"doctype": "Page Role",
"role": "Material Manager"
},
{
"doctype": "Page Role",
"role": "Analytics"
}
]

View File

@ -0,0 +1,40 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.query_reports["Stock Ageing"] = {
"filters": [
{
"fieldname":"company",
"label": wn._("Company"),
"fieldtype": "Link",
"options": "Company",
"default": wn.defaults.get_user_default("company"),
"reqd": 1
},
{
"fieldname":"to_date",
"label": wn._("To Date"),
"fieldtype": "Date",
"default": wn.datetime.get_today(),
"reqd": 1
},
{
"fieldname":"warehouse",
"label": wn._("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse"
},
{
"fieldname":"item_code",
"label": wn._("Item"),
"fieldtype": "Link",
"options": "Item"
},
{
"fieldname":"brand",
"label": wn._("Brand"),
"fieldtype": "Link",
"options": "Brand"
}
]
}

View File

@ -0,0 +1,94 @@
# 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 webnotes
from webnotes.utils import date_diff
def execute(filters=None):
columns = get_columns()
item_details = get_fifo_queue(filters)
to_date = filters["to_date"]
data = []
for item, item_dict in item_details.items():
fifo_queue = item_dict["fifo_queue"]
details = item_dict["details"]
if not fifo_queue: continue
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
latest_age = date_diff(to_date, fifo_queue[-1][1])
data.append([item, details.item_name, details.description, details.item_group,
details.brand, average_age, earliest_age, latest_age, details.stock_uom])
return columns, data
def get_average_age(fifo_queue, to_date):
batch_age = age_qty = total_qty = 0.0
for batch in fifo_queue:
batch_age = date_diff(to_date, batch[1])
age_qty += batch_age * batch[0]
total_qty += batch[0]
return (age_qty / total_qty) if total_qty else 0.0
def get_columns():
return ["Item Code:Link/Item:100", "Item Name::100", "Description::200",
"Item Group:Link/Item Group:100", "Brand:Link/Brand:100", "Average Age:Float:100",
"Earliest:Int:80", "Latest:Int:80", "UOM:Link/UOM:100"]
def get_fifo_queue(filters):
item_details = {}
for d in get_stock_ledger_entries(filters):
item_details.setdefault(d.name, {"details": d, "fifo_queue": []})
fifo_queue = item_details[d.name]["fifo_queue"]
if d.actual_qty > 0:
fifo_queue.append([d.actual_qty, d.posting_date])
else:
qty_to_pop = abs(d.actual_qty)
while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None]
if 0 < batch[0] <= qty_to_pop:
# if batch qty > 0
# not enough or exactly same qty in current batch, clear batch
qty_to_pop -= batch[0]
fifo_queue.pop(0)
else:
# all from current batch
batch[0] -= qty_to_pop
qty_to_pop = 0
return item_details
def get_stock_ledger_entries(filters):
return webnotes.conn.sql("""select
item.name, item.item_name, item_group, brand, description, item.stock_uom,
actual_qty, posting_date
from `tabStock Ledger Entry` sle,
(select name, item_name, description, stock_uom, brand
from `tabItem` {item_conditions}) item
where item_code = item.name and
company = %(company)s and
posting_date <= %(to_date)s
{sle_conditions}
order by posting_date, posting_time, sle.name"""\
.format(item_conditions=get_item_conditions(filters),
sle_conditions=get_sle_conditions(filters)), filters, as_dict=True)
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
conditions.append("item_code=%(item_code)s")
if filters.get("brand"):
conditions.append("brand=%(brand)s")
return "where {}".format(" and ".join(conditions)) if conditions else ""
def get_sle_conditions(filters):
conditions = []
if filters.get("warehouse"):
conditions.append("warehouse=%(warehouse)s")
return "and {}".format(" and ".join(conditions)) if conditions else ""

View File

@ -0,0 +1,21 @@
[
{
"creation": "2013-12-02 17:09:31",
"docstatus": 0,
"modified": "2013-12-02 17:09:31",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"ref_doctype": "Item",
"report_name": "Stock Ageing",
"report_type": "Script Report"
},
{
"doctype": "Report",
"name": "Stock Ageing"
}
]

View File

@ -0,0 +1,33 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
wn.query_reports["Stock Projected Qty"] = {
"filters": [
{
"fieldname":"company",
"label": wn._("Company"),
"fieldtype": "Link",
"options": "Company",
"default": wn.defaults.get_user_default("company"),
"reqd": 1
},
{
"fieldname":"warehouse",
"label": wn._("Warehouse"),
"fieldtype": "Link",
"options": "Warehouse"
},
{
"fieldname":"item_code",
"label": wn._("Item"),
"fieldtype": "Link",
"options": "Item"
},
{
"fieldname":"brand",
"label": wn._("Brand"),
"fieldtype": "Link",
"options": "Brand"
}
]
}

View File

@ -0,0 +1,44 @@
# 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 webnotes
def execute(filters=None):
columns = get_columns()
data = webnotes.conn.sql("""select
item.name, item.item_name, description, item_group, brand, warehouse, item.stock_uom,
actual_qty, planned_qty, indented_qty, ordered_qty, reserved_qty,
projected_qty, item.re_order_level, item.re_order_qty
from `tabBin` bin,
(select name, company from tabWarehouse
where company=%(company)s {warehouse_conditions}) wh,
(select name, item_name, description, stock_uom, item_group,
brand, re_order_level, re_order_qty
from `tabItem` {item_conditions}) item
where item_code = item.name and warehouse = wh.name
order by item.name, wh.name"""\
.format(item_conditions=get_item_conditions(filters),
warehouse_conditions=get_warehouse_conditions(filters)), filters, debug=1)
return columns, data
def get_columns():
return ["Item Code:Link/Item:140", "Item Name::100", "Description::200",
"Brand:Link/Brand:100", "Warehouse:Link/Warehouse:120", "UOM:Link/UOM:100",
"Actual Qty:Float:100", "Planned Qty:Float:100", "Requested Qty:Float:110",
"Ordered Qty:Float:100", "Reserved Qty:Float:100", "Projected Qty:Float:100",
"Reorder Level:Float:100", "Reorder Qty:Float:100"]
def get_item_conditions(filters):
conditions = []
if filters.get("item_code"):
conditions.append("name=%(item_code)s")
if filters.get("brand"):
conditions.append("brand=%(brand)s")
return "where {}".format(" and ".join(conditions)) if conditions else ""
def get_warehouse_conditions(filters):
return " and name=%(warehouse)s" if filters.get("warehouse") else ""

View File

@ -0,0 +1,22 @@
[
{
"creation": "2013-12-04 18:21:56",
"docstatus": 0,
"modified": "2013-12-04 18:21:56",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"add_total_row": 1,
"doctype": "Report",
"is_standard": "Yes",
"name": "__common__",
"ref_doctype": "Item",
"report_name": "Stock Projected Qty",
"report_type": "Script Report"
},
{
"doctype": "Report",
"name": "Stock Projected Qty"
}
]