Merge branch 'master' of github.com:webnotes/erpnext
This commit is contained in:
commit
5a7328a005
@ -1,21 +1,22 @@
|
||||
[
|
||||
{
|
||||
"owner": "Administrator",
|
||||
"docstatus": 0,
|
||||
"creation": "2011-12-21 11:08:55",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-16 16:15:46",
|
||||
"modified_by": "Administrator",
|
||||
"modified": "2012-03-20 12:29:49"
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doc_type": "Sales Invoice",
|
||||
"name": "__common__",
|
||||
"module": "Accounts",
|
||||
"doctype": "Print Format",
|
||||
"html": "<html>\n<head>\n<!--Other charges function-->\n<script>\nvar make_row = function(title,val,bold){\nvar bstart = '<b>'; var bend = '</b>';\nreturn '<tr><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:20%\">'+doc.currency+'</td>'\n +'<td style=\"width:30%;text-align:right\">'+(val?val:'0.00')+'</td>'\n +'</tr>'\n}\nvar make_row1 = function(title,val,bold){\n var bstart = '<b>'; var bend = '</b>';\n \n return '<tr style=\"font-family:courier new; line-height:150%\"><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:50%;text-align:right\">'+(bold?bstart:'')+(val?val:'0.00')+(bold?bend:'')+'</td>'\n +'</tr>'\n}\n\nfunction get_letter_head() {\n // add letter head\n var cp = wn.boot.control_panel;\n if(doc.letter_head)\n var lh= cstr(_p.letter_heads[doc.letter_head]);\n else if(cp.letter_head)\n var lh= cp.letter_head;\n else \n var lh= '';\n \n return lh;\n}\n\nfunction get_tax_details(){\n var cl = getchildren('Sales Taxes and Charges',doc.name,'other_charges');\n var out = '';\n\n out += '<div><table style=\"float: right;\">';\n for(var i=0;i<cl.length;i++){\n if(cl[i].tax_amount) {\n out += make_row1(cl[i].description,fmt_money(convert_rate(cl[i].tax_amount)),0);\n }\n }\n out += make_row1('TOTAL',doc.currency + \" \" + fmt_money(convert_rate(doc.grand_total)),1);\n out +='</table></div>';\n return out;\n}\n\nfunction convert_rate(val){ \n var new_val = flt(val)/flt(doc.conversion_rate);\n return new_val;\n}\n</script>\n<style>\n table, td, tr, div, span {\n font-family: courier new;\n line-height: 200%;\n }\n</style>\n</head>\n\n<body>\n<table width=\"100%\" style=\"font-family: courier new; line-height:200%\">\n<tr>\n <td align=\"left\">NO: <script>doc.name</script></td>\n <td align=\"right\">DATE: <script>date.str_to_user(doc.posting_date)</script></td>\n</tr>\n<tr>\n <td>M/s <script>doc.contact_display</script></td>\n</tr>\n</table>\n<!--Item Table-->\n<div>\n<script>\nvar t = print_table('Sales Invoice', doc.name, 'entries', 'Sales Invoice Item',\n ['description','qty','export_rate','export_amount'], ['ITEM', 'QTY','RATE','AMOUNT'],\n ['35%','20%','20%','25%']);\nif(t.appendChild) { // single\n out = t.innerHTML.replace(/style=\"/gi,'style=\"font-family:courier new;line-height:150%;');\n} else { //multiple\n out = '<table class=\"None\" border=\"0px\" width=\"100%\" style=\"border:0px; font-family:courier-new\">';\n\n for(var i=0;i<t.length;i++) {\n if(i==0) {\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[0].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[1].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '</table>';\n}\nout;\n</script>\n</div>\n<!--Tax table-->\n<div><script>get_tax_details();</script></div><br />\n<table style=\"font-family:courier new;\">\n <tr><td><b><script>doc.terms</script></b></td></tr>\n <tr><td nowrap><b>For <script>doc.company</script></b></td></tr>\n <tr><td> </td></tr>\n <tr><td nowrap><b>Signatory</b></td></tr>\n</table>\n</body>\n</html>\n",
|
||||
"html": "<html>\n<head>\n<!--Other charges function-->\n<script>\nvar make_row = function(title,val,bold){\nvar bstart = '<b>'; var bend = '</b>';\nreturn '<tr><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:20%\">'+doc.currency+'</td>'\n +'<td style=\"width:30%;text-align:right\">'+(val?val:'0.00')+'</td>'\n +'</tr>'\n}\nvar make_row1 = function(title,val,bold){\n var bstart = '<b>'; var bend = '</b>';\n \n return '<tr style=\"font-family:courier new; line-height:150%\"><td style=\"width:50%\">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'\n +'<td style=\"width:50%;text-align:right\">'+(bold?bstart:'')+(val?val:'0.00')+(bold?bend:'')+'</td>'\n +'</tr>'\n}\n\nfunction get_letter_head() {\n // add letter head\n var cp = wn.boot.control_panel;\n if(doc.letter_head)\n var lh= cstr(_p.letter_heads[doc.letter_head]);\n else if(cp.letter_head)\n var lh= cp.letter_head;\n else \n var lh= '';\n \n return lh;\n}\n\nfunction get_tax_details(){\n var cl = getchildren('Sales Taxes and Charges',doc.name,'other_charges');\n var out = '';\n\n out += '<div><table style=\"float: right;\">';\n for(var i=0;i<cl.length;i++){\n if(cl[i].tax_amount) {\n out += make_row1(cl[i].description,fmt_money(convert_rate(cl[i].tax_amount)),0);\n }\n }\n out += make_row1('TOTAL',doc.currency + \" \" + fmt_money(convert_rate(doc.grand_total)),1);\n out +='</table></div>';\n return out;\n}\n\nfunction convert_rate(val){ \n var new_val = flt(val)/flt(doc.conversion_rate);\n return new_val;\n}\n</script>\n<style>\n table, td, tr, div, span {\n font-family: courier new;\n line-height: 200%;\n }\n</style>\n</head>\n\n<body>\n<table width=\"100%\" style=\"font-family: courier new; line-height:200%\">\n<tr>\n <td align=\"left\">NO: <script>doc.name</script></td>\n <td align=\"right\">DATE: <script>date.str_to_user(doc.posting_date)</script></td>\n</tr>\n<tr>\n <td>M/s <script>doc.contact_display</script></td>\n</tr>\n</table>\n<!--Item Table-->\n<div>\n<script>\nvar t = print_table(\n \t\t\t'Sales Invoice',\n\t\t\t\tdoc.name,\n\t\t\t\t'entries',\n\t\t\t\t'Sales Invoice Item',\n\t\t\t\t[// Here specify the table columns to be displayed\n\t\t\t\t\t'SR', 'description', 'qty', 'export_rate', 'export_amount'\n\t\t\t\t],\n\t\t\t\t[// Here specify the labels of column headings\n\t\t\t\t\t'Sr', 'Description', 'Qty', 'Rate', 'Amount'\n\t\t\t\t],\n\t\t\t\t[// Here specify the column widths\n\t\t\t\t\t'5%', '35%', '20%', '20%', '20%'\n\t\t\t\t],\n\t\t\t\tnull,\n\t\t\t\tnull\n\t\t\t);\nif(t.appendChild) { // single\n out = t.innerHTML.replace(/style=\"/gi,'style=\"font-family:courier new;line-height:150%;');\n} else { //multiple\n out = '<table class=\"None\" border=\"0px\" width=\"100%\" style=\"border:0px; font-family:courier-new\">';\n\n for(var i=0;i<t.length;i++) {\n if(i==0) {\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[0].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '<tr>' + t[i].childNodes[0].childNodes[0].childNodes[1].innerHTML\n .replace(/style=\"border: 1px solid rgb\\(0, 0, 0\\);/gi,'style=\"font-family:courier new;')\n + '</tr>';\n }\n out += '</table>';\n}\nout;\n</script>\n</div>\n<!--Tax table-->\n<div><script>get_tax_details();</script></div><br />\n<table style=\"font-family:courier new;\">\n <tr><td><b><script>doc.terms</script></b></td></tr>\n <tr><td nowrap><b>For <script>doc.company</script></b></td></tr>\n <tr><td> </td></tr>\n <tr><td nowrap><b>Signatory</b></td></tr>\n</table>\n</body>\n</html>\n",
|
||||
"module": "Accounts",
|
||||
"name": "__common__",
|
||||
"print_format_type": "Client",
|
||||
"standard": "Yes"
|
||||
},
|
||||
{
|
||||
"name": "POS Invoice",
|
||||
"doctype": "Print Format"
|
||||
"doctype": "Print Format",
|
||||
"name": "POS Invoice"
|
||||
}
|
||||
]
|
@ -231,11 +231,12 @@ class DocType(AccountsController):
|
||||
for d in self.doclist.get({"parentfield": "entries"}):
|
||||
if d.against_invoice and webnotes.conn.get_value("Sales Invoice",
|
||||
d.against_invoice, "debit_to") != d.account:
|
||||
msgprint("Debit account is not matching with Sales Invoice", raise_exception=1)
|
||||
webnotes.throw(_("Credited account (Customer) is not matching with Sales Invoice"))
|
||||
|
||||
if d.against_voucher and webnotes.conn.get_value("Purchase Invoice",
|
||||
d.against_voucher, "credit_to") != d.account:
|
||||
msgprint("Credit account is not matching with Purchase Invoice", raise_exception=1)
|
||||
webnotes.throw(_("Debited account (Supplier) is not matching with \
|
||||
Purchase Invoice"))
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
@ -338,11 +339,72 @@ def get_default_bank_cash_account(company, voucher_type):
|
||||
account = webnotes.conn.get_value("Company", company,
|
||||
voucher_type=="Bank Voucher" and "default_bank_account" or "default_cash_account")
|
||||
if account:
|
||||
return [{
|
||||
return {
|
||||
"account": account,
|
||||
"balance": get_balance_on(account)
|
||||
}]
|
||||
}
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_payment_entry_from_sales_invoice(sales_invoice):
|
||||
from accounts.utils import get_balance_on
|
||||
si = webnotes.bean("Sales Invoice", sales_invoice)
|
||||
jv = get_payment_entry(si.doc)
|
||||
jv.doc.remark = 'Payment received against Sales Invoice %(name)s. %(remarks)s' % si.doc.fields
|
||||
|
||||
# credit customer
|
||||
jv.doclist[1].account = si.doc.debit_to
|
||||
jv.doclist[1].balance = get_balance_on(si.doc.debit_to)
|
||||
jv.doclist[1].credit = si.doc.outstanding_amount
|
||||
jv.doclist[1].against_invoice = si.doc.name
|
||||
|
||||
# debit bank
|
||||
jv.doclist[2].debit = si.doc.outstanding_amount
|
||||
|
||||
return [d.fields for d in jv.doclist]
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_payment_entry_from_purchase_invoice(purchase_invoice):
|
||||
from accounts.utils import get_balance_on
|
||||
pi = webnotes.bean("Purchase Invoice", purchase_invoice)
|
||||
jv = get_payment_entry(pi.doc)
|
||||
jv.doc.remark = 'Payment against Purchase Invoice %(name)s. %(remarks)s' % pi.doc.fields
|
||||
|
||||
# credit supplier
|
||||
jv.doclist[1].account = pi.doc.credit_to
|
||||
jv.doclist[1].balance = get_balance_on(pi.doc.credit_to)
|
||||
jv.doclist[1].debit = pi.doc.outstanding_amount
|
||||
jv.doclist[1].against_voucher = pi.doc.name
|
||||
|
||||
# credit bank
|
||||
jv.doclist[2].credit = pi.doc.outstanding_amount
|
||||
|
||||
return [d.fields for d in jv.doclist]
|
||||
|
||||
def get_payment_entry(doc):
|
||||
bank_account = get_default_bank_cash_account(doc.company, "Bank Voucher")
|
||||
|
||||
jv = webnotes.new_bean('Journal Voucher')
|
||||
jv.doc.voucher_type = 'Bank Voucher'
|
||||
|
||||
jv.doc.company = doc.company
|
||||
jv.doc.fiscal_year = doc.fiscal_year
|
||||
|
||||
jv.doclist.append({
|
||||
"doctype": "Journal Voucher Detail",
|
||||
"parentfield": "entries"
|
||||
})
|
||||
|
||||
jv.doclist.append({
|
||||
"doctype": "Journal Voucher Detail",
|
||||
"parentfield": "entries"
|
||||
})
|
||||
|
||||
if bank_account:
|
||||
jv.doclist[2].account = bank_account["account"]
|
||||
jv.doclist[2].balance = bank_account["balance"]
|
||||
|
||||
return jv
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_opening_accounts(company):
|
||||
"""get all balance sheet accounts for opening entry"""
|
||||
|
@ -106,13 +106,13 @@ cur_frm.cscript.is_opening = function(doc, dt, dn) {
|
||||
|
||||
cur_frm.cscript.make_bank_voucher = function() {
|
||||
return wn.call({
|
||||
method: "accounts.doctype.journal_voucher.journal_voucher.get_default_bank_cash_account",
|
||||
method: "accounts.doctype.journal_voucher.journal_voucher.get_payment_entry_from_purchase_invoice",
|
||||
args: {
|
||||
"company": cur_frm.doc.company,
|
||||
"voucher_type": "Bank Voucher"
|
||||
"purchase_invoice": cur_frm.doc.name,
|
||||
},
|
||||
callback: function(r) {
|
||||
cur_frm.cscript.make_jv(cur_frm.doc, null, null, r.message);
|
||||
var doclist = wn.model.sync(r.message);
|
||||
wn.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -198,31 +198,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn){
|
||||
refresh_field('entries');
|
||||
}
|
||||
|
||||
cur_frm.cscript.make_jv = function(doc, dt, dn, bank_account) {
|
||||
var jv = wn.model.make_new_doc_and_get_name('Journal Voucher');
|
||||
jv = locals['Journal Voucher'][jv];
|
||||
jv.voucher_type = 'Bank Voucher';
|
||||
jv.remark = repl('Payment against voucher %(vn)s for %(rem)s', {vn:doc.name, rem:doc.remarks});
|
||||
jv.total_debit = doc.outstanding_amount;
|
||||
jv.total_credit = doc.outstanding_amount;
|
||||
jv.fiscal_year = doc.fiscal_year;
|
||||
jv.company = doc.company;
|
||||
|
||||
// debit to creditor
|
||||
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
|
||||
d1.account = doc.credit_to;
|
||||
d1.debit = doc.outstanding_amount;
|
||||
d1.against_voucher = doc.name;
|
||||
|
||||
// credit to bank
|
||||
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
|
||||
d1.account = bank_account.account;
|
||||
d1.credit = doc.outstanding_amount;
|
||||
d1.balance = bank_account.balance;
|
||||
|
||||
loaddoc('Journal Voucher', jv.name);
|
||||
}
|
||||
|
||||
cur_frm.fields_dict['entries'].grid.get_field('project_name').get_query = function(doc, cdt, cdn) {
|
||||
return{
|
||||
filters:[
|
||||
|
@ -127,9 +127,9 @@ class DocType(BuyingController):
|
||||
if not self.doc.remarks and self.doc.bill_date:
|
||||
self.doc.remarks = (self.doc.remarks or '') + "\n" + ("Against Bill %s dated %s"
|
||||
% (self.doc.bill_no, formatdate(self.doc.bill_date)))
|
||||
else:
|
||||
if not self.doc.remarks:
|
||||
self.doc.remarks = "No Remarks"
|
||||
|
||||
if not self.doc.remarks:
|
||||
self.doc.remarks = "No Remarks"
|
||||
|
||||
def validate_credit_acc(self):
|
||||
acc = sql("select debit_or_credit, is_pl_account from tabAccount where name = %s",
|
||||
@ -261,13 +261,11 @@ class DocType(BuyingController):
|
||||
if d.purchase_order:
|
||||
submitted = sql("select name from `tabPurchase Order` where docstatus = 1 and name = '%s'" % d.purchase_order)
|
||||
if not submitted:
|
||||
msgprint("Purchase Order : "+ cstr(d.purchase_order) +" is not submitted")
|
||||
raise Exception , "Validation Error."
|
||||
webnotes.throw("Purchase Order : "+ cstr(d.purchase_order) +" is not submitted")
|
||||
if d.purchase_receipt:
|
||||
submitted = sql("select name from `tabPurchase Receipt` where docstatus = 1 and name = '%s'" % d.purchase_receipt)
|
||||
if not submitted:
|
||||
msgprint("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted")
|
||||
raise Exception , "Validation Error."
|
||||
webnotes.throw("Purchase Receipt : "+ cstr(d.purchase_receipt) +" is not submitted")
|
||||
|
||||
|
||||
def update_against_document_in_jv(self):
|
||||
|
@ -5,10 +5,73 @@ erpnext.POS = Class.extend({
|
||||
init: function(wrapper, frm) {
|
||||
this.wrapper = wrapper;
|
||||
this.frm = frm;
|
||||
this.wrapper.html('<div class="customer-area"></div>\
|
||||
<div class="item-area"></div>\
|
||||
<div><button class="btn btn-default btn-add">Add</button>');
|
||||
|
||||
this.wrapper.html('<div class="container">\
|
||||
<div class="row">\
|
||||
<div class="customer-area col-sm-3 col-xs-6"></div>\
|
||||
<div class="barcode-area col-sm-3 col-xs-6"></div>\
|
||||
<div class="search-area col-sm-3 col-xs-6"></div>\
|
||||
<div class="item-group-area col-sm-3 col-xs-6"></div>\
|
||||
</div>\
|
||||
<div class="row">\
|
||||
<div class="col-sm-6">\
|
||||
<div class="pos-bill">\
|
||||
<div class="item-cart">\
|
||||
<table class="table table-condensed table-hover" id="cart" style="table-layout: fixed;">\
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th style="width: 50%">Item</th>\
|
||||
<th style="width: 25%; text-align: right;">Qty</th>\
|
||||
<th style="width: 25%; text-align: right;">Rate</th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody>\
|
||||
</tbody>\
|
||||
</table>\
|
||||
</div>\
|
||||
<br>\
|
||||
<div class="net-total-area" style="margin-left: 40%;">\
|
||||
<table class="table table-condensed">\
|
||||
<tr>\
|
||||
<td><b>Net Total</b></td>\
|
||||
<td style="text-align: right;" class="net-total"></td>\
|
||||
</tr>\
|
||||
</table>\
|
||||
<div class="tax-table" style="display: none;">\
|
||||
<table class="table table-condensed">\
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th style="width: 60%">Taxes</th>\
|
||||
<th style="width: 40%; text-align: right;"></th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody>\
|
||||
</tbody>\
|
||||
</table>\
|
||||
</div>\
|
||||
<table class="table table-condensed">\
|
||||
<tr>\
|
||||
<td style="vertical-align: middle;"><b>Grand Total</b></td>\
|
||||
<td style="text-align: right; font-size: 200%; \
|
||||
font-size: bold;" class="grand-total"></td>\
|
||||
</tr>\
|
||||
</table>\
|
||||
</div>\
|
||||
</div>\
|
||||
<br><br>\
|
||||
<button class="btn btn-success btn-lg make-payment">\
|
||||
<i class="icon-money"></i> Make Payment</button>\
|
||||
<button class="btn btn-default btn-lg delete-items pull-right" style="display: none;">\
|
||||
<i class="icon-trash"></i> Del</button>\
|
||||
<br><br>\
|
||||
</div>\
|
||||
<div class="col-sm-6">\
|
||||
<div class="item-list-area">\
|
||||
<div class="col-sm-12">\
|
||||
<div class="row item-list"></div></div>\
|
||||
</div>\
|
||||
</div>\
|
||||
</div></div>');
|
||||
|
||||
this.make();
|
||||
|
||||
var me = this;
|
||||
@ -16,10 +79,20 @@ erpnext.POS = Class.extend({
|
||||
me.refresh();
|
||||
});
|
||||
|
||||
this.wrapper.find(".delete-items").on("click", function() {
|
||||
me.remove_selected_item();
|
||||
});
|
||||
|
||||
this.wrapper.find(".make-payment").on("click", function() {
|
||||
me.make_payment();
|
||||
});
|
||||
},
|
||||
make: function() {
|
||||
this.make_customer();
|
||||
this.make_items();
|
||||
this.make_item_group();
|
||||
this.make_search();
|
||||
this.make_barcode();
|
||||
this.make_item_list();
|
||||
},
|
||||
make_customer: function() {
|
||||
var me = this;
|
||||
@ -28,7 +101,8 @@ erpnext.POS = Class.extend({
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"label": "Customer",
|
||||
"fieldname": "pos_customer"
|
||||
"fieldname": "pos_customer",
|
||||
"placeholder": "Customer"
|
||||
},
|
||||
parent: this.wrapper.find(".customer-area")
|
||||
});
|
||||
@ -36,25 +110,302 @@ erpnext.POS = Class.extend({
|
||||
this.customer.$input.on("change", function() {
|
||||
if(!me.customer.autocomplete_open)
|
||||
wn.model.set_value("Sales Invoice", me.frm.docname, "customer", this.value);
|
||||
});
|
||||
},
|
||||
make_items: function() {
|
||||
var me = this;
|
||||
this.wrapper.find(".btn-add").click(function() {
|
||||
var child = wn.model.add_child(me.frm.doc, "Sales Invoice Item", "entries");
|
||||
child.item_code = "Test Item";
|
||||
me.frm.cscript.item_code(me.frm.doc, child.doctype, child.name);
|
||||
});
|
||||
},
|
||||
make_item_group: function() {
|
||||
var me = this;
|
||||
this.item_group = wn.ui.form.make_control({
|
||||
df: {
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group",
|
||||
"label": "Item Group",
|
||||
"fieldname": "pos_item_group",
|
||||
"placeholder": "Filter by Item Group"
|
||||
},
|
||||
parent: this.wrapper.find(".item-group-area")
|
||||
});
|
||||
this.item_group.make_input();
|
||||
this.item_group.$input.on("change", function() {
|
||||
if(!me.item_group.autocomplete_open)
|
||||
me.make_item_list();
|
||||
});
|
||||
},
|
||||
make_search: function() {
|
||||
var me = this;
|
||||
this.search = wn.ui.form.make_control({
|
||||
df: {
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"label": "Item",
|
||||
"fieldname": "pos_item",
|
||||
"placeholder": "Select Item"
|
||||
},
|
||||
parent: this.wrapper.find(".search-area")
|
||||
});
|
||||
this.search.make_input();
|
||||
this.search.$input.on("change", function() {
|
||||
if(!me.search.autocomplete_open)
|
||||
me.make_item_list();
|
||||
});
|
||||
},
|
||||
make_barcode: function() {
|
||||
var me = this;
|
||||
this.barcode = wn.ui.form.make_control({
|
||||
df: {
|
||||
"fieldtype": "Data",
|
||||
"label": "Barcode",
|
||||
"fieldname": "pos_barcode",
|
||||
"placeholder": "Select Barcode"
|
||||
},
|
||||
parent: this.wrapper.find(".barcode-area")
|
||||
});
|
||||
this.barcode.make_input();
|
||||
this.barcode.$input.on("change", function() {
|
||||
me.add_item_thru_barcode();
|
||||
});
|
||||
},
|
||||
make_item_list: function() {
|
||||
var me = this;
|
||||
wn.call({
|
||||
method: 'accounts.doctype.sales_invoice.pos.get_items',
|
||||
args: {
|
||||
price_list: cur_frm.doc.selling_price_list,
|
||||
item_group: this.item_group.$input.val(),
|
||||
item: this.search.$input.val()
|
||||
},
|
||||
callback: function(r) {
|
||||
var $wrap = me.wrapper.find(".item-list");
|
||||
me.wrapper.find(".item-list").empty();
|
||||
$.each(r.message, function(index, obj) {
|
||||
if (obj.image)
|
||||
image = "<img src='" + obj.image + "' class='img-responsive'>";
|
||||
else
|
||||
image = '<div class="missing-image"><i class="icon-camera"></i></div>';
|
||||
|
||||
$(repl('<div class="col-xs-3 pos-item" data-item_code="%(item_code)s">\
|
||||
%(item_image)s\
|
||||
<div class="small">%(item_code)s</div>\
|
||||
<div class="small">%(item_name)s</div>\
|
||||
<div class="small">%(item_price)s</div>\
|
||||
</div>',
|
||||
{
|
||||
item_code: obj.name,
|
||||
item_price: format_currency(obj.ref_rate, obj.ref_currency),
|
||||
item_name: obj.name===obj.item_name ? "" : obj.item_name,
|
||||
item_image: image
|
||||
})).appendTo($wrap);
|
||||
});
|
||||
|
||||
$("div.pos-item").on("click", function() {
|
||||
if(!cur_frm.doc.customer) {
|
||||
msgprint("Please select customer first.");
|
||||
return;
|
||||
}
|
||||
me.add_to_cart($(this).attr("data-item_code"));
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
add_to_cart: function(item_code) {
|
||||
var me = this;
|
||||
var caught = false;
|
||||
|
||||
// get no_of_items
|
||||
no_of_items = me.wrapper.find("#cart tbody").length;
|
||||
|
||||
// check whether the item is already added
|
||||
if (no_of_items != 0) {
|
||||
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
|
||||
"Sales Invoice"), function(i, d) {
|
||||
if (d.item_code == item_code)
|
||||
caught = true;
|
||||
});
|
||||
}
|
||||
|
||||
// if duplicate row then append the qty
|
||||
if (caught) {
|
||||
me.update_qty(item_code, 1);
|
||||
}
|
||||
else {
|
||||
var child = wn.model.add_child(me.frm.doc, "Sales Invoice Item", "entries");
|
||||
child.item_code = item_code;
|
||||
me.frm.cscript.item_code(me.frm.doc, child.doctype, child.name);
|
||||
//me.refresh();
|
||||
}
|
||||
},
|
||||
update_qty: function(item_code, qty) {
|
||||
var me = this;
|
||||
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
|
||||
"Sales Invoice"), function(i, d) {
|
||||
if (d.item_code == item_code) {
|
||||
if (qty == 1)
|
||||
d.qty += 1;
|
||||
else
|
||||
d.qty = qty;
|
||||
|
||||
me.frm.cscript.qty(me.frm.doc, d.doctype, d.name);
|
||||
}
|
||||
});
|
||||
me.refresh();
|
||||
},
|
||||
refresh: function() {
|
||||
var me = this;
|
||||
this.customer.set_input(this.frm.doc.customer);
|
||||
|
||||
this.barcode.set_input("");
|
||||
|
||||
// add items
|
||||
var $items = me.wrapper.find(".item-area").empty();
|
||||
var $items = me.wrapper.find("#cart tbody").empty();
|
||||
|
||||
$.each(wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
|
||||
"Sales Invoice"), function(i, d) {
|
||||
$(repl("<div>%(item_code)s</div>", d)).appendTo($items);
|
||||
$(repl('<tr id="%(item_code)s" data-selected="false">\
|
||||
<td>%(item_code)s%(item_name)s</td>\
|
||||
<td><input type="text" value="%(qty)s" \
|
||||
class="form-control qty" style="text-align: right;"></td>\
|
||||
<td style="text-align: right;">%(rate)s<br><b>%(amount)s</b></td>\
|
||||
</tr>',
|
||||
{
|
||||
item_code: d.item_code,
|
||||
item_name: d.item_name===d.item_code ? "" : ("<br>" + d.item_name),
|
||||
qty: d.qty,
|
||||
rate: format_currency(d.ref_rate, cur_frm.doc.price_list_currency),
|
||||
amount: format_currency(d.export_amount, cur_frm.doc.price_list_currency)
|
||||
}
|
||||
)).appendTo($items);
|
||||
});
|
||||
|
||||
// taxes
|
||||
var taxes = wn.model.get_children("Sales Taxes and Charges", this.frm.doc.name, "other_charges",
|
||||
"Sales Invoice");
|
||||
$(".tax-table")
|
||||
.toggle((taxes && taxes.length) ? true : false)
|
||||
.find("tbody").empty();
|
||||
|
||||
$.each(taxes, function(i, d) {
|
||||
$(repl('<tr>\
|
||||
<td>%(description)s</td>\
|
||||
<td style="text-align: right;">%(tax_amount)s</td>\
|
||||
<tr>', {
|
||||
description: d.description,
|
||||
tax_amount: format_currency(d.tax_amount, me.frm.doc.price_list_currency)
|
||||
})).appendTo(".tax-table tbody");
|
||||
});
|
||||
|
||||
// set totals
|
||||
this.wrapper.find(".net-total").text(format_currency(this.frm.doc.net_total_export,
|
||||
cur_frm.doc.price_list_currency));
|
||||
this.wrapper.find(".grand-total").text(format_currency(this.frm.doc.grand_total_export,
|
||||
cur_frm.doc.price_list_currency));
|
||||
|
||||
// append quantity to the respective item after change from input box
|
||||
$("input.qty").on("change", function() {
|
||||
var item_code = $(this).closest("tr")[0].id;
|
||||
me.update_qty(item_code, $(this).val());
|
||||
});
|
||||
|
||||
// on td click highlight the respective row
|
||||
$("td").on("click", function() {
|
||||
var row = $(this).closest("tr");
|
||||
if (row.attr("data-selected") == "false") {
|
||||
row.attr("class", "warning");
|
||||
row.attr("data-selected", "true");
|
||||
}
|
||||
else {
|
||||
row.prop("class", null);
|
||||
row.attr("data-selected", "false");
|
||||
}
|
||||
me.refresh_delete_btn();
|
||||
|
||||
});
|
||||
|
||||
me.refresh_delete_btn();
|
||||
},
|
||||
refresh_delete_btn: function() {
|
||||
$(".delete-items").toggle($(".item-cart .warning").length ? true : false);
|
||||
},
|
||||
add_item_thru_barcode: function() {
|
||||
var me = this;
|
||||
wn.call({
|
||||
method: 'accounts.doctype.sales_invoice.pos.get_item_from_barcode',
|
||||
args: {barcode: this.barcode.$input.val()},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
me.add_to_cart(r.message[0].name);
|
||||
me.refresh();
|
||||
}
|
||||
else
|
||||
msgprint(wn._("Invalid Barcode"));
|
||||
}
|
||||
});
|
||||
},
|
||||
remove_selected_item: function() {
|
||||
var me = this;
|
||||
var selected_items = [];
|
||||
var no_of_items = $("#cart tbody tr").length;
|
||||
for(var x=0; x<=no_of_items - 1; x++) {
|
||||
var row = $("#cart tbody tr:eq(" + x + ")");
|
||||
if(row.attr("data-selected") == "true") {
|
||||
selected_items.push(row.attr("id"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!selected_items[0])
|
||||
msgprint(wn._("Please select any item to remove it"));
|
||||
|
||||
var child = wn.model.get_children("Sales Invoice Item", this.frm.doc.name, "entries",
|
||||
"Sales Invoice");
|
||||
$.each(child, function(i, d) {
|
||||
for (var i in selected_items) {
|
||||
if (d.item_code == selected_items[i]) {
|
||||
wn.model.clear_doc(d.doctype, d.name);
|
||||
}
|
||||
}
|
||||
});
|
||||
cur_frm.fields_dict["entries"].grid.refresh();
|
||||
me.refresh();
|
||||
},
|
||||
make_payment: function() {
|
||||
var me = this;
|
||||
var no_of_items = $("#cart tbody tr").length;
|
||||
var mode_of_payment = [];
|
||||
|
||||
if (no_of_items == 0)
|
||||
msgprint(wn._("Payment cannot be made for empty cart"));
|
||||
else {
|
||||
wn.call({
|
||||
method: 'accounts.doctype.sales_invoice.pos.get_mode_of_payment',
|
||||
callback: function(r) {
|
||||
for (x=0; x<=r.message.length - 1; x++) {
|
||||
mode_of_payment.push(r.message[x].name);
|
||||
}
|
||||
|
||||
// show payment wizard
|
||||
var dialog = new wn.ui.Dialog({
|
||||
width: 400,
|
||||
title: 'Payment',
|
||||
fields: [
|
||||
{fieldtype:'Data', fieldname:'total_amount', label:'Total Amount', read_only:1},
|
||||
{fieldtype:'Select', fieldname:'mode_of_payment', label:'Mode of Payment',
|
||||
options:mode_of_payment.join('\n'), reqd: 1},
|
||||
{fieldtype:'Button', fieldname:'pay', label:'Pay'}
|
||||
]
|
||||
});
|
||||
dialog.set_values({
|
||||
"total_amount": $(".grand-total").text()
|
||||
});
|
||||
dialog.show();
|
||||
|
||||
dialog.get_input("total_amount").attr("disabled", "disabled");
|
||||
|
||||
dialog.fields_dict.pay.input.onclick = function() {
|
||||
cur_frm.set_value("mode_of_payment", dialog.get_values().mode_of_payment);
|
||||
cur_frm.set_value("paid_amount", dialog.get_values().total_amount);
|
||||
cur_frm.save();
|
||||
dialog.hide();
|
||||
me.refresh();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
32
accounts/doctype/sales_invoice/pos.py
Normal file
32
accounts/doctype/sales_invoice/pos.py
Normal file
@ -0,0 +1,32 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_items(price_list, item=None, item_group=None):
|
||||
condition = ""
|
||||
|
||||
if item_group and item_group != "All Item Groups":
|
||||
condition = "and i.item_group='%s'" % item_group
|
||||
|
||||
if item:
|
||||
condition = "and i.name='%s'" % item
|
||||
|
||||
return webnotes.conn.sql("""select
|
||||
i.name, i.item_name, i.image, ip.ref_rate, ip.ref_currency
|
||||
from `tabItem` i LEFT JOIN `tabItem Price` ip
|
||||
ON ip.parent=i.name
|
||||
and ip.price_list=%s
|
||||
where
|
||||
i.is_sales_item='Yes'%s""" % ('%s', condition), (price_list), as_dict=1)
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_item_from_barcode(barcode):
|
||||
return webnotes.conn.sql("""select name from `tabItem` where barcode=%s""",
|
||||
(barcode), as_dict=1)
|
||||
|
||||
@webnotes.whitelist()
|
||||
def get_mode_of_payment():
|
||||
return webnotes.conn.sql("""select name from `tabMode of Payment`""", as_dict=1)
|
15
accounts/doctype/sales_invoice/sales_invoice.css
Normal file
15
accounts/doctype/sales_invoice/sales_invoice.css
Normal file
@ -0,0 +1,15 @@
|
||||
.pos-item {
|
||||
height: 200px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
padding-left: 5px !important;
|
||||
padding-right: 5px !important;
|
||||
}
|
||||
|
||||
.pos-bill {
|
||||
padding: 20px 5px;
|
||||
font-family: Monospace;
|
||||
border: 1px solid #eee;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
}
|
@ -12,7 +12,7 @@ cur_frm.pformat.print_heading = 'Invoice';
|
||||
wn.require('app/accounts/doctype/sales_taxes_and_charges_master/sales_taxes_and_charges_master.js');
|
||||
wn.require('app/utilities/doctype/sms_control/sms_control.js');
|
||||
wn.require('app/selling/doctype/sales_common/sales_common.js');
|
||||
// wn.require('app/accounts/doctype/sales_invoice/pos.js');
|
||||
wn.require('app/accounts/doctype/sales_invoice/pos.js');
|
||||
|
||||
wn.provide("erpnext.accounts");
|
||||
erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.extend({
|
||||
@ -25,9 +25,8 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||
}
|
||||
}
|
||||
// if(this.frm.doc.is_pos && this.frm.doc.docstatus===0) {
|
||||
// cur_frm.cscript.toggle_pos(true);
|
||||
// }
|
||||
|
||||
cur_frm.cscript.toggle_pos(true);
|
||||
},
|
||||
|
||||
refresh: function(doc, dt, dn) {
|
||||
@ -58,7 +57,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
cur_frm.add_custom_button('Make Payment Entry', cur_frm.cscript.make_bank_voucher);
|
||||
}
|
||||
|
||||
if (this.frm.doc.docstatus===0) {
|
||||
if (doc.docstatus===0) {
|
||||
cur_frm.add_custom_button(wn._('From Sales Order'),
|
||||
function() {
|
||||
wn.model.map_current_doc({
|
||||
@ -92,32 +91,62 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if(cint(sys_defaults.fs_pos_view)===1)
|
||||
cur_frm.cscript.pos_btn();
|
||||
|
||||
// cur_frm.add_custom_button(wn._("POS View"), function() {
|
||||
// cur_frm.cscript.toggle_pos();
|
||||
// }, 'icon-desktop');
|
||||
|
||||
// setTimeout(function() { cur_frm.$pos_btn.click(); }, 1000);
|
||||
|
||||
} else {
|
||||
// hide shown pos for submitted records
|
||||
if(cur_frm.pos_active) cur_frm.cscript.toggle_pos(false);
|
||||
}
|
||||
},
|
||||
|
||||
pos_btn: function() {
|
||||
if(cur_frm.$pos_btn)
|
||||
cur_frm.$pos_btn.remove();
|
||||
|
||||
if(!cur_frm.pos_active) {
|
||||
var btn_label = wn._("POS View"),
|
||||
icon = "icon-desktop";
|
||||
} else {
|
||||
var btn_label = wn._("Invoice View"),
|
||||
icon = "icon-file-text";
|
||||
}
|
||||
|
||||
cur_frm.$pos_btn = cur_frm.add_custom_button(btn_label, function() {
|
||||
cur_frm.cscript.toggle_pos();
|
||||
cur_frm.cscript.pos_btn();
|
||||
}, icon);
|
||||
|
||||
},
|
||||
|
||||
toggle_pos: function(show) {
|
||||
if((show===true && cur_frm.pos_active) || (show===false && !cur_frm.pos_active)) return;
|
||||
if(cint(sys_defaults.fs_pos_view)===0) return;
|
||||
if(!(this.frm.doc.is_pos && this.frm.doc.docstatus===0)) return;
|
||||
|
||||
// make pos
|
||||
if(!cur_frm.pos) {
|
||||
cur_frm.layout.add_view("pos");
|
||||
cur_frm.pos = new erpnext.POS(cur_frm.layout.views.pos, cur_frm);
|
||||
if (!this.frm.doc.selling_price_list)
|
||||
msgprint(wn._("Please select Price List"))
|
||||
else {
|
||||
if((show===true && cur_frm.pos_active) || (show===false && !cur_frm.pos_active)) return;
|
||||
|
||||
// make pos
|
||||
if(!cur_frm.pos) {
|
||||
cur_frm.layout.add_view("pos");
|
||||
cur_frm.pos = new erpnext.POS(cur_frm.layout.views.pos, cur_frm);
|
||||
}
|
||||
|
||||
// toggle view
|
||||
cur_frm.layout.set_view(cur_frm.pos_active ? "" : "pos");
|
||||
cur_frm.pos_active = !cur_frm.pos_active;
|
||||
|
||||
// refresh
|
||||
if(cur_frm.pos_active)
|
||||
cur_frm.pos.refresh();
|
||||
}
|
||||
|
||||
// toggle view
|
||||
cur_frm.layout.set_view(cur_frm.pos_active ? "" : "pos");
|
||||
cur_frm.pos_active = !cur_frm.pos_active;
|
||||
|
||||
// refresh
|
||||
if(cur_frm.pos_active)
|
||||
cur_frm.pos.refresh();
|
||||
|
||||
},
|
||||
|
||||
tc_name: function() {
|
||||
this.get_terms();
|
||||
},
|
||||
@ -181,7 +210,12 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
set_dynamic_labels: function() {
|
||||
this._super();
|
||||
this.hide_fields(this.frm.doc);
|
||||
},
|
||||
|
||||
entries_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no(grid_row)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
@ -250,13 +284,13 @@ cur_frm.cscript['Make Delivery Note'] = function() {
|
||||
|
||||
cur_frm.cscript.make_bank_voucher = function() {
|
||||
return wn.call({
|
||||
method: "accounts.doctype.journal_voucher.journal_voucher.get_default_bank_cash_account",
|
||||
method: "accounts.doctype.journal_voucher.journal_voucher.get_payment_entry_from_sales_invoice",
|
||||
args: {
|
||||
"company": cur_frm.doc.company,
|
||||
"voucher_type": "Bank Voucher"
|
||||
"sales_invoice": cur_frm.doc.name
|
||||
},
|
||||
callback: function(r) {
|
||||
cur_frm.cscript.make_jv(cur_frm.doc, null, null, r.message);
|
||||
var doclist = wn.model.sync(r.message);
|
||||
wn.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -390,34 +424,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn){
|
||||
refresh_field(cur_frm.cscript.fname);
|
||||
}
|
||||
|
||||
// Make Journal Voucher
|
||||
// --------------------
|
||||
cur_frm.cscript.make_jv = function(doc, dt, dn, bank_account) {
|
||||
var jv = wn.model.make_new_doc_and_get_name('Journal Voucher');
|
||||
jv = locals['Journal Voucher'][jv];
|
||||
jv.voucher_type = 'Bank Voucher';
|
||||
|
||||
jv.company = doc.company;
|
||||
jv.remark = repl('Payment received against invoice %(vn)s for %(rem)s', {vn:doc.name, rem:doc.remarks});
|
||||
jv.fiscal_year = doc.fiscal_year;
|
||||
|
||||
// debit to creditor
|
||||
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
|
||||
d1.account = doc.debit_to;
|
||||
d1.credit = doc.outstanding_amount;
|
||||
d1.against_invoice = doc.name;
|
||||
|
||||
|
||||
// credit to bank
|
||||
var d1 = wn.model.add_child(jv, 'Journal Voucher Detail', 'entries');
|
||||
d1.account = bank_account.account;
|
||||
d1.debit = doc.outstanding_amount;
|
||||
d1.balance = bank_account.balance;
|
||||
|
||||
loaddoc('Journal Voucher', jv.name);
|
||||
}
|
||||
|
||||
|
||||
cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
|
||||
if(cint(wn.boot.notification_settings.sales_invoice)) {
|
||||
cur_frm.email_doc(wn.boot.notification_settings.sales_invoice_message);
|
||||
|
@ -65,9 +65,6 @@ class DocType(SellingController):
|
||||
self.validate_write_off_account()
|
||||
|
||||
if cint(self.doc.update_stock):
|
||||
sl = get_obj('Stock Ledger')
|
||||
sl.validate_serial_no(self, 'entries')
|
||||
sl.validate_serial_no(self, 'packing_details')
|
||||
self.validate_item_code()
|
||||
self.update_current_stock()
|
||||
self.validate_delivery_note()
|
||||
@ -84,15 +81,9 @@ class DocType(SellingController):
|
||||
"delivery_note_details")
|
||||
|
||||
def on_submit(self):
|
||||
if cint(self.doc.update_stock) == 1:
|
||||
sl_obj = get_obj("Stock Ledger")
|
||||
sl_obj.validate_serial_no_warehouse(self, 'entries')
|
||||
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
|
||||
|
||||
sl_obj.update_serial_record(self, 'entries', is_submit = 1, is_incoming = 0)
|
||||
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
|
||||
|
||||
if cint(self.doc.update_stock) == 1:
|
||||
self.update_stock_ledger(update_stock=1)
|
||||
self.update_serial_nos()
|
||||
else:
|
||||
# Check for Approving Authority
|
||||
if not self.doc.recurring_id:
|
||||
@ -120,11 +111,8 @@ class DocType(SellingController):
|
||||
|
||||
def on_cancel(self):
|
||||
if cint(self.doc.update_stock) == 1:
|
||||
sl = get_obj('Stock Ledger')
|
||||
sl.update_serial_record(self, 'entries', is_submit = 0, is_incoming = 0)
|
||||
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
|
||||
|
||||
self.update_stock_ledger(update_stock = -1)
|
||||
self.update_serial_nos(cancel = True)
|
||||
|
||||
sales_com_obj = get_obj(dt = 'Sales Common')
|
||||
sales_com_obj.check_stop_sales_order(self)
|
||||
@ -162,12 +150,22 @@ class DocType(SellingController):
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.doc.debit_to:
|
||||
self.doc.debit_to = self.get_customer_account()
|
||||
if not self.doc.due_date:
|
||||
self.doc.due_date = self.get_due_date()
|
||||
|
||||
super(DocType, self).set_missing_values(for_validate)
|
||||
|
||||
def set_customer_defaults(self):
|
||||
# TODO cleanup these methods
|
||||
self.doc.fields.update(self.get_debit_to())
|
||||
self.get_cust_and_due_date()
|
||||
if self.doc.customer:
|
||||
self.doc.debit_to = self.get_customer_account()
|
||||
elif self.doc.debit_to:
|
||||
self.doc.customer = webnotes.conn.get_value('Account', self.doc.debit_to, 'master_name')
|
||||
|
||||
self.doc.due_date = self.get_due_date()
|
||||
|
||||
super(DocType, self).set_customer_defaults()
|
||||
|
||||
@ -243,27 +241,24 @@ class DocType(SellingController):
|
||||
You must first create it from the Customer Master" %
|
||||
(self.doc.customer, self.doc.company))
|
||||
|
||||
def get_debit_to(self):
|
||||
acc_head = self.get_customer_account()
|
||||
return acc_head and {'debit_to' : acc_head} or {}
|
||||
|
||||
|
||||
def get_cust_and_due_date(self):
|
||||
def get_due_date(self):
|
||||
"""Set Due Date = Posting Date + Credit Days"""
|
||||
due_date = None
|
||||
if self.doc.posting_date:
|
||||
credit_days = 0
|
||||
if self.doc.debit_to:
|
||||
credit_days = webnotes.conn.get_value("Account", self.doc.debit_to, "credit_days")
|
||||
if self.doc.customer and not credit_days:
|
||||
credit_days = webnotes.conn.get_value("Customer", self.doc.customer, "credit_days")
|
||||
if self.doc.company and not credit_days:
|
||||
credit_days = webnotes.conn.get_value("Company", self.doc.company, "credit_days")
|
||||
|
||||
if credit_days:
|
||||
self.doc.due_date = add_days(self.doc.posting_date, credit_days)
|
||||
due_date = add_days(self.doc.posting_date, credit_days)
|
||||
else:
|
||||
self.doc.due_date = self.doc.posting_date
|
||||
|
||||
if self.doc.debit_to:
|
||||
self.doc.customer = webnotes.conn.get_value('Account',self.doc.debit_to,'master_name')
|
||||
due_date = self.doc.posting_date
|
||||
|
||||
return due_date
|
||||
|
||||
def get_barcode_details(self, barcode):
|
||||
return get_obj('Sales Common').get_barcode_details(barcode)
|
||||
@ -487,10 +482,6 @@ class DocType(SellingController):
|
||||
|
||||
def make_packing_list(self):
|
||||
get_obj('Sales Common').make_packing_list(self,'entries')
|
||||
sl = get_obj('Stock Ledger')
|
||||
sl.scrub_serial_nos(self)
|
||||
sl.scrub_serial_nos(self, 'packing_details')
|
||||
|
||||
|
||||
def on_update(self):
|
||||
if cint(self.doc.update_stock) == 1:
|
||||
@ -502,18 +493,20 @@ class DocType(SellingController):
|
||||
if not d.warehouse:
|
||||
d.warehouse = cstr(w)
|
||||
|
||||
if flt(self.doc.paid_amount) == 0:
|
||||
if self.doc.cash_bank_account:
|
||||
webnotes.conn.set(self.doc, 'paid_amount',
|
||||
(flt(self.doc.grand_total) - flt(self.doc.write_off_amount)))
|
||||
else:
|
||||
# show message that the amount is not paid
|
||||
webnotes.conn.set(self.doc,'paid_amount',0)
|
||||
webnotes.msgprint("Note: Payment Entry will not be created since 'Cash/Bank Account' was not specified.")
|
||||
|
||||
self.make_packing_list()
|
||||
else:
|
||||
self.doclist = self.doc.clear_table(self.doclist, 'packing_details')
|
||||
|
||||
if cint(self.doc.is_pos) == 1:
|
||||
if flt(self.doc.paid_amount) == 0:
|
||||
if self.doc.cash_bank_account:
|
||||
webnotes.conn.set(self.doc, 'paid_amount',
|
||||
(flt(self.doc.grand_total) - flt(self.doc.write_off_amount)))
|
||||
else:
|
||||
# show message that the amount is not paid
|
||||
webnotes.conn.set(self.doc,'paid_amount',0)
|
||||
webnotes.msgprint("Note: Payment Entry will not be created since 'Cash/Bank Account' was not specified.")
|
||||
else:
|
||||
webnotes.conn.set(self.doc,'paid_amount',0)
|
||||
|
||||
def check_prev_docstatus(self):
|
||||
@ -556,9 +549,7 @@ class DocType(SellingController):
|
||||
self.values = []
|
||||
items = get_obj('Sales Common').get_item_list(self)
|
||||
for d in items:
|
||||
stock_item = webnotes.conn.sql("SELECT is_stock_item, is_sample_item \
|
||||
FROM tabItem where name = '%s'"%(d['item_code']), as_dict = 1)
|
||||
if stock_item[0]['is_stock_item'] == "Yes":
|
||||
if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes":
|
||||
if not d['warehouse']:
|
||||
msgprint("Message: Please enter Warehouse for item %s as it is stock item." \
|
||||
% d['item_code'], raise_exception=1)
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-05-24 19:29:05",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-09 14:45:42",
|
||||
"modified": "2013-08-31 10:19:01",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -179,7 +179,6 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"description": "Enter the date by which payments from customer is expected against this invoice.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "due_date",
|
||||
|
@ -644,7 +644,61 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
count = no_of_months == 12 and 3 or 13
|
||||
for i in xrange(count):
|
||||
base_si = _test(i)
|
||||
|
||||
def test_serialized(self):
|
||||
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||
|
||||
si = webnotes.bean(copy=test_records[0])
|
||||
si.doc.update_stock = 1
|
||||
si.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
si.doclist[1].qty = 1
|
||||
si.doclist[1].serial_no = serial_nos[0]
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||
"delivery_document_no"), si.doc.name)
|
||||
|
||||
return si
|
||||
|
||||
def test_serialized_cancel(self):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||
si = self.test_serialized()
|
||||
si.cancel()
|
||||
|
||||
serial_nos = get_serial_nos(si.doclist[1].serial_no)
|
||||
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||
"delivery_document_no"))
|
||||
|
||||
def test_serialize_status(self):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
|
||||
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||
|
||||
sr = webnotes.bean("Serial No", serial_nos[0])
|
||||
sr.doc.status = "Not Available"
|
||||
sr.save()
|
||||
|
||||
si = webnotes.bean(copy=test_records[0])
|
||||
si.doc.update_stock = 1
|
||||
si.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
si.doclist[1].qty = 1
|
||||
si.doclist[1].serial_no = serial_nos[0]
|
||||
si.insert()
|
||||
|
||||
self.assertRaises(SerialNoStatusError, si.submit)
|
||||
|
||||
test_dependencies = ["Journal Voucher", "POS Setting", "Contact", "Address"]
|
||||
|
||||
test_records = [
|
||||
@ -916,7 +970,6 @@ test_records = [
|
||||
"item_name": "_Test Item Home Desktop 100",
|
||||
"qty": 10,
|
||||
"ref_rate": 62.5,
|
||||
"export_rate": 62.5,
|
||||
"stock_uom": "_Test UOM",
|
||||
"item_tax_rate": json.dumps({"_Test Account Excise Duty - _TC": 10}),
|
||||
"income_account": "Sales - _TC",
|
||||
@ -930,7 +983,6 @@ test_records = [
|
||||
"item_name": "_Test Item Home Desktop 200",
|
||||
"qty": 5,
|
||||
"ref_rate": 190.66,
|
||||
"export_rate": 190.66,
|
||||
"stock_uom": "_Test UOM",
|
||||
"income_account": "Sales - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-01-27 16:30:52",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-11 14:41:59",
|
||||
"modified": "2013-08-14 12:47:45",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -19,14 +19,18 @@
|
||||
"name": "__common__",
|
||||
"parent": "Financial Statements",
|
||||
"parentfield": "roles",
|
||||
"parenttype": "Page",
|
||||
"role": "Accounts Manager"
|
||||
"parenttype": "Page"
|
||||
},
|
||||
{
|
||||
"doctype": "Page",
|
||||
"name": "Financial Statements"
|
||||
},
|
||||
{
|
||||
"doctype": "Page Role"
|
||||
"doctype": "Page Role",
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"doctype": "Page Role",
|
||||
"role": "Analytics"
|
||||
}
|
||||
]
|
@ -141,13 +141,13 @@ erpnext.GeneralLedger = wn.views.GridReport.extend({
|
||||
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"));
|
||||
// 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;
|
||||
@ -389,7 +389,8 @@ erpnext.GeneralLedger = wn.views.GridReport.extend({
|
||||
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() }
|
||||
max: dateutil.str_to_obj(this.to_date).getTime() },
|
||||
series: { downsample: { threshold: 1000 } }
|
||||
}
|
||||
},
|
||||
});
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import flt
|
||||
from stock.utils import get_buying_amount
|
||||
from stock.utils import get_buying_amount, get_sales_bom_buying_amount
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
@ -21,10 +21,15 @@ def execute(filters=None):
|
||||
data = []
|
||||
for row in source:
|
||||
selling_amount = flt(row.amount)
|
||||
|
||||
buying_amount = get_buying_amount(row.item_code, row.parenttype, row.name, row.item_row,
|
||||
stock_ledger_entries.get((row.item_code, row.warehouse), []),
|
||||
item_sales_bom.get(row.parenttype, {}).get(row.name, webnotes._dict()))
|
||||
|
||||
item_sales_bom_map = item_sales_bom.get(row.parenttype, {}).get(row.name, webnotes._dict())
|
||||
|
||||
if item_sales_bom_map.get(row.item_code):
|
||||
buying_amount = get_sales_bom_buying_amount(row.item_code, row.warehouse,
|
||||
row.parenttype, row.name, row.item_row, stock_ledger_entries, item_sales_bom_map)
|
||||
else:
|
||||
buying_amount = get_buying_amount(row.parenttype, row.name, row.item_row,
|
||||
stock_ledger_entries.get((row.item_code, row.warehouse), []))
|
||||
|
||||
buying_amount = buying_amount > 0 and buying_amount or 0
|
||||
|
||||
|
@ -20,8 +20,7 @@ class DocType(BuyingController):
|
||||
|
||||
def is_item_table_empty(self, obj):
|
||||
if not len(obj.doclist.get({"parentfield": obj.fname})):
|
||||
msgprint(_("Hey there! You need to put at least one item in \
|
||||
the item table."), raise_exception=True)
|
||||
msgprint(_("You need to put at least one item in the item table."), raise_exception=True)
|
||||
|
||||
def get_supplier_details(self, name = ''):
|
||||
details = sql("select supplier_name,address from `tabSupplier` where name = '%s' and docstatus != 2" %(name), as_dict = 1)
|
||||
|
@ -46,10 +46,10 @@ cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) {
|
||||
if (doc.item_code) {
|
||||
filter = {
|
||||
'item_code': doc.item_code,
|
||||
'status': "In Store"
|
||||
'status': "Available"
|
||||
}
|
||||
} else
|
||||
filter = { 'status': "In Store" }
|
||||
filter = { 'status': "Available" }
|
||||
|
||||
return { filters: filter }
|
||||
}
|
@ -110,6 +110,11 @@ wn.module_page["Buying"] = [
|
||||
right: true,
|
||||
icon: "icon-list",
|
||||
items: [
|
||||
{
|
||||
"label":wn._("Items To Be Requested"),
|
||||
route: "query-report/Items To Be Requested",
|
||||
doctype: "Item"
|
||||
},
|
||||
{
|
||||
"label":wn._("Requested Items To Be Ordered"),
|
||||
route: "query-report/Requested Items To Be Ordered",
|
||||
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"app_name": "ERPNext",
|
||||
"modules": {
|
||||
"Selling": {
|
||||
"link": "selling-home",
|
||||
@ -129,7 +130,7 @@
|
||||
},
|
||||
"writers": {
|
||||
"template": "app/website/templates/pages/writers",
|
||||
"args_method": "website.helpers.blog.get_writers_args"
|
||||
"args_method": "website.doctype.blogger.blogger.get_writers_args"
|
||||
},
|
||||
"profile": {
|
||||
"no_cache": true,
|
||||
|
@ -75,7 +75,7 @@ class AccountsController(TransactionBase):
|
||||
self.doc.conversion_rate = self.doc.plc_conversion_rate
|
||||
|
||||
if self.meta.get_field("currency"):
|
||||
if self.doc.currency != company_currency:
|
||||
if self.doc.currency and self.doc.currency != company_currency:
|
||||
if not self.doc.conversion_rate:
|
||||
exchange = self.doc.currency + "-" + company_currency
|
||||
self.doc.conversion_rate = flt(webnotes.conn.get_value("Currency Exchange",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes import _, msgprint
|
||||
from webnotes.utils import flt
|
||||
from webnotes.utils import flt, _round
|
||||
|
||||
from buying.utils import get_item_details
|
||||
from setup.utils import get_company_currency
|
||||
@ -17,7 +17,6 @@ class BuyingController(StockController):
|
||||
def onload_post_render(self):
|
||||
# contact, address, item details
|
||||
self.set_missing_values()
|
||||
self.set_taxes("purchase_tax_details", "purchase_other_charges")
|
||||
|
||||
def validate(self):
|
||||
super(BuyingController, self).validate()
|
||||
@ -40,6 +39,8 @@ class BuyingController(StockController):
|
||||
self.doc.fields[fieldname] = val
|
||||
|
||||
self.set_missing_item_details(get_item_details)
|
||||
if self.doc.fields.get("__islocal"):
|
||||
self.set_taxes("purchase_tax_details", "purchase_other_charges")
|
||||
|
||||
def set_supplier_from_item_default(self):
|
||||
if self.meta.get_field("supplier") and not self.doc.supplier:
|
||||
@ -98,7 +99,7 @@ class BuyingController(StockController):
|
||||
|
||||
if item.discount_rate == 100.0:
|
||||
item.import_rate = 0.0
|
||||
elif item.import_ref_rate:
|
||||
elif not item.import_rate:
|
||||
item.import_rate = flt(item.import_ref_rate * (1.0 - (item.discount_rate / 100.0)),
|
||||
self.precision("import_rate", item))
|
||||
|
||||
@ -129,10 +130,10 @@ class BuyingController(StockController):
|
||||
self.precision("total_tax"))
|
||||
|
||||
if self.meta.get_field("rounded_total"):
|
||||
self.doc.rounded_total = round(self.doc.grand_total)
|
||||
self.doc.rounded_total = _round(self.doc.grand_total)
|
||||
|
||||
if self.meta.get_field("rounded_total_import"):
|
||||
self.doc.rounded_total_import = round(self.doc.grand_total_import)
|
||||
self.doc.rounded_total_import = _round(self.doc.grand_total_import)
|
||||
|
||||
def calculate_outstanding_amount(self):
|
||||
if self.doc.doctype == "Purchase Invoice" and self.doc.docstatus < 2:
|
||||
|
@ -16,11 +16,14 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
if(doc.__islocal) {
|
||||
var last_route = wn.route_history.slice(-2, -1)[0];
|
||||
if(last_route && last_route[0]==="Form") {
|
||||
var doctype = last_route[1],
|
||||
docname = last_route.slice(2).join("/");
|
||||
|
||||
if(["Customer", "Quotation", "Sales Order", "Sales Invoice", "Delivery Note",
|
||||
"Installation Note", "Opportunity", "Customer Issue", "Maintenance Visit",
|
||||
"Maintenance Schedule"]
|
||||
.indexOf(last_route[1])!==-1) {
|
||||
var refdoc = wn.model.get_doc(last_route[1], last_route[2]);
|
||||
.indexOf(doctype)!==-1) {
|
||||
var refdoc = wn.model.get_doc(doctype, docname);
|
||||
|
||||
if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Customer" : true) {
|
||||
cur_frm.set_value("customer", refdoc.customer || refdoc.name);
|
||||
@ -30,16 +33,16 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
}
|
||||
}
|
||||
if(["Supplier", "Supplier Quotation", "Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
||||
.indexOf(last_route[1])!==-1) {
|
||||
var refdoc = wn.model.get_doc(last_route[1], last_route[2]);
|
||||
.indexOf(doctype)!==-1) {
|
||||
var refdoc = wn.model.get_doc(doctype, docname);
|
||||
cur_frm.set_value("supplier", refdoc.supplier || refdoc.name);
|
||||
cur_frm.set_value("supplier_name", refdoc.supplier_name);
|
||||
if(cur_frm.doc.doctype==="Address")
|
||||
cur_frm.set_value("address_title", cur_frm.doc.supplier_name);
|
||||
}
|
||||
if(["Lead", "Quotation"]
|
||||
.indexOf(last_route[1])!==-1) {
|
||||
var refdoc = wn.model.get_doc(last_route[1], last_route[2]);
|
||||
.indexOf(doctype)!==-1) {
|
||||
var refdoc = wn.model.get_doc(doctype, docname);
|
||||
|
||||
if(refdoc.doctype == "Quotation" ? refdoc.quotation_to=="Lead" : true) {
|
||||
cur_frm.set_value("lead", refdoc.lead || refdoc.name);
|
||||
|
@ -207,4 +207,4 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
"fcond": get_filters_cond(doctype, filters, []),
|
||||
"mcond": get_match_cond(doctype),
|
||||
"start": "%(start)s", "page_len": "%(page_len)s", "txt": "%(txt)s"
|
||||
}, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) }, debug=True)
|
||||
}, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) })
|
@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import cint, flt, comma_or
|
||||
from webnotes.utils import cint, flt, comma_or, _round, add_days, cstr
|
||||
from setup.utils import get_company_currency
|
||||
from selling.utils import get_item_details
|
||||
from webnotes import msgprint, _
|
||||
@ -15,18 +15,14 @@ class SellingController(StockController):
|
||||
# contact, address, item details and pos details (if applicable)
|
||||
self.set_missing_values()
|
||||
|
||||
self.set_taxes("other_charges", "charge")
|
||||
|
||||
if self.meta.get_field("debit_to") and not self.doc.debit_to:
|
||||
self.doc.debit_to = self.get_debit_to().get("debit_to")
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
super(SellingController, self).set_missing_values(for_validate)
|
||||
|
||||
# set contact and address details for customer, if they are not mentioned
|
||||
self.set_missing_lead_customer_details()
|
||||
|
||||
self.set_price_list_and_item_details()
|
||||
if self.doc.fields.get("__islocal"):
|
||||
self.set_taxes("other_charges", "charge")
|
||||
|
||||
def set_missing_lead_customer_details(self):
|
||||
if self.doc.customer:
|
||||
@ -89,7 +85,7 @@ class SellingController(StockController):
|
||||
self.doc.grand_total_export or self.doc.rounded_total_export, self.doc.currency)
|
||||
|
||||
def set_buying_amount(self, stock_ledger_entries = None):
|
||||
from stock.utils import get_buying_amount
|
||||
from stock.utils import get_buying_amount, get_sales_bom_buying_amount
|
||||
if not stock_ledger_entries:
|
||||
stock_ledger_entries = self.get_stock_ledger_entries()
|
||||
|
||||
@ -103,13 +99,18 @@ class SellingController(StockController):
|
||||
for item in self.doclist.get({"parentfield": self.fname}):
|
||||
if item.item_code in self.stock_items or \
|
||||
(item_sales_bom and item_sales_bom.get(item.item_code)):
|
||||
buying_amount = get_buying_amount(item.item_code, self.doc.doctype, self.doc.name, item.name,
|
||||
stock_ledger_entries.get((item.item_code, item.warehouse), []),
|
||||
item_sales_bom)
|
||||
if item.item_code in self.stock_items:
|
||||
buying_amount = get_buying_amount(self.doc.doctype, self.doc.name,
|
||||
item.name, stock_ledger_entries.get((item.item_code,
|
||||
item.warehouse), []))
|
||||
elif item_sales_bom and item_sales_bom.get(item.item_code):
|
||||
buying_amount = get_sales_bom_buying_amount(item.item_code, item.warehouse,
|
||||
self.doc.doctype, self.doc.name, item.name, stock_ledger_entries,
|
||||
item_sales_bom)
|
||||
|
||||
item.buying_amount = buying_amount >= 0.01 and buying_amount or 0
|
||||
webnotes.conn.set_value(item.doctype, item.name, "buying_amount",
|
||||
item.buying_amount)
|
||||
item.buying_amount = buying_amount >= 0.01 and buying_amount or 0
|
||||
webnotes.conn.set_value(item.doctype, item.name, "buying_amount",
|
||||
item.buying_amount)
|
||||
|
||||
def check_expense_account(self, item):
|
||||
if item.buying_amount and not item.expense_account:
|
||||
@ -191,7 +192,7 @@ class SellingController(StockController):
|
||||
|
||||
if item.adj_rate == 100:
|
||||
item.export_rate = 0
|
||||
elif item.ref_rate:
|
||||
elif not item.export_rate:
|
||||
item.export_rate = flt(item.ref_rate * (1.0 - (item.adj_rate / 100.0)),
|
||||
self.precision("export_rate", item))
|
||||
|
||||
@ -222,8 +223,8 @@ class SellingController(StockController):
|
||||
self.doc.other_charges_total_export = flt(self.doc.grand_total_export - self.doc.net_total_export,
|
||||
self.precision("other_charges_total_export"))
|
||||
|
||||
self.doc.rounded_total = round(self.doc.grand_total)
|
||||
self.doc.rounded_total_export = round(self.doc.grand_total_export)
|
||||
self.doc.rounded_total = _round(self.doc.grand_total)
|
||||
self.doc.rounded_total_export = _round(self.doc.grand_total_export)
|
||||
|
||||
def calculate_outstanding_amount(self):
|
||||
# NOTE:
|
||||
@ -271,3 +272,32 @@ class SellingController(StockController):
|
||||
msgprint(_(self.meta.get_label("order_type")) + " " +
|
||||
_("must be one of") + ": " + comma_or(valid_types),
|
||||
raise_exception=True)
|
||||
|
||||
def update_serial_nos(self, cancel=False):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
|
||||
update_serial_nos_after_submit(self, self.doc.doctype, self.fname)
|
||||
update_serial_nos_after_submit(self, self.doc.doctype, "packing_details")
|
||||
|
||||
for table_fieldname in (self.fname, "packing_details"):
|
||||
for d in self.doclist.get({"parentfield": table_fieldname}):
|
||||
for serial_no in get_serial_nos(d.serial_no):
|
||||
sr = webnotes.bean("Serial No", serial_no)
|
||||
if cancel:
|
||||
sr.doc.status = "Available"
|
||||
for fieldname in ("warranty_expiry_date", "delivery_document_type",
|
||||
"delivery_document_no", "delivery_date", "delivery_time", "customer",
|
||||
"customer_name"):
|
||||
sr.doc.fields[fieldname] = None
|
||||
else:
|
||||
sr.doc.delivery_document_type = self.doc.doctype
|
||||
sr.doc.delivery_document_no = self.doc.name
|
||||
sr.doc.delivery_date = self.doc.posting_date
|
||||
sr.doc.delivery_time = self.doc.posting_time
|
||||
sr.doc.customer = self.doc.customer
|
||||
sr.doc.customer_name = self.doc.customer_name
|
||||
if sr.doc.warranty_period:
|
||||
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date),
|
||||
cint(sr.doc.warranty_period))
|
||||
sr.doc.status = 'Delivered'
|
||||
|
||||
sr.save()
|
||||
|
@ -1,14 +0,0 @@
|
||||
---
|
||||
{
|
||||
"_label": "Get Involved"
|
||||
}
|
||||
---
|
||||
If you are an ERPNext user:
|
||||
|
||||
[https://groups.google.com/group/erpnext-user-forum](https://groups.google.com/group/erpnext-user-forum)
|
||||
|
||||
If you are an ERPNext developer:
|
||||
|
||||
[https://groups.google.com/group/erpnext-developer-forum](https://groups.google.com/group/erpnext-developer-forum)
|
||||
|
||||
|
23
docs/docs.md
23
docs/docs.md
@ -7,28 +7,25 @@
|
||||
"docs.dev",
|
||||
"docs.download",
|
||||
"docs.community",
|
||||
"docs.blog",
|
||||
"docs.about"
|
||||
"docs.blog"
|
||||
],
|
||||
"_no_toc": 1
|
||||
}
|
||||
---
|
||||
<div style="margin: 10px 0px">
|
||||
<h1 style="text-align: center">All-in-One Platform to Manage Your Organization.</h1>
|
||||
<h3 style="text-align: center; font-weight: normal; color: #888">100% Free and Open Source.</h1>
|
||||
<div class="text-center" style="margin: 10px 0px">
|
||||
<h1>ERPNext Docs (beta)</h1>
|
||||
<h3 class="text-muted">Open Source ERP Built for The Web.</h3>
|
||||
<p>For the main site, go to <a href="https://erpnext.com/">https://erpnext.com</a></p>
|
||||
</div>
|
||||
|
||||

|
||||
|
||||
Welcome to the ERPNext Documentation + Community Site
|
||||
|
||||
### What is ERPNext?
|
||||
|
||||
ERPNext is an information system that links together an entire organization's operations. It is a software package that offers convenience of managing all the business functions from a single platform. No need of going to different applications to process different requests. No need of saving data in different functional packages. Under one ERP "roof" you can manage Accounting, Warehouse Management, CRM, Human Resources, Supply Chain Management, Sales Management, and Website Design.
|
||||
ERPNext is an Open Source integrated app (that manages Financial Accounting, Inventory, CRM) that is built grounds up for the web, using some of the latest web technologies and frameworks. ERPNext helps your organization manage Financial Accounting, Inventory, Sales, Purchase, Payroll, Customer Support, E-Commerce all in one platform. Learn more at [https://erpnext.com](https://erpnext.com)
|
||||
|
||||
ERPNext is written by Web Notes Technologies keeping small and medium businesses in mind.
|
||||
### Site Contents
|
||||
|
||||
- It gives better access to crucial information as a whole rather than in fragments of different versions.
|
||||
- It provides comparable financial reports.
|
||||
- It avoids duplication of reports and redundant data.
|
||||
- It allows better alignment across cross-functional departments.
|
||||
- It facilitates Website Design and provides shopping cart facility.
|
||||
- It gives better deployment on mobiles, tablets, desktops and large screens.
|
||||
This site contains the full User and Developer Documentation for ERPNext. This is still a work-in-progress. Please feel free to contribute issues and documentation.
|
||||
|
@ -156,20 +156,23 @@ class DocType:
|
||||
raise_exception=InvalidLeaveApproverError)
|
||||
|
||||
def update_dob_event(self):
|
||||
if self.doc.date_of_birth:
|
||||
get_events = webnotes.conn.sql("""select name from `tabEvent` where repeat_on='Every Year'
|
||||
if self.doc.status == "Active" and self.doc.date_of_birth:
|
||||
birthday_event = webnotes.conn.sql("""select name from `tabEvent` where repeat_on='Every Year'
|
||||
and ref_type='Employee' and ref_name=%s""", self.doc.name)
|
||||
|
||||
starts_on = self.doc.date_of_birth + " 00:00:00"
|
||||
ends_on = self.doc.date_of_birth + " 00:15:00"
|
||||
|
||||
if get_events:
|
||||
webnotes.conn.sql("""update `tabEvent` set starts_on=%s, ends_on=%s
|
||||
where name=%s""", (starts_on, ends_on, get_events[0][0]))
|
||||
if birthday_event:
|
||||
event = webnotes.bean("Event", birthday_event[0][0])
|
||||
event.doc.starts_on = starts_on
|
||||
event.doc.ends_on = ends_on
|
||||
event.save()
|
||||
else:
|
||||
webnotes.bean({
|
||||
"doctype": "Event",
|
||||
"subject": _("Birthday") + ": " + self.doc.employee_name,
|
||||
"description": _("Happy Birthday!") + " " + self.doc.employee_name,
|
||||
"starts_on": starts_on,
|
||||
"ends_on": ends_on,
|
||||
"event_type": "Public",
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
from webnotes.utils import add_days, cint, cstr, flt, getdate, nowdate
|
||||
from webnotes.utils import add_days, cint, cstr, flt, getdate, nowdate, _round
|
||||
from webnotes.model.doc import make_autoname
|
||||
from webnotes.model.bean import getlist
|
||||
from webnotes.model.code import get_obj
|
||||
@ -164,7 +164,7 @@ class DocType(TransactionBase):
|
||||
self.doc.gross_pay = flt(self.doc.arrear_amount) + flt(self.doc.leave_encashment_amount)
|
||||
for d in self.doclist.get({"parentfield": "earning_details"}):
|
||||
if cint(d.e_depends_on_lwp) == 1:
|
||||
d.e_modified_amount = round(flt(d.e_amount) * flt(self.doc.payment_days)
|
||||
d.e_modified_amount = _round(flt(d.e_amount) * flt(self.doc.payment_days)
|
||||
/ cint(self.doc.total_days_in_month), 2)
|
||||
elif not self.doc.payment_days:
|
||||
d.e_modified_amount = 0
|
||||
@ -176,7 +176,7 @@ class DocType(TransactionBase):
|
||||
self.doc.total_deduction = 0
|
||||
for d in getlist(self.doclist, 'deduction_details'):
|
||||
if cint(d.d_depends_on_lwp) == 1:
|
||||
d.d_modified_amount = round(flt(d.d_amount) * flt(self.doc.payment_days)
|
||||
d.d_modified_amount = _round(flt(d.d_amount) * flt(self.doc.payment_days)
|
||||
/ cint(self.doc.total_days_in_month), 2)
|
||||
elif not self.doc.payment_days:
|
||||
d.d_modified_amount = 0
|
||||
@ -189,7 +189,7 @@ class DocType(TransactionBase):
|
||||
self.calculate_earning_total()
|
||||
self.calculate_ded_total()
|
||||
self.doc.net_pay = flt(self.doc.gross_pay) - flt(self.doc.total_deduction)
|
||||
self.doc.rounded_total = round(self.doc.net_pay)
|
||||
self.doc.rounded_total = _round(self.doc.net_pay)
|
||||
|
||||
def on_submit(self):
|
||||
if(self.doc.email_check == 1):
|
||||
|
@ -51,7 +51,7 @@ def validate_install():
|
||||
distribution = platform.linux_distribution()[0].lower().replace('"', '')
|
||||
print "Distribution = ", distribution
|
||||
is_redhat = distribution in ("redhat", "centos", "centos linux", "fedora")
|
||||
is_debian = distribution in ("debian", "ubuntu", "elementary os")
|
||||
is_debian = distribution in ("debian", "ubuntu", "elementary os", "linuxmint")
|
||||
|
||||
if not (is_redhat or is_debian):
|
||||
raise Exception, "Sorry! This installer works only with yum or apt-get package management"
|
||||
@ -59,7 +59,7 @@ def validate_install():
|
||||
return is_redhat, is_debian
|
||||
|
||||
def install_using_yum():
|
||||
packages = "python python-setuptools MySQL-python httpd git memcached ntp vim-enhanced screen"
|
||||
packages = "python python-setuptools gcc python-devel MySQL-python httpd git memcached ntp vim-enhanced screen"
|
||||
|
||||
print "-"*80
|
||||
print "Installing Packages: (This may take some time)"
|
||||
@ -108,7 +108,7 @@ def update_config_for_redhat():
|
||||
|
||||
def install_using_apt():
|
||||
exec_in_shell("apt-get update")
|
||||
packages = "python python-setuptools python-mysqldb apache2 git memcached ntp vim screen htop"
|
||||
packages = "python python-setuptools python-dev build-essential python-pip python-mysqldb apache2 git memcached ntp vim screen htop"
|
||||
print "-"*80
|
||||
print "Installing Packages: (This may take some time)"
|
||||
print packages
|
||||
@ -145,7 +145,11 @@ def install_python_modules():
|
||||
print python_modules
|
||||
print "-"*80
|
||||
|
||||
exec_in_shell("easy_install pip")
|
||||
if not exec_in_shell("which pip"):
|
||||
exec_in_shell("easy_install pip")
|
||||
|
||||
exec_in_shell("pip install --upgrade pip")
|
||||
exec_in_shell("pip install --upgrade virtualenv")
|
||||
exec_in_shell("pip install -q %s" % python_modules)
|
||||
|
||||
def install_erpnext(install_path):
|
||||
|
@ -143,11 +143,18 @@ def get_item_details(item):
|
||||
def make_stock_entry(production_order_id, purpose):
|
||||
production_order = webnotes.bean("Production Order", production_order_id)
|
||||
|
||||
# validate already existing
|
||||
ste = webnotes.conn.get_value("Stock Entry", {
|
||||
"production_order":production_order_id,
|
||||
"purpose": purpose
|
||||
}, "name")
|
||||
|
||||
stock_entry = webnotes.new_bean("Stock Entry")
|
||||
stock_entry.doc.purpose = purpose
|
||||
stock_entry.doc.production_order = production_order_id
|
||||
stock_entry.doc.company = production_order.doc.company
|
||||
stock_entry.doc.bom_no = production_order.doc.bom_no
|
||||
stock_entry.doc.use_multi_level_bom = production_order.doc.use_multi_level_bom
|
||||
stock_entry.doc.fg_completed_qty = flt(production_order.doc.qty) - flt(production_order.doc.produced_qty)
|
||||
|
||||
if purpose=="Material Transfer":
|
||||
@ -156,4 +163,5 @@ def make_stock_entry(production_order_id, purpose):
|
||||
stock_entry.doc.from_warehouse = production_order.doc.wip_warehouse
|
||||
stock_entry.doc.to_warehouse = production_order.doc.fg_warehouse
|
||||
|
||||
stock_entry.run_method("get_items")
|
||||
return [d.fields for d in stock_entry.doclist]
|
||||
|
@ -1,10 +0,0 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
def execute():
|
||||
import webnotes
|
||||
from webnotes.modules import reload_doc
|
||||
reload_doc('stock', 'doctype', 'serial_no')
|
||||
|
||||
webnotes.conn.sql("update `tabSerial No` set sle_exists = 1")
|
49
patches/august_2013/fix_fiscal_year.py
Normal file
49
patches/august_2013/fix_fiscal_year.py
Normal file
@ -0,0 +1,49 @@
|
||||
import webnotes
|
||||
|
||||
def execute():
|
||||
create_fiscal_years()
|
||||
|
||||
doctypes = webnotes.conn.sql_list("""select parent from tabDocField
|
||||
where (fieldtype="Link" and options='Fiscal Year')
|
||||
or (fieldtype="Select" and options='link:Fiscal Year')""")
|
||||
|
||||
for dt in doctypes:
|
||||
date_fields = webnotes.conn.sql_list("""select fieldname from tabDocField
|
||||
where parent=%s and fieldtype='Date'""", dt)
|
||||
|
||||
date_field = get_date_field(date_fields, dt)
|
||||
|
||||
if not date_field:
|
||||
print dt, date_field
|
||||
else:
|
||||
webnotes.conn.sql("""update `tab%s` set fiscal_year =
|
||||
if(%s<='2013-06-30', '2012-2013', '2013-2014')""" % (dt, date_field))
|
||||
|
||||
def create_fiscal_years():
|
||||
fiscal_years = {
|
||||
"2012-2013": ["2012-07-01", "2013-06-30"],
|
||||
"2013-2014": ["2013-07-01", "2014-06-30"]
|
||||
}
|
||||
|
||||
for d in fiscal_years:
|
||||
webnotes.bean({
|
||||
"doctype": "Fiscal Year",
|
||||
"year": d,
|
||||
"year_start_date": fiscal_years[d][0],
|
||||
"is_fiscal_year_closed": "No"
|
||||
}).insert()
|
||||
|
||||
|
||||
def get_date_field(date_fields, dt):
|
||||
date_field = None
|
||||
if date_fields:
|
||||
if "posting_date" in date_fields:
|
||||
date_field = "posting_date"
|
||||
elif "transaction_date" in date_fields:
|
||||
date_field = 'transaction_date'
|
||||
else:
|
||||
date_field = date_fields[0]
|
||||
# print dt, date_fields
|
||||
|
||||
return date_field
|
||||
|
@ -5,6 +5,8 @@ from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
def execute():
|
||||
webnotes.reload_doc("website", "doctype", "shopping_cart_price_list")
|
||||
|
||||
for t in [
|
||||
("Supplier Quotation", "price_list_name", "buying_price_list"),
|
||||
("Purchase Order", "price_list_name", "buying_price_list"),
|
||||
@ -23,7 +25,7 @@ def execute():
|
||||
if t[2] in table_columns and t[1] in table_columns:
|
||||
# already reloaded, so copy into new column and drop old column
|
||||
webnotes.conn.sql("""update `tab%s` set `%s`=`%s`""" % (t[0], t[2], t[1]))
|
||||
webnotes.conn.sql("""alter table `tab%s` drop column `%s`""" % (t[0], t[1]))
|
||||
webnotes.conn.sql_ddl("""alter table `tab%s` drop column `%s`""" % (t[0], t[1]))
|
||||
elif t[1] in table_columns:
|
||||
webnotes.conn.sql_ddl("alter table `tab%s` change `%s` `%s` varchar(180)" % t)
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
def execute():
|
||||
for employee in webnotes.conn.sql_list("""select name from `tabEmployee` where ifnull(date_of_birth, '')!=''"""):
|
||||
obj = webnotes.get_obj("Employee", employee)
|
||||
obj.update_dob_event()
|
||||
|
14
patches/august_2013/p05_employee_birthdays.py
Normal file
14
patches/august_2013/p05_employee_birthdays.py
Normal file
@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
def execute():
|
||||
webnotes.reload_doc("core", "doctype", "event")
|
||||
webnotes.conn.sql("""delete from `tabEvent` where repeat_on='Every Year' and ref_type='Employee'""")
|
||||
for employee in webnotes.conn.sql_list("""select name from `tabEmployee` where status='Active' and
|
||||
ifnull(date_of_birth, '')!=''"""):
|
||||
obj = webnotes.get_obj("Employee", employee)
|
||||
obj.update_dob_event()
|
||||
|
5
patches/august_2013/p05_update_serial_no_status.py
Normal file
5
patches/august_2013/p05_update_serial_no_status.py
Normal file
@ -0,0 +1,5 @@
|
||||
import webnotes
|
||||
|
||||
def execute():
|
||||
webnotes.conn.sql("""update `tabSerial No` set status = 'Not Available' where status='Not In Store'""")
|
||||
webnotes.conn.sql("""update `tabSerial No` set status = 'Available' where status='In Store'""")
|
117
patches/august_2013/p06_fix_sle_against_stock_entry.py
Normal file
117
patches/august_2013/p06_fix_sle_against_stock_entry.py
Normal file
@ -0,0 +1,117 @@
|
||||
import webnotes
|
||||
|
||||
cancelled = []
|
||||
uncancelled = []
|
||||
|
||||
def execute():
|
||||
global cancelled, uncancelled
|
||||
stock_entries = webnotes.conn.sql("""select * from `tabStock Entry`
|
||||
where docstatus >= 1 and date(modified) >= "2013-08-16"
|
||||
and ifnull(production_order, '') != '' and ifnull(bom_no, '') != ''
|
||||
order by modified desc, name desc""", as_dict=True)
|
||||
|
||||
for entry in stock_entries:
|
||||
if not webnotes.conn.sql("""select name from `tabStock Entry Detail`
|
||||
where parent=%s""", entry.name):
|
||||
res = webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s
|
||||
and is_cancelled='No'""", entry.name, as_dict=True)
|
||||
if res:
|
||||
make_stock_entry_detail(entry, res)
|
||||
|
||||
if cancelled or uncancelled:
|
||||
send_email()
|
||||
|
||||
def make_stock_entry_detail(entry, res):
|
||||
global cancelled, uncancelled
|
||||
|
||||
fg_item = webnotes.conn.get_value("Production Order", entry.production_order,
|
||||
"production_item")
|
||||
voucher_detail_entries_map = {}
|
||||
for sle in res:
|
||||
voucher_detail_entries_map.setdefault(sle.voucher_detail_no, []).append(sle)
|
||||
|
||||
for i, voucher_detail_no in enumerate(sorted(voucher_detail_entries_map.keys())):
|
||||
sl_entries = voucher_detail_entries_map[voucher_detail_no]
|
||||
# create stock entry details back from stock ledger entries
|
||||
stock_entry_detail = webnotes.doc({
|
||||
"doctype": "Stock Entry Detail",
|
||||
"parentfield": "mtn_details",
|
||||
"parenttype": "Stock Entry",
|
||||
"parent": entry.name,
|
||||
"__islocal": 1,
|
||||
"idx": i+1,
|
||||
"docstatus": 1,
|
||||
"owner": entry.owner,
|
||||
"name": voucher_detail_no,
|
||||
"transfer_qty": abs(sl_entries[0].actual_qty),
|
||||
"qty": abs(sl_entries[0].actual_qty),
|
||||
"stock_uom": sl_entries[0].stock_uom,
|
||||
"uom": sl_entries[0].stock_uom,
|
||||
"conversion_factor": 1,
|
||||
"item_code": sl_entries[0].item_code,
|
||||
"description": webnotes.conn.get_value("Item", sl_entries[0].item_code,
|
||||
"description"),
|
||||
"incoming_rate": sl_entries[0].incoming_rate,
|
||||
"batch_no": sl_entries[0].batch_no,
|
||||
"serial_no": sl_entries[0].serial_no
|
||||
})
|
||||
|
||||
if sl_entries[0].item_code == fg_item:
|
||||
stock_entry_detail.bom_no = entry.bom_no
|
||||
|
||||
for sle in sl_entries:
|
||||
if sle.actual_qty < 0:
|
||||
stock_entry_detail.s_warehouse = sle.warehouse
|
||||
else:
|
||||
stock_entry_detail.t_warehouse = sle.warehouse
|
||||
|
||||
stock_entry_detail.save()
|
||||
|
||||
if entry.docstatus == 2:
|
||||
webnotes.conn.set_value("Stock Entry", entry.name, "docstatus", 1)
|
||||
|
||||
# call for cancelled ones
|
||||
se = webnotes.bean("Stock Entry", entry.name)
|
||||
controller = se.make_controller()
|
||||
controller.update_production_order(1)
|
||||
|
||||
res = webnotes.conn.sql("""select name from `tabStock Entry`
|
||||
where amended_from=%s""", entry.name)
|
||||
if res:
|
||||
cancelled.append(res[0][0])
|
||||
if res[0][0] in uncancelled:
|
||||
uncancelled.remove(res[0][0])
|
||||
|
||||
webnotes.bean("Stock Entry", res[0][0]).cancel()
|
||||
|
||||
uncancelled.append(se.doc.name)
|
||||
|
||||
def send_email():
|
||||
from webnotes.utils.email_lib import sendmail_to_system_managers
|
||||
global cancelled, uncancelled
|
||||
uncancelled = "we have undone the cancellation of the following Stock Entries through a patch:\n" + \
|
||||
"\n".join(uncancelled) if uncancelled else ""
|
||||
cancelled = "and cancelled the following Stock Entries:\n" + "\n".join(cancelled) \
|
||||
if cancelled else ""
|
||||
|
||||
subject = "[ERPNext] [Important] Cancellation undone for some Stock Entries"
|
||||
content = """Dear System Manager,
|
||||
|
||||
An error got introduced into the code that cleared the item table in a Stock Entry associated to a Production Order.
|
||||
|
||||
To undo its effect,
|
||||
%s
|
||||
|
||||
%s
|
||||
|
||||
You will have to edit them again.
|
||||
|
||||
Sorry for the inconvenience this has caused.
|
||||
|
||||
Regards,
|
||||
Team ERPNext.""" % (uncancelled, cancelled)
|
||||
|
||||
# print subject, content
|
||||
|
||||
sendmail_to_system_managers(subject, content)
|
@ -13,7 +13,7 @@ def execute():
|
||||
where account = %s and voucher_type = 'Sales Invoice' and voucher_no = %s
|
||||
and ifnull(is_cancelled, 'No') = 'No' limit 1""", (r.debit_to, r.name), as_dict=1)
|
||||
if gle:
|
||||
diff = round((flt(r.grand_total) - flt(gle[0]['debit'])), 2)
|
||||
diff = flt((flt(r.grand_total) - flt(gle[0]['debit'])), 2)
|
||||
|
||||
if abs(diff) == 0.01:
|
||||
# print r.name, r.grand_total, gle[0]['debit'], diff
|
||||
|
@ -39,7 +39,7 @@ def execute():
|
||||
|
||||
status = 'Not in Use'
|
||||
if sle and flt(sle[0]['actual_qty']) > 0:
|
||||
status = 'In Store'
|
||||
status = 'Available'
|
||||
elif sle and flt(sle[0]['actual_qty']) < 0:
|
||||
status = 'Delivered'
|
||||
|
||||
|
@ -13,17 +13,27 @@ def execute():
|
||||
name = question.question[:180]
|
||||
if webnotes.conn.exists("Note", name):
|
||||
webnotes.delete_doc("Note", name)
|
||||
note = webnotes.bean({
|
||||
|
||||
similar_questions = webnotes.conn.sql_list("""select name from `tabQuestion`
|
||||
where question like %s""", "%s%%" % name)
|
||||
answers = [markdown2.markdown(c) for c in webnotes.conn.sql_list("""
|
||||
select answer from tabAnswer where question in (%s)""" % \
|
||||
", ".join(["%s"]*len(similar_questions)), similar_questions)]
|
||||
|
||||
webnotes.bean({
|
||||
"doctype":"Note",
|
||||
"title": name,
|
||||
"content": "<hr>".join([markdown2.markdown(c) for c in webnotes.conn.sql_list("""
|
||||
select answer from tabAnswer where question=%s""", question.name)]),
|
||||
"content": "<hr>".join(answers),
|
||||
"owner": question.owner,
|
||||
"creation": question.creation,
|
||||
"public": 1
|
||||
}).insert()
|
||||
|
||||
except NameError:
|
||||
pass
|
||||
except Exception, e:
|
||||
if e.args[0] != 1062:
|
||||
raise e
|
||||
|
||||
webnotes.delete_doc("DocType", "Question")
|
||||
webnotes.delete_doc("DocType", "Answer")
|
||||
|
@ -22,7 +22,6 @@ patch_list = [
|
||||
"patches.april_2012.update_role_in_address",
|
||||
"patches.april_2012.update_permlevel_in_address",
|
||||
"patches.april_2012.update_appraisal_permission",
|
||||
"patches.april_2012.serial_no_fixes",
|
||||
"patches.april_2012.repost_stock_for_posting_time",
|
||||
"patches.may_2012.cleanup_property_setter",
|
||||
"patches.may_2012.rename_prev_doctype",
|
||||
@ -229,7 +228,6 @@ patch_list = [
|
||||
"execute:webnotes.delete_doc('Report', 'Received Items To Be Billed')",
|
||||
"patches.july_2013.p02_copy_shipping_address",
|
||||
"patches.july_2013.p03_cost_center_company",
|
||||
"execute:webnotes.bean('Style Settings').save() #2013-07-16",
|
||||
"patches.july_2013.p04_merge_duplicate_leads",
|
||||
"patches.july_2013.p05_custom_doctypes_in_list_view",
|
||||
"patches.july_2013.p06_same_sales_rate",
|
||||
@ -254,5 +252,9 @@ patch_list = [
|
||||
"patches.august_2013.p01_hr_settings",
|
||||
"patches.august_2013.p02_rename_price_list",
|
||||
"patches.august_2013.p03_pos_setting_replace_customer_account",
|
||||
"patches.august_2013.p04_employee_birthdays",
|
||||
"patches.august_2013.p05_update_serial_no_status",
|
||||
"patches.august_2013.p05_employee_birthdays",
|
||||
"execute:webnotes.reload_doc('accounts', 'Print Format', 'POS Invoice') # 2013-08-16",
|
||||
"patches.august_2013.p06_fix_sle_against_stock_entry",
|
||||
"execute:webnotes.bean('Style Settings').save() #2013-08-20",
|
||||
]
|
@ -6,9 +6,9 @@ wn.require("app/js/controllers/stock_controller.js");
|
||||
|
||||
erpnext.TransactionController = erpnext.stock.StockController.extend({
|
||||
onload: function() {
|
||||
var me = this;
|
||||
if(this.frm.doc.__islocal) {
|
||||
var me = this,
|
||||
today = get_today(),
|
||||
var today = get_today(),
|
||||
currency = wn.defaults.get_default("currency");
|
||||
|
||||
$.each({
|
||||
@ -30,6 +30,14 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({
|
||||
|
||||
me.frm.script_manager.trigger("company");
|
||||
}
|
||||
|
||||
if(this.other_fname) {
|
||||
this[this.other_fname + "_remove"] = this.calculate_taxes_and_totals;
|
||||
}
|
||||
|
||||
if(this.fname) {
|
||||
this[this.fname + "_remove"] = this.calculate_taxes_and_totals;
|
||||
}
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
@ -311,9 +319,14 @@ erpnext.TransactionController = erpnext.stock.StockController.extend({
|
||||
function(item_code, tax_data) {
|
||||
if(!item_tax[item_code]) item_tax[item_code] = {};
|
||||
if($.isArray(tax_data)) {
|
||||
var tax_rate = tax_data[0] == null ? "" : (flt(tax_data[0], tax_rate_precision) + "%"),
|
||||
tax_amount = format_currency(flt(tax_data[1], tax_amount_precision), company_currency,
|
||||
tax_amount_precision);
|
||||
var tax_rate = "";
|
||||
if(tax_data[0] != null) {
|
||||
tax_rate = (tax.charge_type === "Actual") ?
|
||||
format_currency(flt(tax_data[0], tax_amount_precision), company_currency, tax_amount_precision) :
|
||||
(flt(tax_data[0], tax_rate_precision) + "%");
|
||||
}
|
||||
var tax_amount = format_currency(flt(tax_data[1], tax_amount_precision), company_currency,
|
||||
tax_amount_precision);
|
||||
|
||||
item_tax[item_code][tax.name] = [tax_rate, tax_amount];
|
||||
} else {
|
||||
|
@ -36,4 +36,47 @@ $.extend(erpnext, {
|
||||
territory.territory = wn.defaults.get_default("territory");
|
||||
}
|
||||
},
|
||||
|
||||
setup_serial_no: function(grid_row) {
|
||||
if(!grid_row.fields_dict.serial_no ||
|
||||
grid_row.fields_dict.serial_no.get_status()!=="Write") return;
|
||||
|
||||
var $btn = $('<button class="btn btn-sm btn-default">Add Serial No</button>')
|
||||
.appendTo($("<div>")
|
||||
.css({"margin-bottom": "10px", "margin-top": "-10px"})
|
||||
.appendTo(grid_row.fields_dict.serial_no.$wrapper));
|
||||
|
||||
$btn.on("click", function() {
|
||||
var d = new wn.ui.Dialog({
|
||||
title: "Add Serial No",
|
||||
fields: [
|
||||
{
|
||||
"fieldtype": "Link",
|
||||
"options": "Serial No",
|
||||
"label": "Serial No",
|
||||
"get_query": {
|
||||
item_code: grid_row.doc.item_code,
|
||||
warehouse: grid_row.doc.warehouse
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldtype": "Button",
|
||||
"label": "Add"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
d.get_input("add").on("click", function() {
|
||||
var serial_no = d.get_value("serial_no");
|
||||
if(serial_no) {
|
||||
var val = (grid_row.doc.serial_no || "").split("\n").concat([serial_no]).join("\n");
|
||||
grid_row.fields_dict.serial_no.set_model_value(val.trim());
|
||||
}
|
||||
d.hide();
|
||||
return false;
|
||||
});
|
||||
|
||||
d.show();
|
||||
});
|
||||
}
|
||||
});
|
@ -77,9 +77,9 @@ class DocType(TransactionBase):
|
||||
msgprint("Please Select Company under which you want to create account head")
|
||||
|
||||
def update_credit_days_limit(self):
|
||||
sql("""update tabAccount set credit_days = %s, credit_limit = %s
|
||||
where name = %s""", (self.doc.credit_days or 0, self.doc.credit_limit or 0,
|
||||
self.doc.name + " - " + self.get_company_abbr()))
|
||||
webnotes.conn.sql("""update tabAccount set credit_days = %s, credit_limit = %s
|
||||
where master_type='Customer' and master_name = %s""",
|
||||
(self.doc.credit_days or 0, self.doc.credit_limit or 0, self.doc.name))
|
||||
|
||||
def create_lead_address_contact(self):
|
||||
if self.doc.lead_name:
|
||||
|
@ -105,7 +105,6 @@ class DocType(TransactionBase):
|
||||
msgprint("Please fetch items from Delivery Note selected", raise_exception=1)
|
||||
|
||||
def on_update(self):
|
||||
get_obj("Stock Ledger").scrub_serial_nos(self, 'installed_item_details')
|
||||
webnotes.conn.set(self.doc, 'status', 'Draft')
|
||||
|
||||
def on_submit(self):
|
||||
|
@ -9,17 +9,8 @@ from core.doctype.communication.communication import make
|
||||
|
||||
def add_sales_communication(subject, content, sender, real_name, mail=None,
|
||||
status="Open", date=None):
|
||||
def set_status(doctype, name):
|
||||
w = webnotes.bean(doctype, name)
|
||||
w.ignore_permissions = True
|
||||
w.doc.status = is_system_user and "Replied" or status
|
||||
w.doc.save()
|
||||
if mail:
|
||||
mail.save_attachments_in_doc(w.doc)
|
||||
|
||||
lead_name = webnotes.conn.get_value("Lead", {"email_id": sender})
|
||||
contact_name = webnotes.conn.get_value("Contact", {"email_id": sender})
|
||||
is_system_user = webnotes.conn.get_value("Profile", sender)
|
||||
|
||||
if not (lead_name or contact_name):
|
||||
# none, create a new Lead
|
||||
@ -34,14 +25,13 @@ def add_sales_communication(subject, content, sender, real_name, mail=None,
|
||||
lead.insert()
|
||||
lead_name = lead.doc.name
|
||||
|
||||
make(content=content, sender=sender, subject=subject,
|
||||
message = make(content=content, sender=sender, subject=subject,
|
||||
lead=lead_name, contact=contact_name, date=date)
|
||||
|
||||
if contact_name:
|
||||
set_status("Contact", contact_name)
|
||||
elif lead_name:
|
||||
set_status("Lead", lead_name)
|
||||
|
||||
if mail:
|
||||
# save attachments to parent if from mail
|
||||
bean = webnotes.bean("Contact" if contact_name else "Lead", contact_name or lead_name)
|
||||
mail.save_attachments_in_doc(bean.doc)
|
||||
|
||||
class SalesMailbox(POP3Mailbox):
|
||||
def setup(self, args=None):
|
||||
|
@ -39,17 +39,14 @@ class DocType(SellingController):
|
||||
|
||||
def validate(self):
|
||||
if self.doc.status == 'Lead Lost' and not self.doc.order_lost_reason:
|
||||
msgprint("Please Enter Lost Reason under More Info section")
|
||||
raise Exception
|
||||
webnotes.throw("Please Enter Lost Reason under More Info section")
|
||||
|
||||
if self.doc.source == 'Campaign' and not self.doc.campaign_name and session['user'] != 'Guest':
|
||||
msgprint("Please specify campaign name")
|
||||
raise Exception
|
||||
webnotes.throw("Please specify campaign name")
|
||||
|
||||
if self.doc.email_id:
|
||||
if not validate_email_add(self.doc.email_id):
|
||||
msgprint('Please enter valid email id.')
|
||||
raise Exception
|
||||
webnotes.throw('Please enter valid email id.')
|
||||
|
||||
def on_update(self):
|
||||
self.check_email_id_is_unique()
|
||||
|
@ -76,8 +76,8 @@ class DocType:
|
||||
|
||||
def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
|
||||
from controllers.queries import get_match_cond
|
||||
|
||||
return webnotes.conn.sql("""select name, description from tabItem
|
||||
|
||||
return webnotes.conn.sql("""select name, item_name, description from tabItem
|
||||
where is_stock_item="No" and is_sales_item="Yes"
|
||||
and name not in (select name from `tabSales BOM`) and %s like %s
|
||||
%s limit %s, %s""" % (searchfield, "%s",
|
||||
|
@ -95,7 +95,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
return{
|
||||
query : "selling.doctype.sales_common.sales_common.get_batch_no",
|
||||
filters: {
|
||||
'item': item.item_code,
|
||||
'item_code': item.item_code,
|
||||
'posting_date': me.frm.doc.posting_date
|
||||
}
|
||||
}
|
||||
@ -640,7 +640,7 @@ var set_sales_bom_help = function(doc) {
|
||||
$(cur_frm.fields_dict.packing_list.row.wrapper).toggle(true);
|
||||
|
||||
if (inList(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
|
||||
help_msg = "<div class='alert'> \
|
||||
help_msg = "<div class='alert alert-warning'> \
|
||||
For 'Sales BOM' items, warehouse, serial no and batch no \
|
||||
will be considered from the 'Packing List' table. \
|
||||
If warehouse and batch no are same for all packing items for any 'Sales BOM' item, \
|
||||
|
@ -340,7 +340,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
and batch_no like '%(txt)s'
|
||||
and exists(select * from `tabBatch`
|
||||
where name = sle.batch_no
|
||||
and expiry_date >= '%(posting_date)s'
|
||||
and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
|
||||
and docstatus != 2)
|
||||
%(mcond)s
|
||||
group by batch_no having sum(actual_qty) > 0
|
||||
@ -353,11 +353,11 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
return webnotes.conn.sql("""select name from tabBatch
|
||||
where docstatus != 2
|
||||
and item = '%(item_code)s'
|
||||
and expiry_date >= '%(posting_date)s'
|
||||
and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
|
||||
and name like '%(txt)s'
|
||||
%(mcond)s
|
||||
order by name desc
|
||||
limit %(start)s, %(page_len)s""" % {'item_code': filters['item_code'],
|
||||
'posting_date': filters['posting_date'], 'txt': "%%%s%%" % txt,
|
||||
'mcond':get_match_cond(doctype, searchfield),'start': start,
|
||||
'page_len': page_len})
|
||||
'page_len': page_len})
|
||||
|
@ -126,8 +126,9 @@ class DocType(SellingController):
|
||||
self.validate_mandatory()
|
||||
self.validate_proj_cust()
|
||||
self.validate_po()
|
||||
self.validate_uom_is_integer("stock_uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "qty")
|
||||
self.validate_for_items()
|
||||
self.validate_warehouse_user()
|
||||
sales_com_obj = get_obj(dt = 'Sales Common')
|
||||
sales_com_obj.check_active_sales_items(self)
|
||||
sales_com_obj.check_conversion_rate(self)
|
||||
@ -147,6 +148,16 @@ class DocType(SellingController):
|
||||
if not self.doc.billing_status: self.doc.billing_status = 'Not Billed'
|
||||
if not self.doc.delivery_status: self.doc.delivery_status = 'Not Delivered'
|
||||
|
||||
|
||||
def validate_warehouse_user(self):
|
||||
from stock.utils import validate_warehouse_user
|
||||
|
||||
warehouses = list(set([d.reserved_warehouse for d in
|
||||
self.doclist.get({"doctype": self.tname}) if d.reserved_warehouse]))
|
||||
|
||||
for w in warehouses:
|
||||
validate_warehouse_user(w)
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super(DocType, self).validate_with_previous_doc(self.tname, {
|
||||
"Quotation": {
|
||||
|
@ -272,6 +272,29 @@ class TestSalesOrder(unittest.TestCase):
|
||||
self.check_reserved_qty(sbom_test_records[0][2]["item_code"],
|
||||
so.doclist[1].reserved_warehouse, 20.0)
|
||||
|
||||
def test_warehouse_user(self):
|
||||
webnotes.session.user = "test@example.com"
|
||||
|
||||
webnotes.bean("Profile", "test@example.com").get_controller()\
|
||||
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
|
||||
|
||||
webnotes.bean("Profile", "test2@example.com").get_controller()\
|
||||
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
|
||||
|
||||
|
||||
from stock.utils import UserNotAllowedForWarehouse
|
||||
so = webnotes.bean(copy = test_records[0])
|
||||
so.doc.company = "_Test Company 1"
|
||||
so.doc.conversion_rate = 0.02
|
||||
so.doc.plc_conversion_rate = 0.02
|
||||
so.doclist[1].reserved_warehouse = "_Test Warehouse 2 - _TC1"
|
||||
self.assertRaises(UserNotAllowedForWarehouse, so.insert)
|
||||
|
||||
webnotes.session.user = "test2@example.com"
|
||||
so.insert()
|
||||
|
||||
webnotes.session.user = "Administrator"
|
||||
|
||||
test_dependencies = ["Sales BOM"]
|
||||
|
||||
test_records = [
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-03-07 11:42:58",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-07 14:44:50",
|
||||
"modified": "2013-08-22 15:43:07",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -230,7 +230,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 0,
|
||||
"label": "Reserved Warehouse",
|
||||
"no_copy": 1,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "reserved_warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
|
@ -69,10 +69,24 @@ def get_item_details(args):
|
||||
|
||||
if cint(args.is_pos):
|
||||
pos_settings = get_pos_settings(args.company)
|
||||
out.update(apply_pos_settings(pos_settings, out))
|
||||
|
||||
if pos_settings:
|
||||
out.update(apply_pos_settings(pos_settings, out))
|
||||
|
||||
if args.doctype in ("Sales Invoice", "Delivery Note"):
|
||||
if item_bean.doc.has_serial_no and not args.serial_no:
|
||||
out.serial_no = _get_serial_nos_by_fifo(args, item_bean)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def _get_serial_nos_by_fifo(args, item_bean):
|
||||
return "\n".join(webnotes.conn.sql_list("""select name from `tabSerial No`
|
||||
where item_code=%(item_code)s and warehouse=%(warehouse)s and status='Available'
|
||||
order by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", {
|
||||
"item_code": args.item_code,
|
||||
"warehouse": args.warehouse,
|
||||
"qty": cint(args.qty)
|
||||
}))
|
||||
|
||||
def _get_item_code(barcode):
|
||||
item_code = webnotes.conn.sql_list("""select name from `tabItem` where barcode=%s""", barcode)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2012-12-20 12:50:49",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-05 14:37:59",
|
||||
"modified": "2013-08-22 15:36:43",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -184,6 +184,13 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Point of Sale"
|
||||
},
|
||||
{
|
||||
"description": "To enable <b>Point of Sale</b> view",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "fs_pos_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "POS View"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "production",
|
||||
|
@ -41,15 +41,15 @@ wn.pages.Setup.make = function(wrapper) {
|
||||
})
|
||||
.appendTo(body);
|
||||
|
||||
$('<div class="col col-lg-1"></div>').appendTo(row);
|
||||
$('<div class="col-md-1"></div>').appendTo(row);
|
||||
|
||||
if(item.type==="Link") {
|
||||
var col = $('<div class="col col-lg-5"><b><a href="#'
|
||||
var col = $('<div class="col-md-5"><b><a href="#'
|
||||
+item.route+'"><i class="'+item.icon+'"></i> '
|
||||
+item.title+'</a></b></div>').appendTo(row);
|
||||
|
||||
} else {
|
||||
var col = $(repl('<div class="col col-lg-5">\
|
||||
var col = $(repl('<div class="col-md-5">\
|
||||
<span class="badge view-link">%(count)s</span>\
|
||||
<b><i class="%(icon)s"></i>\
|
||||
<a class="data-link">%(title)s</a></b>\
|
||||
@ -73,9 +73,9 @@ wn.pages.Setup.make = function(wrapper) {
|
||||
}
|
||||
|
||||
if(dependency)
|
||||
col.addClass("col-offset-1");
|
||||
col.addClass("col-md-offset-1");
|
||||
else
|
||||
$('<div class="col col-lg-1"></div>').appendTo(row);
|
||||
$('<div class="col-md-1"></div>').appendTo(row);
|
||||
|
||||
if(item.doctype) {
|
||||
var badge = col.find(".badge, .data-link")
|
||||
@ -94,7 +94,7 @@ wn.pages.Setup.make = function(wrapper) {
|
||||
}
|
||||
|
||||
// tree
|
||||
$links = $('<div class="col col-lg-5">').appendTo(row);
|
||||
$links = $('<div class="col-md-5">').appendTo(row);
|
||||
|
||||
if(item.tree) {
|
||||
$('<a class="view-link"><i class="icon-sitemap"></i> Browse</a>\
|
||||
|
@ -32,12 +32,13 @@ lang_names = {
|
||||
"nederlands": "nl",
|
||||
"српски":"sr",
|
||||
"தமிழ்": "ta",
|
||||
"Hrvatski": "hr",
|
||||
"hrvatski": "hr",
|
||||
"italiano": "it",
|
||||
"ไทย": "th",
|
||||
"العربية":"ar"
|
||||
}
|
||||
|
||||
lang_list = ["ar", "de", "en", "hi", "es", "fr", "pt-BR", "pt", "nl", "hr", "th"]
|
||||
lang_list = ["ar", "de", "en", "es", "fr", "hi", "hr", "it", "nl", "pt-BR", "pt", "th", "sr", "ta"]
|
||||
|
||||
product_name = "ERPNext"
|
||||
profile_defaults = {
|
||||
|
@ -72,7 +72,7 @@ def feature_setup():
|
||||
'fs_exports', 'fs_imports', 'fs_discounts', 'fs_purchase_discounts',
|
||||
'fs_after_sales_installations', 'fs_projects', 'fs_sales_extras',
|
||||
'fs_recurring_invoice', 'fs_pos', 'fs_manufacturing', 'fs_quality',
|
||||
'fs_page_break', 'fs_more_info'
|
||||
'fs_page_break', 'fs_more_info', 'fs_pos_view'
|
||||
]
|
||||
doc.fields.update(dict(zip(flds, [1]*len(flds))))
|
||||
doc.save()
|
||||
|
@ -74,6 +74,10 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
tc_name: function() {
|
||||
this.get_terms();
|
||||
},
|
||||
|
||||
delivery_note_details_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no(grid_row)
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
from webnotes.utils import cstr, flt, cint
|
||||
from webnotes.utils import cstr, flt, cint, add_days
|
||||
from webnotes.model.bean import getlist
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint, _
|
||||
@ -175,9 +175,6 @@ class DocType(SellingController):
|
||||
|
||||
def on_update(self):
|
||||
self.doclist = get_obj('Sales Common').make_packing_list(self,'delivery_note_details')
|
||||
sl = get_obj('Stock Ledger')
|
||||
sl.scrub_serial_nos(self)
|
||||
sl.scrub_serial_nos(self, 'packing_details')
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_packed_qty()
|
||||
@ -185,22 +182,12 @@ class DocType(SellingController):
|
||||
# Check for Approving Authority
|
||||
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total, self)
|
||||
|
||||
# validate serial no for item table (non-sales-bom item) and packing list (sales-bom item)
|
||||
sl_obj = get_obj("Stock Ledger")
|
||||
sl_obj.validate_serial_no(self, 'delivery_note_details')
|
||||
sl_obj.validate_serial_no_warehouse(self, 'delivery_note_details')
|
||||
sl_obj.validate_serial_no(self, 'packing_details')
|
||||
sl_obj.validate_serial_no_warehouse(self, 'packing_details')
|
||||
|
||||
# update delivery details in serial no
|
||||
sl_obj.update_serial_record(self, 'delivery_note_details', is_submit = 1, is_incoming = 0)
|
||||
sl_obj.update_serial_record(self, 'packing_details', is_submit = 1, is_incoming = 0)
|
||||
|
||||
# update delivered qty in sales order
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# create stock ledger entry
|
||||
self.update_stock_ledger(update_stock = 1)
|
||||
self.update_serial_nos()
|
||||
|
||||
self.credit_limit()
|
||||
|
||||
@ -211,6 +198,50 @@ class DocType(SellingController):
|
||||
webnotes.conn.set(self.doc, 'status', 'Submitted')
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
sales_com_obj = get_obj(dt = 'Sales Common')
|
||||
sales_com_obj.check_stop_sales_order(self)
|
||||
self.check_next_docstatus()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_stock_ledger(update_stock = -1)
|
||||
self.update_serial_nos(cancel=True)
|
||||
|
||||
webnotes.conn.set(self.doc, 'status', 'Cancelled')
|
||||
self.cancel_packing_slips()
|
||||
|
||||
self.make_cancel_gl_entries()
|
||||
|
||||
def update_serial_nos(self, cancel=False):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
|
||||
update_serial_nos_after_submit(self, "Delivery Note", "delivery_note_details")
|
||||
update_serial_nos_after_submit(self, "Delivery Note", "packing_details")
|
||||
|
||||
for table_fieldname in ("delivery_note_details", "packing_details"):
|
||||
for d in self.doclist.get({"parentfield": table_fieldname}):
|
||||
for serial_no in get_serial_nos(d.serial_no):
|
||||
sr = webnotes.bean("Serial No", serial_no)
|
||||
if cancel:
|
||||
sr.doc.status = "Available"
|
||||
for fieldname in ("warranty_expiry_date", "delivery_document_type",
|
||||
"delivery_document_no", "delivery_date", "delivery_time", "customer",
|
||||
"customer_name"):
|
||||
sr.doc.fields[fieldname] = None
|
||||
else:
|
||||
sr.doc.delivery_document_type = "Delivery Note"
|
||||
sr.doc.delivery_document_no = self.doc.name
|
||||
sr.doc.delivery_date = self.doc.posting_date
|
||||
sr.doc.delivery_time = self.doc.posting_time
|
||||
sr.doc.customer = self.doc.customer
|
||||
sr.doc.customer_name = self.doc.customer_name
|
||||
if sr.doc.warranty_period:
|
||||
sr.doc.warranty_expiry_date = add_days(cstr(self.doc.posting_date),
|
||||
cint(sr.doc.warranty_period))
|
||||
sr.doc.status = 'Delivered'
|
||||
|
||||
sr.save()
|
||||
|
||||
def validate_packed_qty(self):
|
||||
"""
|
||||
Validate that if packed qty exists, it should be equal to qty
|
||||
@ -232,26 +263,6 @@ class DocType(SellingController):
|
||||
+ ", Packed: " + cstr(d[2])) for d in packing_error_list])
|
||||
webnotes.msgprint("Packing Error:\n" + err_msg, raise_exception=1)
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
sales_com_obj = get_obj(dt = 'Sales Common')
|
||||
sales_com_obj.check_stop_sales_order(self)
|
||||
self.check_next_docstatus()
|
||||
|
||||
# remove delivery details from serial no
|
||||
sl = get_obj('Stock Ledger')
|
||||
sl.update_serial_record(self, 'delivery_note_details', is_submit = 0, is_incoming = 0)
|
||||
sl.update_serial_record(self, 'packing_details', is_submit = 0, is_incoming = 0)
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_stock_ledger(update_stock = -1)
|
||||
webnotes.conn.set(self.doc, 'status', 'Cancelled')
|
||||
self.cancel_packing_slips()
|
||||
|
||||
self.make_cancel_gl_entries()
|
||||
|
||||
|
||||
def check_next_docstatus(self):
|
||||
submit_rv = sql("select t1.name from `tabSales Invoice` t1,`tabSales Invoice Item` t2 where t1.name = t2.parent and t2.delivery_note = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||
if submit_rv:
|
||||
@ -263,7 +274,6 @@ class DocType(SellingController):
|
||||
msgprint("Installation Note : "+cstr(submit_in[0][0]) +" has already been submitted !")
|
||||
raise Exception , "Validation Error."
|
||||
|
||||
|
||||
def cancel_packing_slips(self):
|
||||
"""
|
||||
Cancel submitted packing slips related to this delivery note
|
||||
|
@ -33,7 +33,7 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
self.assertEquals(len(si), len(dn.doclist))
|
||||
|
||||
# modify export_amount
|
||||
si[1].ref_rate = 200
|
||||
si[1].export_rate = 200
|
||||
self.assertRaises(webnotes.ValidationError, webnotes.bean(si).insert)
|
||||
|
||||
|
||||
@ -96,6 +96,59 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
self.assertEquals(bal, prev_bal - 375.0)
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
|
||||
def test_serialized(self):
|
||||
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||
|
||||
dn = webnotes.bean(copy=test_records[0])
|
||||
dn.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
dn.doclist[1].qty = 1
|
||||
dn.doclist[1].serial_no = serial_nos[0]
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Delivered")
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||
"delivery_document_no"), dn.doc.name)
|
||||
|
||||
return dn
|
||||
|
||||
def test_serialized_cancel(self):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import get_serial_nos
|
||||
dn = self.test_serialized()
|
||||
dn.cancel()
|
||||
|
||||
serial_nos = get_serial_nos(dn.doclist[1].serial_no)
|
||||
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "status"), "Available")
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", serial_nos[0], "warehouse"), "_Test Warehouse - _TC")
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", serial_nos[0],
|
||||
"delivery_document_no"))
|
||||
|
||||
def test_serialize_status(self):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import SerialNoStatusError, get_serial_nos
|
||||
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item()
|
||||
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||
|
||||
sr = webnotes.bean("Serial No", serial_nos[0])
|
||||
sr.doc.status = "Not Available"
|
||||
sr.save()
|
||||
|
||||
dn = webnotes.bean(copy=test_records[0])
|
||||
dn.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
dn.doclist[1].qty = 1
|
||||
dn.doclist[1].serial_no = serial_nos[0]
|
||||
dn.insert()
|
||||
|
||||
self.assertRaises(SerialNoStatusError, dn.submit)
|
||||
|
||||
|
||||
test_records = [
|
||||
[
|
||||
|
@ -272,4 +272,4 @@ class DocType(DocListController):
|
||||
from stock.stock_ledger import update_entries_after
|
||||
for wh in webnotes.conn.sql("""select warehouse from `tabBin`
|
||||
where item_code=%s""", newdn):
|
||||
update_entries_after({"item_code": newdn, "warehouse": wh})
|
||||
update_entries_after({"item_code": newdn, "warehouse": wh[0]})
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-05-03 10:45:46",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-08 14:22:25",
|
||||
"modified": "2013-08-30 16:21:38",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -35,7 +35,9 @@
|
||||
"parentfield": "permissions",
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
"read": 1
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"submit": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocType",
|
||||
@ -300,6 +302,14 @@
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.has_serial_no===\"Yes\"",
|
||||
"description": "Example: ABCD.#####\nIf series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "serial_no_series",
|
||||
"fieldtype": "Data",
|
||||
"label": "Serial Number Series"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_stock_item==\"Yes\"",
|
||||
"doctype": "DocField",
|
||||
@ -549,20 +559,6 @@
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "No",
|
||||
"depends_on": "eval:doc.is_sales_item==\"Yes\"",
|
||||
"description": "Select \"Yes\" if this item is to be sent to a customer or received from a supplier as a sample. Delivery notes and Purchase Receipts will update stock levels but there will be no invoice against this item.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "is_sample_item",
|
||||
"fieldtype": "Select",
|
||||
"label": "Allow Samples",
|
||||
"oldfieldname": "is_sample_item",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Yes\nNo",
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_sales_item==\"Yes\"",
|
||||
"doctype": "DocField",
|
||||
@ -870,9 +866,7 @@
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material Master Manager",
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
@ -880,9 +874,7 @@
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material Manager",
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
@ -890,21 +882,7 @@
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material User",
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocPerm",
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"doctype": "DocPerm",
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"doctype": "DocPerm",
|
||||
"role": "Accounts User"
|
||||
}
|
||||
]
|
@ -44,7 +44,6 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
@ -82,7 +81,6 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
@ -108,7 +106,6 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
@ -128,7 +125,6 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
@ -149,7 +145,6 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "Yes",
|
||||
"is_sub_contracted_item": "Yes",
|
||||
@ -168,7 +163,6 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
@ -188,7 +182,26 @@ test_records = [
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"is_sample_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
"stock_uom": "_Test UOM"
|
||||
}],
|
||||
[{
|
||||
"doctype": "Item",
|
||||
"item_code": "_Test Serialized Item With Series",
|
||||
"item_name": "_Test Serialized Item With Series",
|
||||
"description": "_Test Serialized Item",
|
||||
"item_group": "_Test Item Group Desktops",
|
||||
"is_stock_item": "Yes",
|
||||
"default_warehouse": "_Test Warehouse - _TC",
|
||||
"is_asset_item": "No",
|
||||
"has_batch_no": "No",
|
||||
"has_serial_no": "Yes",
|
||||
"serial_no_series": "ABCD.#####",
|
||||
"is_purchase_item": "Yes",
|
||||
"is_sales_item": "Yes",
|
||||
"is_service_item": "No",
|
||||
"inspection_required": "No",
|
||||
"is_pro_applicable": "No",
|
||||
"is_sub_contracted_item": "No",
|
||||
|
@ -51,10 +51,9 @@ class DocType(BuyingController):
|
||||
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)
|
||||
|
||||
def validate_schedule_date(self):
|
||||
#:::::::: validate schedule date v/s indent date ::::::::::::
|
||||
for d in getlist(self.doclist, 'indent_details'):
|
||||
if d.schedule_date < self.doc.transaction_date:
|
||||
msgprint("Expected Schedule Date cannot be before Material Request Date")
|
||||
msgprint("Expected Date cannot be before Material Request Date")
|
||||
raise Exception
|
||||
|
||||
# Validate
|
||||
|
@ -103,7 +103,7 @@ cur_frm.cscript.calc_net_total_pkg = function(doc, ps_detail) {
|
||||
net_weight_pkg += flt(item.net_weight) * flt(item.qty);
|
||||
}
|
||||
|
||||
doc.net_weight_pkg = roundNumber(net_weight_pkg, 2);
|
||||
doc.net_weight_pkg = _round(net_weight_pkg, 2);
|
||||
if(!flt(doc.gross_weight_pkg)) {
|
||||
doc.gross_weight_pkg = doc.net_weight_pkg
|
||||
}
|
||||
|
@ -23,24 +23,23 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
|
||||
this.show_stock_ledger();
|
||||
this.show_general_ledger();
|
||||
} else {
|
||||
cur_frm.add_custom_button(wn._('From Purchase Order'),
|
||||
function() {
|
||||
wn.model.map_current_doc({
|
||||
method: "buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
||||
source_doctype: "Purchase Order",
|
||||
get_query_filters: {
|
||||
supplier: cur_frm.doc.supplier || undefined,
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_received: ["<", 99.99],
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
cur_frm.add_custom_button(wn._('From Purchase Order'),
|
||||
function() {
|
||||
wn.model.map_current_doc({
|
||||
method: "buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
|
||||
source_doctype: "Purchase Order",
|
||||
get_query_filters: {
|
||||
supplier: cur_frm.doc.supplier || undefined,
|
||||
docstatus: 1,
|
||||
status: ["!=", "Stopped"],
|
||||
per_received: ["<", 99.99],
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
if(wn.boot.control_panel.country == 'India') {
|
||||
unhide_field(['challan_no', 'challan_date']);
|
||||
}
|
||||
@ -127,7 +126,7 @@ cur_frm.cscript.new_contact = function(){
|
||||
cur_frm.fields_dict['purchase_receipt_details'].grid.get_field('project_name').get_query = function(doc, cdt, cdn) {
|
||||
return{
|
||||
filters:[
|
||||
['project', 'status', 'not in', 'Completed, Cancelled']
|
||||
['Project', 'status', 'not in', 'Completed, Cancelled']
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -127,8 +127,6 @@ class DocType(BuyingController):
|
||||
self.validate_inspection()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
|
||||
get_obj('Stock Ledger').validate_serial_no(self, 'purchase_receipt_details')
|
||||
self.validate_challan_no()
|
||||
|
||||
pc_obj = get_obj(dt='Purchase Common')
|
||||
@ -147,16 +145,6 @@ class DocType(BuyingController):
|
||||
for d in getlist(self.doclist,'purchase_receipt_details'):
|
||||
d.rejected_warehouse = self.doc.rejected_warehouse
|
||||
|
||||
get_obj('Stock Ledger').scrub_serial_nos(self)
|
||||
self.scrub_rejected_serial_nos()
|
||||
|
||||
|
||||
def scrub_rejected_serial_nos(self):
|
||||
for d in getlist(self.doclist, 'purchase_receipt_details'):
|
||||
if d.rejected_serial_no:
|
||||
d.rejected_serial_no = cstr(d.rejected_serial_no).strip().replace(',', '\n')
|
||||
d.save()
|
||||
|
||||
def update_stock(self, is_submit):
|
||||
pc_obj = get_obj('Purchase Common')
|
||||
self.values = []
|
||||
@ -207,11 +195,6 @@ class DocType(BuyingController):
|
||||
|
||||
# make Stock Entry
|
||||
def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0):
|
||||
if rejected:
|
||||
serial_no = cstr(d.rejected_serial_no).strip()
|
||||
else:
|
||||
serial_no = cstr(d.serial_no).strip()
|
||||
|
||||
self.values.append({
|
||||
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
|
||||
'warehouse' : wh,
|
||||
@ -227,7 +210,7 @@ class DocType(BuyingController):
|
||||
'fiscal_year' : self.doc.fiscal_year,
|
||||
'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
|
||||
'batch_no' : cstr(d.batch_no).strip(),
|
||||
'serial_no' : serial_no,
|
||||
'serial_no' : d.serial_no,
|
||||
"project" : d.project_name
|
||||
})
|
||||
|
||||
@ -257,20 +240,34 @@ class DocType(BuyingController):
|
||||
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype, self.doc.company, self.doc.grand_total)
|
||||
|
||||
# Set status as Submitted
|
||||
webnotes.conn.set(self.doc,'status', 'Submitted')
|
||||
webnotes.conn.set(self.doc, 'status', 'Submitted')
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# Update Serial Record
|
||||
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 1, is_incoming = 1)
|
||||
|
||||
|
||||
# Update Stock
|
||||
self.update_stock(is_submit = 1)
|
||||
|
||||
self.update_serial_nos()
|
||||
|
||||
# Update last purchase rate
|
||||
purchase_controller.update_last_purchase_rate(self, 1)
|
||||
|
||||
self.make_gl_entries()
|
||||
|
||||
def update_serial_nos(self, cancel=False):
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
|
||||
update_serial_nos_after_submit(self, "Purchase Receipt", "purchase_receipt_details")
|
||||
|
||||
for d in self.doclist.get({"parentfield": "purchase_receipt_details"}):
|
||||
for serial_no in get_serial_nos(d.serial_no):
|
||||
sr = webnotes.bean("Serial No", serial_no)
|
||||
if cancel:
|
||||
sr.doc.supplier = None
|
||||
sr.doc.supplier_name = None
|
||||
else:
|
||||
sr.doc.supplier = self.doc.supplier
|
||||
sr.doc.supplier_name = self.doc.supplier_name
|
||||
sr.save()
|
||||
|
||||
def check_next_docstatus(self):
|
||||
submit_rv = sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % (self.doc.name))
|
||||
@ -295,10 +292,10 @@ class DocType(BuyingController):
|
||||
webnotes.conn.set(self.doc,'status','Cancelled')
|
||||
|
||||
# 3. Cancel Serial No
|
||||
get_obj('Stock Ledger').update_serial_record(self, 'purchase_receipt_details', is_submit = 0, is_incoming = 1)
|
||||
|
||||
# 4.Update Bin
|
||||
self.update_stock(is_submit = 0)
|
||||
self.update_serial_nos(cancel=True)
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
// render
|
||||
wn.listview_settings['Purchase Receipt'] = {
|
||||
add_fields: ["group_concat(`tabPurchase Receipt Item`.prevdoc_docname) \
|
||||
as purchase_order_no"],
|
||||
add_columns: [{"content":"purchase_order_no", width:"30%"}],
|
||||
group_by: "`tabPurchase Receipt`.name",
|
||||
prepare_data: function(data) {
|
||||
if(data.purchase_order_no) {
|
||||
data.purchase_order_no = $.unique(data.purchase_order_no.split(","));
|
||||
var po_list = [];
|
||||
$.each(data.purchase_order_no, function(i, v){
|
||||
if(po_list.indexOf(v)==-1) po_list.push(
|
||||
repl("<a href=\"#Form/Purchase Order/%(name)s\">%(name)s</a>",
|
||||
{name: v}));
|
||||
});
|
||||
data.purchase_order_no = po_list.join(", ");
|
||||
}
|
||||
}
|
||||
};
|
@ -76,8 +76,31 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
|
||||
self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2)
|
||||
|
||||
def test_serial_no_supplier(self):
|
||||
pr = webnotes.bean(copy=test_records[0])
|
||||
pr.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
pr.doclist[1].qty = 1
|
||||
pr.doclist[1].received_qty = 1
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
self.assertEquals(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||
"supplier"), pr.doc.supplier)
|
||||
|
||||
return pr
|
||||
|
||||
def test_serial_no_cancel(self):
|
||||
pr = self.test_serial_no_supplier()
|
||||
pr.cancel()
|
||||
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||
"warehouse"))
|
||||
|
||||
self.assertEqual(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||
"status"), "Not Available")
|
||||
|
||||
|
||||
|
||||
test_dependencies = ["BOM"]
|
||||
|
||||
test_records = [
|
||||
|
@ -1,69 +1,10 @@
|
||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
if(!doc.status) set_multiple(cdt, cdn, {status:'In Store'});
|
||||
if(doc.__islocal) hide_field(['supplier_name','address_display'])
|
||||
}
|
||||
cur_frm.add_fetch("customer", "customer_name", "customer_name")
|
||||
cur_frm.add_fetch("supplier", "supplier_name", "supplier_name")
|
||||
|
||||
|
||||
cur_frm.cscript.refresh = function(doc, cdt, cdn) {
|
||||
flds = ['status', 'item_code', 'warehouse', 'purchase_document_type',
|
||||
'purchase_document_no', 'purchase_date', 'purchase_time', 'purchase_rate',
|
||||
'supplier']
|
||||
for(i=0;i<flds.length;i++) {
|
||||
cur_frm.set_df_property(flds[i], 'read_only', doc.__islocal ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
// item details
|
||||
// -------------
|
||||
cur_frm.add_fetch('item_code', 'item_name', 'item_name')
|
||||
cur_frm.add_fetch('item_code', 'item_group', 'item_group')
|
||||
cur_frm.add_fetch('item_code', 'brand', 'brand')
|
||||
cur_frm.add_fetch('item_code', 'description', 'description')
|
||||
cur_frm.add_fetch('item_code', 'warranty_period', 'warranty_period')
|
||||
|
||||
// customer
|
||||
// ---------
|
||||
cur_frm.add_fetch('customer', 'customer_name', 'customer_name')
|
||||
cur_frm.add_fetch('customer', 'address', 'delivery_address')
|
||||
cur_frm.add_fetch('customer', 'territory', 'territory')
|
||||
|
||||
// territory
|
||||
// ----------
|
||||
cur_frm.fields_dict['territory'].get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
filters:{'is_group': "No"}
|
||||
}
|
||||
}
|
||||
|
||||
// Supplier
|
||||
//-------------
|
||||
cur_frm.cscript.supplier = function(doc,dt,dn) {
|
||||
if(doc.supplier) return get_server_fields('get_default_supplier_address', JSON.stringify({supplier: doc.supplier}),'', doc, dt, dn, 1);
|
||||
if(doc.supplier) unhide_field(['supplier_name','address_display']);
|
||||
}
|
||||
|
||||
//item code
|
||||
//----------
|
||||
cur_frm.fields_dict['item_code'].get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
query:"controllers.queries.item_query",
|
||||
filters:{
|
||||
'has_serial_no': 'Yes'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.customer.get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
query:"controllers.queries.customer_query"
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.supplier.get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
query:"controllers.queries.supplier_query"
|
||||
}
|
||||
}
|
||||
cur_frm.add_fetch("item_code", "item_name", "item_name")
|
||||
cur_frm.add_fetch("item_code", "description", "description")
|
||||
cur_frm.add_fetch("item_code", "item_group", "item_group")
|
||||
cur_frm.add_fetch("item_code", "brand", "brand")
|
||||
|
@ -7,13 +7,27 @@ import webnotes
|
||||
from webnotes.utils import cint, getdate, nowdate
|
||||
import datetime
|
||||
from webnotes import msgprint, _
|
||||
|
||||
|
||||
from controllers.stock_controller import StockController
|
||||
|
||||
class SerialNoCannotCreateDirectError(webnotes.ValidationError): pass
|
||||
class SerialNoCannotCannotChangeError(webnotes.ValidationError): pass
|
||||
|
||||
class DocType(StockController):
|
||||
def __init__(self, doc, doclist=[]):
|
||||
self.doc = doc
|
||||
self.doclist = doclist
|
||||
self.via_stock_ledger = False
|
||||
|
||||
def validate(self):
|
||||
if self.doc.fields.get("__islocal") and self.doc.warehouse:
|
||||
webnotes.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"),
|
||||
SerialNoCannotCreateDirectError)
|
||||
|
||||
self.validate_warranty_status()
|
||||
self.validate_amc_status()
|
||||
self.validate_warehouse()
|
||||
self.validate_item()
|
||||
|
||||
def validate_amc_status(self):
|
||||
"""
|
||||
@ -31,78 +45,40 @@ class DocType(StockController):
|
||||
|
||||
|
||||
def validate_warehouse(self):
|
||||
if self.doc.status=='In Store' and not self.doc.warehouse:
|
||||
msgprint("Warehouse is mandatory if this Serial No is <b>In Store</b>", raise_exception=1)
|
||||
if not self.doc.fields.get("__islocal"):
|
||||
item_code, warehouse = webnotes.conn.get_value("Serial No",
|
||||
self.doc.name, ["item_code", "warehouse"])
|
||||
if item_code != self.doc.item_code:
|
||||
webnotes.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
|
||||
if not self.via_stock_ledger and warehouse != self.doc.warehouse:
|
||||
webnotes.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
|
||||
|
||||
if not self.doc.warehouse and self.doc.status=="Available":
|
||||
self.doc.status = "Not Available"
|
||||
|
||||
def validate_item(self):
|
||||
"""
|
||||
Validate whether serial no is required for this item
|
||||
"""
|
||||
item = webnotes.conn.sql("select name, has_serial_no from tabItem where name = '%s'" % self.doc.item_code)
|
||||
if not item:
|
||||
msgprint("Item is not exists in the system", raise_exception=1)
|
||||
elif item[0][1] == 'No':
|
||||
msgprint("To proceed please select 'Yes' in 'Has Serial No' in Item master: '%s'" % self.doc.item_code, raise_exception=1)
|
||||
item = webnotes.doc("Item", self.doc.item_code)
|
||||
if item.has_serial_no!="Yes":
|
||||
webnotes.throw(_("Item must have 'Has Serial No' as 'Yes'") + ": " + self.doc.item_code)
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.validate_warranty_status()
|
||||
self.validate_amc_status()
|
||||
self.validate_warehouse()
|
||||
self.validate_item()
|
||||
|
||||
def on_update(self):
|
||||
if self.doc.warehouse and self.doc.status == 'In Store' \
|
||||
and cint(self.doc.sle_exists) == 0 and \
|
||||
not webnotes.conn.sql("""select name from `tabStock Ledger Entry`
|
||||
where serial_no = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name):
|
||||
self.make_stock_ledger_entry(1)
|
||||
webnotes.conn.set(self.doc, 'sle_exists', 1)
|
||||
self.doc.item_group = item.item_group
|
||||
self.doc.description = item.description
|
||||
self.doc.item_name = item.item_name
|
||||
self.doc.brand = item.brand
|
||||
self.doc.warranty_period = item.warranty_period
|
||||
|
||||
self.make_gl_entries()
|
||||
|
||||
def make_stock_ledger_entry(self, qty):
|
||||
from webnotes.model.code import get_obj
|
||||
values = [{
|
||||
'item_code' : self.doc.item_code,
|
||||
'warehouse' : self.doc.warehouse,
|
||||
'posting_date' : self.doc.purchase_date or (self.doc.creation and self.doc.creation.split(' ')[0]) or nowdate(),
|
||||
'posting_time' : self.doc.purchase_time or '00:00',
|
||||
'voucher_type' : 'Serial No',
|
||||
'voucher_no' : self.doc.name,
|
||||
'voucher_detail_no' : '',
|
||||
'actual_qty' : qty,
|
||||
'stock_uom' : webnotes.conn.get_value('Item', self.doc.item_code, 'stock_uom'),
|
||||
'incoming_rate' : self.doc.purchase_rate,
|
||||
'company' : self.doc.company,
|
||||
'fiscal_year' : self.doc.fiscal_year,
|
||||
'is_cancelled' : 'No', # is_cancelled is always 'No' because while deleted it can not find creation entry if it not created directly, voucher no != serial no
|
||||
'batch_no' : '',
|
||||
'serial_no' : self.doc.name
|
||||
}]
|
||||
get_obj('Stock Ledger').update_stock(values)
|
||||
|
||||
|
||||
def on_trash(self):
|
||||
if self.doc.status == 'Delivered':
|
||||
msgprint("Cannot trash Serial No : %s as it is already Delivered" % (self.doc.name), raise_exception = 1)
|
||||
elif self.doc.status == 'In Store':
|
||||
webnotes.conn.set(self.doc, 'status', 'Not in Use')
|
||||
self.make_stock_ledger_entry(-1)
|
||||
|
||||
if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) \
|
||||
and webnotes.conn.sql("""select name from `tabGL Entry`
|
||||
where voucher_type=%s and voucher_no=%s and ifnull(is_cancelled, 'No')='No'""",
|
||||
(self.doc.doctype, self.doc.name)):
|
||||
self.make_gl_entries(cancel=True)
|
||||
|
||||
if self.doc.warehouse:
|
||||
webnotes.throw(_("Cannot delete Serial No in warehouse. First remove from warehouse, then delete.") + \
|
||||
": " + self.doc.name)
|
||||
|
||||
def on_cancel(self):
|
||||
self.on_trash()
|
||||
|
||||
def on_restore(self):
|
||||
self.make_stock_ledger_entry(1)
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_rename(self, new, old, merge=False):
|
||||
"""rename serial_no text fields"""
|
||||
@ -119,18 +95,3 @@ class DocType(StockController):
|
||||
webnotes.conn.sql("""update `tab%s` set serial_no = %s
|
||||
where name=%s""" % (dt[0], '%s', '%s'),
|
||||
('\n'.join(serial_nos), item[0]))
|
||||
|
||||
def make_gl_entries(self, cancel=False):
|
||||
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
|
||||
return
|
||||
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
against_stock_account = self.get_company_default("stock_adjustment_account")
|
||||
gl_entries = self.get_gl_entries_for_stock(against_stock_account, self.doc.purchase_rate)
|
||||
|
||||
for entry in gl_entries:
|
||||
entry["posting_date"] = self.doc.purchase_date or (self.doc.creation and
|
||||
self.doc.creation.split(' ')[0]) or nowdate()
|
||||
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, cancel)
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-05-16 10:59:15",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-22 15:29:43",
|
||||
"modified": "2013-08-21 13:37:01",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -14,6 +14,7 @@
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"icon": "icon-barcode",
|
||||
"in_create": 0,
|
||||
"module": "Stock",
|
||||
"name": "__common__",
|
||||
"search_fields": "item_code,status"
|
||||
@ -34,8 +35,7 @@
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"submit": 0
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocType",
|
||||
@ -57,6 +57,7 @@
|
||||
},
|
||||
{
|
||||
"default": "In Store",
|
||||
"description": "Only Serial Nos with status \"Available\" can be delivered.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
@ -66,8 +67,8 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "status",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nIn Store\nDelivered\nNot in Use\nPurchase Returned",
|
||||
"read_only": 1,
|
||||
"options": "\nAvailable\nNot Available\nDelivered\nPurchase Returned\nSales Returned",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@ -98,6 +99,22 @@
|
||||
"reqd": 1,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"description": "Warehouse can only be changed via Stock Entry / Delivery Note / Purchase Receipt",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"read_only": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break1",
|
||||
@ -134,7 +151,7 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Item Group",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
@ -154,7 +171,7 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_details",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Purchase Details",
|
||||
"label": "Purchase / Manufacture Details",
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
@ -168,30 +185,30 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_document_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Purchase Document Type",
|
||||
"label": "Creation Document Type",
|
||||
"no_copy": 1,
|
||||
"options": "\nPurchase Receipt\nStock Entry",
|
||||
"read_only": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_document_no",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"label": "Purchase Document No",
|
||||
"label": "Creation Document No",
|
||||
"no_copy": 1,
|
||||
"read_only": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_date",
|
||||
"fieldtype": "Date",
|
||||
"in_filter": 1,
|
||||
"label": "Purchase Date",
|
||||
"label": "Creation Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "purchase_date",
|
||||
"oldfieldtype": "Date",
|
||||
"read_only": 0,
|
||||
"read_only": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
@ -199,10 +216,10 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Incoming Time",
|
||||
"label": "Creation Time",
|
||||
"no_copy": 1,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
"read_only": 1,
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@ -214,8 +231,8 @@
|
||||
"oldfieldname": "purchase_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"read_only": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
@ -225,21 +242,6 @@
|
||||
"read_only": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"read_only": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "supplier",
|
||||
@ -259,14 +261,6 @@
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "address_display",
|
||||
"fieldtype": "Text",
|
||||
"label": "Supplier Address",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_details",
|
||||
@ -275,13 +269,6 @@
|
||||
"oldfieldtype": "Column Break",
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break4",
|
||||
"fieldtype": "Column Break",
|
||||
"read_only": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_document_type",
|
||||
@ -301,15 +288,6 @@
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "customer_address",
|
||||
"fieldtype": "Text",
|
||||
"label": "Customer Address",
|
||||
"oldfieldname": "customer_address",
|
||||
"oldfieldtype": "Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_date",
|
||||
@ -359,7 +337,7 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
@ -374,28 +352,6 @@
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "delivery_address",
|
||||
"fieldtype": "Text",
|
||||
"label": "Delivery Address",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"label": "Territory",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "territory",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "warranty_amc_details",
|
||||
@ -485,62 +441,30 @@
|
||||
"in_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "link:Company",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Select",
|
||||
"in_filter": 1,
|
||||
"label": "Fiscal Year",
|
||||
"options": "link:Fiscal Year",
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "trash_reason",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Trash Reason",
|
||||
"oldfieldname": "trash_reason",
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "sle_exists",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "SLE Exists",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"doctype": "DocPerm",
|
||||
"role": "Material Master Manager",
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material Manager",
|
||||
"write": 0
|
||||
"submit": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material User",
|
||||
"submit": 0
|
||||
},
|
||||
{
|
||||
"create": 0,
|
||||
"doctype": "DocPerm",
|
||||
"role": "Material User",
|
||||
"write": 0
|
||||
"role": "Accounts User"
|
||||
}
|
||||
]
|
@ -7,99 +7,23 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes, unittest
|
||||
|
||||
class TestSerialNo(unittest.TestCase):
|
||||
def test_aii_gl_entries_for_serial_no_in_store(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
|
||||
sr = webnotes.bean(copy=test_records[0])
|
||||
sr.doc.serial_no = "_Test Serial No 1"
|
||||
sr.insert()
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
|
||||
"stock_in_hand_account")
|
||||
against_stock_account = webnotes.conn.get_value("Company", "_Test Company",
|
||||
"stock_adjustment_account")
|
||||
|
||||
# check stock ledger entries
|
||||
sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
||||
where voucher_type = 'Serial No' and voucher_no = %s""", sr.doc.name, as_dict=1)[0]
|
||||
self.assertTrue(sle)
|
||||
self.assertEquals([sle.item_code, sle.warehouse, sle.actual_qty],
|
||||
["_Test Serialized Item", "_Test Warehouse - _TC", 1.0])
|
||||
|
||||
# check gl entries
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
|
||||
order by account desc""", sr.doc.name, as_dict=1)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = [
|
||||
[stock_in_hand_account, 1000.0, 0.0],
|
||||
[against_stock_account, 0.0, 1000.0]
|
||||
]
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[i][0], gle.account)
|
||||
self.assertEquals(expected_values[i][1], gle.debit)
|
||||
self.assertEquals(expected_values[i][2], gle.credit)
|
||||
|
||||
sr.load_from_db()
|
||||
self.assertEquals(sr.doc.sle_exists, 1)
|
||||
|
||||
# save again
|
||||
sr.save()
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
|
||||
order by account desc""", sr.doc.name, as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[i][0], gle.account)
|
||||
self.assertEquals(expected_values[i][1], gle.debit)
|
||||
self.assertEquals(expected_values[i][2], gle.credit)
|
||||
|
||||
# trash/cancel
|
||||
sr.submit()
|
||||
sr.cancel()
|
||||
|
||||
gl_count = webnotes.conn.sql("""select count(name) from `tabGL Entry`
|
||||
where voucher_type='Serial No' and voucher_no=%s and ifnull(is_cancelled, 'No') = 'Yes'
|
||||
order by account asc, name asc""", sr.doc.name)
|
||||
|
||||
self.assertEquals(gl_count[0][0], 4)
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
|
||||
|
||||
def test_aii_gl_entries_for_serial_no_delivered(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
|
||||
sr = webnotes.bean(copy=test_records[0])
|
||||
sr.doc.serial_no = "_Test Serial No 2"
|
||||
sr.doc.status = "Delivered"
|
||||
sr.insert()
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Serial No' and voucher_no=%s
|
||||
order by account desc""", sr.doc.name, as_dict=1)
|
||||
self.assertFalse(gl_entries)
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
test_records = [
|
||||
[
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"doctype": "Serial No",
|
||||
"serial_no": "_Test Serial No",
|
||||
"status": "In Store",
|
||||
"item_code": "_Test Serialized Item",
|
||||
"item_group": "_Test Item Group",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"purchase_rate": 1000.0,
|
||||
"purchase_time": "11:37:39",
|
||||
"purchase_date": "2013-02-26",
|
||||
'fiscal_year': "_Test Fiscal Year 2013"
|
||||
}
|
||||
]
|
||||
]
|
||||
test_records = []
|
||||
|
||||
from stock.doctype.serial_no.serial_no import *
|
||||
|
||||
class TestSerialNo(unittest.TestCase):
|
||||
def test_cannot_create_direct(self):
|
||||
sr = webnotes.new_bean("Serial No")
|
||||
sr.doc.item_code = "_Test Serialized Item"
|
||||
sr.doc.warehouse = "_Test Warehouse - _TC"
|
||||
sr.doc.serial_no = "_TCSER0001"
|
||||
sr.doc.purchase_rate = 10
|
||||
self.assertRaises(SerialNoCannotCreateDirectError, sr.insert)
|
||||
|
||||
sr.doc.warehouse = None
|
||||
sr.insert()
|
||||
self.assertTrue(sr.doc.name)
|
||||
|
||||
sr.doc.warehouse = "_Test Warehouse - _TC"
|
||||
self.assertTrue(SerialNoCannotCannotChangeError, sr.doc.save)
|
@ -103,12 +103,8 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message);
|
||||
|
||||
me.get_items();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
me.get_items();
|
||||
}
|
||||
},
|
||||
|
||||
@ -122,16 +118,15 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
},
|
||||
|
||||
get_items: function() {
|
||||
if(this.frm.doc.__islocal && (this.frm.doc.production_order || this.frm.doc.bom_no)
|
||||
&& !getchildren('Stock Entry Detail', this.frm.doc.name, 'mtn_details').length) {
|
||||
// if production order / bom is mentioned, get items
|
||||
return this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "get_items",
|
||||
callback: function(r) {
|
||||
if(!r.exc) refresh_field("mtn_details");
|
||||
}
|
||||
});
|
||||
if(this.frm.doc.production_order || this.frm.doc.bom_no) {
|
||||
// if production order / bom is mentioned, get items
|
||||
return this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "get_items",
|
||||
callback: function(r) {
|
||||
if(!r.exc) refresh_field("mtn_details");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -223,6 +218,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
||||
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
||||
},
|
||||
|
||||
mtn_details_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no(grid_row)
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.script_manager.make(erpnext.stock.StockEntry);
|
||||
|
@ -20,6 +20,7 @@ sql = webnotes.conn.sql
|
||||
class NotUpdateStockError(webnotes.ValidationError): pass
|
||||
class StockOverReturnError(webnotes.ValidationError): pass
|
||||
class IncorrectValuationRateError(webnotes.ValidationError): pass
|
||||
class DuplicateEntryForProductionOrderError(webnotes.ValidationError): pass
|
||||
|
||||
from controllers.stock_controller import StockController
|
||||
|
||||
@ -32,7 +33,6 @@ class DocType(StockController):
|
||||
def validate(self):
|
||||
self.validate_posting_time()
|
||||
self.validate_purpose()
|
||||
self.validate_serial_nos()
|
||||
pro_obj = self.doc.production_order and \
|
||||
get_obj('Production Order', self.doc.production_order) or None
|
||||
|
||||
@ -52,14 +52,14 @@ class DocType(StockController):
|
||||
self.set_total_amount()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_serial_no(1)
|
||||
self.update_stock_ledger(0)
|
||||
self.update_serial_no(1)
|
||||
self.update_production_order(1)
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_serial_no(0)
|
||||
self.update_stock_ledger(1)
|
||||
self.update_serial_no(0)
|
||||
self.update_production_order(0)
|
||||
self.make_cancel_gl_entries()
|
||||
|
||||
@ -74,11 +74,6 @@ class DocType(StockController):
|
||||
if self.doc.purpose not in valid_purposes:
|
||||
msgprint(_("Purpose must be one of ") + comma_or(valid_purposes),
|
||||
raise_exception=True)
|
||||
|
||||
def validate_serial_nos(self):
|
||||
sl_obj = get_obj("Stock Ledger")
|
||||
sl_obj.scrub_serial_nos(self)
|
||||
sl_obj.validate_serial_no(self, 'mtn_details')
|
||||
|
||||
def validate_item(self):
|
||||
for item in self.doclist.get({"parentfield": "mtn_details"}):
|
||||
@ -152,21 +147,33 @@ class DocType(StockController):
|
||||
return
|
||||
|
||||
if self.doc.purpose == "Manufacture/Repack":
|
||||
if not flt(self.doc.fg_completed_qty):
|
||||
msgprint(_("Manufacturing Quantity") + _(" is mandatory"), raise_exception=1)
|
||||
|
||||
if flt(pro_obj.doc.qty) < (flt(pro_obj.doc.produced_qty)
|
||||
+ flt(self.doc.fg_completed_qty)):
|
||||
# do not allow manufacture of qty > production order qty
|
||||
msgprint(_("For Item ") + pro_obj.doc.production_item
|
||||
+ _("Quantity already manufactured")
|
||||
+ " = %s." % flt(pro_obj.doc.produced_qty)
|
||||
+ _("Hence, maximum allowed Manufacturing Quantity")
|
||||
+ " = %s." % (flt(pro_obj.doc.qty) - flt(pro_obj.doc.produced_qty)),
|
||||
raise_exception=1)
|
||||
# check for double entry
|
||||
self.check_duplicate_entry_for_production_order()
|
||||
elif self.doc.purpose != "Material Transfer":
|
||||
self.doc.production_order = None
|
||||
|
||||
def check_duplicate_entry_for_production_order(self):
|
||||
other_ste = [t[0] for t in webnotes.conn.get_values("Stock Entry", {
|
||||
"production_order": self.doc.production_order,
|
||||
"purpose": self.doc.purpose,
|
||||
"docstatus": ["!=", 2],
|
||||
"name": ["!=", self.doc.name]
|
||||
}, "name")]
|
||||
|
||||
if other_ste:
|
||||
production_item, qty = webnotes.conn.get_value("Production Order",
|
||||
self.doc.production_order, ["production_item", "qty"])
|
||||
args = other_ste + [production_item]
|
||||
fg_qty_already_entered = webnotes.conn.sql("""select sum(actual_qty)
|
||||
from `tabStock Entry Detail`
|
||||
where parent in (%s)
|
||||
and item_code = %s
|
||||
and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
|
||||
|
||||
if fg_qty_already_entered >= qty:
|
||||
webnotes.throw(_("Stock Entries already created for Production Order ")
|
||||
+ self.doc.production_order + ":" + ", ".join(other_ste), DuplicateEntryForProductionOrderError)
|
||||
|
||||
def set_total_amount(self):
|
||||
self.doc.total_amount = sum([flt(item.amount) for item in self.doclist.get({"parentfield": "mtn_details"})])
|
||||
|
||||
@ -206,7 +213,7 @@ class DocType(StockController):
|
||||
"posting_date": self.doc.posting_date,
|
||||
"posting_time": self.doc.posting_time,
|
||||
"qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty,
|
||||
"serial_no": cstr(d.serial_no).strip(),
|
||||
"serial_no": d.serial_no,
|
||||
"bom_no": d.bom_no,
|
||||
})
|
||||
# get actual stock at source warehouse
|
||||
@ -317,27 +324,21 @@ class DocType(StockController):
|
||||
|
||||
def update_serial_no(self, is_submit):
|
||||
"""Create / Update Serial No"""
|
||||
from stock.utils import get_valid_serial_nos
|
||||
|
||||
sl_obj = get_obj('Stock Ledger')
|
||||
if is_submit:
|
||||
sl_obj.validate_serial_no_warehouse(self, 'mtn_details')
|
||||
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import update_serial_nos_after_submit, get_serial_nos
|
||||
update_serial_nos_after_submit(self, "Stock Entry", "mtn_details")
|
||||
|
||||
for d in getlist(self.doclist, 'mtn_details'):
|
||||
if cstr(d.serial_no).strip():
|
||||
for x in get_valid_serial_nos(d.serial_no):
|
||||
serial_no = x.strip()
|
||||
if d.s_warehouse:
|
||||
sl_obj.update_serial_delivery_details(self, d, serial_no, is_submit)
|
||||
if d.t_warehouse:
|
||||
sl_obj.update_serial_purchase_details(self, d, serial_no, is_submit,
|
||||
self.doc.purpose)
|
||||
|
||||
if self.doc.purpose == 'Purchase Return':
|
||||
serial_doc = Document("Serial No", serial_no)
|
||||
serial_doc.status = is_submit and 'Purchase Returned' or 'In Store'
|
||||
serial_doc.docstatus = is_submit and 2 or 0
|
||||
serial_doc.save()
|
||||
for serial_no in get_serial_nos(d.serial_no):
|
||||
if self.doc.purpose == 'Purchase Return':
|
||||
sr = webnotes.bean("Serial No", serial_no)
|
||||
sr.doc.status = "Purchase Returned" if is_submit else "Available"
|
||||
sr.save()
|
||||
|
||||
if self.doc.purpose == "Sales Return":
|
||||
sr = webnotes.bean("Serial No", serial_no)
|
||||
sr.doc.status = "Sales Returned" if is_submit else "Delivered"
|
||||
sr.save()
|
||||
|
||||
def update_stock_ledger(self, is_cancelled=0):
|
||||
self.values = []
|
||||
@ -571,7 +572,6 @@ class DocType(StockController):
|
||||
for item in item_dict:
|
||||
pending_to_issue = (max_qty * item_dict[item]["qty"]) - issued_item_qty.get(item, 0)
|
||||
desire_to_transfer = flt(self.doc.fg_completed_qty) * item_dict[item]["qty"]
|
||||
|
||||
if desire_to_transfer <= pending_to_issue:
|
||||
item_dict[item]["qty"] = desire_to_transfer
|
||||
else:
|
||||
|
@ -7,6 +7,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes, unittest
|
||||
from webnotes.utils import flt
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
|
||||
|
||||
class TestStockEntry(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
@ -40,11 +41,42 @@ class TestStockEntry(unittest.TestCase):
|
||||
webnotes.conn.set_default("company", self.old_default_company)
|
||||
|
||||
def test_warehouse_company_validation(self):
|
||||
webnotes.session.user = "test2@example.com"
|
||||
webnotes.bean("Profile", "test2@example.com").get_controller()\
|
||||
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
|
||||
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import InvalidWarehouseCompany
|
||||
st1 = webnotes.bean(copy=test_records[0])
|
||||
st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
|
||||
st1.insert()
|
||||
self.assertRaises(InvalidWarehouseCompany, st1.submit)
|
||||
|
||||
webnotes.session.user = "Administrator"
|
||||
|
||||
def test_warehouse_user(self):
|
||||
from stock.utils import UserNotAllowedForWarehouse
|
||||
|
||||
webnotes.session.user = "test@example.com"
|
||||
webnotes.bean("Profile", "test@example.com").get_controller()\
|
||||
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
|
||||
|
||||
webnotes.bean("Profile", "test2@example.com").get_controller()\
|
||||
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
|
||||
|
||||
st1 = webnotes.bean(copy=test_records[0])
|
||||
st1.doc.company = "_Test Company 1"
|
||||
st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
|
||||
st1.insert()
|
||||
self.assertRaises(UserNotAllowedForWarehouse, st1.submit)
|
||||
|
||||
webnotes.session.user = "test2@example.com"
|
||||
st1 = webnotes.bean(copy=test_records[0])
|
||||
st1.doc.company = "_Test Company 1"
|
||||
st1.doclist[1].t_warehouse="_Test Warehouse 2 - _TC1"
|
||||
st1.insert()
|
||||
st1.submit()
|
||||
|
||||
webnotes.session.user = "Administrator"
|
||||
|
||||
def test_material_receipt_gl_entry(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
@ -180,6 +212,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
def _clear_stock(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.conn.sql("""delete from `tabSerial No`""")
|
||||
|
||||
self.old_default_company = webnotes.conn.get_default("company")
|
||||
webnotes.conn.set_default("company", "_Test Company")
|
||||
@ -571,12 +604,139 @@ class TestStockEntry(unittest.TestCase):
|
||||
|
||||
return se, pr.doc.name
|
||||
|
||||
def test_serial_no_not_reqd(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].serial_no = "ABCD"
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoNotRequiredError, se.submit)
|
||||
|
||||
def test_serial_no_reqd(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 2
|
||||
se.doclist[1].transfer_qty = 2
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoRequiredError, se.submit)
|
||||
|
||||
def test_serial_no_qty_more(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 2
|
||||
se.doclist[1].serial_no = "ABCD\nEFGH\nXYZ"
|
||||
se.doclist[1].transfer_qty = 2
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoQtyError, se.submit)
|
||||
|
||||
def test_serial_no_qty_less(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 2
|
||||
se.doclist[1].serial_no = "ABCD"
|
||||
se.doclist[1].transfer_qty = 2
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoQtyError, se.submit)
|
||||
|
||||
def test_serial_no_transfer_in(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 2
|
||||
se.doclist[1].serial_no = "ABCD\nEFGH"
|
||||
se.doclist[1].transfer_qty = 2
|
||||
se.insert()
|
||||
se.submit()
|
||||
|
||||
self.assertTrue(webnotes.conn.exists("Serial No", "ABCD"))
|
||||
self.assertTrue(webnotes.conn.exists("Serial No", "EFGH"))
|
||||
|
||||
def test_serial_no_not_exists(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doc.purpose = "Material Issue"
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 2
|
||||
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
|
||||
se.doclist[1].t_warehouse = None
|
||||
se.doclist[1].serial_no = "ABCD\nEFGH"
|
||||
se.doclist[1].transfer_qty = 2
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoNotExistsError, se.submit)
|
||||
|
||||
def test_serial_by_series(self):
|
||||
se = make_serialized_item()
|
||||
|
||||
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||
|
||||
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[0]))
|
||||
self.assertTrue(webnotes.conn.exists("Serial No", serial_nos[1]))
|
||||
|
||||
return se
|
||||
|
||||
def test_serial_item_error(self):
|
||||
self.test_serial_by_series()
|
||||
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doc.purpose = "Material Transfer"
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 1
|
||||
se.doclist[1].transfer_qty = 1
|
||||
se.doclist[1].serial_no = "ABCD00001"
|
||||
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
|
||||
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoItemError, se.submit)
|
||||
|
||||
def test_serial_move(self):
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
|
||||
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doc.purpose = "Material Transfer"
|
||||
se.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
se.doclist[1].qty = 1
|
||||
se.doclist[1].transfer_qty = 1
|
||||
se.doclist[1].serial_no = serial_no
|
||||
se.doclist[1].s_warehouse = "_Test Warehouse - _TC"
|
||||
se.doclist[1].t_warehouse = "_Test Warehouse 1 - _TC"
|
||||
se.insert()
|
||||
se.submit()
|
||||
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC")
|
||||
|
||||
def test_serial_warehouse_error(self):
|
||||
make_serialized_item()
|
||||
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doc.purpose = "Material Transfer"
|
||||
se.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
se.doclist[1].qty = 1
|
||||
se.doclist[1].transfer_qty = 1
|
||||
se.doclist[1].serial_no = "ABCD00001"
|
||||
se.doclist[1].s_warehouse = "_Test Warehouse 1 - _TC"
|
||||
se.doclist[1].t_warehouse = "_Test Warehouse - _TC"
|
||||
se.insert()
|
||||
self.assertRaises(SerialNoWarehouseError, se.submit)
|
||||
|
||||
def test_serial_cancel(self):
|
||||
se = self.test_serial_by_series()
|
||||
se.cancel()
|
||||
|
||||
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", serial_no, "warehouse"))
|
||||
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "status"), "Not Available")
|
||||
|
||||
def make_serialized_item():
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].item_code = "_Test Serialized Item With Series"
|
||||
se.doclist[1].qty = 2
|
||||
se.doclist[1].transfer_qty = 2
|
||||
se.insert()
|
||||
se.submit()
|
||||
return se
|
||||
|
||||
test_records = [
|
||||
[
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"doctype": "Stock Entry",
|
||||
"posting_date": "2013-01-25",
|
||||
"posting_date": "2013-01-01",
|
||||
"posting_time": "17:14:24",
|
||||
"purpose": "Material Receipt",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
|
@ -17,174 +17,10 @@ class DocType:
|
||||
def __init__(self, doc, doclist=[]):
|
||||
self.doc = doc
|
||||
self.doclist = doclist
|
||||
|
||||
|
||||
def scrub_serial_nos(self, obj, table_name = ''):
|
||||
if not table_name:
|
||||
table_name = obj.fname
|
||||
|
||||
for d in getlist(obj.doclist, table_name):
|
||||
if d.serial_no:
|
||||
serial_nos = cstr(d.serial_no).strip().replace(',', '\n').split('\n')
|
||||
d.serial_no = "\n".join(map(lambda x: x.strip(), serial_nos))
|
||||
d.save()
|
||||
|
||||
def validate_serial_no_warehouse(self, obj, fname):
|
||||
for d in getlist(obj.doclist, fname):
|
||||
wh = d.warehouse or d.s_warehouse
|
||||
if cstr(d.serial_no).strip() and wh:
|
||||
serial_nos = get_valid_serial_nos(d.serial_no)
|
||||
for s in serial_nos:
|
||||
s = s.strip()
|
||||
sr_war = webnotes.conn.sql("select warehouse,name from `tabSerial No` where name = '%s'" % (s))
|
||||
if not sr_war:
|
||||
msgprint("Serial No %s does not exists"%s, raise_exception = 1)
|
||||
elif not sr_war[0][0]:
|
||||
msgprint("Warehouse not mentioned in the Serial No <b>%s</b>" % s, raise_exception = 1)
|
||||
elif sr_war[0][0] != wh:
|
||||
msgprint("Serial No : %s for Item : %s doesn't exists in Warehouse : %s" % (s, d.item_code, wh), raise_exception = 1)
|
||||
|
||||
|
||||
def validate_serial_no(self, obj, fname):
|
||||
"""check whether serial no is required"""
|
||||
for d in getlist(obj.doclist, fname):
|
||||
is_stock_item = webnotes.conn.get_value('Item', d.item_code, 'is_stock_item')
|
||||
ar_required = webnotes.conn.get_value('Item', d.item_code, 'has_serial_no')
|
||||
|
||||
# [bug fix] need to strip serial nos of all spaces and new lines for validation
|
||||
serial_no = cstr(d.serial_no).strip()
|
||||
if serial_no:
|
||||
if is_stock_item != 'Yes':
|
||||
msgprint("Serial No is not required for non-stock item: %s" % d.item_code, raise_exception=1)
|
||||
elif ar_required != 'Yes':
|
||||
msgprint("If serial no required, please select 'Yes' in 'Has Serial No' in Item :" + d.item_code + \
|
||||
', otherwise please remove serial no', raise_exception=1)
|
||||
elif ar_required == 'Yes' and not serial_no and d.qty:
|
||||
msgprint("Serial no is mandatory for item: "+ d.item_code, raise_exception = 1)
|
||||
|
||||
# validate rejected serial nos
|
||||
if fname == 'purchase_receipt_details' and flt(d.rejected_qty) > 0 and ar_required == 'Yes' and not d.rejected_serial_no:
|
||||
msgprint("Rejected serial no is mandatory for rejected qty of item: "+ d.item_code, raise_exception = 1)
|
||||
|
||||
|
||||
def set_pur_serial_no_values(self, obj, serial_no, d, s, new_rec, rejected=None):
|
||||
item_details = webnotes.conn.sql("""select item_group, warranty_period
|
||||
from `tabItem` where name = '%s' and (ifnull(end_of_life,'')='' or
|
||||
end_of_life = '0000-00-00' or end_of_life > now()) """ %(d.item_code), as_dict=1)
|
||||
|
||||
s.purchase_document_type = obj.doc.doctype
|
||||
s.purchase_document_no = obj.doc.name
|
||||
s.purchase_date = obj.doc.posting_date
|
||||
s.purchase_time = obj.doc.posting_time
|
||||
s.purchase_rate = d.valuation_rate or d.incoming_rate
|
||||
s.item_code = d.item_code
|
||||
s.item_name = d.item_name
|
||||
s.brand = d.brand
|
||||
s.description = d.description
|
||||
s.item_group = item_details and item_details[0]['item_group'] or ''
|
||||
s.warranty_period = item_details and item_details[0]['warranty_period'] or 0
|
||||
s.supplier = obj.doc.supplier
|
||||
s.supplier_name = obj.doc.supplier_name
|
||||
s.address_display = obj.doc.address_display or obj.doc.supplier_address
|
||||
s.warehouse = rejected and obj.doc.rejected_warehouse \
|
||||
or d.warehouse or d.t_warehouse or ""
|
||||
s.docstatus = 0
|
||||
s.status = 'In Store'
|
||||
s.modified = nowdate()
|
||||
s.modified_by = session['user']
|
||||
s.serial_no = serial_no
|
||||
s.sle_exists = 1
|
||||
s.company = obj.doc.company
|
||||
s.save(new_rec)
|
||||
|
||||
|
||||
def update_serial_purchase_details(self, obj, d, serial_no, is_submit, purpose = '', rejected=None):
|
||||
exists = webnotes.conn.sql("select name, status, docstatus from `tabSerial No` where name = '%s'" % (serial_no))
|
||||
if is_submit:
|
||||
if exists and exists[0][2] != 2 and \
|
||||
purpose not in ['Material Transfer', "Material Receipt", 'Sales Return']:
|
||||
msgprint("Serial No: %s already %s" % (serial_no, exists and exists[0][1]), raise_exception = 1)
|
||||
elif exists:
|
||||
s = Document('Serial No', exists and exists[0][0])
|
||||
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 0, rejected=rejected)
|
||||
else:
|
||||
s = Document('Serial No')
|
||||
self.set_pur_serial_no_values(obj, serial_no, d, s, new_rec = 1, rejected=rejected)
|
||||
else:
|
||||
if exists and exists[0][1] == 'Delivered' and exists[0][2] != 2:
|
||||
msgprint("Serial No: %s is already delivered, you can not cancel the document." % serial_no, raise_exception=1)
|
||||
elif purpose == 'Material Transfer':
|
||||
webnotes.conn.sql("update `tabSerial No` set status = 'In Store', purchase_document_type = '', purchase_document_no = '', warehouse = '%s' where name = '%s'" % (d.s_warehouse, serial_no))
|
||||
elif purpose == 'Sales Return':
|
||||
webnotes.conn.sql("update `tabSerial No` set status = 'Delivered', purchase_document_type = '', purchase_document_no = '' where name = '%s'" % serial_no)
|
||||
else:
|
||||
webnotes.conn.sql("update `tabSerial No` set docstatus = 2, status = 'Not in Use', purchase_document_type = '', purchase_document_no = '', purchase_date = null, purchase_rate = 0, supplier = null, supplier_name = '', address_display = '', warehouse = '' where name = '%s'" % serial_no)
|
||||
|
||||
|
||||
def check_serial_no_exists(self, serial_no, item_code):
|
||||
chk = webnotes.conn.sql("select name, status, docstatus, item_code from `tabSerial No` where name = %s", (serial_no), as_dict=1)
|
||||
if not chk:
|
||||
msgprint("Serial No: %s does not exists in the system" % serial_no, raise_exception=1)
|
||||
elif chk and chk[0]['item_code'] != item_code:
|
||||
msgprint("Serial No: %s not belong to item: %s" % (serial_no, item_code), raise_exception=1)
|
||||
elif chk and chk[0]['docstatus'] == 2:
|
||||
msgprint("Serial No: %s of Item : %s is trashed in the system" % (serial_no, item_code), raise_exception = 1)
|
||||
elif chk and chk[0]['status'] == 'Delivered':
|
||||
msgprint("Serial No: %s of Item : %s is already delivered." % (serial_no, item_code), raise_exception = 1)
|
||||
|
||||
|
||||
def set_delivery_serial_no_values(self, obj, serial_no):
|
||||
s = Document('Serial No', serial_no)
|
||||
s.delivery_document_type = obj.doc.doctype
|
||||
s.delivery_document_no = obj.doc.name
|
||||
s.delivery_date = obj.doc.posting_date
|
||||
s.delivery_time = obj.doc.posting_time
|
||||
s.customer = obj.doc.customer
|
||||
s.customer_name = obj.doc.customer_name
|
||||
s.delivery_address = obj.doc.address_display
|
||||
s.territory = obj.doc.territory
|
||||
s.warranty_expiry_date = cint(s.warranty_period) and \
|
||||
add_days(cstr(obj.doc.posting_date), cint(s.warranty_period)) or s.warranty_expiry_date
|
||||
s.docstatus = 1
|
||||
s.status = 'Delivered'
|
||||
s.modified = nowdate()
|
||||
s.modified_by = session['user']
|
||||
s.save()
|
||||
|
||||
|
||||
def update_serial_delivery_details(self, obj, d, serial_no, is_submit):
|
||||
if is_submit:
|
||||
self.check_serial_no_exists(serial_no, d.item_code)
|
||||
self.set_delivery_serial_no_values(obj, serial_no)
|
||||
else:
|
||||
webnotes.conn.sql("update `tabSerial No` set docstatus = 0, status = 'In Store', delivery_document_type = '', delivery_document_no = '', delivery_date = null, customer = null, customer_name = '', delivery_address = '', territory = null where name = '%s'" % (serial_no))
|
||||
|
||||
|
||||
def update_serial_record(self, obj, fname, is_submit = 1, is_incoming = 0):
|
||||
for d in getlist(obj.doclist, fname):
|
||||
if d.serial_no:
|
||||
serial_nos = get_valid_serial_nos(d.serial_no)
|
||||
for a in serial_nos:
|
||||
serial_no = a.strip()
|
||||
if is_incoming:
|
||||
self.update_serial_purchase_details(obj, d, serial_no, is_submit)
|
||||
else:
|
||||
self.update_serial_delivery_details(obj, d, serial_no, is_submit)
|
||||
|
||||
if fname == 'purchase_receipt_details' and d.rejected_qty and d.rejected_serial_no:
|
||||
serial_nos = get_valid_serial_nos(d.rejected_serial_no)
|
||||
for a in serial_nos:
|
||||
self.update_serial_purchase_details(obj, d, a, is_submit, rejected=True)
|
||||
|
||||
|
||||
def update_stock(self, values, is_amended = 'No'):
|
||||
for v in values:
|
||||
sle_id, valid_serial_nos = '', ''
|
||||
# get serial nos
|
||||
if v.get("serial_no", "").strip():
|
||||
valid_serial_nos = get_valid_serial_nos(v["serial_no"],
|
||||
v['actual_qty'], v['item_code'])
|
||||
v["serial_no"] = valid_serial_nos and "\n".join(valid_serial_nos) or ""
|
||||
sle_id = ''
|
||||
|
||||
# reverse quantities for cancel
|
||||
if v.get('is_cancelled') == 'Yes':
|
||||
|
@ -3,11 +3,21 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes import _, msgprint
|
||||
from webnotes.utils import cint, flt, getdate
|
||||
from webnotes import _, msgprint, ValidationError
|
||||
from webnotes.utils import cint, flt, getdate, cstr
|
||||
from webnotes.model.controller import DocListController
|
||||
|
||||
class InvalidWarehouseCompany(Exception): pass
|
||||
class InvalidWarehouseCompany(ValidationError): pass
|
||||
class SerialNoNotRequiredError(ValidationError): pass
|
||||
class SerialNoRequiredError(ValidationError): pass
|
||||
class SerialNoQtyError(ValidationError): pass
|
||||
class SerialNoItemError(ValidationError): pass
|
||||
class SerialNoWarehouseError(ValidationError): pass
|
||||
class SerialNoStatusError(ValidationError): pass
|
||||
class SerialNoNotExistsError(ValidationError): pass
|
||||
|
||||
def get_serial_nos(serial_no):
|
||||
return [s.strip() for s in cstr(serial_no).strip().replace(',', '\n').split('\n') if s.strip()]
|
||||
|
||||
class DocType(DocListController):
|
||||
def __init__(self, doc, doclist=[]):
|
||||
@ -15,9 +25,14 @@ class DocType(DocListController):
|
||||
self.doclist = doclist
|
||||
|
||||
def validate(self):
|
||||
from stock.utils import validate_warehouse_user
|
||||
if not hasattr(webnotes, "new_stock_ledger_entries"):
|
||||
webnotes.new_stock_ledger_entries = []
|
||||
|
||||
webnotes.new_stock_ledger_entries.append(self.doc)
|
||||
self.validate_mandatory()
|
||||
self.validate_item()
|
||||
self.validate_warehouse_user()
|
||||
validate_warehouse_user(self.doc.warehouse)
|
||||
self.validate_warehouse_company()
|
||||
self.actual_amt_check()
|
||||
self.check_stock_frozen_date()
|
||||
@ -39,16 +54,6 @@ class DocType(DocListController):
|
||||
|
||||
self.doc.fields.pop('batch_bal')
|
||||
|
||||
def validate_warehouse_user(self):
|
||||
if webnotes.session.user=="Administrator":
|
||||
return
|
||||
warehouse_users = [p[0] for p in webnotes.conn.sql("""select user from `tabWarehouse User`
|
||||
where parent=%s""", self.doc.warehouse)]
|
||||
|
||||
if warehouse_users and not webnotes.session.user in warehouse_users:
|
||||
webnotes.msgprint(_("User not allowed entry in the Warehouse") \
|
||||
+ ": " + webnotes.session.user + " / " + self.doc.warehouse, raise_exception = 1)
|
||||
|
||||
def validate_warehouse_company(self):
|
||||
warehouse_company = webnotes.conn.get_value("Warehouse", self.doc.warehouse, "company")
|
||||
if warehouse_company and warehouse_company != self.doc.company:
|
||||
@ -56,10 +61,10 @@ class DocType(DocListController):
|
||||
self.doc.warehouse + ", " + self.doc.company +")",
|
||||
raise_exception=InvalidWarehouseCompany)
|
||||
|
||||
def validate_mandatory(self):
|
||||
def validate_mandatory(self):
|
||||
mandatory = ['warehouse','posting_date','voucher_type','voucher_no','actual_qty','company']
|
||||
for k in mandatory:
|
||||
if self.doc.fields.get(k)==None:
|
||||
if not self.doc.fields.get(k):
|
||||
msgprint("Stock Ledger Entry: '%s' is mandatory" % k, raise_exception = 1)
|
||||
elif k == 'warehouse':
|
||||
if not webnotes.conn.sql("select name from tabWarehouse where name = '%s'" % self.doc.fields.get(k)):
|
||||
@ -67,35 +72,105 @@ class DocType(DocListController):
|
||||
|
||||
def validate_item(self):
|
||||
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
|
||||
ifnull(is_stock_item, 'No') from tabItem where name=%s""",
|
||||
self.doc.item_code)
|
||||
is_stock_item, has_serial_no, serial_no_series
|
||||
from tabItem where name=%s""",
|
||||
self.doc.item_code, as_dict=True)[0]
|
||||
|
||||
# check item exists
|
||||
if item_det:
|
||||
item_det = item_det and item_det[0]
|
||||
else:
|
||||
msgprint("Item: '%s' does not exist in the system. Please check." % self.doc.item_code, raise_exception = 1)
|
||||
|
||||
if item_det[3]!='Yes':
|
||||
webnotes.msgprint("""Item: "%s" is not a Stock Item.""" % self.doc.item_code,
|
||||
raise_exception=1)
|
||||
|
||||
# check if item is trashed
|
||||
if cint(item_det[2])==2:
|
||||
msgprint("Item: '%s' is trashed, cannot make a stock transaction against a trashed item" % self.doc.item_code, raise_exception = 1)
|
||||
if item_det.is_stock_item != 'Yes':
|
||||
webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
|
||||
|
||||
# check if batch number is required
|
||||
if item_det[1]=='Yes' and self.doc.voucher_type != 'Stock Reconciliation':
|
||||
if item_det.has_batch_no =='Yes' and self.doc.voucher_type != 'Stock Reconciliation':
|
||||
if not self.doc.batch_no:
|
||||
msgprint("Batch number is mandatory for Item '%s'" % self.doc.item_code, raise_exception = 1)
|
||||
raise Exception
|
||||
webnotes.throw("Batch number is mandatory for Item '%s'" % self.doc.item_code)
|
||||
|
||||
# check if batch belongs to item
|
||||
if not webnotes.conn.sql("select name from `tabBatch` where item='%s' and name ='%s' and docstatus != 2" % (self.doc.item_code, self.doc.batch_no)):
|
||||
msgprint("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code), raise_exception = 1)
|
||||
if not webnotes.conn.sql("""select name from `tabBatch`
|
||||
where item='%s' and name ='%s' and docstatus != 2""" % (self.doc.item_code, self.doc.batch_no)):
|
||||
webnotes.throw("'%s' is not a valid Batch Number for Item '%s'" % (self.doc.batch_no, self.doc.item_code))
|
||||
|
||||
# Nobody can do SL Entries where posting date is before freezing date except authorized person
|
||||
#----------------------------------------------------------------------------------------------
|
||||
self.validate_serial_no(item_det)
|
||||
|
||||
def validate_serial_no(self, item_det):
|
||||
if item_det.has_serial_no=="No":
|
||||
if self.doc.serial_no:
|
||||
webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item),
|
||||
SerialNoNotRequiredError)
|
||||
else:
|
||||
if self.doc.serial_no:
|
||||
serial_nos = get_serial_nos(self.doc.serial_no)
|
||||
if cint(self.doc.actual_qty) != flt(self.doc.actual_qty):
|
||||
webnotes.throw(_("Serial No qty cannot be a fraction") + \
|
||||
(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)))
|
||||
if len(serial_nos) and len(serial_nos) != abs(cint(self.doc.actual_qty)):
|
||||
webnotes.throw(_("Serial Nos do not match with qty") + \
|
||||
(": %s (%s)" % (self.doc.item_code, self.doc.actual_qty)), SerialNoQtyError)
|
||||
|
||||
# check serial no exists, if yes then source
|
||||
for serial_no in serial_nos:
|
||||
if webnotes.conn.exists("Serial No", serial_no):
|
||||
sr = webnotes.bean("Serial No", serial_no)
|
||||
|
||||
if sr.doc.item_code!=self.doc.item_code:
|
||||
webnotes.throw(_("Serial No does not belong to Item") + \
|
||||
(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoItemError)
|
||||
|
||||
sr.make_controller().via_stock_ledger = True
|
||||
|
||||
if self.doc.actual_qty < 0:
|
||||
if sr.doc.warehouse!=self.doc.warehouse:
|
||||
webnotes.throw(_("Warehouse does not belong to Item") + \
|
||||
(": %s (%s)" % (self.doc.item_code, serial_no)), SerialNoWarehouseError)
|
||||
|
||||
if self.doc.voucher_type in ("Delivery Note", "Sales Invoice") \
|
||||
and sr.doc.status != "Available":
|
||||
webnotes.throw(_("Serial No status must be 'Available' to Deliver") + \
|
||||
": " + serial_no, SerialNoStatusError)
|
||||
|
||||
|
||||
sr.doc.warehouse = None
|
||||
sr.save()
|
||||
else:
|
||||
sr.doc.warehouse = self.doc.warehouse
|
||||
sr.save()
|
||||
else:
|
||||
if self.doc.actual_qty < 0:
|
||||
# transfer out
|
||||
webnotes.throw(_("Serial No must exist to transfer out.") + \
|
||||
": " + serial_no, SerialNoNotExistsError)
|
||||
else:
|
||||
# transfer in
|
||||
self.make_serial_no(serial_no)
|
||||
else:
|
||||
if item_det.serial_no_series:
|
||||
from webnotes.model.doc import make_autoname
|
||||
serial_nos = []
|
||||
for i in xrange(cint(self.doc.actual_qty)):
|
||||
serial_nos.append(self.make_serial_no(make_autoname(item_det.serial_no_series)))
|
||||
self.doc.serial_no = "\n".join(serial_nos)
|
||||
else:
|
||||
webnotes.throw(_("Serial Number Required for Serialized Item" + ": " + self.doc.item),
|
||||
SerialNoRequiredError)
|
||||
|
||||
def make_serial_no(self, serial_no):
|
||||
sr = webnotes.new_bean("Serial No")
|
||||
sr.doc.serial_no = serial_no
|
||||
sr.doc.item_code = self.doc.item_code
|
||||
sr.doc.purchase_rate = self.doc.incoming_rate
|
||||
sr.doc.purchase_document_type = self.doc.voucher_type
|
||||
sr.doc.purchase_document_no = self.doc.voucher_no
|
||||
sr.doc.purchase_date = self.doc.posting_date
|
||||
sr.doc.purchase_time = self.doc.posting_time
|
||||
sr.make_controller().via_stock_ledger = True
|
||||
sr.insert()
|
||||
|
||||
# set warehouse
|
||||
sr.doc.warehouse = self.doc.warehouse
|
||||
sr.doc.status = "Available"
|
||||
sr.save()
|
||||
webnotes.msgprint(_("Serial No created") + ": " + sr.doc.name)
|
||||
return sr.doc.name
|
||||
|
||||
def check_stock_frozen_date(self):
|
||||
stock_frozen_upto = webnotes.conn.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
|
||||
if stock_frozen_upto:
|
||||
@ -107,6 +182,21 @@ class DocType(DocListController):
|
||||
if not self.doc.posting_time or self.doc.posting_time == '00:0':
|
||||
self.doc.posting_time = '00:00'
|
||||
|
||||
def update_serial_nos_after_submit(controller, parenttype, parentfield):
|
||||
if not hasattr(webnotes, "new_stock_ledger_entries"):
|
||||
return
|
||||
|
||||
for d in controller.doclist.get({"parentfield": parentfield}):
|
||||
serial_no = None
|
||||
for sle in webnotes.new_stock_ledger_entries:
|
||||
if sle.voucher_detail_no==d.name:
|
||||
serial_no = sle.serial_no
|
||||
break
|
||||
|
||||
if d.serial_no != serial_no:
|
||||
d.serial_no = serial_no
|
||||
webnotes.conn.set_value(d.doctype, d.name, "serial_no", serial_no)
|
||||
|
||||
def on_doctype_update():
|
||||
if not webnotes.conn.sql("""show index from `tabStock Ledger Entry`
|
||||
where Key_name="posting_sort_index" """):
|
||||
|
@ -293,7 +293,7 @@ class DocType(StockController):
|
||||
|
||||
self.doc.stock_value_difference = 0.0
|
||||
for d in self.entries:
|
||||
self.doc.stock_value_difference -= get_buying_amount(d.item_code, self.doc.doctype, self.doc.name,
|
||||
self.doc.stock_value_difference -= get_buying_amount(self.doc.doctype, self.doc.name,
|
||||
d.voucher_detail_no, stock_ledger_entries.get((d.item_code, d.warehouse), []))
|
||||
webnotes.conn.set(self.doc, "stock_value_difference", self.doc.stock_value_difference)
|
||||
|
||||
|
@ -16,5 +16,9 @@ test_records = [
|
||||
"doctype": "Warehouse",
|
||||
"warehouse_name": "_Test Warehouse 2",
|
||||
"company": "_Test Company 1"
|
||||
}, {
|
||||
"doctype": "Warehouse User",
|
||||
"parentfield": "warehouse_users",
|
||||
"user": "test2@example.com"
|
||||
}]
|
||||
]
|
||||
|
@ -176,7 +176,8 @@ erpnext.StockAgeing = erpnext.StockGridReport.extend({
|
||||
xaxis: {
|
||||
ticks: $.map(me.data, function(item, idx) { return [[idx+1, item.name]] }),
|
||||
max: 20
|
||||
}
|
||||
},
|
||||
series: { downsample: { threshold: 1000 } }
|
||||
}
|
||||
}
|
||||
});
|
@ -180,6 +180,11 @@ wn.module_page["Stock"] = [
|
||||
route: "query-report/Purchase Order Items To Be Received",
|
||||
doctype: "Purchase Receipt"
|
||||
},
|
||||
{
|
||||
"label":wn._("Item Shortage Report"),
|
||||
route: "Report/Bin/Item Shortage Report",
|
||||
doctype: "Purchase Receipt"
|
||||
},
|
||||
{
|
||||
"label":wn._("Serial No Service Contract Expiry"),
|
||||
route: "Report/Serial No/Serial No Service Contract Expiry",
|
||||
|
@ -235,6 +235,7 @@ erpnext.StockLedger = erpnext.StockGridReport.extend({
|
||||
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) {
|
||||
|
0
stock/report/item_shortage_report/__init__.py
Normal file
0
stock/report/item_shortage_report/__init__.py
Normal file
22
stock/report/item_shortage_report/item_shortage_report.txt
Normal file
22
stock/report/item_shortage_report/item_shortage_report.txt
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"creation": "2013-08-20 13:43:30",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-20 13:46:15",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"json": "{\"filters\":[[\"Bin\",\"projected_qty\",\"<\",\"0\"]],\"columns\":[[\"warehouse\",\"Bin\"],[\"item_code\",\"Bin\"],[\"actual_qty\",\"Bin\"],[\"ordered_qty\",\"Bin\"],[\"planned_qty\",\"Bin\"],[\"reserved_qty\",\"Bin\"],[\"projected_qty\",\"Bin\"]],\"sort_by\":\"Bin.projected_qty\",\"sort_order\":\"asc\",\"sort_by_next\":\"\",\"sort_order_next\":\"desc\"}",
|
||||
"name": "__common__",
|
||||
"ref_doctype": "Bin",
|
||||
"report_name": "Item Shortage Report",
|
||||
"report_type": "Report Builder"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"name": "Item Shortage Report"
|
||||
}
|
||||
]
|
0
stock/report/items_to_be_requested/__init__.py
Normal file
0
stock/report/items_to_be_requested/__init__.py
Normal file
22
stock/report/items_to_be_requested/items_to_be_requested.txt
Normal file
22
stock/report/items_to_be_requested/items_to_be_requested.txt
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"creation": "2013-08-20 15:08:10",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-20 15:10:45",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"name": "__common__",
|
||||
"query": "SELECT\n tabBin.item_code as \"Item:Link/Item:120\",\n tabBin.warehouse as \"Warehouse:Link/Warehouse:120\",\n tabBin.actual_qty as \"Actual:Float:90\",\n tabBin.indented_qty as \"Requested:Float:90\",\n tabBin.reserved_qty as \"Reserved:Float:90\",\n tabBin.ordered_qty as \"Ordered:Float:90\",\n tabBin.projected_qty as \"Projected:Float:90\"\nFROM\n tabBin, tabItem\nWHERE\n tabBin.item_code = tabItem.name\n AND tabItem.is_purchase_item = \"Yes\"\n AND tabBin.projected_qty < 0\nORDER BY\n tabBin.projected_qty ASC",
|
||||
"ref_doctype": "Item",
|
||||
"report_name": "Items To Be Requested",
|
||||
"report_type": "Query Report"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"name": "Items To Be Requested"
|
||||
}
|
||||
]
|
@ -8,6 +8,8 @@ from webnotes.utils import flt, cstr, nowdate, add_days, cint
|
||||
from webnotes.defaults import get_global_default
|
||||
from webnotes.utils.email_lib import sendmail
|
||||
|
||||
class UserNotAllowedForWarehouse(webnotes.ValidationError): pass
|
||||
|
||||
def validate_end_of_life(item_code, end_of_life=None, verbose=1):
|
||||
if not end_of_life:
|
||||
end_of_life = webnotes.conn.get_value("Item", item_code, "end_of_life")
|
||||
@ -152,20 +154,28 @@ def get_warehouse_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
wlist.append([w])
|
||||
return wlist
|
||||
|
||||
def get_buying_amount(item_code, voucher_type, voucher_no, voucher_detail_no,
|
||||
stock_ledger_entries, item_sales_bom=None):
|
||||
if item_sales_bom and item_sales_bom.get(item_code):
|
||||
# sales bom item
|
||||
buying_amount = 0.0
|
||||
for bom_item in item_sales_bom[item_code]:
|
||||
if bom_item.get("parent_detail_docname")==voucher_detail_no:
|
||||
buying_amount += _get_buying_amount(voucher_type, voucher_no, voucher_detail_no, stock_ledger_entries)
|
||||
return buying_amount
|
||||
else:
|
||||
# doesn't have sales bom
|
||||
return _get_buying_amount(voucher_type, voucher_no, voucher_detail_no, stock_ledger_entries)
|
||||
def validate_warehouse_user(warehouse):
|
||||
if webnotes.session.user=="Administrator":
|
||||
return
|
||||
warehouse_users = [p[0] for p in webnotes.conn.sql("""select user from `tabWarehouse User`
|
||||
where parent=%s""", warehouse)]
|
||||
|
||||
if warehouse_users and not (webnotes.session.user in warehouse_users):
|
||||
webnotes.throw(_("Not allowed entry in Warehouse") \
|
||||
+ ": " + warehouse, UserNotAllowedForWarehouse)
|
||||
|
||||
def get_sales_bom_buying_amount(item_code, warehouse, voucher_type, voucher_no, voucher_detail_no,
|
||||
stock_ledger_entries, item_sales_bom):
|
||||
# sales bom item
|
||||
buying_amount = 0.0
|
||||
for bom_item in item_sales_bom[item_code]:
|
||||
if bom_item.get("parent_detail_docname")==voucher_detail_no:
|
||||
buying_amount += get_buying_amount(voucher_type, voucher_no, voucher_detail_no,
|
||||
stock_ledger_entries.get((bom_item.item_code, warehouse), []))
|
||||
|
||||
return buying_amount
|
||||
|
||||
def _get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries):
|
||||
def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries):
|
||||
# IMP NOTE
|
||||
# stock_ledger_entries should already be filtered by item_code and warehouse and
|
||||
# sorted by posting_date desc, posting_time desc
|
||||
@ -193,8 +203,7 @@ def reorder_item():
|
||||
and exists (select name from `tabItem`
|
||||
where `tabItem`.name = `tabBin`.item_code and
|
||||
is_stock_item='Yes' and (is_purchase_item='Yes' or is_sub_contracted_item='Yes') and
|
||||
(ifnull(end_of_life, '')='') or end_of_life > now())""",
|
||||
as_dict=True)
|
||||
(ifnull(end_of_life, '')='' or end_of_life > now()))""", as_dict=True)
|
||||
for bin in bin_list:
|
||||
#check if re-order is required
|
||||
item_reorder = webnotes.conn.get("Item Reorder",
|
||||
|
@ -193,10 +193,6 @@ class DocType(TransactionBase):
|
||||
if not chk1:
|
||||
msgprint("Serial no "+x+" does not exist in system.")
|
||||
raise Exception
|
||||
else:
|
||||
if status=='In Store' or status=='Note in Use' or status=='Scrapped':
|
||||
msgprint("Serial no "+x+" is '"+status+"'")
|
||||
raise Exception
|
||||
|
||||
def validate(self):
|
||||
self.validate_maintenance_detail()
|
||||
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-02-01 10:36:25",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-08 14:22:34",
|
||||
"modified": "2013-08-28 18:29:06",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@ -224,17 +224,6 @@
|
||||
"oldfieldtype": "Column Break",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "resolution_details",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Resolution Details",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "resolution_details",
|
||||
"oldfieldtype": "Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "first_responded_on",
|
||||
@ -254,6 +243,17 @@
|
||||
"read_only": 1,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "resolution_details",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Resolution Details",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "resolution_details",
|
||||
"oldfieldtype": "Text",
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "content_type",
|
||||
|
3488
translations/it.csv
Normal file
3488
translations/it.csv
Normal file
File diff suppressed because it is too large
Load Diff
0
utilities/demo/__init__.py
Normal file
0
utilities/demo/__init__.py
Normal file
3
utilities/demo/demo-login.css
Normal file
3
utilities/demo/demo-login.css
Normal file
@ -0,0 +1,3 @@
|
||||
body, #container, .outer {
|
||||
background-color: #888 !important;
|
||||
}
|
25
utilities/demo/demo-login.html
Normal file
25
utilities/demo/demo-login.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="container">
|
||||
<div class="row" style="margin-top: 100px;">
|
||||
<div class="col-sm-offset-3 col-sm-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
Start ERPNext Demo
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<input id="lead-email" type="email"
|
||||
class="form-control" placeholder="Your Email Id (optional)">
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" id="login_btn"
|
||||
class="btn btn-primary btn-large">Launch Demo</button>
|
||||
</p>
|
||||
<hr>
|
||||
<p class="text-muted small">Some functionality is disabled for the demo app. The demo data will be cleared regulary. To start your own ERPNext Trial, <a href="https://erpnext.com/pricing-and-signup">click here</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
</div>
|
||||
</div>
|
27
utilities/demo/demo-login.js
Normal file
27
utilities/demo/demo-login.js
Normal file
@ -0,0 +1,27 @@
|
||||
$(document).ready(function() {
|
||||
$(".navbar, footer, .banner, #user-tools").toggle(false);
|
||||
|
||||
$("#login_btn").click(function() {
|
||||
var me = this;
|
||||
$(this).html("Logging In...").attr("disabled", "disabled");
|
||||
wn.call({
|
||||
"method": "login",
|
||||
args: {
|
||||
usr: "demo@erpnext.com",
|
||||
pwd: "demo",
|
||||
lead_email: $("#lead-email").val(),
|
||||
},
|
||||
callback: function(r) {
|
||||
$(me).attr("disabled", false);
|
||||
if(r.exc) {
|
||||
alert("Error, please contact support@erpnext.com");
|
||||
} else {
|
||||
console.log("Logged In");
|
||||
window.location.href = "app.html";
|
||||
}
|
||||
}
|
||||
})
|
||||
return false;
|
||||
})
|
||||
.attr("disabled", false);
|
||||
})
|
13
utilities/demo/demo_control_panel.py
Normal file
13
utilities/demo/demo_control_panel.py
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
def on_login(self):
|
||||
from webnotes.utils import validate_email_add
|
||||
import conf
|
||||
if hasattr(conf, "demo_notify_url"):
|
||||
if webnotes.form_dict.lead_email and validate_email_add(webnotes.form_dict.lead_email):
|
||||
import requests
|
||||
response = requests.post(conf.demo_notify_url, data={
|
||||
"cmd":"website.helpers.contact.send_message",
|
||||
"subject":"Logged into Demo",
|
||||
"sender": webnotes.form_dict.lead_email,
|
||||
"message": "via demo.erpnext.com"
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user