Merge branch 'develop'

This commit is contained in:
Pratik Vyas 2013-12-17 18:03:05 +05:30
commit a1ae0270e3
51 changed files with 800 additions and 1464 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

@ -7,11 +7,9 @@ import webnotes
from webnotes.utils import cstr, flt from webnotes.utils import cstr, flt
from webnotes.model.utils import getlist from webnotes.model.utils import getlist
from webnotes import msgprint, _ from webnotes import msgprint, _
from buying.utils import get_last_purchase_details from buying.utils import get_last_purchase_details
from controllers.buying_controller import BuyingController from controllers.buying_controller import BuyingController
class DocType(BuyingController): class DocType(BuyingController):
def __init__(self, doc, doclist=None): def __init__(self, doc, doclist=None):
self.doc = doc self.doc = doc
@ -38,13 +36,13 @@ class DocType(BuyingController):
if flt(d.conversion_factor): if flt(d.conversion_factor):
last_purchase_rate = flt(d.purchase_rate) / flt(d.conversion_factor) last_purchase_rate = flt(d.purchase_rate) / flt(d.conversion_factor)
else: else:
msgprint(_("Row ") + cstr(d.idx) + ": " + webnotes.throw(_("Row ") + cstr(d.idx) + ": " +
_("UOM Conversion Factor is mandatory"), raise_exception=1) _("UOM Conversion Factor is mandatory"))
# update last purchsae rate # update last purchsae rate
if last_purchase_rate: if last_purchase_rate:
webnotes.conn.sql("update `tabItem` set last_purchase_rate = %s where name = %s", webnotes.conn.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
(flt(last_purchase_rate),d.item_code)) (flt(last_purchase_rate), d.item_code))
def get_last_purchase_rate(self, obj): def get_last_purchase_rate(self, obj):
"""get last purchase rates for all items""" """get last purchase rates for all items"""
@ -76,11 +74,11 @@ class DocType(BuyingController):
for d in getlist( obj.doclist, obj.fname): for d in getlist( obj.doclist, obj.fname):
# validation for valid qty # validation for valid qty
if flt(d.qty) < 0 or (d.parenttype != 'Purchase Receipt' and not flt(d.qty)): if flt(d.qty) < 0 or (d.parenttype != 'Purchase Receipt' and not flt(d.qty)):
msgprint("Please enter valid qty for item %s" % cstr(d.item_code)) webnotes.throw("Please enter valid qty for item %s" % cstr(d.item_code))
raise Exception
# udpate with latest quantities # udpate with latest quantities
bin = webnotes.conn.sql("select projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1) bin = webnotes.conn.sql("""select projected_qty from `tabBin` where
item_code = %s and warehouse = %s""", (d.item_code, d.warehouse), as_dict=1)
f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0} f_lst ={'projected_qty': bin and flt(bin[0]['projected_qty']) or 0, 'ordered_qty': 0, 'received_qty' : 0}
if d.doctype == 'Purchase Receipt Item': if d.doctype == 'Purchase Receipt Item':
@ -89,48 +87,50 @@ class DocType(BuyingController):
if d.fields.has_key(x): if d.fields.has_key(x):
d.fields[x] = f_lst[x] d.fields[x] = f_lst[x]
item = webnotes.conn.sql("select is_stock_item, is_purchase_item, is_sub_contracted_item, end_of_life from tabItem where name=%s", item = webnotes.conn.sql("""select is_stock_item, is_purchase_item,
d.item_code) is_sub_contracted_item, end_of_life from `tabItem` where name=%s""", d.item_code)
if not item: if not item:
msgprint("Item %s does not exist in Item Master." % cstr(d.item_code), raise_exception=True) webnotes.throw("Item %s does not exist in Item Master." % cstr(d.item_code))
from stock.utils import validate_end_of_life from stock.utils import validate_end_of_life
validate_end_of_life(d.item_code, item[0][3]) validate_end_of_life(d.item_code, item[0][3])
# validate stock item # validate stock item
if item[0][0]=='Yes' and d.qty and not d.warehouse: if item[0][0]=='Yes' and d.qty and not d.warehouse:
msgprint("Warehouse is mandatory for %s, since it is a stock item" % webnotes.throw("Warehouse is mandatory for %s, since it is a stock item" % d.item_code)
d.item_code, raise_exception=1)
# validate purchase item # validate purchase item
if item[0][1] != 'Yes' and item[0][2] != 'Yes': if item[0][1] != 'Yes' and item[0][2] != 'Yes':
msgprint("Item %s is not a purchase item or sub-contracted item. Please check" % (d.item_code), raise_exception=True) webnotes.throw("Item %s is not a purchase item or sub-contracted item. Please check" % (d.item_code))
# list criteria that should not repeat if item is stock item # list criteria that should not repeat if item is stock item
e = [d.schedule_date, d.item_code, d.description, d.warehouse, d.uom, d.fields.has_key('prevdoc_docname') and d.prevdoc_docname or '', d.fields.has_key('prevdoc_detail_docname') and d.prevdoc_detail_docname or '', d.fields.has_key('batch_no') and d.batch_no or ''] e = [d.schedule_date, d.item_code, d.description, d.warehouse, d.uom,
d.fields.has_key('prevdoc_docname') and d.prevdoc_docname or d.fields.has_key('sales_order_no') and d.sales_order_no or '',
d.fields.has_key('prevdoc_detail_docname') and d.prevdoc_detail_docname or '',
d.fields.has_key('batch_no') and d.batch_no or '']
# if is not stock item # if is not stock item
f = [d.schedule_date, d.item_code, d.description] f = [d.schedule_date, d.item_code, d.description]
ch = webnotes.conn.sql("select is_stock_item from `tabItem` where name = '%s'"%d.item_code) ch = webnotes.conn.sql("""select is_stock_item from `tabItem` where name = %s""", d.item_code)
if ch and ch[0][0] == 'Yes': if ch and ch[0][0] == 'Yes':
# check for same items # check for same items
if e in check_list: if e in check_list:
msgprint("""Item %s has been entered more than once with same description, schedule date, warehouse and uom.\n webnotes.throw("""Item %s has been entered more than once with same description, schedule date, warehouse and uom.\n
Please change any of the field value to enter the item twice""" % d.item_code, raise_exception = 1) Please change any of the field value to enter the item twice""" % d.item_code)
else: else:
check_list.append(e) check_list.append(e)
elif ch and ch[0][0] == 'No': elif ch and ch[0][0] == 'No':
# check for same items # check for same items
if f in chk_dupl_itm: if f in chk_dupl_itm:
msgprint("""Item %s has been entered more than once with same description, schedule date.\n webnotes.throw("""Item %s has been entered more than once with same description, schedule date.\n
Please change any of the field value to enter the item twice.""" % d.item_code, raise_exception = 1) Please change any of the field value to enter the item twice.""" % d.item_code)
else: else:
chk_dupl_itm.append(f) chk_dupl_itm.append(f)
def get_qty(self,curr_doctype,ref_tab_fname,ref_tab_dn,ref_doc_tname, transaction, curr_parent_name): def get_qty(self, curr_doctype, ref_tab_fname, ref_tab_dn, ref_doc_tname, transaction, curr_parent_name):
# Get total Quantities of current doctype (eg. PR) except for qty of this transaction # Get total Quantities of current doctype (eg. PR) except for qty of this transaction
#------------------------------ #------------------------------
# please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same . # please check as UOM changes from Material Request - Purchase Order ,so doing following else uom should be same .
@ -138,35 +138,37 @@ class DocType(BuyingController):
# but if in Material Request uom KG it can change in PO # but if in Material Request uom KG it can change in PO
get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty' get_qty = (transaction == 'Material Request - Purchase Order') and 'qty * conversion_factor' or 'qty'
qty = webnotes.conn.sql("select sum(%s) from `tab%s` where %s = '%s' and docstatus = 1 and parent != '%s'"% ( get_qty, curr_doctype, ref_tab_fname, ref_tab_dn, curr_parent_name)) qty = webnotes.conn.sql("""select sum(%s) from `tab%s` where %s = %s and
docstatus = 1 and parent != %s""" % (get_qty, curr_doctype, ref_tab_fname, '%s', '%s'),
(ref_tab_dn, curr_parent_name))
qty = qty and flt(qty[0][0]) or 0 qty = qty and flt(qty[0][0]) or 0
# get total qty of ref doctype # get total qty of ref doctype
#-------------------- #--------------------
max_qty = webnotes.conn.sql("select qty from `tab%s` where name = '%s' and docstatus = 1"% (ref_doc_tname, ref_tab_dn)) max_qty = webnotes.conn.sql("""select qty from `tab%s` where name = %s
and docstatus = 1""" % (ref_doc_tname, '%s'), ref_tab_dn)
max_qty = max_qty and flt(max_qty[0][0]) or 0 max_qty = max_qty and flt(max_qty[0][0]) or 0
return cstr(qty)+'~~~'+cstr(max_qty) return cstr(qty)+'~~~'+cstr(max_qty)
def check_for_stopped_status(self, doctype, docname): def check_for_stopped_status(self, doctype, docname):
stopped = webnotes.conn.sql("select name from `tab%s` where name = '%s' and status = 'Stopped'" % stopped = webnotes.conn.sql("""select name from `tab%s` where name = %s and
( doctype, docname)) status = 'Stopped'""" % (doctype, '%s'), docname)
if stopped: if stopped:
msgprint("One cannot do any transaction against %s : %s, it's status is 'Stopped'" % webnotes.throw("One cannot do any transaction against %s : %s, it's status is 'Stopped'" %
( doctype, docname), raise_exception=1) (doctype, docname))
def check_docstatus(self, check, doctype, docname , detail_doctype = ''): def check_docstatus(self, check, doctype, docname, detail_doctype = ''):
if check == 'Next': if check == 'Next':
submitted = webnotes.conn.sql("""select t1.name from `tab%s` t1,`tab%s` t2 submitted = webnotes.conn.sql("""select t1.name from `tab%s` t1,`tab%s` t2
where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""" where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1"""
% (doctype, detail_doctype, '%s'), docname) % (doctype, detail_doctype, '%s'), docname)
if submitted: if submitted:
msgprint(cstr(doctype) + ": " + cstr(submitted[0][0]) webnotes.throw(cstr(doctype) + ": " + cstr(submitted[0][0])
+ _(" has already been submitted."), raise_exception=1) + _("has already been submitted."))
if check == 'Previous': if check == 'Previous':
submitted = webnotes.conn.sql("""select name from `tab%s` submitted = webnotes.conn.sql("""select name from `tab%s`
where docstatus = 1 and name = %s"""% (doctype, '%s'), docname) where docstatus = 1 and name = %s""" % (doctype, '%s'), docname)
if not submitted: if not submitted:
msgprint(cstr(doctype) + ": " + cstr(submitted[0][0]) webnotes.throw(cstr(doctype) + ": " + cstr(submitted[0][0]) + _("not submitted"))
+ _(" not submitted"), raise_exception=1)

View File

@ -1,6 +1,6 @@
{ {
"app_name": "ERPNext", "app_name": "ERPNext",
"app_version": "3.2.3", "app_version": "3.3.0",
"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.0"
} }

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

@ -9,7 +9,6 @@ from webnotes.model.bean import getlist
from webnotes.model.code import get_obj from webnotes.model.code import get_obj
from webnotes import msgprint, _ from webnotes import msgprint, _
class DocType: class DocType:
def __init__(self, doc, doclist=[]): def __init__(self, doc, doclist=[]):
self.doc = doc self.doc = doc
@ -47,7 +46,7 @@ class DocType:
def validate_company(self): def validate_company(self):
if not self.doc.company: if not self.doc.company:
msgprint("Please enter Company", raise_exception=1) webnotes.throw(_("Please enter Company"))
def get_open_sales_orders(self): def get_open_sales_orders(self):
""" Pull sales orders which are pending to deliver based on criteria selected""" """ Pull sales orders which are pending to deliver based on criteria selected"""
@ -106,7 +105,7 @@ class DocType:
def get_items(self): def get_items(self):
so_list = filter(None, [d.sales_order for d in getlist(self.doclist, 'pp_so_details')]) so_list = filter(None, [d.sales_order for d in getlist(self.doclist, 'pp_so_details')])
if not so_list: if not so_list:
msgprint("Please enter sales order in the above table") msgprint(_("Please enter sales order in the above table"))
return [] return []
items = webnotes.conn.sql("""select distinct parent, item_code, reserved_warehouse, items = webnotes.conn.sql("""select distinct parent, item_code, reserved_warehouse,
@ -155,21 +154,21 @@ class DocType:
for d in getlist(self.doclist, 'pp_details'): for d in getlist(self.doclist, 'pp_details'):
self.validate_bom_no(d) self.validate_bom_no(d)
if not flt(d.planned_qty): if not flt(d.planned_qty):
msgprint("Please Enter Planned Qty for item: %s at row no: %s" % webnotes.throw("Please Enter Planned Qty for item: %s at row no: %s" %
(d.item_code, d.idx), raise_exception=1) (d.item_code, d.idx))
def validate_bom_no(self, d): def validate_bom_no(self, d):
if not d.bom_no: if not d.bom_no:
msgprint("Please enter bom no for item: %s at row no: %s" % webnotes.throw("Please enter bom no for item: %s at row no: %s" %
(d.item_code, d.idx), raise_exception=1) (d.item_code, d.idx))
else: else:
bom = webnotes.conn.sql("""select name from `tabBOM` where name = %s and item = %s bom = webnotes.conn.sql("""select name from `tabBOM` where name = %s and item = %s
and docstatus = 1 and is_active = 1""", and docstatus = 1 and is_active = 1""",
(d.bom_no, d.item_code), as_dict = 1) (d.bom_no, d.item_code), as_dict = 1)
if not bom: if not bom:
msgprint("""Incorrect BOM No: %s entered for item: %s at row no: %s webnotes.throw("""Incorrect BOM No: %s entered for item: %s at row no: %s
May be BOM is inactive or for other item or does not exists in the system""" % May be BOM is inactive or for other item or does not exists in the system""" %
(d.bom_no, d.item_doce, d.idx), raise_exception=1) (d.bom_no, d.item_doce, d.idx))
def raise_production_order(self): def raise_production_order(self):
"""It will raise production order (Draft) for all distinct FG items""" """It will raise production order (Draft) for all distinct FG items"""
@ -183,16 +182,20 @@ class DocType:
if pro: if pro:
pro = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \ pro = ["""<a href="#Form/Production Order/%s" target="_blank">%s</a>""" % \
(p, p) for p in pro] (p, p) for p in pro]
msgprint("Production Order(s) created:\n\n" + '\n'.join(pro)) msgprint(_("Production Order(s) created:\n\n") + '\n'.join(pro))
else : else :
msgprint("No Production Order created.") msgprint(_("No Production Order created."))
def get_distinct_items_and_boms(self): def get_distinct_items_and_boms(self):
""" Club similar BOM and item for processing""" """ Club similar BOM and item for processing
bom_dict {
bom_no: ['sales_order', 'qty']
}
"""
item_dict, bom_dict = {}, {} item_dict, bom_dict = {}, {}
for d in self.doclist.get({"parentfield": "pp_details"}): for d in self.doclist.get({"parentfield": "pp_details"}):
bom_dict[d.bom_no] = bom_dict.get(d.bom_no, 0) + flt(d.planned_qty) bom_dict.setdefault(d.bom_no, []).append([d.sales_order, flt(d.planned_qty)])
item_dict[(d.item_code, d.sales_order, d.warehouse)] = { item_dict[(d.item_code, d.sales_order, d.warehouse)] = {
"production_item" : d.item_code, "production_item" : d.item_code,
"sales_order" : d.sales_order, "sales_order" : d.sales_order,
@ -241,48 +244,60 @@ class DocType:
"item_code": [qty_required, description, stock_uom, min_order_qty] "item_code": [qty_required, description, stock_uom, min_order_qty]
} }
""" """
for bom in bom_dict: bom_wise_item_details = {}
item_list = []
for bom, so_wise_qty in bom_dict.items():
if self.doc.use_multi_level_bom: if self.doc.use_multi_level_bom:
# get all raw materials with sub assembly childs # get all raw materials with sub assembly childs
fl_bom_items = webnotes.conn.sql("""select fb.item_code, for d in webnotes.conn.sql("""select fb.item_code,
ifnull(sum(fb.qty_consumed_per_unit), 0)*%s as qty, ifnull(sum(fb.qty_consumed_per_unit), 0) as qty,
fb.description, fb.stock_uom, it.min_order_qty fb.description, fb.stock_uom, it.min_order_qty
from `tabBOM Explosion Item` fb,`tabItem` it from `tabBOM Explosion Item` fb,`tabItem` it
where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No'
and ifnull(it.is_sub_contracted_item, 'No') = 'No' and ifnull(it.is_sub_contracted_item, 'No') = 'No'
and fb.docstatus<2 and fb.parent=%s and fb.docstatus<2 and fb.parent=%s
group by item_code, stock_uom""", (flt(bom_dict[bom]), bom)) group by item_code, stock_uom""", bom, as_dict=1):
bom_wise_item_details.setdefault(d.item_code, d)
else: else:
# Get all raw materials considering SA items as raw materials, # Get all raw materials considering SA items as raw materials,
# so no childs of SA items # so no childs of SA items
fl_bom_items = webnotes.conn.sql("""select bom_item.item_code, for d in webnotes.conn.sql("""select bom_item.item_code,
ifnull(sum(bom_item.qty_consumed_per_unit), 0) * %s, ifnull(sum(bom_item.qty_consumed_per_unit), 0) as qty,
bom_item.description, bom_item.stock_uom, item.min_order_qty bom_item.description, bom_item.stock_uom, item.min_order_qty
from `tabBOM Item` bom_item, tabItem item from `tabBOM Item` bom_item, tabItem item
where bom_item.parent = %s and bom_item.docstatus < 2 where bom_item.parent = %s and bom_item.docstatus < 2
and bom_item.item_code = item.name and bom_item.item_code = item.name
group by item_code""", (flt(bom_dict[bom]), bom)) group by item_code""", bom, as_dict=1):
self.make_items_dict(fl_bom_items) bom_wise_item_details.setdefault(d.item_code, d)
for item, item_details in bom_wise_item_details.items():
for so_qty in so_wise_qty:
item_list.append([item, flt(item_details.qty) * so_qty[1], item_details.description,
item_details.stock_uom, item_details.min_order_qty, so_qty[0]])
self.make_items_dict(item_list)
def make_items_dict(self, item_list): def make_items_dict(self, item_list):
for i in item_list: for i in item_list:
self.item_dict[i[0]] = [(flt(self.item_dict.get(i[0], [0])[0]) + flt(i[1])), self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]])
i[2], i[3], i[4]]
def get_csv(self): def get_csv(self):
item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse', item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse',
'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']] 'Quantity Requested for Purchase', 'Ordered Qty', 'Actual Qty']]
for d in self.item_dict: for item in self.item_dict:
item_list.append([d, self.item_dict[d][1], self.item_dict[d][2], self.item_dict[d][0]]) total_qty = sum([flt(d[0]) for d in self.item_dict[item]])
item_qty= webnotes.conn.sql("""select warehouse, indented_qty, ordered_qty, actual_qty for item_details in self.item_dict[item]:
from `tabBin` where item_code = %s""", d) item_list.append([item, item_details[1], item_details[2], item_details[0]])
i_qty, o_qty, a_qty = 0, 0, 0 item_qty = webnotes.conn.sql("""select warehouse, indented_qty, ordered_qty, actual_qty
for w in item_qty: from `tabBin` where item_code = %s""", item, as_dict=1)
i_qty, o_qty, a_qty = i_qty + flt(w[1]), o_qty + flt(w[2]), a_qty + flt(w[3]) i_qty, o_qty, a_qty = 0, 0, 0
item_list.append(['', '', '', '', w[0], flt(w[1]), flt(w[2]), flt(w[3])]) for w in item_qty:
if item_qty: i_qty, o_qty, a_qty = i_qty + flt(w.indented_qty), o_qty + flt(w.ordered_qty), a_qty + flt(w.actual_qty)
item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty]) item_list.append(['', '', '', '', w.warehouse, flt(w.indented_qty),
flt(w.ordered_qty), flt(w.actual_qty)])
if item_qty:
item_list.append(['', '', '', '', 'Total', i_qty, o_qty, a_qty])
return item_list return item_list
@ -293,31 +308,49 @@ class DocType:
""" """
self.validate_data() self.validate_data()
if not self.doc.purchase_request_for_warehouse: if not self.doc.purchase_request_for_warehouse:
webnotes.msgprint("Please enter Warehouse for which Material Request will be raised", webnotes.throw(_("Please enter Warehouse for which Material Request will be raised"))
raise_exception=1)
bom_dict = self.get_distinct_items_and_boms()[0] bom_dict = self.get_distinct_items_and_boms()[0]
self.get_raw_materials(bom_dict) self.get_raw_materials(bom_dict)
if not self.item_dict: if self.item_dict:
return self.insert_purchase_request()
def get_requested_items(self):
item_projected_qty = self.get_projected_qty() item_projected_qty = self.get_projected_qty()
from accounts.utils import get_fiscal_year
fiscal_year = get_fiscal_year(nowdate())[0]
items_to_be_requested = webnotes._dict() items_to_be_requested = webnotes._dict()
for item in self.item_dict:
if flt(self.item_dict[item][0]) > item_projected_qty.get(item, 0): for item, so_item_qty in self.item_dict.items():
requested_qty = 0
total_qty = sum([flt(d[0]) for d in so_item_qty])
if total_qty > item_projected_qty.get(item, 0):
# shortage # shortage
requested_qty = flt(self.item_dict[item][0]) - item_projected_qty.get(item, 0) requested_qty = total_qty - item_projected_qty.get(item, 0)
# comsider minimum order qty # consider minimum order qty
requested_qty = requested_qty > flt(self.item_dict[item][3]) and \ requested_qty = requested_qty > flt(so_item_qty[0][3]) and \
requested_qty or flt(self.item_dict[item][3]) requested_qty or flt(so_item_qty[0][3])
items_to_be_requested[item] = requested_qty
# distribute requested qty SO wise
self.insert_purchase_request(items_to_be_requested, fiscal_year) for item_details in so_item_qty:
if requested_qty:
sales_order = item_details[4] or "No Sales Order"
if requested_qty <= item_details[0]:
adjusted_qty = requested_qty
else:
adjusted_qty = item_details[0]
items_to_be_requested.setdefault(item, {}).setdefault(sales_order, 0)
items_to_be_requested[item][sales_order] += adjusted_qty
requested_qty -= adjusted_qty
else:
break
# requested qty >= total so qty, due to minimum order qty
if requested_qty:
items_to_be_requested.setdefault(item, {}).setdefault("No Sales Order", 0)
items_to_be_requested[item]["No Sales Order"] += requested_qty
return items_to_be_requested
def get_projected_qty(self): def get_projected_qty(self):
items = self.item_dict.keys() items = self.item_dict.keys()
@ -327,24 +360,29 @@ class DocType:
return dict(item_projected_qty) return dict(item_projected_qty)
def insert_purchase_request(self, items_to_be_requested, fiscal_year): def insert_purchase_request(self):
items_to_be_requested = self.get_requested_items()
from accounts.utils import get_fiscal_year
fiscal_year = get_fiscal_year(nowdate())[0]
purchase_request_list = [] purchase_request_list = []
if items_to_be_requested: if items_to_be_requested:
for item in items_to_be_requested: for item in items_to_be_requested:
item_wrapper = webnotes.bean("Item", item) item_wrapper = webnotes.bean("Item", item)
pr_doclist = [ pr_doclist = [{
{ "doctype": "Material Request",
"doctype": "Material Request", "__islocal": 1,
"__islocal": 1, "naming_series": "IDT",
"naming_series": "IDT", "transaction_date": nowdate(),
"transaction_date": nowdate(), "status": "Draft",
"status": "Draft", "company": self.doc.company,
"company": self.doc.company, "fiscal_year": fiscal_year,
"fiscal_year": fiscal_year, "requested_by": webnotes.session.user,
"requested_by": webnotes.session.user, "material_request_type": "Purchase"
"material_request_type": "Purchase" }]
}, for sales_order, requested_qty in items_to_be_requested[item].items():
{ pr_doclist.append({
"doctype": "Material Request Item", "doctype": "Material Request Item",
"__islocal": 1, "__islocal": 1,
"parentfield": "indent_details", "parentfield": "indent_details",
@ -354,11 +392,12 @@ class DocType:
"uom": item_wrapper.doc.stock_uom, "uom": item_wrapper.doc.stock_uom,
"item_group": item_wrapper.doc.item_group, "item_group": item_wrapper.doc.item_group,
"brand": item_wrapper.doc.brand, "brand": item_wrapper.doc.brand,
"qty": items_to_be_requested[item], "qty": requested_qty,
"schedule_date": add_days(nowdate(), cint(item_wrapper.doc.lead_time_days)), "schedule_date": add_days(nowdate(), cint(item_wrapper.doc.lead_time_days)),
"warehouse": self.doc.purchase_request_for_warehouse "warehouse": self.doc.purchase_request_for_warehouse,
} "sales_order_no": sales_order if sales_order!="No Sales Order" else None
] })
pr_wrapper = webnotes.bean(pr_doclist) pr_wrapper = webnotes.bean(pr_doclist)
pr_wrapper.ignore_permissions = 1 pr_wrapper.ignore_permissions = 1
pr_wrapper.submit() pr_wrapper.submit()
@ -367,7 +406,7 @@ class DocType:
if purchase_request_list: if purchase_request_list:
pur_req = ["""<a href="#Form/Material Request/%s" target="_blank">%s</a>""" % \ pur_req = ["""<a href="#Form/Material Request/%s" target="_blank">%s</a>""" % \
(p, p) for p in purchase_request_list] (p, p) for p in purchase_request_list]
webnotes.msgprint("Material Request(s) created: \n%s" % msgprint("Material Request(s) created: \n%s" %
"\n".join(pur_req)) "\n".join(pur_req))
else: else:
webnotes.msgprint("Nothing to request") msgprint(_("Nothing to request"))

View File

@ -0,0 +1,23 @@
# 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.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,6 +256,9 @@ 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",
] ]

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

@ -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)
@ -448,6 +455,10 @@ class DocType(DocListController):
t for t in open_tickets]) t for t in open_tickets])
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()
@ -466,4 +477,4 @@ def send():
where enabled=1 and docstatus<2""", as_list=1): where enabled=1 and docstatus<2""", as_list=1):
ed_obj = get_obj('Email Digest', ed[0]) ed_obj = get_obj('Email Digest', ed[0])
if (now_date == ed_obj.get_next_sending()): if (now_date == ed_obj.get_next_sending()):
ed_obj.send() ed_obj.send()

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

@ -171,7 +171,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
@ -186,10 +186,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

@ -13,4 +13,5 @@ def on_method(bean, method):
clear_doctype_notifications(bean.controller, method) clear_doctype_notifications(bean.controller, 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)
@ -53,10 +49,11 @@ def execute_daily():
# check reorder level # check reorder level
from stock.utils import reorder_item from stock.utils import reorder_item
run_fn(reorder_item) run_fn(reorder_item)
# email digest
from setup.doctype.email_digest.email_digest import send
run_fn(send)
# scheduler error
scheduler.report_errors()
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
run_fn(take_backups_weekly) run_fn(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]: if bom_mat and bom_mat[0][0]:
msgprint(_(field_label) + _(" should be 'Yes'. As Item: ") + self.doc.name + webnotes.throw(_("Item must be active and purchase item, \
_(" is present in one or many Active BOMs"), raise_exception=1) as it is present in one or many Active BOMs"))
if ((field_label == 'Allow Production Order' if self.doc.is_manufactured_item != "Yes":
and self.doc.is_sub_contracted_item != 'Yes') bom = webnotes.conn.sql("""select name from `tabBOM` where item = %s
or (field_label == 'Is Sub Contracted Item' and is_active = 1""", (self.doc.name,))
and self.doc.is_manufactured_item != 'Yes')): if bom and bom[0][0]:
bom = webnotes.conn.sql("""select name from `tabBOM` where item = %s webnotes.throw(_("""Allow Bill of Materials should be 'Yes'. Because one or many \
and is_active = 1""", (self.doc.name,)) active BOMs present for this item"""))
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")):
fl = {'is_manufactured_item' :'Allow Bill of Materials',
'is_sub_contracted_item':'Is Sub Contracted Item',
'is_purchase_item' :'Is Purchase Item',
'is_pro_applicable' :'Allow Production Order'}
for d in fl:
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

@ -37,20 +37,25 @@ class DocType(BuyingController):
for so_no in so_items.keys(): for so_no in so_items.keys():
for item in so_items[so_no].keys(): for item in so_items[so_no].keys():
already_indented = webnotes.conn.sql("select sum(qty) from `tabMaterial Request Item` where item_code = '%s' and sales_order_no = '%s' and docstatus = 1 and parent != '%s'" % (item, so_no, self.doc.name)) already_indented = webnotes.conn.sql("""select sum(qty) from `tabMaterial Request Item`
where item_code = %s and sales_order_no = %s and
docstatus = 1 and parent != %s""", (item, so_no, self.doc.name))
already_indented = already_indented and flt(already_indented[0][0]) or 0 already_indented = already_indented and flt(already_indented[0][0]) or 0
actual_so_qty = webnotes.conn.sql("select sum(qty) from `tabSales Order Item` where parent = '%s' and item_code = '%s' and docstatus = 1 group by parent" % (so_no, item)) actual_so_qty = webnotes.conn.sql("""select sum(qty) from `tabSales Order Item`
where parent = %s and item_code = %s and docstatus = 1
group by parent""", (so_no, item))
actual_so_qty = actual_so_qty and flt(actual_so_qty[0][0]) or 0 actual_so_qty = actual_so_qty and flt(actual_so_qty[0][0]) or 0
if flt(so_items[so_no][item]) + already_indented > actual_so_qty: if actual_so_qty and (flt(so_items[so_no][item]) + already_indented > actual_so_qty):
msgprint("You can raise indent of maximum qty: %s for item: %s against sales order: %s\n Anyway, you can add more qty in new row for the same item." % (actual_so_qty - already_indented, item, so_no), raise_exception=1) webnotes.throw("You can raise indent of maximum qty: %s for item: %s against sales order: %s\
\n Anyway, you can add more qty in new row for the same item."
% (actual_so_qty - already_indented, item, so_no))
def validate_schedule_date(self): def validate_schedule_date(self):
for d in getlist(self.doclist, 'indent_details'): for d in getlist(self.doclist, 'indent_details'):
if d.schedule_date < self.doc.transaction_date: if d.schedule_date < self.doc.transaction_date:
msgprint("Expected Date cannot be before Material Request Date") webnotes.throw(_("Expected Date cannot be before Material Request Date"))
raise Exception
# Validate # Validate
# --------------------- # ---------------------
@ -80,8 +85,8 @@ class DocType(BuyingController):
for d in getlist(self.doclist, 'indent_details'): for d in getlist(self.doclist, 'indent_details'):
if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes": if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes":
if not d.warehouse: if not d.warehouse:
msgprint("Please Enter Warehouse for Item %s as it is stock item" webnotes.throw("Please Enter Warehouse for Item %s as it is stock item"
% cstr(d.item_code), raise_exception=1) % cstr(d.item_code))
qty =flt(d.qty) qty =flt(d.qty)
if is_stopped: if is_stopped:
@ -96,16 +101,17 @@ class DocType(BuyingController):
update_bin(args) update_bin(args)
def on_submit(self): def on_submit(self):
webnotes.conn.set(self.doc,'status','Submitted') webnotes.conn.set(self.doc, 'status', 'Submitted')
self.update_bin(is_submit = 1, is_stopped = 0) self.update_bin(is_submit = 1, is_stopped = 0)
def check_modified_date(self): def check_modified_date(self):
mod_db = webnotes.conn.sql("select modified from `tabMaterial Request` where name = '%s'" % self.doc.name) mod_db = webnotes.conn.sql("""select modified from `tabMaterial Request` where name = %s""",
date_diff = webnotes.conn.sql("select TIMEDIFF('%s', '%s')" % ( mod_db[0][0],cstr(self.doc.modified))) self.doc.name)
date_diff = webnotes.conn.sql("""select TIMEDIFF('%s', '%s')"""
% (mod_db[0][0], cstr(self.doc.modified)))
if date_diff and date_diff[0][0]: if date_diff and date_diff[0][0]:
msgprint(cstr(self.doc.doctype) +" => "+ cstr(self.doc.name) +" has been modified. Please Refresh. ") webnotes.throw(cstr(self.doc.doctype) + " => " + cstr(self.doc.name) + " has been modified. Please Refresh.")
raise Exception
def update_status(self, status): def update_status(self, status):
self.check_modified_date() self.check_modified_date()
@ -113,10 +119,10 @@ class DocType(BuyingController):
self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1) self.update_bin(is_submit = (status == 'Submitted') and 1 or 0, is_stopped = 1)
# Step 2:=> Set status # Step 2:=> Set status
webnotes.conn.set(self.doc,'status',cstr(status)) webnotes.conn.set(self.doc, 'status', cstr(status))
# Step 3:=> Acknowledge User # Step 3:=> Acknowledge User
msgprint(self.doc.doctype + ": " + self.doc.name + " has been %s." % ((status == 'Submitted') and 'Unstopped' or cstr(status)) ) msgprint(self.doc.doctype + ": " + self.doc.name + " has been %s." % ((status == 'Submitted') and 'Unstopped' or cstr(status)))
def on_cancel(self): def on_cancel(self):
@ -177,9 +183,9 @@ def update_completed_qty(controller, caller_method):
mr_doctype = webnotes.get_doctype("Material Request") mr_doctype = webnotes.get_doctype("Material Request")
if mr_obj.doc.status in ["Stopped", "Cancelled"]: if mr_obj.doc.status in ["Stopped", "Cancelled"]:
msgprint(_("Material Request") + ": %s, " % mr_obj.doc.name webnotes.throw(_("Material Request") + ": %s, " % mr_obj.doc.name
+ _(mr_doctype.get_label("status")) + " = %s. " % _(mr_obj.doc.status) + _(mr_doctype.get_label("status")) + " = %s. " % _(mr_obj.doc.status)
+ _("Cannot continue."), raise_exception=webnotes.InvalidStatusError) + _("Cannot continue."), exc=webnotes.InvalidStatusError)
_update_requested_qty(controller, mr_obj, mr_items) _update_requested_qty(controller, mr_obj, mr_items)

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"
}
]