Merge branch 'develop'
This commit is contained in:
commit
7e8d7d05ef
@ -1,2 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
__version__ = '5.2.1'
|
||||
__version__ = '5.3.0'
|
||||
|
@ -49,7 +49,7 @@ class Account(Document):
|
||||
self.root_type = par.root_type
|
||||
|
||||
def validate_root_details(self):
|
||||
#does not exists parent
|
||||
# does not exists parent
|
||||
if frappe.db.exists("Account", self.name):
|
||||
if not frappe.db.get_value("Account", self.name, "parent_account"):
|
||||
throw(_("Root cannot be edited."))
|
||||
|
@ -17,7 +17,7 @@ erpnext.accounts.CostCenterController = frappe.ui.form.Controller.extend({
|
||||
return {
|
||||
filters:[
|
||||
['Account', 'company', '=', me.frm.doc.company],
|
||||
['Account', 'report_type', '=', 'Profit and Loss'],
|
||||
['Account', 'root_type', '=', 'Expense'],
|
||||
['Account', 'is_group', '=', '0'],
|
||||
]
|
||||
}
|
||||
|
@ -66,7 +66,7 @@
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "Define Budget for this Cost Center. To set budget action, see <a href=\"#!List/Company\">Company Master</a>",
|
||||
"description": "Define Budget for this Cost Center. To set budget action, see \"Company List\"",
|
||||
"fieldname": "sb1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Budget",
|
||||
@ -193,4 +193,4 @@
|
||||
}
|
||||
],
|
||||
"search_fields": "parent_cost_center, is_group"
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe import msgprint, _
|
||||
|
||||
from frappe import _
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
class CostCenter(NestedSet):
|
||||
@ -14,18 +12,46 @@ class CostCenter(NestedSet):
|
||||
def autoname(self):
|
||||
self.name = self.cost_center_name.strip() + ' - ' + \
|
||||
frappe.db.get_value("Company", self.company, "abbr")
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.validate_mandatory()
|
||||
self.validate_accounts()
|
||||
|
||||
def validate_mandatory(self):
|
||||
if self.cost_center_name != self.company and not self.parent_cost_center:
|
||||
msgprint(_("Please enter parent cost center"), raise_exception=1)
|
||||
frappe.throw(_("Please enter parent cost center"))
|
||||
elif self.cost_center_name == self.company and self.parent_cost_center:
|
||||
msgprint(_("Root cannot have a parent cost center"), raise_exception=1)
|
||||
frappe.throw(_("Root cannot have a parent cost center"))
|
||||
|
||||
def validate_accounts(self):
|
||||
if self.is_group==1 and self.get("budgets"):
|
||||
frappe.throw(_("Budget cannot be set for Group Cost Center"))
|
||||
|
||||
check_acc_list = []
|
||||
for d in self.get('budgets'):
|
||||
if d.account:
|
||||
account_details = frappe.db.get_value("Account", d.account,
|
||||
["is_group", "company", "root_type"], as_dict=1)
|
||||
if account_details.is_group:
|
||||
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
|
||||
elif account_details.company != self.company:
|
||||
frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
|
||||
elif account_details.root_type != "Expense":
|
||||
frappe.throw(_("Budget cannot be assigned against {0}, as it's not an Expense account")
|
||||
.format(d.account))
|
||||
|
||||
if [d.account, d.fiscal_year] in check_acc_list:
|
||||
frappe.throw(_("Account {0} has been entered more than once for fiscal year {1}")
|
||||
.format(d.account, d.fiscal_year))
|
||||
else:
|
||||
check_acc_list.append([d.account, d.fiscal_year])
|
||||
|
||||
def convert_group_to_ledger(self):
|
||||
if self.check_if_child_exists():
|
||||
msgprint(_("Cannot convert Cost Center to ledger as it has child nodes"), raise_exception=1)
|
||||
frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
|
||||
elif self.check_gle_exists():
|
||||
msgprint(_("Cost Center with existing transactions can not be converted to ledger"), raise_exception=1)
|
||||
frappe.throw(_("Cost Center with existing transactions can not be converted to ledger"))
|
||||
else:
|
||||
self.is_group = 0
|
||||
self.save()
|
||||
@ -33,7 +59,7 @@ class CostCenter(NestedSet):
|
||||
|
||||
def convert_ledger_to_group(self):
|
||||
if self.check_gle_exists():
|
||||
msgprint(_("Cost Center with existing transactions can not be converted to group"), raise_exception=1)
|
||||
frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
|
||||
else:
|
||||
self.is_group = 1
|
||||
self.save()
|
||||
@ -46,21 +72,6 @@ class CostCenter(NestedSet):
|
||||
return frappe.db.sql("select name from `tabCost Center` where \
|
||||
parent_cost_center = %s and docstatus != 2", self.name)
|
||||
|
||||
def validate_budget_details(self):
|
||||
check_acc_list = []
|
||||
for d in self.get('budgets'):
|
||||
if self.is_group==1:
|
||||
msgprint(_("Budget cannot be set for Group Cost Centers"), raise_exception=1)
|
||||
|
||||
if [d.account, d.fiscal_year] in check_acc_list:
|
||||
msgprint(_("Account {0} has been entered more than once for fiscal year {1}").format(d.account, d.fiscal_year), raise_exception=1)
|
||||
else:
|
||||
check_acc_list.append([d.account, d.fiscal_year])
|
||||
|
||||
def validate(self):
|
||||
self.validate_mandatory()
|
||||
self.validate_budget_details()
|
||||
|
||||
def before_rename(self, olddn, newdn, merge=False):
|
||||
# Add company abbr if not provided
|
||||
from erpnext.setup.doctype.company.company import get_name_with_abbr
|
||||
|
@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate, cstr
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate, cstr, cint
|
||||
from frappe import _
|
||||
|
||||
from frappe.model.document import Document
|
||||
@ -139,9 +139,9 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
||||
if against_voucher_amount < 0:
|
||||
bal = -bal
|
||||
|
||||
# Validation : Outstanding can not be negative
|
||||
if bal < 0 and not on_cancel:
|
||||
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
|
||||
# Validation : Outstanding can not be negative for JV
|
||||
if bal < 0 and not on_cancel:
|
||||
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
|
||||
|
||||
# Update outstanding amt on against voucher
|
||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
|
@ -310,7 +310,7 @@
|
||||
"depends_on": "eval:doc.voucher_type == 'Write Off Entry'",
|
||||
"fieldname": "write_off_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Write Off Amount <=",
|
||||
"label": "Write Off Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
@ -503,4 +503,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title"
|
||||
}
|
||||
}
|
||||
|
@ -21,10 +21,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
|
||||
// Show / Hide button
|
||||
if(doc.docstatus==1 && doc.outstanding_amount > 0)
|
||||
this.frm.add_custom_button(__('Make Payment Entry'), this.make_bank_entry,
|
||||
frappe.boot.doctype_icons["Journal Entry"]);
|
||||
this.frm.add_custom_button(__('Make Payment Entry'), this.make_bank_entry);
|
||||
|
||||
if(doc.docstatus==1) {
|
||||
cur_frm.add_custom_button(__('Make Purchase Return'), this.make_purchase_return);
|
||||
|
||||
cur_frm.add_custom_button(__('View Ledger'), function() {
|
||||
frappe.route_options = {
|
||||
"voucher_no": doc.name,
|
||||
@ -34,7 +35,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
group_by_voucher: 0
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
}, "icon-table");
|
||||
});
|
||||
}
|
||||
|
||||
if(doc.docstatus===0) {
|
||||
@ -51,7 +52,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default");
|
||||
});
|
||||
|
||||
cur_frm.add_custom_button(__('From Purchase Receipt'),
|
||||
function() {
|
||||
@ -64,7 +65,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default");
|
||||
});
|
||||
|
||||
}
|
||||
},
|
||||
@ -109,7 +110,14 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
$.each(this.frm.doc["items"] || [], function(i, row) {
|
||||
if(row.purchase_receipt) frappe.model.clear_doc("Purchase Receipt", row.purchase_receipt)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
make_purchase_return: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_return",
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
cur_frm.script_manager.make(erpnext.accounts.PurchaseInvoice);
|
||||
|
@ -12,7 +12,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "PINV-",
|
||||
"options": "PINV-\nPINV-RET-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
@ -154,6 +154,28 @@
|
||||
"read_only": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Return",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_return",
|
||||
"fieldname": "return_against",
|
||||
"fieldtype": "Link",
|
||||
"label": "Return Against Purchase Invoice",
|
||||
"no_copy": 0,
|
||||
"options": "Purchase Invoice",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "currency_and_price_list",
|
||||
"fieldtype": "Section Break",
|
||||
@ -940,7 +962,7 @@
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-07-03 03:26:32.934540",
|
||||
"modified": "2015-07-24 11:49:59.762109",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
@ -37,14 +37,16 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
super(PurchaseInvoice, self).validate()
|
||||
|
||||
self.po_required()
|
||||
self.pr_required()
|
||||
self.validate_supplier_invoice()
|
||||
if not self.is_return:
|
||||
self.po_required()
|
||||
self.pr_required()
|
||||
self.validate_supplier_invoice()
|
||||
self.validate_advance_jv("advances", "purchase_order")
|
||||
|
||||
self.check_active_purchase_items()
|
||||
self.check_conversion_rate()
|
||||
self.validate_credit_to_acc()
|
||||
self.clear_unallocated_advances("Purchase Invoice Advance", "advances")
|
||||
self.validate_advance_jv("advances", "purchase_order")
|
||||
self.check_for_stopped_status()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
@ -71,8 +73,9 @@ class PurchaseInvoice(BuyingController):
|
||||
super(PurchaseInvoice, self).set_missing_values(for_validate)
|
||||
|
||||
def get_advances(self):
|
||||
super(PurchaseInvoice, self).get_advances(self.credit_to, "Supplier", self.supplier,
|
||||
"Purchase Invoice Advance", "advances", "debit", "purchase_order")
|
||||
if not self.is_return:
|
||||
super(PurchaseInvoice, self).get_advances(self.credit_to, "Supplier", self.supplier,
|
||||
"Purchase Invoice Advance", "advances", "debit", "purchase_order")
|
||||
|
||||
def check_active_purchase_items(self):
|
||||
for d in self.get('items'):
|
||||
@ -226,9 +229,11 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
# this sequence because outstanding may get -negative
|
||||
self.make_gl_entries()
|
||||
self.update_against_document_in_jv()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
if not self.is_return:
|
||||
self.update_against_document_in_jv()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
|
||||
self.update_project()
|
||||
|
||||
def make_gl_entries(self):
|
||||
@ -358,11 +363,12 @@ class PurchaseInvoice(BuyingController):
|
||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2))
|
||||
|
||||
def on_cancel(self):
|
||||
from erpnext.accounts.utils import remove_against_link_from_jv
|
||||
remove_against_link_from_jv(self.doctype, self.name, "against_voucher")
|
||||
if not self.is_return:
|
||||
from erpnext.accounts.utils import remove_against_link_from_jv
|
||||
remove_against_link_from_jv(self.doctype, self.name, "against_voucher")
|
||||
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
self.make_gl_entries_on_cancel()
|
||||
self.update_project()
|
||||
|
||||
@ -403,3 +409,8 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabAccount.%(key)s LIKE '%(txt)s'
|
||||
%(mcond)s""" % {'company': filters['company'], 'key': searchfield,
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype)})
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return make_return_doc("Purchase Invoice", source_name, target_doc)
|
@ -275,5 +275,58 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
purchase_invoice.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Project", "_Test Project", "total_purchase_cost"), 0)
|
||||
|
||||
def test_return_purchase_invoice(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
return_pi = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2)
|
||||
|
||||
|
||||
# check gl entries for return
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
order by account desc""", ("Purchase Invoice", return_pi.name), as_dict=1)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = {
|
||||
"Creditors - _TC": [100.0, 0.0],
|
||||
"Stock Received But Not Billed - _TC": [0.0, 100.0],
|
||||
}
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def make_purchase_invoice(**args):
|
||||
pi = frappe.new_doc("Purchase Invoice")
|
||||
args = frappe._dict(args)
|
||||
if args.posting_date:
|
||||
pi.posting_date = args.posting_date
|
||||
if args.posting_time:
|
||||
pi.posting_time = args.posting_time
|
||||
pi.company = args.company or "_Test Company"
|
||||
pi.supplier = args.supplier or "_Test Supplier"
|
||||
pi.currency = args.currency or "INR"
|
||||
pi.is_return = args.is_return
|
||||
pi.return_against = args.return_against
|
||||
|
||||
pi.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 5,
|
||||
"rate": args.rate or 50,
|
||||
"conversion_factor": 1.0,
|
||||
"serial_no": args.serial_no,
|
||||
"stock_uom": "_Test UOM"
|
||||
})
|
||||
if not args.do_not_save:
|
||||
pi.insert()
|
||||
if not args.do_not_submit:
|
||||
pi.submit()
|
||||
return pi
|
||||
|
||||
test_records = frappe.get_test_records('Purchase Invoice')
|
||||
|
@ -4,10 +4,9 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from frappe.model.document import Document
|
||||
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
|
||||
from erpnext.accounts.doctype.sales_taxes_and_charges_template.sales_taxes_and_charges_template \
|
||||
import valdiate_taxes_and_charges_template
|
||||
|
||||
class PurchaseTaxesandChargesTemplate(Document):
|
||||
def validate(self):
|
||||
for tax in self.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self)
|
||||
valdiate_taxes_and_charges_template(self)
|
||||
|
@ -40,7 +40,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
this._super();
|
||||
|
||||
cur_frm.dashboard.reset();
|
||||
|
||||
|
||||
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
|
||||
|
||||
if(doc.docstatus==1) {
|
||||
cur_frm.add_custom_button('View Ledger', function() {
|
||||
frappe.route_options = {
|
||||
@ -51,10 +53,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
group_by_voucher: 0
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
}, "icon-table");
|
||||
|
||||
// var percent_paid = cint(flt(doc.base_grand_total - doc.outstanding_amount) / flt(doc.base_grand_total) * 100);
|
||||
// cur_frm.dashboard.add_progress(percent_paid + "% Paid", percent_paid);
|
||||
});
|
||||
|
||||
if(cint(doc.update_stock)!=1) {
|
||||
// show Make Delivery Note button only if Sales Invoice is not created from Delivery Note
|
||||
@ -65,13 +64,15 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
});
|
||||
|
||||
if(!from_delivery_note) {
|
||||
cur_frm.add_custom_button(__('Make Delivery'), cur_frm.cscript['Make Delivery Note'], "icon-truck")
|
||||
cur_frm.add_custom_button(__('Make Delivery'), cur_frm.cscript['Make Delivery Note'])
|
||||
}
|
||||
}
|
||||
|
||||
if(doc.outstanding_amount!=0) {
|
||||
cur_frm.add_custom_button(__('Make Payment Entry'), cur_frm.cscript.make_bank_entry, "icon-money");
|
||||
if(doc.outstanding_amount!=0 && !cint(doc.is_return)) {
|
||||
cur_frm.add_custom_button(__('Make Payment Entry'), cur_frm.cscript.make_bank_entry);
|
||||
}
|
||||
|
||||
cur_frm.add_custom_button(__('Make Sales Return'), this.make_sales_return);
|
||||
}
|
||||
|
||||
// Show buttons only when pos view is active
|
||||
@ -205,8 +206,14 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
|
||||
items_on_form_rendered: function() {
|
||||
erpnext.setup_serial_no();
|
||||
},
|
||||
|
||||
make_sales_return: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return",
|
||||
frm: cur_frm
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
@ -283,16 +290,6 @@ cur_frm.cscript.make_bank_entry = function() {
|
||||
});
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.debit_to.get_query = function(doc) {
|
||||
return{
|
||||
filters: {
|
||||
'report_type': 'Balance Sheet',
|
||||
'is_group': 0,
|
||||
'company': doc.company
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.cash_bank_account.get_query = function(doc) {
|
||||
return {
|
||||
filters: [
|
||||
@ -399,4 +396,4 @@ cur_frm.set_query("debit_to", function(doc) {
|
||||
['Account', 'account_type', '=', 'Receivable']
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
@ -21,7 +21,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "SINV-",
|
||||
"options": "SINV-\nSINV-RET-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
@ -156,7 +156,7 @@
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"reqd": 0,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
@ -169,6 +169,28 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Return",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_return",
|
||||
"fieldname": "return_against",
|
||||
"fieldtype": "Link",
|
||||
"label": "Return Against Sales Invoice",
|
||||
"no_copy": 0,
|
||||
"options": "Sales Invoice",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shipping_address_name",
|
||||
"fieldtype": "Link",
|
||||
@ -1252,8 +1274,8 @@
|
||||
],
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-07-09 17:33:28.583808",
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-07-24 11:48:07.544569",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
@ -80,14 +80,16 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.check_prev_docstatus()
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.check_credit_limit()
|
||||
if not self.is_return:
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.check_credit_limit()
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
self.make_gl_entries()
|
||||
|
||||
if not cint(self.is_pos) == 1:
|
||||
if not cint(self.is_pos) == 1 and not self.is_return:
|
||||
self.update_against_document_in_jv()
|
||||
|
||||
self.update_time_log_batch(self.name)
|
||||
@ -100,13 +102,15 @@ class SalesInvoice(SellingController):
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.check_stop_sales_order("sales_order")
|
||||
|
||||
|
||||
from erpnext.accounts.utils import remove_against_link_from_jv
|
||||
remove_against_link_from_jv(self.doctype, self.name, "against_invoice")
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
|
||||
if not self.is_return:
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
|
||||
self.validate_c_form_on_cancel()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
@ -199,8 +203,9 @@ class SalesInvoice(SellingController):
|
||||
self.set_taxes()
|
||||
|
||||
def get_advances(self):
|
||||
super(SalesInvoice, self).get_advances(self.debit_to, "Customer", self.customer,
|
||||
"Sales Invoice Advance", "advances", "credit", "sales_order")
|
||||
if not self.is_return:
|
||||
super(SalesInvoice, self).get_advances(self.debit_to, "Customer", self.customer,
|
||||
"Sales Invoice Advance", "advances", "credit", "sales_order")
|
||||
|
||||
def get_company_abbr(self):
|
||||
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
|
||||
@ -285,6 +290,8 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def so_dn_required(self):
|
||||
"""check in manage account if sales order / delivery note required or not."""
|
||||
if self.is_return:
|
||||
return
|
||||
dic = {'Sales Order':'so_required','Delivery Note':'dn_required'}
|
||||
for i in dic:
|
||||
if frappe.db.get_value('Selling Settings', None, dic[i]) == 'Yes':
|
||||
@ -419,13 +426,16 @@ class SalesInvoice(SellingController):
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
for d in self.get_item_list():
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \
|
||||
and d.warehouse:
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" and d.warehouse:
|
||||
incoming_rate = 0
|
||||
if cint(self.is_return) and self.return_against and self.docstatus==1:
|
||||
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
|
||||
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"actual_qty": -1*flt(d.qty),
|
||||
"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom")
|
||||
"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
|
||||
"incoming_rate": incoming_rate
|
||||
}))
|
||||
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def make_gl_entries(self, repost_future_gle=True):
|
||||
@ -435,8 +445,7 @@ class SalesInvoice(SellingController):
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
# if POS and amount is written off, there's no outstanding and hence no need to update it
|
||||
update_outstanding = cint(self.is_pos) and self.write_off_account \
|
||||
and 'No' or 'Yes'
|
||||
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account) else "Yes"
|
||||
|
||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
|
||||
update_outstanding=update_outstanding, merge_entries=False)
|
||||
@ -484,7 +493,7 @@ class SalesInvoice(SellingController):
|
||||
"against": self.against_income_account,
|
||||
"debit": self.base_grand_total,
|
||||
"remarks": self.remarks,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype
|
||||
})
|
||||
)
|
||||
@ -519,7 +528,6 @@ class SalesInvoice(SellingController):
|
||||
# expense account gl entries
|
||||
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \
|
||||
and cint(self.update_stock):
|
||||
|
||||
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
||||
|
||||
def make_pos_gl_entries(self, gl_entries):
|
||||
@ -533,7 +541,7 @@ class SalesInvoice(SellingController):
|
||||
"against": self.cash_bank_account,
|
||||
"credit": self.paid_amount,
|
||||
"remarks": self.remarks,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
})
|
||||
)
|
||||
@ -557,7 +565,7 @@ class SalesInvoice(SellingController):
|
||||
"against": self.write_off_account,
|
||||
"credit": self.write_off_amount,
|
||||
"remarks": self.remarks,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
})
|
||||
)
|
||||
@ -651,3 +659,9 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return make_return_doc("Sales Invoice", source_name, target_doc)
|
@ -4,11 +4,10 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
import unittest, copy
|
||||
import time
|
||||
from frappe.utils import nowdate, add_days
|
||||
from erpnext.accounts.utils import get_stock_and_account_difference
|
||||
from frappe.utils import nowdate, add_days, flt
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
from erpnext.projects.doctype.time_log_batch.test_time_log_batch import *
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
@ -772,6 +771,53 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si1 = create_sales_invoice(posting_date="2015-07-05")
|
||||
self.assertEqual(si1.due_date, "2015-08-31")
|
||||
|
||||
def test_return_sales_invoice(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100)
|
||||
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
si = create_sales_invoice(qty=5, rate=500, update_stock=1)
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_0 - 5, actual_qty_1)
|
||||
|
||||
# outgoing_rate
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name}, "stock_value_difference") / 5
|
||||
|
||||
# return entry
|
||||
si1 = create_sales_invoice(is_return=1, return_against=si.name, qty=-2, rate=500, update_stock=1)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_1 + 2, actual_qty_2)
|
||||
|
||||
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Sales Invoice", "voucher_no": si1.name},
|
||||
["incoming_rate", "stock_value_difference"])
|
||||
|
||||
self.assertEquals(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
|
||||
|
||||
|
||||
# Check gl entry
|
||||
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si1.name, "account": "_Test Warehouse - _TC"}, "debit")
|
||||
|
||||
self.assertEquals(gle_warehouse_amount, stock_value_difference)
|
||||
|
||||
party_credited = frappe.db.get_value("GL Entry", {"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si1.name, "account": "Debtors - _TC", "party": "_Test Customer"}, "credit")
|
||||
|
||||
self.assertEqual(party_credited, 1000)
|
||||
|
||||
# Check outstanding amount
|
||||
self.assertFalse(si1.outstanding_amount)
|
||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
si = frappe.new_doc("Sales Invoice")
|
||||
@ -784,6 +830,10 @@ def create_sales_invoice(**args):
|
||||
si.debit_to = args.debit_to or "Debtors - _TC"
|
||||
si.update_stock = args.update_stock
|
||||
si.is_pos = args.is_pos
|
||||
si.is_return = args.is_return
|
||||
si.return_against = args.return_against
|
||||
si.currency="INR"
|
||||
si.conversion_rate = 1
|
||||
|
||||
si.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
|
@ -5,21 +5,25 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from erpnext.controllers.accounts_controller import validate_taxes_and_charges, validate_inclusive_tax
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
class SalesTaxesandChargesTemplate(Document):
|
||||
def validate(self):
|
||||
if self.is_default == 1:
|
||||
frappe.db.sql("""update `tabSales Taxes and Charges Template`
|
||||
set is_default = 0
|
||||
where ifnull(is_default,0) = 1
|
||||
and name != %s and company = %s""",
|
||||
(self.name, self.company))
|
||||
valdiate_taxes_and_charges_template(self)
|
||||
|
||||
# at least one territory
|
||||
self.validate_table_has_rows("territories")
|
||||
def valdiate_taxes_and_charges_template(doc):
|
||||
if not doc.is_default and not frappe.get_all(doc.doctype, filters={"is_default": 1}):
|
||||
doc.is_default = 1
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self)
|
||||
if doc.is_default == 1:
|
||||
frappe.db.sql("""update `tab{0}` set is_default = 0
|
||||
where ifnull(is_default,0) = 1 and name != %s and company = %s""".format(doc.doctype),
|
||||
(doc.name, doc.company))
|
||||
|
||||
if doc.meta.get_field("territories"):
|
||||
if not doc.territories:
|
||||
doc.append("territories", {"territory": get_root_of("Territory") })
|
||||
|
||||
for tax in doc.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, doc)
|
||||
|
@ -1,19 +1,19 @@
|
||||
{
|
||||
"creation": "2014-08-28 11:11:39.796473",
|
||||
"disabled": 0,
|
||||
"doc_type": "Journal Entry",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Credit Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Credit To\"), doc.pay_to_recd_from),\n (_(\"Date\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Amount\"), \"<strong>\" + doc.get_formatted(\"total_amount\") + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n\n <div class=\"row\">\n <div class=\"col-xs-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-xs-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n\n",
|
||||
"idx": 2,
|
||||
"modified": "2015-01-12 11:02:25.716825",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Credit Note",
|
||||
"owner": "Administrator",
|
||||
"parent": "Journal Entry",
|
||||
"parentfield": "__print_formats",
|
||||
"parenttype": "DocType",
|
||||
"print_format_type": "Server",
|
||||
"creation": "2014-08-28 11:11:39.796473",
|
||||
"custom_format": 0,
|
||||
"disabled": 0,
|
||||
"doc_type": "Journal Entry",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"html": "{%- from \"templates/print_formats/standard_macros.html\" import add_header -%}\n\n<div class=\"page-break\">\n {%- if not doc.get(\"print_heading\") and not doc.get(\"select_print_heading\") \n and doc.set(\"select_print_heading\", _(\"Credit Note\")) -%}{%- endif -%}\n {{ add_header(0, 1, doc, letter_head, no_letterhead) }}\n\n {%- for label, value in (\n (_(\"Credit To\"), doc.pay_to_recd_from),\n (_(\"Date\"), frappe.utils.formatdate(doc.voucher_date)),\n (_(\"Amount\"), \"<strong>\" + doc.get_formatted(\"total_amount\") + \"</strong><br>\" + (doc.total_amount_in_words or \"\") + \"<br>\"),\n (_(\"Remarks\"), doc.remark)\n ) -%}\n\n <div class=\"row\">\n <div class=\"col-xs-3\"><label class=\"text-right\">{{ label }}</label></div>\n <div class=\"col-xs-9\">{{ value }}</div>\n </div>\n\n {%- endfor -%}\n\n <hr>\n <br>\n <p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n </p>\n</div>\n\n\n",
|
||||
"idx": 2,
|
||||
"modified": "2015-07-22 17:42:01.560817",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Credit Note",
|
||||
"owner": "Administrator",
|
||||
"parent": "Journal Entry",
|
||||
"parentfield": "__print_formats",
|
||||
"parenttype": "DocType",
|
||||
"print_format_type": "Server",
|
||||
"standard": "Yes"
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"creation": "2015-07-22 17:45:22.220567",
|
||||
"custom_format": 1,
|
||||
"disabled": 0,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "Default",
|
||||
"html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Monospace;\n\t\tline-height: 200%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 6in;\n\t\t}\n\t}\n</style>\n\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{{ doc.select_print_heading or _(\"Credit Note\") }}<br>\n</p>\n\n<hr>\n\n{%- for label, value in (\n (_(\"Receipt No\"), doc.name),\n (_(\"Date\"), doc.get_formatted(\"posting_date\")),\n\t(_(\"Customer\"), doc.customer_name),\n (_(\"Amount\"), \"<strong>\" + doc.get_formatted(\"grand_total\", absolute_value=True) + \"</strong><br>\" + (doc.in_words or \"\")),\n\t(_(\"Against\"), doc.return_against),\n (_(\"Remarks\"), doc.remarks)\n) -%}\n\n\t\t<div class=\"row\">\n\t\t <div class=\"col-xs-4\"><label class=\"text-right\">{{ label }}</label></div>\n\t\t <div class=\"col-xs-8\">{{ value }}</div>\n\t\t</div>\n{%- endfor -%}\n\n<hr>\n<br>\n<p class=\"strong\">\n {{ _(\"For\") }} {{ doc.company }},<br>\n <br>\n <br>\n <br>\n {{ _(\"Authorized Signatory\") }}\n</p>",
|
||||
"modified": "2015-07-22 17:45:22.220567",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Credit Note - Negative Invoice",
|
||||
"owner": "Administrator",
|
||||
"print_format_builder": 0,
|
||||
"print_format_type": "Server",
|
||||
"standard": "Yes"
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-04-22 16:16:03",
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 1,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2014-06-03 07:18:10.985354",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Payable",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Accounts Payable",
|
||||
"report_type": "Report Builder"
|
||||
}
|
||||
"add_total_row": 1,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-04-22 16:16:03",
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 1,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2015-07-24 01:08:20.996267",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Payable",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Accounts Payable",
|
||||
"report_type": "Script Report"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div style="margin-bottom: 7px;" class="text-center">
|
||||
{%= frappe.boot.letter_heads[frappe.defaults.get_default("letter_head")] %}
|
||||
{%= frappe.boot.letter_heads[filters.letter_head || frappe.defaults.get_default("letter_head")] %}
|
||||
</div>
|
||||
<h2 class="text-center">{%= __("Statement of Account") %}</h2>
|
||||
<h4 class="text-center">{%= (filters.party || filters.account) && ((filters.party || filters.account) + ", ") || "" %} {%= filters.company %}</h4>
|
||||
|
@ -80,6 +80,13 @@ frappe.query_reports["General Ledger"] = {
|
||||
"fieldname":"group_by_account",
|
||||
"label": __("Group by Account"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname":"letter_head",
|
||||
"label": __("Letter Head"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Letter Head",
|
||||
"default": frappe.defaults.get_default("letter_head"),
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -174,12 +174,12 @@ class GrossProfitGenerator(object):
|
||||
return flt(row.qty) * item_rate
|
||||
|
||||
else:
|
||||
if row.update_stock or row.dn_detail:
|
||||
my_sle = self.sle.get((item_code, row.warehouse))
|
||||
if (row.update_stock or row.dn_detail) and my_sle:
|
||||
parenttype, parent, item_row = row.parenttype, row.parent, row.item_row
|
||||
if row.dn_detail:
|
||||
parenttype, parent, item_row = "Delivery Note", row.delivery_note, row.dn_detail
|
||||
|
||||
my_sle = self.sle.get((item_code, row.warehouse))
|
||||
|
||||
for i, sle in enumerate(my_sle):
|
||||
# find the stock valution rate from stock ledger entry
|
||||
if sle.voucher_type == parenttype and parent == sle.voucher_no and \
|
||||
|
@ -164,8 +164,10 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
frappe.model.round_floats_in(this.frm.doc, ["base_grand_total", "total_advance", "write_off_amount"]);
|
||||
this.frm.doc.total_amount_to_pay = flt(this.frm.doc.base_grand_total - this.frm.doc.write_off_amount,
|
||||
precision("total_amount_to_pay"));
|
||||
this.frm.doc.outstanding_amount = flt(this.frm.doc.total_amount_to_pay - this.frm.doc.total_advance,
|
||||
precision("outstanding_amount"));
|
||||
if (!this.frm.doc.is_return) {
|
||||
this.frm.doc.outstanding_amount = flt(this.frm.doc.total_amount_to_pay - this.frm.doc.total_advance,
|
||||
precision("outstanding_amount"));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -41,8 +41,7 @@ class PurchaseCommon(BuyingController):
|
||||
def validate_for_items(self, obj):
|
||||
items = []
|
||||
for d in obj.get("items"):
|
||||
# validation for valid qty
|
||||
if flt(d.qty) < 0 or (d.parenttype != 'Purchase Receipt' and not flt(d.qty)):
|
||||
if not d.qty:
|
||||
frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code))
|
||||
|
||||
# udpate with latest quantities
|
||||
|
@ -11,39 +11,32 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
this._super();
|
||||
// this.frm.dashboard.reset();
|
||||
|
||||
if(doc.docstatus == 1 && doc.status != 'Stopped'){
|
||||
// cur_frm.dashboard.add_progress(cint(doc.per_received) + __("% Received"),
|
||||
// doc.per_received);
|
||||
// cur_frm.dashboard.add_progress(cint(doc.per_billed) + __("% Billed"),
|
||||
// doc.per_billed);
|
||||
|
||||
if(doc.docstatus == 1 && doc.status != 'Stopped') {
|
||||
if(flt(doc.per_received, 2) < 100) {
|
||||
cur_frm.add_custom_button(__('Make Purchase Receipt'),
|
||||
this.make_purchase_receipt);
|
||||
cur_frm.add_custom_button(__('Make Purchase Receipt'), this.make_purchase_receipt);
|
||||
|
||||
if(doc.is_subcontracted==="Yes") {
|
||||
cur_frm.add_custom_button(__('Transfer Material to Supplier'),
|
||||
function() { me.make_stock_entry() });
|
||||
cur_frm.add_custom_button(__('Transfer Material to Supplier'), this.make_stock_entry);
|
||||
}
|
||||
}
|
||||
if(flt(doc.per_billed, 2) < 100)
|
||||
cur_frm.add_custom_button(__('Make Invoice'), this.make_purchase_invoice,
|
||||
frappe.boot.doctype_icons["Purchase Invoice"]);
|
||||
cur_frm.add_custom_button(__('Make Invoice'), this.make_purchase_invoice);
|
||||
|
||||
if(flt(doc.per_billed, 2) < 100 || doc.per_received < 100)
|
||||
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Purchase Order'],
|
||||
"icon-exclamation", "btn-default");
|
||||
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Purchase Order']);
|
||||
|
||||
} else if(doc.docstatus===0) {
|
||||
cur_frm.cscript.add_from_mappers();
|
||||
}
|
||||
|
||||
if(doc.docstatus == 1 && doc.status == 'Stopped')
|
||||
cur_frm.add_custom_button(__('Unstop Purchase Order'),
|
||||
cur_frm.cscript['Unstop Purchase Order'], "icon-check");
|
||||
cur_frm.add_custom_button(__('Unstop Purchase Order'), cur_frm.cscript['Unstop Purchase Order']);
|
||||
},
|
||||
|
||||
make_stock_entry: function() {
|
||||
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; }),
|
||||
me = this;
|
||||
var me = this;
|
||||
|
||||
if(items.length===1) {
|
||||
me._make_stock_entry(items[0]);
|
||||
return;
|
||||
@ -96,7 +89,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default"
|
||||
}
|
||||
);
|
||||
|
||||
cur_frm.add_custom_button(__('From Supplier Quotation'),
|
||||
@ -110,7 +103,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default"
|
||||
}
|
||||
);
|
||||
|
||||
cur_frm.add_custom_button(__('For Supplier'),
|
||||
@ -122,7 +115,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
docstatus: ["!=", 2],
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default"
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
11
erpnext/change_log/v5/v5_3_0.md
Normal file
11
erpnext/change_log/v5/v5_3_0.md
Normal file
@ -0,0 +1,11 @@
|
||||
- **Sales Return**: Create Delivery Note or Sales Invoice ('Updated Stock' option checked) with negative quantity.
|
||||
- **Purchase Return**: Create Purchase Receipt with negative quantity
|
||||
- **Credit / Debit Note**: Create Sales / Purchase Invoice with negative qtuantity against original invoice.
|
||||
- Outgoing rate in Purchase Return based on reference / original Purchase Receipt rate
|
||||
- Global switch added to disable capacity planning in manufacturing settings
|
||||
- Opening Balance row added to Stock Ledger Report
|
||||
- SMS delivery message and log
|
||||
- Added users, employees, sample data via Setup Wizard
|
||||
- Letter Head option in General Ledger report
|
||||
- Fetch Template Bom if no BOM is set against Item Variant in Production Order
|
||||
- Fetch items from Packing List while raising Material Request against SO
|
@ -42,6 +42,11 @@ def get_data():
|
||||
"name": "SMS Center",
|
||||
"description":_("Send mass SMS to your contacts"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "SMS Log",
|
||||
"description":_("Logs for maintaining sms delivery status"),
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -48,6 +48,11 @@ def get_data():
|
||||
"name": "SMS Center",
|
||||
"description":_("Send mass SMS to your contacts"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "SMS Log",
|
||||
"description":_("Logs for maintaining sms delivery status"),
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Newsletter",
|
||||
|
@ -1,92 +0,0 @@
|
||||
{
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:party_type_name",
|
||||
"creation": "2014-04-07 12:32:18.010384",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "party_type_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Party Type Name",
|
||||
"permlevel": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parent_party_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Parent Party Type",
|
||||
"options": "Party Type",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"default": "Yes",
|
||||
"fieldname": "allow_children",
|
||||
"fieldtype": "Select",
|
||||
"label": "Allow Children",
|
||||
"options": "Yes\nNo",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "default_price_list",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Default Price List",
|
||||
"options": "Price List",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "lft",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"label": "LFT",
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rgt",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 1,
|
||||
"label": "RGT",
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "old_parent",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Old Parent",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"modified": "2015-02-05 05:11:42.046004",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Contacts",
|
||||
"name": "Party Type",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"apply_user_permissions": 1,
|
||||
"create": 1,
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"role": "Sales User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"apply_user_permissions": 1,
|
||||
"create": 1,
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"role": "Purchase User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
]
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
class PartyType(NestedSet):
|
||||
nsm_parent_field = 'parent_party_type';
|
@ -9,6 +9,7 @@ from erpnext.setup.utils import get_company_currency, get_exchange_rate
|
||||
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
def validate(self):
|
||||
@ -17,10 +18,14 @@ class AccountsController(TransactionBase):
|
||||
self.validate_date_with_fiscal_year()
|
||||
if self.meta.get_field("currency"):
|
||||
self.calculate_taxes_and_totals()
|
||||
self.validate_value("base_grand_total", ">=", 0)
|
||||
if not self.meta.get_field("is_return") or not self.is_return:
|
||||
self.validate_value("base_grand_total", ">=", 0)
|
||||
|
||||
validate_return(self)
|
||||
self.set_total_in_words()
|
||||
|
||||
self.validate_due_date()
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice") and not self.is_return:
|
||||
self.validate_due_date()
|
||||
|
||||
if self.meta.get_field("is_recurring"):
|
||||
validate_recurring_document(self)
|
||||
@ -74,6 +79,9 @@ class AccountsController(TransactionBase):
|
||||
def validate_due_date(self):
|
||||
from erpnext.accounts.party import validate_due_date
|
||||
if self.doctype == "Sales Invoice":
|
||||
if not self.due_date:
|
||||
frappe.throw(_("Due Date is mandatory"))
|
||||
|
||||
validate_due_date(self.posting_date, self.due_date, "Customer", self.customer, self.company)
|
||||
elif self.doctype == "Purchase Invoice":
|
||||
validate_due_date(self.posting_date, self.due_date, "Supplier", self.supplier, self.company)
|
||||
|
@ -26,8 +26,7 @@ class BuyingController(StockController):
|
||||
def validate(self):
|
||||
super(BuyingController, self).validate()
|
||||
if getattr(self, "supplier", None) and not self.supplier_name:
|
||||
self.supplier_name = frappe.db.get_value("Supplier",
|
||||
self.supplier, "supplier_name")
|
||||
self.supplier_name = frappe.db.get_value("Supplier", self.supplier, "supplier_name")
|
||||
self.is_item_table_empty()
|
||||
self.set_qty_as_per_stock_uom()
|
||||
self.validate_stock_or_nonstock_items()
|
||||
|
138
erpnext/controllers/sales_and_purchase_return.py
Normal file
138
erpnext/controllers/sales_and_purchase_return.py
Normal file
@ -0,0 +1,138 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, get_datetime, format_datetime
|
||||
|
||||
class StockOverReturnError(frappe.ValidationError): pass
|
||||
|
||||
|
||||
def validate_return(doc):
|
||||
if not doc.meta.get_field("is_return") or not doc.is_return:
|
||||
return
|
||||
|
||||
validate_return_against(doc)
|
||||
validate_returned_items(doc)
|
||||
|
||||
def validate_return_against(doc):
|
||||
if not doc.return_against:
|
||||
frappe.throw(_("{0} is mandatory for Return").format(doc.meta.get_label("return_against")))
|
||||
else:
|
||||
filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company}
|
||||
if doc.meta.get_field("customer"):
|
||||
filters["customer"] = doc.customer
|
||||
elif doc.meta.get_field("supplier"):
|
||||
filters["supplier"] = doc.supplier
|
||||
|
||||
if not frappe.db.exists(filters):
|
||||
frappe.throw(_("Invalid {0}: {1}")
|
||||
.format(doc.meta.get_label("return_against"), doc.return_against))
|
||||
else:
|
||||
ref_doc = frappe.get_doc(doc.doctype, doc.return_against)
|
||||
|
||||
# validate posting date time
|
||||
return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00")
|
||||
ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00")
|
||||
|
||||
if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime):
|
||||
frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime)))
|
||||
|
||||
# validate same exchange rate
|
||||
if doc.conversion_rate != ref_doc.conversion_rate:
|
||||
frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})")
|
||||
.format(doc.doctype, doc.return_against, ref_doc.conversion_rate))
|
||||
|
||||
# validate update stock
|
||||
if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock:
|
||||
frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}")
|
||||
.format(doc.return_against))
|
||||
|
||||
def validate_returned_items(doc):
|
||||
valid_items = frappe._dict()
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty, rate from `tab{0} Item`
|
||||
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty from `tabPacked Item`
|
||||
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
||||
valid_items.setdefault(d.item_code, d)
|
||||
|
||||
already_returned_items = get_already_returned_items(doc)
|
||||
|
||||
items_returned = False
|
||||
for d in doc.get("items"):
|
||||
if flt(d.qty) < 0:
|
||||
if d.item_code not in valid_items:
|
||||
frappe.throw(_("Row # {0}: Returned Item {1} does not exists in {2} {3}")
|
||||
.format(d.idx, d.item_code, doc.doctype, doc.return_against))
|
||||
else:
|
||||
ref = valid_items.get(d.item_code, frappe._dict())
|
||||
already_returned_qty = flt(already_returned_items.get(d.item_code))
|
||||
max_return_qty = flt(ref.qty) - already_returned_qty
|
||||
|
||||
if already_returned_qty >= ref.qty:
|
||||
frappe.throw(_("Item {0} has already been returned").format(d.item_code), StockOverReturnError)
|
||||
elif abs(d.qty) > max_return_qty:
|
||||
frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
|
||||
.format(d.idx, ref.qty, d.item_code), StockOverReturnError)
|
||||
elif ref.rate and flt(d.rate) != ref.rate:
|
||||
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
|
||||
.format(d.idx, doc.doctype, doc.return_against))
|
||||
|
||||
|
||||
items_returned = True
|
||||
|
||||
if not items_returned:
|
||||
frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
|
||||
|
||||
def get_already_returned_items(doc):
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select
|
||||
child.item_code, sum(abs(child.qty)) as qty
|
||||
from
|
||||
`tab{0} Item` child, `tab{1}` par
|
||||
where
|
||||
child.parent = par.name and par.docstatus = 1
|
||||
and ifnull(par.is_return, 0) = 1 and par.return_against = %s and child.qty < 0
|
||||
group by item_code
|
||||
""".format(doc.doctype, doc.doctype), doc.return_against))
|
||||
|
||||
def make_return_doc(doctype, source_name, target_doc=None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
def set_missing_values(source, target):
|
||||
doc = frappe.get_doc(target)
|
||||
doc.is_return = 1
|
||||
doc.return_against = source.name
|
||||
doc.ignore_pricing_rule = 1
|
||||
doc.run_method("calculate_taxes_and_totals")
|
||||
|
||||
def update_item(source_doc, target_doc, source_parent):
|
||||
target_doc.qty = -1* source_doc.qty
|
||||
if doctype == "Purchase Receipt":
|
||||
target_doc.received_qty = -1* source_doc.qty
|
||||
elif doctype == "Purchase Invoice":
|
||||
target_doc.purchase_receipt = source_doc.purchase_receipt
|
||||
target_doc.pr_detail = source_doc.pr_detail
|
||||
|
||||
doclist = get_mapped_doc(doctype, source_name, {
|
||||
doctype: {
|
||||
"doctype": doctype,
|
||||
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
doctype +" Item": {
|
||||
"doctype": doctype + " Item",
|
||||
"fields": {
|
||||
"purchase_order": "purchase_order",
|
||||
"purchase_receipt": "purchase_receipt"
|
||||
},
|
||||
"postprocess": update_item
|
||||
},
|
||||
}, target_doc, set_missing_values)
|
||||
|
||||
return doclist
|
@ -110,15 +110,14 @@ class SellingController(StockController):
|
||||
from frappe.utils import money_in_words
|
||||
company_currency = get_company_currency(self.company)
|
||||
|
||||
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None,
|
||||
"disable_rounded_total"))
|
||||
disable_rounded_total = cint(frappe.db.get_value("Global Defaults", None, "disable_rounded_total"))
|
||||
|
||||
if self.meta.get_field("base_in_words"):
|
||||
self.base_in_words = money_in_words(disable_rounded_total and
|
||||
self.base_grand_total or self.base_rounded_total, company_currency)
|
||||
abs(self.base_grand_total) or abs(self.base_rounded_total), company_currency)
|
||||
if self.meta.get_field("in_words"):
|
||||
self.in_words = money_in_words(disable_rounded_total and
|
||||
self.grand_total or self.rounded_total, self.currency)
|
||||
abs(self.grand_total) or abs(self.rounded_total), self.currency)
|
||||
|
||||
def calculate_commission(self):
|
||||
if self.meta.get_field("commission_rate"):
|
||||
@ -175,7 +174,7 @@ class SellingController(StockController):
|
||||
if flt(d.qty) > flt(d.delivered_qty):
|
||||
reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty)
|
||||
|
||||
elif self.doctype == "Delivery Note" and d.against_sales_order:
|
||||
elif self.doctype == "Delivery Note" and d.against_sales_order and not self.is_return:
|
||||
# if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12.
|
||||
# But in this case reserved qty should only be reduced by 10 and not 12
|
||||
|
||||
@ -211,7 +210,7 @@ class SellingController(StockController):
|
||||
'qty': d.qty,
|
||||
'reserved_qty': reserved_qty_for_main_item,
|
||||
'uom': d.stock_uom,
|
||||
'stock_uom': d.stock_uom,
|
||||
'stock_uom': d.stock_uom,
|
||||
'batch_no': cstr(d.get("batch_no")).strip(),
|
||||
'serial_no': cstr(d.get("serial_no")).strip(),
|
||||
'name': d.name
|
||||
|
@ -216,6 +216,17 @@ class StockController(AccountsController):
|
||||
tuple(item_codes))
|
||||
|
||||
return serialized_items
|
||||
|
||||
def get_incoming_rate_for_sales_return(self, item_code, against_document):
|
||||
incoming_rate = 0.0
|
||||
if against_document and item_code:
|
||||
incoming_rate = frappe.db.sql("""select abs(ifnull(stock_value_difference, 0) / actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where voucher_type = %s and voucher_no = %s and item_code = %s limit 1""",
|
||||
(self.doctype, against_document, item_code))
|
||||
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
|
||||
|
||||
return incoming_rate
|
||||
|
||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||
warehouse_account=None):
|
||||
|
@ -77,6 +77,9 @@ class calculate_taxes_and_totals(object):
|
||||
if not self.discount_amount_applied:
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self.doc)
|
||||
|
||||
if self.doc.meta.get_field("is_return") and self.doc.is_return and tax.charge_type == "Actual":
|
||||
tax.tax_amount = -1 * tax.tax_amount
|
||||
|
||||
tax.item_wise_tax_detail = {}
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
@ -396,13 +399,15 @@ class calculate_taxes_and_totals(object):
|
||||
# total_advance is only for non POS Invoice
|
||||
|
||||
if self.doc.doctype == "Sales Invoice":
|
||||
self.doc.round_floats_in(self.doc, ["base_grand_total", "total_advance", "write_off_amount", "paid_amount"])
|
||||
total_amount_to_pay = self.doc.base_grand_total - self.doc.write_off_amount
|
||||
self.doc.outstanding_amount = flt(total_amount_to_pay - self.doc.total_advance - self.doc.paid_amount,
|
||||
self.doc.precision("outstanding_amount"))
|
||||
if not self.doc.is_return:
|
||||
self.doc.round_floats_in(self.doc, ["base_grand_total", "total_advance", "write_off_amount", "paid_amount"])
|
||||
total_amount_to_pay = self.doc.base_grand_total - self.doc.write_off_amount
|
||||
self.doc.outstanding_amount = flt(total_amount_to_pay - self.doc.total_advance - self.doc.paid_amount,
|
||||
self.doc.precision("outstanding_amount"))
|
||||
else:
|
||||
self.doc.round_floats_in(self.doc, ["total_advance", "write_off_amount"])
|
||||
self.doc.total_amount_to_pay = flt(self.doc.base_grand_total - self.doc.write_off_amount,
|
||||
self.doc.precision("total_amount_to_pay"))
|
||||
self.doc.outstanding_amount = flt(self.doc.total_amount_to_pay - self.doc.total_advance,
|
||||
self.doc.precision("outstanding_amount"))
|
||||
if not self.doc.is_return:
|
||||
self.doc.outstanding_amount = flt(self.doc.total_amount_to_pay - self.doc.total_advance,
|
||||
self.doc.precision("outstanding_amount"))
|
||||
|
@ -52,7 +52,7 @@
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Message",
|
||||
"permlevel": 0,
|
||||
"reqd": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"description": "",
|
||||
@ -78,7 +78,7 @@
|
||||
],
|
||||
"icon": "icon-envelope",
|
||||
"idx": 1,
|
||||
"modified": "2015-03-20 05:27:31.613881",
|
||||
"modified": "2015-07-20 05:43:33.818567",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Newsletter",
|
||||
|
@ -1,11 +1,34 @@
|
||||
from __future__ import unicode_literals
|
||||
app_name = "erpnext"
|
||||
app_title = "ERPNext"
|
||||
app_publisher = "Frappe Technologies Pvt. Ltd. and Contributors"
|
||||
app_description = "Open Source Enterprise Resource Planning for Small and Midsized Organizations"
|
||||
app_publisher = "Frappe Technologies Pvt. Ltd."
|
||||
app_description = """## ERPNext
|
||||
|
||||
ERPNext is a fully featured ERP system designed for Small and Medium Sized
|
||||
business. ERPNext covers a wide range of features including Accounting, CRM,
|
||||
Inventory management, Selling, Purchasing, Manufacturing, Projects, HR &
|
||||
Payroll, Website, E-Commerce and much more.
|
||||
|
||||
ERPNext is based on the Frappe Framework is highly customizable and extendable.
|
||||
You can create Custom Form, Fields, Scripts and can also create your own Apps
|
||||
to extend ERPNext functionality.
|
||||
|
||||
ERPNext is Open Source under the GNU General Public Licence v3 and has been
|
||||
listed as one of the Best Open Source Softwares in the world by my online
|
||||
blogs.
|
||||
|
||||
### Links
|
||||
|
||||
- Website: [https://erpnext.com](https://erpnext.com)
|
||||
- GitHub: [https://github.com/frappe/erpnext](https://github.com/frappe/erpnext)
|
||||
- Forum: [https://discuss.erpnext.com](https://discuss.erpnext.com)
|
||||
- Frappe Framework: [https://frappe.io](https://frappe.io)
|
||||
|
||||
"""
|
||||
app_icon = "icon-th"
|
||||
app_color = "#e74c3c"
|
||||
app_version = "5.2.1"
|
||||
app_version = "5.3.0"
|
||||
github_link = "https://github.com/frappe/erpnext"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
|
||||
|
@ -15,6 +15,14 @@
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "Disables creation of time logs against Production Orders.\nOperations shall not be tracked against Production Order",
|
||||
"fieldname": "disable_capacity_planning",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Capacity Planning and Time Tracking",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "Plan time logs outside Workstation Working Hours.",
|
||||
"fieldname": "allow_overtime",
|
||||
@ -72,7 +80,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-06-15 05:52:22.986958",
|
||||
"modified": "2015-07-23 08:12:33.889753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
@ -186,27 +186,16 @@ $.extend(cur_frm.cscript, {
|
||||
},
|
||||
|
||||
bom_no: function() {
|
||||
if (this.frm.doc.track_operations) {
|
||||
return this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "set_production_order_operations"
|
||||
});
|
||||
}
|
||||
return this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: "set_production_order_operations"
|
||||
});
|
||||
},
|
||||
|
||||
qty: function() {
|
||||
frappe.ui.form.trigger("Production Order", 'bom_no')
|
||||
},
|
||||
|
||||
track_operations: function(doc) {
|
||||
if (doc.track_operations) {
|
||||
frappe.ui.form.trigger("Production Order", 'bom_no')
|
||||
}
|
||||
else {
|
||||
doc.operations =[];
|
||||
}
|
||||
},
|
||||
|
||||
show_time_logs: function(doc, cdt, cdn) {
|
||||
var child = locals[cdt][cdn]
|
||||
frappe.route_options = {"operation_id": child.name};
|
||||
@ -262,7 +251,8 @@ cur_frm.fields_dict['production_item'].get_query = function(doc) {
|
||||
return {
|
||||
filters:[
|
||||
['Item', 'is_pro_applicable', '=', 'Yes'],
|
||||
['Item', 'has_variants', '=', 'No']
|
||||
['Item', 'has_variants', '=', 'No'],
|
||||
['Item', 'end_of_life', '>=', frappe.datetime.nowdate()]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -73,14 +73,6 @@
|
||||
"label": "Use Multi-Level BOM",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "track_operations",
|
||||
"fieldtype": "Check",
|
||||
"label": "Track Operations",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
@ -215,7 +207,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "track_operations",
|
||||
"depends_on": "",
|
||||
"fieldname": "operations_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operations",
|
||||
@ -234,7 +226,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "track_operations",
|
||||
"depends_on": "operations",
|
||||
"fieldname": "section_break_22",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Operation Cost",
|
||||
@ -368,7 +360,7 @@
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-07-13 05:28:23.259016",
|
||||
"modified": "2015-07-21 07:45:53.206902",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Order",
|
||||
|
@ -9,10 +9,13 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
|
||||
class OverProductionError(frappe.ValidationError): pass
|
||||
class StockOverProductionError(frappe.ValidationError): pass
|
||||
class OperationTooLongError(frappe.ValidationError): pass
|
||||
class ProductionNotApplicableError(frappe.ValidationError): pass
|
||||
class ItemHasVariantError(frappe.ValidationError): pass
|
||||
|
||||
from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError, NotInWorkingHoursError
|
||||
from erpnext.projects.doctype.time_log.time_log import OverlapError
|
||||
@ -174,17 +177,12 @@ class ProductionOrder(Document):
|
||||
|
||||
def set_production_order_operations(self):
|
||||
"""Fetch operations from BOM and set in 'Production Order'"""
|
||||
if not self.bom_no:
|
||||
if not self.bom_no or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
|
||||
return
|
||||
self.set('operations', [])
|
||||
operations = frappe.db.sql("""select operation, description, workstation, idx,
|
||||
hour_rate, time_in_mins, "Pending" as status from `tabBOM Operation`
|
||||
where parent = %s order by idx""", self.bom_no, as_dict=1)
|
||||
if operations:
|
||||
self.track_operations=1
|
||||
else:
|
||||
self.track_operations=0
|
||||
frappe.msgprint(_("Cannot 'track operations' as selected BOM does not have Operations."))
|
||||
self.set('operations', operations)
|
||||
self.calculate_time()
|
||||
|
||||
@ -325,22 +323,27 @@ class ProductionOrder(Document):
|
||||
|
||||
def validate_production_item(self):
|
||||
if frappe.db.get_value("Item", self.production_item, "is_pro_applicable")=='No':
|
||||
frappe.throw(_("Item is not allowed to have Production Order."))
|
||||
frappe.throw(_("Item is not allowed to have Production Order."), ProductionNotApplicableError)
|
||||
|
||||
if frappe.db.get_value("Item", self.production_item, "has_variants"):
|
||||
frappe.throw(_("Production Order cannot be raised against a Item Template"))
|
||||
frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError)
|
||||
|
||||
validate_end_of_life(self.production_item)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item):
|
||||
res = frappe.db.sql("""select stock_uom, description
|
||||
from `tabItem` where (ifnull(end_of_life, "0000-00-00")="0000-00-00" or end_of_life > now())
|
||||
and name=%s""", item, as_dict=1)
|
||||
|
||||
if not res:
|
||||
return {}
|
||||
|
||||
res = res[0]
|
||||
res["bom_no"] = frappe.db.get_value("BOM", filters={"item": item, "is_default": 1})
|
||||
if not res["bom_no"]:
|
||||
variant_of= frappe.db.get_value("Item", item, "variant_of")
|
||||
if variant_of:
|
||||
res["bom_no"] = frappe.db.get_value("BOM", filters={"item": variant_of, "is_default": 1})
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
|
@ -7,7 +7,8 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import flt, get_datetime, time_diff_in_hours
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import make_stock_entry, make_time_log
|
||||
from erpnext.manufacturing.doctype.production_order.production_order \
|
||||
import make_stock_entry, make_time_log, ProductionNotApplicableError,ItemHasVariantError
|
||||
from erpnext.stock.doctype.stock_entry import test_stock_entry
|
||||
from erpnext.projects.doctype.time_log.time_log import OverProductionLoggedError
|
||||
|
||||
@ -135,6 +136,22 @@ class TestProductionOrder(unittest.TestCase):
|
||||
prod_order.set_production_order_operations()
|
||||
self.assertEqual(prod_order.planned_operating_cost, cost*2)
|
||||
|
||||
def test_production_item(self):
|
||||
frappe.db.set_value("Item", "_Test FG Item", "is_pro_applicable", "No")
|
||||
|
||||
prod_order = make_prod_order_test_record(item="_Test FG Item", qty=1, do_not_save=True)
|
||||
self.assertRaises(ProductionNotApplicableError, prod_order.save)
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "is_pro_applicable", "Yes")
|
||||
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", "2000-1-1")
|
||||
|
||||
self.assertRaises(frappe.ValidationError, prod_order.save)
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", None)
|
||||
|
||||
prod_order = make_prod_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True)
|
||||
self.assertRaises(ItemHasVariantError, prod_order.save)
|
||||
|
||||
def make_prod_order_test_record(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
|
@ -9,6 +9,7 @@ from frappe import msgprint, _
|
||||
|
||||
from frappe.model.document import Document
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details
|
||||
|
||||
class ProductionPlanningTool(Document):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
@ -27,16 +28,7 @@ class ProductionPlanningTool(Document):
|
||||
return ret
|
||||
|
||||
def get_item_details(self, item_code):
|
||||
""" Pull other item details from item master"""
|
||||
|
||||
item = frappe.db.sql("""select description, stock_uom, default_bom
|
||||
from `tabItem` where name = %s""", item_code, as_dict =1)
|
||||
ret = {
|
||||
'description' : item and item[0]['description'],
|
||||
'stock_uom' : item and item[0]['stock_uom'],
|
||||
'bom_no' : item and item[0]['default_bom']
|
||||
}
|
||||
return ret
|
||||
return get_item_details(item_code)
|
||||
|
||||
def clear_so_table(self):
|
||||
self.set('sales_orders', [])
|
||||
@ -142,15 +134,14 @@ class ProductionPlanningTool(Document):
|
||||
self.clear_item_table()
|
||||
|
||||
for p in items:
|
||||
item_details = frappe.db.sql("""select description, stock_uom, default_bom
|
||||
from tabItem where name=%s""", p['item_code'])
|
||||
item_details = get_item_details(p['item_code'])
|
||||
pi = self.append('items', {})
|
||||
pi.sales_order = p['parent']
|
||||
pi.warehouse = p['warehouse']
|
||||
pi.item_code = p['item_code']
|
||||
pi.description = item_details and item_details[0][0] or ''
|
||||
pi.stock_uom = item_details and item_details[0][1] or ''
|
||||
pi.bom_no = item_details and item_details[0][2] or ''
|
||||
pi.description = item_details and item_details.description or ''
|
||||
pi.stock_uom = item_details and item_details.stock_uom or ''
|
||||
pi.bom_no = item_details and item_details.bom_no or ''
|
||||
pi.so_pending_qty = flt(p['pending_qty'])
|
||||
pi.planned_qty = flt(p['pending_qty'])
|
||||
|
||||
|
@ -9,6 +9,5 @@ Manufacturing
|
||||
Stock
|
||||
Support
|
||||
Utilities
|
||||
Contacts
|
||||
Shopping Cart
|
||||
Hub Node
|
||||
|
@ -173,7 +173,7 @@ erpnext.patches.v5_0.item_variants
|
||||
erpnext.patches.v5_0.update_item_desc_in_invoice
|
||||
erpnext.patches.v5_1.fix_against_account
|
||||
erpnext.patches.v5_1.fix_credit_days_based_on
|
||||
erpnext.patches.v5_1.track_operations
|
||||
execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force=True)
|
||||
erpnext.patches.v5_1.rename_roles
|
||||
erpnext.patches.v5_1.default_bom
|
||||
execute:frappe.delete_doc("DocType", "Party Type")
|
@ -1,8 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Production Order")
|
||||
frappe.db.sql("""Update `tabProduction Order` as po set track_operations=1 where
|
||||
exists(select name from `tabProduction Order Operation` as po_operation where po_operation.parent = po.name )""")
|
@ -13,8 +13,9 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
||||
this.apply_discount_amount();
|
||||
|
||||
// Advance calculation applicable to Sales /Purchase Invoice
|
||||
if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype) && this.frm.doc.docstatus < 2) {
|
||||
this.calculate_total_advance(update_paid_amount);
|
||||
if(in_list(["Sales Invoice", "Purchase Invoice"], this.frm.doc.doctype)
|
||||
&& this.frm.doc.docstatus < 2 && !this.frm.doc.is_return) {
|
||||
this.calculate_total_advance(update_paid_amount);
|
||||
}
|
||||
|
||||
// Sales person's commission
|
||||
@ -93,6 +94,10 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
||||
tax_fields = ["total", "tax_amount_after_discount_amount",
|
||||
"tax_amount_for_current_item", "grand_total_for_current_item",
|
||||
"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||
|
||||
if (frappe.meta.get_docfield(me.frm.doc.doctype, "is_return") && me.frm.doc.is_return
|
||||
&& tax.charge_type == "Actual")
|
||||
tax.tax_amount = -1 * tax.tax_amount;
|
||||
|
||||
if (cstr(tax.charge_type) != "Actual" &&
|
||||
!(me.discount_amount_applied && me.frm.doc.apply_discount_on=="Grand Total"))
|
||||
|
@ -46,6 +46,23 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["return_against"]) {
|
||||
this.frm.set_query("return_against", function(doc) {
|
||||
var filters = {
|
||||
"docstatus": 1,
|
||||
"is_return": 0,
|
||||
"company": doc.company
|
||||
};
|
||||
if (me.frm.fields_dict["customer"] && doc.customer) filters["customer"] = doc.customer;
|
||||
if (me.frm.fields_dict["supplier"] && doc.supplier) filters["supplier"] = doc.supplier;
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
@ -354,7 +371,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
plc_conversion_rate: function() {
|
||||
if(this.frm.doc.price_list_currency === this.get_company_currency()) {
|
||||
this.frm.set_value("plc_conversion_rate", 1.0);
|
||||
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency && this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
|
||||
} else if(this.frm.doc.price_list_currency === this.frm.doc.currency
|
||||
&& this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
|
||||
cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) {
|
||||
this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);
|
||||
}
|
||||
|
@ -18,35 +18,31 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
|
||||
// delivery note
|
||||
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1)
|
||||
cur_frm.add_custom_button(__('Make Delivery'), this.make_delivery_note, "icon-truck");
|
||||
cur_frm.add_custom_button(__('Make Delivery'), this.make_delivery_note);
|
||||
|
||||
// indent
|
||||
if(!doc.order_type || ["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1)
|
||||
cur_frm.add_custom_button(__('Make ') + __('Material Request'),
|
||||
this.make_material_request, "icon-ticket");
|
||||
this.make_material_request);
|
||||
|
||||
// sales invoice
|
||||
if(flt(doc.per_billed, 2) < 100) {
|
||||
cur_frm.add_custom_button(__('Make Invoice'), this.make_sales_invoice,
|
||||
frappe.boot.doctype_icons["Sales Invoice"]);
|
||||
cur_frm.add_custom_button(__('Make Invoice'), this.make_sales_invoice);
|
||||
}
|
||||
|
||||
// stop
|
||||
if(flt(doc.per_delivered, 2) < 100 || doc.per_billed < 100)
|
||||
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Sales Order'],
|
||||
"icon-exclamation", "btn-default")
|
||||
cur_frm.add_custom_button(__('Stop'), cur_frm.cscript['Stop Sales Order'])
|
||||
|
||||
// maintenance
|
||||
if(flt(doc.per_delivered, 2) < 100 && ["Sales", "Shopping Cart"].indexOf(doc.order_type)===-1) {
|
||||
cur_frm.add_custom_button(__('Make Maint. Visit'),
|
||||
this.make_maintenance_visit, null, "btn-default");
|
||||
cur_frm.add_custom_button(__('Make Maint. Schedule'),
|
||||
this.make_maintenance_schedule, null, "btn-default");
|
||||
cur_frm.add_custom_button(__('Make Maint. Visit'), this.make_maintenance_visit);
|
||||
cur_frm.add_custom_button(__('Make Maint. Schedule'), this.make_maintenance_schedule);
|
||||
}
|
||||
|
||||
} else {
|
||||
// un-stop
|
||||
cur_frm.add_custom_button(__('Unstop'), cur_frm.cscript['Unstop Sales Order'], "icon-check");
|
||||
cur_frm.add_custom_button(__('Unstop'), cur_frm.cscript['Unstop Sales Order']);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +60,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default");
|
||||
});
|
||||
}
|
||||
|
||||
this.order_type(doc);
|
||||
|
@ -272,6 +272,10 @@ def make_material_request(source_name, target_doc=None):
|
||||
def postprocess(source, doc):
|
||||
doc.material_request_type = "Purchase"
|
||||
|
||||
so = frappe.get_doc("Sales Order", source_name)
|
||||
|
||||
item_table = "Packed Item" if so.packed_items else "Sales Order Item"
|
||||
|
||||
doc = get_mapped_doc("Sales Order", source_name, {
|
||||
"Sales Order": {
|
||||
"doctype": "Material Request",
|
||||
@ -279,7 +283,7 @@ def make_material_request(source_name, target_doc=None):
|
||||
"docstatus": ["=", 1]
|
||||
}
|
||||
},
|
||||
"Sales Order Item": {
|
||||
item_table: {
|
||||
"doctype": "Material Request Item",
|
||||
"field_map": {
|
||||
"parent": "sales_order_no",
|
||||
|
@ -210,7 +210,7 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
|
||||
// NOTE:
|
||||
// paid_amount and write_off_amount is only for POS Invoice
|
||||
// total_advance is only for non POS Invoice
|
||||
if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.docstatus==0) {
|
||||
if(this.frm.doc.doctype == "Sales Invoice" && this.frm.doc.docstatus==0 && !this.frm.doc.is_return) {
|
||||
frappe.model.round_floats_in(this.frm.doc, ["base_grand_total", "total_advance", "write_off_amount",
|
||||
"paid_amount"]);
|
||||
var total_amount_to_pay = this.frm.doc.base_grand_total - this.frm.doc.write_off_amount
|
||||
|
@ -18,7 +18,7 @@
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "To track items in sales and purchase documents with batch nos<br><b>Preferred Industry: Chemicals etc</b>",
|
||||
"description": "To track items in sales and purchase documents with batch nos. \"Preferred Industry: Chemicals\"",
|
||||
"fieldname": "fs_item_batch_nos",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
@ -139,14 +139,14 @@
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "To enable <b>Point of Sale</b> features",
|
||||
"description": "To enable \"Point of Sale\" features",
|
||||
"fieldname": "fs_pos",
|
||||
"fieldtype": "Check",
|
||||
"label": "Point of Sale",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"description": "To enable <b>Point of Sale</b> view",
|
||||
"description": "To enable \"Point of Sale\" view",
|
||||
"fieldname": "fs_pos_view",
|
||||
"fieldtype": "Check",
|
||||
"label": "POS View",
|
||||
@ -237,4 +237,4 @@
|
||||
"write": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe import _, throw, msgprint
|
||||
from frappe.utils import cstr, nowdate
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
@ -63,8 +63,7 @@ def send_sms(receiver_list, msg, sender_name = ''):
|
||||
}
|
||||
|
||||
if frappe.db.get_value('SMS Settings', None, 'sms_gateway_url'):
|
||||
ret = send_via_gateway(arg)
|
||||
msgprint(ret)
|
||||
send_via_gateway(arg)
|
||||
else:
|
||||
msgprint(_("Please Update SMS Settings"))
|
||||
|
||||
@ -74,12 +73,17 @@ def send_via_gateway(arg):
|
||||
for d in ss.get("parameters"):
|
||||
args[d.parameter] = d.value
|
||||
|
||||
resp = []
|
||||
success_list = []
|
||||
for d in arg.get('receiver_list'):
|
||||
args[ss.receiver_parameter] = d
|
||||
resp.append(send_request(ss.sms_gateway_url, args))
|
||||
status = send_request(ss.sms_gateway_url, args)
|
||||
if status == 200:
|
||||
success_list.append(d)
|
||||
|
||||
return resp
|
||||
if len(success_list) > 0:
|
||||
args.update(arg)
|
||||
create_sms_log(args, success_list)
|
||||
frappe.msgprint(_("SMS sent to following numbers: {0}").format("\n" + "\n".join(success_list)))
|
||||
|
||||
# Send Request
|
||||
# =========================================================
|
||||
@ -90,11 +94,8 @@ def send_request(gateway_url, args):
|
||||
headers = {}
|
||||
headers['Accept'] = "text/plain, text/html, */*"
|
||||
conn.request('GET', api_url + urllib.urlencode(args), headers = headers) # send request
|
||||
resp = conn.getresponse() # get response
|
||||
resp = resp.read()
|
||||
if resp.status==200:
|
||||
create_sms_log()
|
||||
return resp
|
||||
resp = conn.getresponse() # get response
|
||||
return resp.status
|
||||
|
||||
# Split gateway url to server and api url
|
||||
# =========================================================
|
||||
@ -109,12 +110,13 @@ def scrub_gateway_url(url):
|
||||
|
||||
# Create SMS Log
|
||||
# =========================================================
|
||||
def create_sms_log(arg, sent_sms):
|
||||
sl = frappe.get_doc('SMS Log')
|
||||
sl.sender_name = arg['sender_name']
|
||||
def create_sms_log(args, sent_to):
|
||||
sl = frappe.new_doc('SMS Log')
|
||||
sl.sender_name = args['sender_name']
|
||||
sl.sent_on = nowdate()
|
||||
sl.receiver_list = cstr(arg['receiver_list'])
|
||||
sl.message = arg['message']
|
||||
sl.no_of_requested_sms = len(arg['receiver_list'])
|
||||
sl.no_of_sent_sms = sent_sms
|
||||
sl.message = args['message']
|
||||
sl.no_of_requested_sms = len(args['receiver_list'])
|
||||
sl.requested_numbers = "\n".join(args['receiver_list'])
|
||||
sl.no_of_sent_sms = len(sent_to)
|
||||
sl.sent_to = "\n".join(sent_to)
|
||||
sl.save()
|
||||
|
@ -183,4 +183,4 @@ def install(country=None):
|
||||
parent_link_field = ("parent_" + scrub(doc.doctype))
|
||||
if doc.meta.get_field(parent_link_field) and not doc.get(parent_link_field):
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.insert()
|
||||
doc.insert(ignore_permissions=True)
|
||||
|
122
erpnext/setup/page/setup_wizard/sample_data.py
Normal file
122
erpnext/setup/page/setup_wizard/sample_data.py
Normal file
@ -0,0 +1,122 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.utils.make_random import add_random_children, get_random
|
||||
import frappe.utils
|
||||
|
||||
def make_sample_data():
|
||||
"""Create a few opportunities, quotes, material requests, issues, todos, projects
|
||||
to help the user get started"""
|
||||
|
||||
selling_items = frappe.get_all("Item", filters = {"is_sales_item": "Yes"})
|
||||
buying_items = frappe.get_all("Item", filters = {"is_sales_item": "No"})
|
||||
|
||||
if selling_items:
|
||||
for i in range(3):
|
||||
make_opportunity(selling_items)
|
||||
make_quote(selling_items)
|
||||
|
||||
make_projects()
|
||||
|
||||
if buying_items:
|
||||
make_material_request(buying_items)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def make_opportunity(selling_items):
|
||||
b = frappe.get_doc({
|
||||
"doctype": "Opportunity",
|
||||
"enquiry_from": "Customer",
|
||||
"customer": get_random("Customer"),
|
||||
"enquiry_type": "Sales",
|
||||
"with_items": 1
|
||||
})
|
||||
|
||||
add_random_children(b, "items", rows=len(selling_items), randomize = {
|
||||
"qty": (1, 5),
|
||||
"item_code": ("Item", {"is_sales_item": "Yes"})
|
||||
}, unique="item_code")
|
||||
|
||||
b.insert(ignore_permissions=True)
|
||||
|
||||
b.add_comment("This is a dummy record")
|
||||
|
||||
def make_quote(selling_items):
|
||||
qtn = frappe.get_doc({
|
||||
"doctype": "Quotation",
|
||||
"quotation_to": "Customer",
|
||||
"customer": get_random("Customer"),
|
||||
"order_type": "Sales"
|
||||
})
|
||||
|
||||
add_random_children(qtn, "items", rows=len(selling_items), randomize = {
|
||||
"qty": (1, 5),
|
||||
"item_code": ("Item", {"is_sales_item": "Yes"})
|
||||
}, unique="item_code")
|
||||
|
||||
qtn.insert(ignore_permissions=True)
|
||||
|
||||
qtn.add_comment("This is a dummy record")
|
||||
|
||||
def make_material_request(buying_items):
|
||||
for i in buying_items:
|
||||
mr = frappe.get_doc({
|
||||
"doctype": "Material Request",
|
||||
"material_request_type": "Purchase",
|
||||
"items": [{
|
||||
"schedule_date": frappe.utils.add_days(frappe.utils.nowdate(), 7),
|
||||
"item_code": i.name,
|
||||
"qty": 10
|
||||
}]
|
||||
})
|
||||
mr.insert()
|
||||
mr.submit()
|
||||
|
||||
mr.add_comment("This is a dummy record")
|
||||
|
||||
|
||||
def make_issue():
|
||||
pass
|
||||
|
||||
def make_projects():
|
||||
project = frappe.get_doc({
|
||||
"doctype": "Project",
|
||||
"project_name": "ERPNext Implementation",
|
||||
})
|
||||
current_date = frappe.utils.nowdate()
|
||||
project.set("tasks", [
|
||||
{
|
||||
"title": "Explore ERPNext",
|
||||
"start_date": frappe.utils.add_days(current_date, 1),
|
||||
"end_date": frappe.utils.add_days(current_date, 2)
|
||||
},
|
||||
{
|
||||
"title": "Run Sales Cycle",
|
||||
"start_date": frappe.utils.add_days(current_date, 2),
|
||||
"end_date": frappe.utils.add_days(current_date, 3)
|
||||
},
|
||||
{
|
||||
"title": "Run Billing Cycle",
|
||||
"start_date": frappe.utils.add_days(current_date, 3),
|
||||
"end_date": frappe.utils.add_days(current_date, 4)
|
||||
},
|
||||
{
|
||||
"title": "Run Purchase Cycle",
|
||||
"start_date": frappe.utils.add_days(current_date, 4),
|
||||
"end_date": frappe.utils.add_days(current_date, 5)
|
||||
},
|
||||
{
|
||||
"title": "Import Data",
|
||||
"start_date": frappe.utils.add_days(current_date, 5),
|
||||
"end_date": frappe.utils.add_days(current_date, 6)
|
||||
},
|
||||
{
|
||||
"title": "Go Live!",
|
||||
"start_date": frappe.utils.add_days(current_date, 6),
|
||||
"end_date": frappe.utils.add_days(current_date, 7)
|
||||
}])
|
||||
|
||||
project.insert(ignore_permissions=True)
|
@ -25,6 +25,7 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
|
||||
erpnext.wiz.user.slide,
|
||||
erpnext.wiz.org.slide,
|
||||
erpnext.wiz.branding.slide,
|
||||
erpnext.wiz.users.slide,
|
||||
erpnext.wiz.taxes.slide,
|
||||
erpnext.wiz.customers.slide,
|
||||
erpnext.wiz.suppliers.slide,
|
||||
@ -137,7 +138,7 @@ erpnext.wiz.WizardSlide = Class.extend({
|
||||
});
|
||||
this.form.make();
|
||||
} else {
|
||||
$(this.body).html(this.html)
|
||||
$(this.body).html(this.html);
|
||||
}
|
||||
|
||||
if(this.id > 0) {
|
||||
@ -412,11 +413,30 @@ $.extend(erpnext.wiz, {
|
||||
onload: function(slide) {
|
||||
erpnext.wiz.org.load_chart_of_accounts(slide);
|
||||
erpnext.wiz.org.bind_events(slide);
|
||||
erpnext.wiz.org.set_fy_dates(slide);
|
||||
},
|
||||
|
||||
css_class: "single-column"
|
||||
},
|
||||
|
||||
set_fy_dates: function(slide) {
|
||||
var country = slide.wiz.get_values().country;
|
||||
|
||||
if(country) {
|
||||
var fy = erpnext.wiz.fiscal_years[country];
|
||||
var current_year = moment(new Date()).year();
|
||||
var next_year = current_year + 1;
|
||||
if(!fy) {
|
||||
fy = ["01-01", "12-31"];
|
||||
next_year = current_year;
|
||||
}
|
||||
|
||||
slide.get_field("fy_start_date").set_input(current_year + "-" + fy[0]);
|
||||
slide.get_field("fy_end_date").set_input(next_year + "-" + fy[1]);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
load_chart_of_accounts: function(slide) {
|
||||
var country = slide.wiz.get_values().country;
|
||||
|
||||
@ -486,11 +506,41 @@ $.extend(erpnext.wiz, {
|
||||
},
|
||||
},
|
||||
|
||||
users: {
|
||||
slide: {
|
||||
icon: "icon-money",
|
||||
"title": __("Add Users"),
|
||||
"help": __("Add users to your organization"),
|
||||
"fields": [],
|
||||
before_load: function(slide) {
|
||||
slide.fields = [];
|
||||
for(var i=1; i<5; i++) {
|
||||
slide.fields = slide.fields.concat([
|
||||
{fieldtype:"Section Break"},
|
||||
{fieldtype:"Data", fieldname:"user_fullname_"+ i,
|
||||
label:__("Full Name")},
|
||||
{fieldtype:"Data", fieldname:"user_email_" + i,
|
||||
label:__("Email ID"), placeholder:__("user@example.com"),
|
||||
options: "Email"},
|
||||
{fieldtype:"Column Break"},
|
||||
{fieldtype: "Check", fieldname: "user_sales_" + i,
|
||||
label:__("Sales"), default: 1},
|
||||
{fieldtype: "Check", fieldname: "user_purchaser_" + i,
|
||||
label:__("Purchaser"), default: 1},
|
||||
{fieldtype: "Check", fieldname: "user_accountant_" + i,
|
||||
label:__("Accountant"), default: 1},
|
||||
]);
|
||||
}
|
||||
},
|
||||
css_class: "two-column"
|
||||
},
|
||||
},
|
||||
|
||||
taxes: {
|
||||
slide: {
|
||||
icon: "icon-money",
|
||||
"title": __("Add Taxes"),
|
||||
"help": __("List your tax heads (e.g. VAT, Excise; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."),
|
||||
"help": __("List your tax heads (e.g. VAT, Customs etc; they should have unique names) and their standard rates. This will create a standard template, which you can edit and add more later."),
|
||||
"fields": [],
|
||||
before_load: function(slide) {
|
||||
slide.fields = [];
|
||||
@ -526,6 +576,7 @@ $.extend(erpnext.wiz, {
|
||||
label:__("Contact Name") + " " + i, placeholder:__("Contact Name")}
|
||||
])
|
||||
}
|
||||
slide.fields[1].reqd = 1;
|
||||
},
|
||||
css_class: "two-column"
|
||||
},
|
||||
@ -549,6 +600,7 @@ $.extend(erpnext.wiz, {
|
||||
label:__("Contact Name") + " " + i, placeholder:__("Contact Name")},
|
||||
])
|
||||
}
|
||||
slide.fields[1].reqd = 1;
|
||||
},
|
||||
css_class: "two-column"
|
||||
},
|
||||
@ -578,9 +630,11 @@ $.extend(erpnext.wiz, {
|
||||
{fieldtype: "Check", fieldname: "is_sales_item_" + i, label:__("We sell this Item"), default: 1},
|
||||
{fieldtype: "Check", fieldname: "is_purchase_item_" + i, label:__("We buy this Item")},
|
||||
{fieldtype:"Column Break"},
|
||||
{fieldtype:"Currency", fieldname:"item_price_" + i, label:__("Rate")},
|
||||
{fieldtype:"Attach Image", fieldname:"item_img_" + i, label:__("Attach Image")},
|
||||
])
|
||||
}
|
||||
slide.fields[1].reqd = 1;
|
||||
},
|
||||
css_class: "two-column"
|
||||
},
|
||||
@ -627,3 +681,25 @@ $.extend(erpnext.wiz, {
|
||||
},
|
||||
});
|
||||
|
||||
// Source: https://en.wikipedia.org/wiki/Fiscal_year
|
||||
// default 1st Jan - 31st Dec
|
||||
|
||||
erpnext.wiz.fiscal_years = {
|
||||
"Afghanistan": ["12-20", "12-21"],
|
||||
"Australia": ["07-01", "06-30"],
|
||||
"Bangladesh": ["07-01", "06-30"],
|
||||
"Canada": ["04-01", "03-31"],
|
||||
"Costa Rica": ["10-01", "09-30"],
|
||||
"Egypt": ["07-01", "06-30"],
|
||||
"Hong Kong": ["04-01", "03-31"],
|
||||
"India": ["04-01", "03-31"],
|
||||
"Iran": ["06-23", "06-22"],
|
||||
"Italy": ["07-01", "06-30"],
|
||||
"Myanmar": ["04-01", "03-31"],
|
||||
"New Zealand": ["04-01", "03-31"],
|
||||
"Pakistan": ["07-01", "06-30"],
|
||||
"Singapore": ["04-01", "03-31"],
|
||||
"South Africa": ["03-01", "02-28"],
|
||||
"Thailand": ["10-01", "09-30"],
|
||||
"United Kingdom": ["04-01", "03-31"],
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
import frappe, json, copy
|
||||
|
||||
from frappe.utils import cstr, flt, getdate
|
||||
from frappe import _
|
||||
@ -13,6 +13,7 @@ from frappe.geo.country_info import get_country_info
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
from .default_website import website_maker
|
||||
import install_fixtures
|
||||
from .sample_data import make_sample_data
|
||||
|
||||
@frappe.whitelist()
|
||||
def setup_account(args=None):
|
||||
@ -38,6 +39,9 @@ def setup_account(args=None):
|
||||
create_fiscal_year_and_company(args)
|
||||
frappe.local.message_log = []
|
||||
|
||||
create_users(args)
|
||||
frappe.local.message_log = []
|
||||
|
||||
set_defaults(args)
|
||||
frappe.local.message_log = []
|
||||
|
||||
@ -81,6 +85,7 @@ def setup_account(args=None):
|
||||
|
||||
frappe.clear_cache()
|
||||
|
||||
make_sample_data()
|
||||
except:
|
||||
if args:
|
||||
traceback = frappe.get_traceback()
|
||||
@ -297,21 +302,45 @@ def create_taxes(args):
|
||||
tax_group = frappe.db.get_value("Account", {"company": args.get("company_name"),
|
||||
"is_group": 1, "account_type": "Tax", "root_type": "Liability"})
|
||||
if tax_group:
|
||||
frappe.get_doc({
|
||||
"doctype":"Account",
|
||||
"company": args.get("company_name").strip(),
|
||||
"parent_account": tax_group,
|
||||
"account_name": args.get("tax_" + str(i)),
|
||||
"is_group": 0,
|
||||
"report_type": "Balance Sheet",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": flt(tax_rate) if tax_rate else None
|
||||
}).insert()
|
||||
account = make_tax_head(args, i, tax_group, tax_rate)
|
||||
make_sales_and_purchase_tax_templates(account)
|
||||
|
||||
except frappe.NameError, e:
|
||||
if e.args[2][0]==1062:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
def make_tax_head(args, i, tax_group, tax_rate):
|
||||
return frappe.get_doc({
|
||||
"doctype":"Account",
|
||||
"company": args.get("company_name").strip(),
|
||||
"parent_account": tax_group,
|
||||
"account_name": args.get("tax_" + str(i)),
|
||||
"is_group": 0,
|
||||
"report_type": "Balance Sheet",
|
||||
"account_type": "Tax",
|
||||
"tax_rate": flt(tax_rate) if tax_rate else None
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
def make_sales_and_purchase_tax_templates(account):
|
||||
doc = {
|
||||
"doctype": "Sales Taxes and Charges Template",
|
||||
"title": account.name,
|
||||
"taxes": [{
|
||||
"category": "Valuation and Total",
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": account.name,
|
||||
"description": "{0} @ {1}".format(account.account_name, account.tax_rate),
|
||||
"rate": account.tax_rate
|
||||
}]
|
||||
}
|
||||
|
||||
# Sales
|
||||
frappe.get_doc(copy.deepcopy(doc)).insert()
|
||||
|
||||
# Purchase
|
||||
doc["doctype"] = "Purchase Taxes and Charges Template"
|
||||
frappe.get_doc(copy.deepcopy(doc)).insert()
|
||||
|
||||
def create_items(args):
|
||||
for i in xrange(1,6):
|
||||
@ -349,9 +378,30 @@ def create_items(args):
|
||||
filename, filetype, content = item_image
|
||||
fileurl = save_file(filename, content, "Item", item, decode=True).file_url
|
||||
frappe.db.set_value("Item", item, "image", fileurl)
|
||||
|
||||
if args.get("item_price_" + str(i)):
|
||||
item_price = flt(args.get("item_price_" + str(i)))
|
||||
|
||||
if is_sales_item:
|
||||
price_list_name = frappe.db.get_value("Price List", {"selling": 1})
|
||||
make_item_price(item, price_list_name, item_price)
|
||||
|
||||
if is_purchase_item:
|
||||
price_list_name = frappe.db.get_value("Price List", {"buying": 1})
|
||||
make_item_price(item, price_list_name, item_price)
|
||||
|
||||
except frappe.NameError:
|
||||
pass
|
||||
|
||||
def make_item_price(item, price_list_name, item_price):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item Price",
|
||||
"price_list": price_list_name,
|
||||
"item_code": item,
|
||||
"price_list_rate": item_price
|
||||
}).insert()
|
||||
|
||||
|
||||
def create_customers(args):
|
||||
for i in xrange(1,6):
|
||||
customer = args.get("customer_" + str(i))
|
||||
@ -367,13 +417,8 @@ def create_customers(args):
|
||||
}).insert()
|
||||
|
||||
if args.get("customer_contact_" + str(i)):
|
||||
contact = args.get("customer_contact_" + str(i)).split(" ")
|
||||
frappe.get_doc({
|
||||
"doctype":"Contact",
|
||||
"customer": customer,
|
||||
"first_name":contact[0],
|
||||
"last_name": len(contact) > 1 and contact[1] or ""
|
||||
}).insert()
|
||||
create_contact(args.get("customer_contact_" + str(i)),
|
||||
"customer", customer)
|
||||
except frappe.NameError:
|
||||
pass
|
||||
|
||||
@ -390,16 +435,21 @@ def create_suppliers(args):
|
||||
}).insert()
|
||||
|
||||
if args.get("supplier_contact_" + str(i)):
|
||||
contact = args.get("supplier_contact_" + str(i)).split(" ")
|
||||
frappe.get_doc({
|
||||
"doctype":"Contact",
|
||||
"supplier": supplier,
|
||||
"first_name":contact[0],
|
||||
"last_name": len(contact) > 1 and contact[1] or ""
|
||||
}).insert()
|
||||
create_contact(args.get("supplier_contact_" + str(i)),
|
||||
"supplier", supplier)
|
||||
except frappe.NameError:
|
||||
pass
|
||||
|
||||
def create_contact(contact, party_type, party):
|
||||
"""Create contact based on given contact name"""
|
||||
contact = contact.strip().split(" ")
|
||||
|
||||
frappe.get_doc({
|
||||
"doctype":"Contact",
|
||||
party_type: party,
|
||||
"first_name":contact[0],
|
||||
"last_name": len(contact) > 1 and contact[1] or ""
|
||||
}).insert()
|
||||
|
||||
def create_letter_head(args):
|
||||
if args.get("attach_letterhead"):
|
||||
@ -451,6 +501,60 @@ def login_as_first_user(args):
|
||||
if args.get("email") and hasattr(frappe.local, "login_manager"):
|
||||
frappe.local.login_manager.login_as(args.get("email"))
|
||||
|
||||
def create_users(args):
|
||||
# create employee for self
|
||||
emp = frappe.get_doc({
|
||||
"doctype": "Employee",
|
||||
"full_name": " ".join(filter(None, [args.get("first_name"), args.get("last_name")])),
|
||||
"user_id": frappe.session.user,
|
||||
"status": "Active",
|
||||
"company": args.get("company_name")
|
||||
})
|
||||
emp.flags.ignore_mandatory = True
|
||||
emp.insert(ignore_permissions = True)
|
||||
|
||||
for i in xrange(1,5):
|
||||
email = args.get("user_email_" + str(i))
|
||||
fullname = args.get("user_fullname_" + str(i))
|
||||
if email:
|
||||
if not fullname:
|
||||
fullname = email.split("@")[0]
|
||||
|
||||
parts = fullname.split(" ", 1)
|
||||
|
||||
user = frappe.get_doc({
|
||||
"doctype": "User",
|
||||
"email": email,
|
||||
"first_name": parts[0],
|
||||
"last_name": parts[1] if len(parts) > 1 else "",
|
||||
"enabled": 1,
|
||||
"user_type": "System User"
|
||||
})
|
||||
|
||||
# default roles
|
||||
user.append_roles("Projects User", "Stock User", "Support Team")
|
||||
|
||||
if args.get("user_sales_" + str(i)):
|
||||
user.append_roles("Sales User", "Sales Manager", "Accounts User")
|
||||
if args.get("user_purchaser_" + str(i)):
|
||||
user.append_roles("Purchase User", "Purchase Manager", "Accounts User")
|
||||
if args.get("user_accountant_" + str(i)):
|
||||
user.append_roles("Accounts Manager", "Accounts User")
|
||||
|
||||
user.flags.delay_emails = True
|
||||
user.insert(ignore_permissions=True)
|
||||
|
||||
# create employee
|
||||
emp = frappe.get_doc({
|
||||
"doctype": "Employee",
|
||||
"full_name": fullname,
|
||||
"user_id": user.name,
|
||||
"status": "Active",
|
||||
"company": args.get("company_name")
|
||||
})
|
||||
emp.flags.ignore_mandatory = True
|
||||
emp.insert(ignore_permissions = True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def load_messages(language):
|
||||
frappe.clear_cache()
|
||||
|
@ -51,4 +51,15 @@ args = {
|
||||
"timezone": "America/New_York",
|
||||
"password": "password",
|
||||
"email": "test@erpnext.com",
|
||||
"user_email_1": "testsetup1@example.com",
|
||||
"user_fullname_1": "test setup user",
|
||||
"user_sales_1": 1,
|
||||
"user_purchaser_1": 1,
|
||||
"user_accountant_1": 1,
|
||||
"user_email_1": "testsetup2@example.com",
|
||||
"user_fullname_1": "test setup user",
|
||||
"user_sales_2": 1,
|
||||
"user_purchaser_2": 0,
|
||||
"user_accountant_2": 0
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ def get_notification_config():
|
||||
"Issue": {"status": "Open"},
|
||||
"Warranty Claim": {"status": "Open"},
|
||||
"Task": {"status": "Open"},
|
||||
"Project": {"status": "Open"},
|
||||
"Lead": {"status": "Open"},
|
||||
"Contact": {"status": "Open"},
|
||||
"Opportunity": {"status": "Open"},
|
||||
|
@ -24,14 +24,15 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
cur_frm.add_custom_button(__('Make Installation Note'), this.make_installation_note);
|
||||
|
||||
if (doc.docstatus==1) {
|
||||
|
||||
cur_frm.add_custom_button(__('Make Sales Return'), this.make_sales_return);
|
||||
|
||||
this.show_stock_ledger();
|
||||
this.show_general_ledger();
|
||||
}
|
||||
|
||||
if(doc.docstatus==0 && !doc.__islocal) {
|
||||
cur_frm.add_custom_button(__('Make Packing Slip'),
|
||||
cur_frm.cscript['Make Packing Slip'], frappe.boot.doctype_icons["Packing Slip"], "btn-default");
|
||||
cur_frm.cscript['Make Packing Slip'], frappe.boot.doctype_icons["Packing Slip"]);
|
||||
}
|
||||
|
||||
erpnext.stock.delivery_note.set_print_hide(doc, dt, dn);
|
||||
@ -55,7 +56,7 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default");
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
@ -73,6 +74,13 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
frm: cur_frm
|
||||
});
|
||||
},
|
||||
|
||||
make_sales_return: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_return",
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
|
||||
tc_name: function() {
|
||||
this.get_terms();
|
||||
|
@ -29,7 +29,7 @@
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "DN-",
|
||||
"options": "DN-\nDN-RET-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
@ -205,6 +205,28 @@
|
||||
"read_only": 1,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Return",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_return",
|
||||
"fieldname": "return_against",
|
||||
"fieldtype": "Link",
|
||||
"label": "Return Against Delivery Note",
|
||||
"no_copy": 0,
|
||||
"options": "Delivery Note",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cusrrency_and_price_list",
|
||||
"fieldtype": "Section Break",
|
||||
@ -1070,7 +1092,7 @@
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-07-13 05:28:29.814096",
|
||||
"modified": "2015-07-24 11:49:15.056249",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
@ -84,7 +84,7 @@ class DeliveryNote(SellingController):
|
||||
|
||||
def so_required(self):
|
||||
"""check in manage account if sales order required or not"""
|
||||
if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
|
||||
if not self.is_return and frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
|
||||
for d in self.get('items'):
|
||||
if not d.against_sales_order:
|
||||
frappe.throw(_("Sales Order required for Item {0}").format(d.item_code))
|
||||
@ -175,17 +175,15 @@ class DeliveryNote(SellingController):
|
||||
# Check for Approving Authority
|
||||
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self)
|
||||
|
||||
# update delivered qty in sales order
|
||||
self.update_prevdoc_status()
|
||||
if not self.is_return:
|
||||
# update delivered qty in sales order
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.check_credit_limit()
|
||||
self.check_credit_limit()
|
||||
|
||||
# create stock ledger entry
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.make_gl_entries()
|
||||
|
||||
# set DN status
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
|
||||
|
||||
@ -193,7 +191,8 @@ class DeliveryNote(SellingController):
|
||||
self.check_stop_sales_order("against_sales_order")
|
||||
self.check_next_docstatus()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
if not self.is_return:
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_stock_ledger()
|
||||
|
||||
@ -251,9 +250,14 @@ class DeliveryNote(SellingController):
|
||||
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == "Yes" \
|
||||
and d.warehouse and flt(d['qty']):
|
||||
self.update_reserved_qty(d)
|
||||
|
||||
|
||||
incoming_rate = 0
|
||||
if cint(self.is_return) and self.return_against and self.docstatus==1:
|
||||
incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
|
||||
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"actual_qty": -1*flt(d['qty']),
|
||||
"incoming_rate": incoming_rate
|
||||
}))
|
||||
|
||||
self.make_sl_entries(sl_entries)
|
||||
@ -387,3 +391,9 @@ def make_packing_slip(source_name, target_doc=None):
|
||||
}, target_doc)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return make_return_doc("Delivery Note", source_name, target_doc)
|
@ -13,8 +13,10 @@ from erpnext.accounts.utils import get_balance_on
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
|
||||
import get_gl_entries, set_perpetual_inventory
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, make_serialized_item
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry \
|
||||
import make_stock_entry, make_serialized_item, get_qty_after_transaction
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoStatusError
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
|
||||
class TestDeliveryNote(unittest.TestCase):
|
||||
def test_over_billing_against_dn(self):
|
||||
@ -175,9 +177,155 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
self.assertRaises(SerialNoStatusError, dn.submit)
|
||||
|
||||
def check_serial_no_values(self, serial_no, field_values):
|
||||
serial_no = frappe.get_doc("Serial No", serial_no)
|
||||
for field, value in field_values.items():
|
||||
self.assertEquals(cstr(frappe.db.get_value("Serial No", serial_no, field)), value)
|
||||
self.assertEquals(cstr(serial_no.get(field)), value)
|
||||
|
||||
def test_sales_return_for_non_bundled_items(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100)
|
||||
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
dn = create_delivery_note(qty=5, rate=500)
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_0 - 5, actual_qty_1)
|
||||
|
||||
# outgoing_rate
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
|
||||
"voucher_no": dn.name}, "stock_value_difference") / 5
|
||||
|
||||
# return entry
|
||||
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_1 + 2, actual_qty_2)
|
||||
|
||||
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn1.name},
|
||||
["incoming_rate", "stock_value_difference"])
|
||||
|
||||
self.assertEquals(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
|
||||
|
||||
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note",
|
||||
"voucher_no": dn1.name, "account": "_Test Warehouse - _TC"}, "debit")
|
||||
|
||||
self.assertEquals(gle_warehouse_amount, stock_value_difference)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_return_single_item_from_bundled_items(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
create_stock_reconciliation(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, rate=100)
|
||||
create_stock_reconciliation(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
|
||||
qty=50, rate=100)
|
||||
|
||||
dn = create_delivery_note(item_code="_Test Product Bundle Item", qty=5, rate=500)
|
||||
|
||||
# Qty after delivery
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_1, 25)
|
||||
|
||||
# outgoing_rate
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
|
||||
"voucher_no": dn.name, "item_code": "_Test Item"}, "stock_value_difference") / 25
|
||||
|
||||
# return 'test item' from packed items
|
||||
dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-10, rate=500)
|
||||
|
||||
# qty after return
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_2, 35)
|
||||
|
||||
# Check incoming rate for return entry
|
||||
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn1.name},
|
||||
["incoming_rate", "stock_value_difference"])
|
||||
|
||||
self.assertEquals(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
|
||||
|
||||
# Check gl entry for warehouse
|
||||
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note",
|
||||
"voucher_no": dn1.name, "account": "_Test Warehouse - _TC"}, "debit")
|
||||
|
||||
self.assertEquals(gle_warehouse_amount, stock_value_difference)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_return_entire_bundled_items(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
create_stock_reconciliation(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, rate=100)
|
||||
create_stock_reconciliation(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
|
||||
qty=50, rate=100)
|
||||
|
||||
dn = create_delivery_note(item_code="_Test Product Bundle Item", qty=5, rate=500)
|
||||
|
||||
# return bundled item
|
||||
dn1 = create_delivery_note(item_code='_Test Product Bundle Item', is_return=1,
|
||||
return_against=dn.name, qty=-2, rate=500)
|
||||
|
||||
# qty after return
|
||||
actual_qty = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty, 35)
|
||||
|
||||
# Check incoming rate for return entry
|
||||
incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn1.name},
|
||||
["incoming_rate", "stock_value_difference"])
|
||||
|
||||
self.assertEquals(incoming_rate, 100)
|
||||
|
||||
# Check gl entry for warehouse
|
||||
gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note",
|
||||
"voucher_no": dn1.name, "account": "_Test Warehouse - _TC"}, "debit")
|
||||
|
||||
self.assertEquals(gle_warehouse_amount, 1400)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_return_for_serialized_items(self):
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
|
||||
|
||||
dn = create_delivery_note(item_code="_Test Serialized Item With Series", rate=500, serial_no=serial_no)
|
||||
|
||||
self.check_serial_no_values(serial_no, {
|
||||
"status": "Delivered",
|
||||
"warehouse": "",
|
||||
"delivery_document_no": dn.name
|
||||
})
|
||||
|
||||
# return entry
|
||||
dn1 = create_delivery_note(item_code="_Test Serialized Item With Series",
|
||||
is_return=1, return_against=dn.name, qty=-1, rate=500, serial_no=serial_no)
|
||||
|
||||
self.check_serial_no_values(serial_no, {
|
||||
"status": "Sales Returned",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"delivery_document_no": ""
|
||||
})
|
||||
|
||||
dn1.cancel()
|
||||
|
||||
self.check_serial_no_values(serial_no, {
|
||||
"status": "Delivered",
|
||||
"warehouse": "",
|
||||
"delivery_document_no": dn.name
|
||||
})
|
||||
|
||||
dn.cancel()
|
||||
|
||||
self.check_serial_no_values(serial_no, {
|
||||
"status": "Available",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"delivery_document_no": "",
|
||||
"purchase_document_no": se.name
|
||||
})
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
@ -190,6 +338,8 @@ def create_delivery_note(**args):
|
||||
dn.company = args.company or "_Test Company"
|
||||
dn.customer = args.customer or "_Test Customer"
|
||||
dn.currency = args.currency or "INR"
|
||||
dn.is_return = args.is_return
|
||||
dn.return_against = args.return_against
|
||||
|
||||
dn.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
|
@ -86,8 +86,12 @@ frappe.ui.form.on("Item", {
|
||||
},
|
||||
|
||||
manage_variants: function(frm) {
|
||||
frappe.route_options = {"item_code": frm.doc.name };
|
||||
frappe.set_route("List", "Manage Variants");
|
||||
if (cur_frm.doc.__unsaved==1) {
|
||||
frappe.throw(__("You have unsaved changes. Please save."))
|
||||
} else {
|
||||
frappe.route_options = {"item_code": frm.doc.name };
|
||||
frappe.set_route("List", "Manage Variants");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -325,7 +325,8 @@ class Item(WebsiteGenerator):
|
||||
for d in variants:
|
||||
update_variant(self.name, d)
|
||||
updated.append(d.item_code)
|
||||
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
|
||||
if updated:
|
||||
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
|
||||
|
||||
def validate_has_variants(self):
|
||||
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
|
||||
|
@ -31,9 +31,10 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
|
||||
if(this.frm.doc.docstatus == 1) {
|
||||
if(this.frm.doc.__onload && !this.frm.doc.__onload.billing_complete) {
|
||||
cur_frm.add_custom_button(__('Make Purchase Invoice'), this.make_purchase_invoice,
|
||||
frappe.boot.doctype_icons["Purchase Invoice"]);
|
||||
cur_frm.add_custom_button(__('Make Purchase Invoice'), this.make_purchase_invoice);
|
||||
}
|
||||
|
||||
cur_frm.add_custom_button(__('Make Purchase Return'), this.make_purchase_return);
|
||||
|
||||
this.show_stock_ledger();
|
||||
this.show_general_ledger();
|
||||
@ -51,7 +52,7 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
company: cur_frm.doc.company
|
||||
}
|
||||
})
|
||||
}, "icon-download", "btn-default");
|
||||
});
|
||||
}
|
||||
|
||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
|
||||
@ -105,6 +106,13 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
|
||||
make_purchase_return: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.purchase_receipt.purchase_receipt.make_purchase_return",
|
||||
frm: cur_frm
|
||||
})
|
||||
},
|
||||
|
||||
tc_name: function() {
|
||||
this.get_terms();
|
||||
|
@ -21,13 +21,14 @@
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "naming_series",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "PREC-",
|
||||
"options": "PREC-\nPREC-RET-",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"reqd": 1
|
||||
@ -130,6 +131,28 @@
|
||||
"search_index": 0,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Return",
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_return",
|
||||
"fieldname": "return_against",
|
||||
"fieldtype": "Link",
|
||||
"label": "Return Against Purchase Receipt",
|
||||
"no_copy": 0,
|
||||
"options": "Purchase Receipt",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "currency_and_price_list",
|
||||
"fieldtype": "Section Break",
|
||||
@ -854,7 +877,7 @@
|
||||
"icon": "icon-truck",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-07-13 05:28:27.389559",
|
||||
"modified": "2015-07-24 11:49:35.580382",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
@ -44,6 +44,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.set_status()
|
||||
self.po_required()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_purchase_return()
|
||||
self.validate_rejected_warehouse()
|
||||
self.validate_accepted_rejected_qty()
|
||||
self.validate_inspection()
|
||||
@ -60,12 +61,20 @@ class PurchaseReceipt(BuyingController):
|
||||
self.set_landed_cost_voucher_amount()
|
||||
self.update_valuation_rate("items")
|
||||
|
||||
|
||||
def set_landed_cost_voucher_amount(self):
|
||||
for d in self.get("items"):
|
||||
lc_voucher_amount = frappe.db.sql("""select sum(ifnull(applicable_charges, 0))
|
||||
from `tabLanded Cost Item`
|
||||
where docstatus = 1 and purchase_receipt_item = %s""", d.name)
|
||||
d.landed_cost_voucher_amount = lc_voucher_amount[0][0] if lc_voucher_amount else 0.0
|
||||
|
||||
def validate_purchase_return(self):
|
||||
for d in self.get("items"):
|
||||
if self.is_return and flt(d.rejected_qty) != 0:
|
||||
frappe.throw(_("Row #{0}: Rejected Qty can not be entered in Purchase Return").format(d.idx))
|
||||
|
||||
# validate rate with ref PR
|
||||
|
||||
def validate_rejected_warehouse(self):
|
||||
for d in self.get("items"):
|
||||
@ -108,7 +117,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.validate_rate_with_reference_doc([["Purchase Order", "prevdoc_docname", "prevdoc_detail_docname"]])
|
||||
|
||||
def po_required(self):
|
||||
if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
|
||||
if not self.is_return and frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
|
||||
for d in self.get('items'):
|
||||
if not d.prevdoc_docname:
|
||||
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
|
||||
@ -123,11 +132,20 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
if pr_qty:
|
||||
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
rate = flt(d.valuation_rate, val_rate_db_precision)
|
||||
sle = self.get_sl_entries(d, {
|
||||
"actual_qty": flt(pr_qty),
|
||||
"serial_no": cstr(d.serial_no).strip(),
|
||||
"incoming_rate": flt(d.valuation_rate, val_rate_db_precision)
|
||||
}))
|
||||
"serial_no": cstr(d.serial_no).strip()
|
||||
})
|
||||
if self.is_return:
|
||||
sle.update({
|
||||
"outgoing_rate": rate
|
||||
})
|
||||
else:
|
||||
sle.update({
|
||||
"incoming_rate": rate
|
||||
})
|
||||
sl_entries.append(sle)
|
||||
|
||||
if flt(d.rejected_qty) > 0:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
@ -176,7 +194,6 @@ class PurchaseReceipt(BuyingController):
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.supplier_warehouse,
|
||||
"actual_qty": -1*flt(d.consumed_qty),
|
||||
"incoming_rate": 0
|
||||
}))
|
||||
|
||||
def validate_inspection(self):
|
||||
@ -207,17 +224,16 @@ class PurchaseReceipt(BuyingController):
|
||||
# Set status as Submitted
|
||||
frappe.db.set(self, 'status', 'Submitted')
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_ordered_qty()
|
||||
if not self.is_return:
|
||||
self.update_prevdoc_status()
|
||||
self.update_ordered_qty()
|
||||
purchase_controller.update_last_purchase_rate(self, 1)
|
||||
|
||||
self.update_stock_ledger()
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
|
||||
purchase_controller.update_last_purchase_rate(self, 1)
|
||||
|
||||
self.make_gl_entries()
|
||||
|
||||
def check_next_docstatus(self):
|
||||
@ -244,12 +260,13 @@ class PurchaseReceipt(BuyingController):
|
||||
|
||||
self.update_stock_ledger()
|
||||
|
||||
self.update_prevdoc_status()
|
||||
if not self.is_return:
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# Must be called after updating received qty in PO
|
||||
self.update_ordered_qty()
|
||||
# Must be called after updating received qty in PO
|
||||
self.update_ordered_qty()
|
||||
|
||||
pc_obj.update_last_purchase_rate(self, 0)
|
||||
pc_obj.update_last_purchase_rate(self, 0)
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
@ -417,7 +434,7 @@ def make_purchase_invoice(source_name, target_doc=None):
|
||||
"doctype": "Purchase Invoice",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
},
|
||||
"Purchase Receipt Item": {
|
||||
"doctype": "Purchase Invoice Item",
|
||||
@ -449,3 +466,8 @@ def get_invoiced_qty_map(purchase_receipt):
|
||||
invoiced_qty_map[pr_detail] += qty
|
||||
|
||||
return invoiced_qty_map
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_return(source_name, target_doc=None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
return make_return_doc("Purchase Receipt", source_name, target_doc)
|
@ -6,7 +6,7 @@ from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
from frappe.utils import cint, flt
|
||||
from frappe.utils import cint, flt, cstr
|
||||
|
||||
class TestPurchaseReceipt(unittest.TestCase):
|
||||
def test_make_purchase_invoice(self):
|
||||
@ -119,6 +119,65 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
for serial_no in rejected_serial_nos:
|
||||
self.assertEquals(frappe.db.get_value("Serial No", serial_no, "warehouse"),
|
||||
pr.get("items")[0].rejected_warehouse)
|
||||
|
||||
def test_purchase_return(self):
|
||||
set_perpetual_inventory()
|
||||
|
||||
pr = make_purchase_receipt()
|
||||
|
||||
return_pr = make_purchase_receipt(is_return=1, return_against=pr.name, qty=-2)
|
||||
|
||||
# check sle
|
||||
outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": return_pr.name}, "outgoing_rate")
|
||||
|
||||
self.assertEqual(outgoing_rate, 50)
|
||||
|
||||
|
||||
# check gl entries for return
|
||||
gl_entries = get_gl_entries("Purchase Receipt", return_pr.name)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = {
|
||||
"_Test Warehouse - _TC": [0.0, 100.0],
|
||||
"Stock Received But Not Billed - _TC": [100.0, 0.0],
|
||||
}
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_purchase_return_for_serialized_items(self):
|
||||
def _check_serial_no_values(serial_no, field_values):
|
||||
serial_no = frappe.get_doc("Serial No", serial_no)
|
||||
for field, value in field_values.items():
|
||||
self.assertEquals(cstr(serial_no.get(field)), value)
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
|
||||
|
||||
serial_no = get_serial_nos(pr.get("items")[0].serial_no)[0]
|
||||
|
||||
_check_serial_no_values(serial_no, {
|
||||
"status": "Available",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"purchase_document_no": pr.name
|
||||
})
|
||||
|
||||
return_pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=-1,
|
||||
is_return=1, return_against=pr.name, serial_no=serial_no)
|
||||
|
||||
_check_serial_no_values(serial_no, {
|
||||
"status": "Purchase Returned",
|
||||
"warehouse": "",
|
||||
"purchase_document_no": pr.name,
|
||||
"delivery_document_no": return_pr.name
|
||||
})
|
||||
|
||||
|
||||
def get_gl_entries(voucher_type, voucher_no):
|
||||
return frappe.db.sql("""select account, debit, credit
|
||||
@ -142,6 +201,8 @@ def make_purchase_receipt(**args):
|
||||
pr.is_subcontracted = args.is_subcontracted or "No"
|
||||
pr.supplier_warehouse = "_Test Warehouse 1 - _TC"
|
||||
pr.currency = args.currency or "INR"
|
||||
pr.is_return = args.is_return
|
||||
pr.return_against = args.return_against
|
||||
|
||||
pr.append("items", {
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
|
@ -244,7 +244,7 @@
|
||||
"in_filter": 1,
|
||||
"label": "Delivery Document Type",
|
||||
"no_copy": 1,
|
||||
"options": "\nDelivery Note\nSales Invoice\nStock Entry",
|
||||
"options": "\nDelivery Note\nSales Invoice\nStock Entry\nPurchase Receipt",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
@ -418,7 +418,7 @@
|
||||
"icon": "icon-barcode",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"modified": "2015-07-13 05:28:27.961178",
|
||||
"modified": "2015-07-24 03:55:29.946944",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Serial No",
|
||||
|
@ -33,10 +33,7 @@ class SerialNo(StockController):
|
||||
self.validate_warehouse()
|
||||
self.validate_item()
|
||||
self.on_stock_ledger_entry()
|
||||
|
||||
valid_purchase_document_type = ("Purchase Receipt", "Stock Entry", "Serial No")
|
||||
self.validate_value("purchase_document_type", "in", valid_purchase_document_type)
|
||||
|
||||
|
||||
def set_maintenance_status(self):
|
||||
if not self.warranty_expiry_date and not self.amc_expiry_date:
|
||||
self.maintenance_status = None
|
||||
@ -81,20 +78,19 @@ class SerialNo(StockController):
|
||||
def set_status(self, last_sle):
|
||||
if last_sle:
|
||||
if last_sle.voucher_type == "Stock Entry":
|
||||
document_type = frappe.db.get_value("Stock Entry", last_sle.voucher_no,
|
||||
"purpose")
|
||||
document_type = frappe.db.get_value("Stock Entry", last_sle.voucher_no, "purpose")
|
||||
else:
|
||||
document_type = last_sle.voucher_type
|
||||
|
||||
if last_sle.actual_qty > 0:
|
||||
if document_type == "Sales Return":
|
||||
if document_type in ("Delivery Note", "Sales Invoice", "Sales Return"):
|
||||
self.status = "Sales Returned"
|
||||
else:
|
||||
self.status = "Available"
|
||||
else:
|
||||
if document_type == "Purchase Return":
|
||||
if document_type in ("Purchase Receipt", "Purchase Invoice", "Purchase Return"):
|
||||
self.status = "Purchase Returned"
|
||||
elif last_sle.voucher_type in ("Delivery Note", "Sales Invoice"):
|
||||
elif document_type in ("Delivery Note", "Sales Invoice"):
|
||||
self.status = "Delivered"
|
||||
else:
|
||||
self.status = "Not Available"
|
||||
@ -123,9 +119,10 @@ class SerialNo(StockController):
|
||||
self.delivery_document_no = delivery_sle.voucher_no
|
||||
self.delivery_date = delivery_sle.posting_date
|
||||
self.delivery_time = delivery_sle.posting_time
|
||||
self.customer, self.customer_name = \
|
||||
frappe.db.get_value(delivery_sle.voucher_type, delivery_sle.voucher_no,
|
||||
["customer", "customer_name"])
|
||||
if delivery_sle.voucher_type in ("Delivery Note", "Sales Invoice"):
|
||||
self.customer, self.customer_name = \
|
||||
frappe.db.get_value(delivery_sle.voucher_type, delivery_sle.voucher_no,
|
||||
["customer", "customer_name"])
|
||||
if self.warranty_period:
|
||||
self.warranty_expiry_date = add_days(cstr(delivery_sle.posting_date),
|
||||
cint(self.warranty_period))
|
||||
@ -235,10 +232,10 @@ def validate_serial_no(sle, item_det):
|
||||
frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no,
|
||||
sle.warehouse), SerialNoWarehouseError)
|
||||
|
||||
if sle.voucher_type in ("Delivery Note", "Sales Invoice") \
|
||||
if sle.voucher_type in ("Delivery Note", "Sales Invoice") and sle.is_cancelled=="No" \
|
||||
and sr.status != "Available":
|
||||
frappe.throw(_("Serial No {0} status must be 'Available' to Deliver").format(serial_no),
|
||||
SerialNoStatusError)
|
||||
frappe.throw(_("Serial No {0} status must be 'Available' to Deliver").format(serial_no),
|
||||
SerialNoStatusError)
|
||||
|
||||
elif sle.actual_qty < 0:
|
||||
# transfer out
|
||||
|
@ -7,20 +7,7 @@ frappe.provide("erpnext.stock");
|
||||
erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
setup: function() {
|
||||
var me = this;
|
||||
|
||||
this.frm.fields_dict.delivery_note_no.get_query = function() {
|
||||
return { query: "erpnext.stock.doctype.stock_entry.stock_entry.query_sales_return_doc" };
|
||||
};
|
||||
|
||||
this.frm.fields_dict.sales_invoice_no.get_query =
|
||||
this.frm.fields_dict.delivery_note_no.get_query;
|
||||
|
||||
this.frm.fields_dict.purchase_receipt_no.get_query = function() {
|
||||
return {
|
||||
filters:{ 'docstatus': 1 }
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
this.frm.fields_dict.bom_no.get_query = function() {
|
||||
return {
|
||||
filters:{ 'docstatus': 1 }
|
||||
@ -28,20 +15,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
};
|
||||
|
||||
this.frm.fields_dict.items.grid.get_field('item_code').get_query = function() {
|
||||
if(in_list(["Sales Return", "Purchase Return"], me.frm.doc.purpose) &&
|
||||
me.get_doctype_docname()) {
|
||||
return {
|
||||
query: "erpnext.stock.doctype.stock_entry.stock_entry.query_return_item",
|
||||
filters: {
|
||||
purpose: me.frm.doc.purpose,
|
||||
delivery_note_no: me.frm.doc.delivery_note_no,
|
||||
sales_invoice_no: me.frm.doc.sales_invoice_no,
|
||||
purchase_receipt_no: me.frm.doc.purchase_receipt_no
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return erpnext.queries.item({is_stock_item: "Yes"});
|
||||
}
|
||||
return erpnext.queries.item({is_stock_item: "Yes"});
|
||||
};
|
||||
|
||||
this.frm.set_query("purchase_order", function() {
|
||||
@ -84,19 +58,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
this.toggle_enable_bom();
|
||||
this.show_stock_ledger();
|
||||
this.show_general_ledger();
|
||||
|
||||
if(this.frm.doc.docstatus === 1 && frappe.boot.user.can_create.indexOf("Journal Entry")!==-1
|
||||
&& this.frm.doc.__onload.credit_debit_note_exists == 0 ) {
|
||||
if(this.frm.doc.purpose === "Sales Return") {
|
||||
this.frm.add_custom_button(__("Make Credit Note"),
|
||||
function() { me.make_return_jv(); }, frappe.boot.doctype_icons["Journal Entry"]);
|
||||
this.add_excise_button();
|
||||
} else if(this.frm.doc.purpose === "Purchase Return") {
|
||||
this.frm.add_custom_button(__("Make Debit Note"),
|
||||
function() { me.make_return_jv(); }, frappe.boot.doctype_icons["Journal Entry"]);
|
||||
this.add_excise_button();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
on_submit: function() {
|
||||
@ -111,15 +72,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
var me = this;
|
||||
|
||||
if(cint(frappe.defaults.get_default("auto_accounting_for_stock")) && this.frm.doc.company) {
|
||||
var account_for = "stock_adjustment_account";
|
||||
|
||||
if (this.frm.doc.purpose == "Purchase Return")
|
||||
account_for = "stock_received_but_not_billed";
|
||||
|
||||
return this.frm.call({
|
||||
method: "erpnext.accounts.utils.get_company_default",
|
||||
args: {
|
||||
"fieldname": account_for,
|
||||
"fieldname": "stock_adjustment_account",
|
||||
"company": this.frm.doc.company
|
||||
},
|
||||
callback: function(r) {
|
||||
@ -192,35 +148,6 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
this.frm.toggle_enable("bom_no", !in_list(["Manufacture", "Material Transfer for Manufacture"], this.frm.doc.purpose));
|
||||
},
|
||||
|
||||
get_doctype_docname: function() {
|
||||
if(this.frm.doc.purpose === "Sales Return") {
|
||||
if(this.frm.doc.delivery_note_no && this.frm.doc.sales_invoice_no) {
|
||||
// both specified
|
||||
msgprint(__("You can not enter both Delivery Note No and Sales Invoice No. Please enter any one."));
|
||||
|
||||
} else if(!(this.frm.doc.delivery_note_no || this.frm.doc.sales_invoice_no)) {
|
||||
// none specified
|
||||
msgprint(__("Please enter Delivery Note No or Sales Invoice No to proceed"));
|
||||
|
||||
} else if(this.frm.doc.delivery_note_no) {
|
||||
return {doctype: "Delivery Note", docname: this.frm.doc.delivery_note_no};
|
||||
|
||||
} else if(this.frm.doc.sales_invoice_no) {
|
||||
return {doctype: "Sales Invoice", docname: this.frm.doc.sales_invoice_no};
|
||||
|
||||
}
|
||||
} else if(this.frm.doc.purpose === "Purchase Return") {
|
||||
if(this.frm.doc.purchase_receipt_no) {
|
||||
return {doctype: "Purchase Receipt", docname: this.frm.doc.purchase_receipt_no};
|
||||
|
||||
} else {
|
||||
// not specified
|
||||
msgprint(__("Please enter Purchase Receipt No to proceed"));
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
add_excise_button: function() {
|
||||
if(frappe.boot.sysdefaults.country === "India")
|
||||
this.frm.add_custom_button(__("Make Excise Invoice"), function() {
|
||||
@ -231,37 +158,16 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
}, frappe.boot.doctype_icons["Journal Entry"], "btn-default");
|
||||
},
|
||||
|
||||
make_return_jv: function() {
|
||||
if(this.get_doctype_docname()) {
|
||||
return this.frm.call({
|
||||
method: "make_return_jv",
|
||||
args: {
|
||||
stock_entry: this.frm.doc.name
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
items_add: function(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row,
|
||||
["expense_account", "cost_center"]);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["expense_account", "cost_center"]);
|
||||
|
||||
if(!row.s_warehouse) row.s_warehouse = this.frm.doc.from_warehouse;
|
||||
if(!row.t_warehouse) row.t_warehouse = this.frm.doc.to_warehouse;
|
||||
},
|
||||
|
||||
source_mandatory: ["Material Issue", "Material Transfer", "Purchase Return", "Subcontract",
|
||||
"Material Transfer for Manufacture"],
|
||||
target_mandatory: ["Material Receipt", "Material Transfer", "Sales Return", "Subcontract",
|
||||
"Material Transfer for Manufacture"],
|
||||
source_mandatory: ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"],
|
||||
target_mandatory: ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"],
|
||||
|
||||
from_warehouse: function(doc) {
|
||||
var me = this;
|
||||
@ -295,92 +201,21 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
|
||||
items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
},
|
||||
|
||||
customer: function() {
|
||||
this.get_party_details({
|
||||
party: this.frm.doc.customer,
|
||||
party_type:"Customer",
|
||||
doctype: this.frm.doc.doctype
|
||||
});
|
||||
},
|
||||
|
||||
supplier: function() {
|
||||
this.get_party_details({
|
||||
party: this.frm.doc.supplier,
|
||||
party_type:"Supplier",
|
||||
doctype: this.frm.doc.doctype
|
||||
});
|
||||
},
|
||||
|
||||
get_party_details: function(args) {
|
||||
var me = this;
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.party.get_party_details",
|
||||
args: args,
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
me.frm.set_value({
|
||||
"customer_name": r.message["customer_name"],
|
||||
"customer_address": r.message["address_display"]
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
delivery_note_no: function() {
|
||||
this.get_party_details_from_against_voucher({
|
||||
ref_dt: "Delivery Note",
|
||||
ref_dn: this.frm.doc.delivery_note_no
|
||||
})
|
||||
},
|
||||
|
||||
sales_invoice_no: function() {
|
||||
this.get_party_details_from_against_voucher({
|
||||
ref_dt: "Sales Invoice",
|
||||
ref_dn: this.frm.doc.sales_invoice_no
|
||||
})
|
||||
},
|
||||
|
||||
purchase_receipt_no: function() {
|
||||
this.get_party_details_from_against_voucher({
|
||||
ref_dt: "Purchase Receipt",
|
||||
ref_dn: this.frm.doc.purchase_receipt_no
|
||||
})
|
||||
},
|
||||
|
||||
get_party_details_from_against_voucher: function(args) {
|
||||
return this.frm.call({
|
||||
method: "erpnext.stock.doctype.stock_entry.stock_entry.get_party_details",
|
||||
args: args,
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
cur_frm.script_manager.make(erpnext.stock.StockEntry);
|
||||
|
||||
cur_frm.cscript.toggle_related_fields = function(doc) {
|
||||
disable_from_warehouse = inList(["Material Receipt", "Sales Return"], doc.purpose);
|
||||
disable_to_warehouse = inList(["Material Issue", "Purchase Return"], doc.purpose);
|
||||
cur_frm.toggle_enable("from_warehouse", doc.purpose!='Material Receipt');
|
||||
cur_frm.toggle_enable("to_warehouse", doc.purpose!='Material Issue');
|
||||
|
||||
cur_frm.toggle_enable("from_warehouse", !disable_from_warehouse);
|
||||
cur_frm.toggle_enable("to_warehouse", !disable_to_warehouse);
|
||||
|
||||
cur_frm.fields_dict["items"].grid.set_column_disp("s_warehouse", !disable_from_warehouse);
|
||||
cur_frm.fields_dict["items"].grid.set_column_disp("t_warehouse", !disable_to_warehouse);
|
||||
cur_frm.fields_dict["items"].grid.set_column_disp("s_warehouse", doc.purpose!='Material Receipt');
|
||||
cur_frm.fields_dict["items"].grid.set_column_disp("t_warehouse", doc.purpose!='Material Issue');
|
||||
|
||||
cur_frm.cscript.toggle_enable_bom();
|
||||
|
||||
if(doc.purpose == 'Purchase Return') {
|
||||
doc.customer = doc.customer_name = doc.customer_address =
|
||||
doc.delivery_note_no = doc.sales_invoice_no = null;
|
||||
doc.bom_no = doc.production_order = doc.fg_completed_qty = null;
|
||||
} else if(doc.purpose == 'Sales Return') {
|
||||
doc.supplier=doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no=null;
|
||||
doc.bom_no = doc.production_order = doc.fg_completed_qty = null;
|
||||
} else if (doc.purpose == 'Subcontract') {
|
||||
if (doc.purpose == 'Subcontract') {
|
||||
doc.customer = doc.customer_name = doc.customer_address =
|
||||
doc.delivery_note_no = doc.sales_invoice_no = null;
|
||||
} else {
|
||||
@ -388,7 +223,7 @@ cur_frm.cscript.toggle_related_fields = function(doc) {
|
||||
doc.delivery_note_no = doc.sales_invoice_no = doc.supplier =
|
||||
doc.supplier_name = doc.supplier_address = doc.purchase_receipt_no = null;
|
||||
}
|
||||
if(in_list(["Material Receipt", "Sales Return", "Purchase Return"], doc.purpose)) {
|
||||
if(doc.purpose == "Material Receipt") {
|
||||
cur_frm.set_value("from_bom", 0);
|
||||
}
|
||||
}
|
||||
@ -505,8 +340,6 @@ cur_frm.cscript.uom = function(doc, cdt, cdn) {
|
||||
}
|
||||
|
||||
cur_frm.cscript.validate = function(doc, cdt, cdn) {
|
||||
if($.inArray(cur_frm.doc.purpose, ["Purchase Return", "Sales Return"])!==-1)
|
||||
validated = cur_frm.cscript.get_doctype_docname() ? true : false;
|
||||
cur_frm.cscript.validate_items(doc);
|
||||
}
|
||||
|
||||
@ -526,14 +359,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
|
||||
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center");
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.customer.get_query = function(doc, cdt, cdn) {
|
||||
return { query: "erpnext.controllers.queries.customer_query" }
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.supplier.get_query = function(doc, cdt, cdn) {
|
||||
return { query: "erpnext.controllers.queries.supplier_query" }
|
||||
}
|
||||
|
||||
cur_frm.cscript.company = function(doc, cdt, cdn) {
|
||||
if(doc.company) {
|
||||
erpnext.get_fiscal_year(doc.company, doc.posting_date, function() {
|
||||
|
@ -54,7 +54,7 @@
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "purpose",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nManufacture\nRepack\nSubcontract\nSales Return\nPurchase Return",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nManufacture\nRepack\nSubcontract",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
@ -678,7 +678,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2015-07-13 05:28:26.085266",
|
||||
"modified": "2015-07-22 18:47:20.328749",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
@ -4,10 +4,8 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
|
||||
from frappe.utils import cstr, cint, flt, comma_or, get_datetime, getdate
|
||||
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, cint, flt, comma_or, get_datetime, getdate
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
@ -15,8 +13,6 @@ from erpnext.stock.get_item_details import get_available_qty, get_default_cost_c
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from erpnext.accounts.utils import validate_fiscal_year
|
||||
|
||||
class NotUpdateStockError(frappe.ValidationError): pass
|
||||
class StockOverReturnError(frappe.ValidationError): pass
|
||||
class IncorrectValuationRateError(frappe.ValidationError): pass
|
||||
class DuplicateEntryForProductionOrderError(frappe.ValidationError): pass
|
||||
class OperationsNotCompleteError(frappe.ValidationError): pass
|
||||
@ -37,13 +33,6 @@ class StockEntry(StockController):
|
||||
item.update(get_available_qty(item.item_code,
|
||||
item.s_warehouse))
|
||||
|
||||
count = frappe.db.exists({
|
||||
"doctype": "Journal Entry",
|
||||
"stock_entry":self.name,
|
||||
"docstatus":1
|
||||
})
|
||||
self.get("__onload").credit_debit_note_exists = 1 if count else 0
|
||||
|
||||
def validate(self):
|
||||
self.pro_doc = None
|
||||
if self.production_order:
|
||||
@ -61,7 +50,6 @@ class StockEntry(StockController):
|
||||
self.get_stock_and_rate()
|
||||
self.validate_bom()
|
||||
self.validate_finished_goods()
|
||||
self.validate_return_reference_doc()
|
||||
self.validate_with_material_request()
|
||||
self.validate_valuation_rate()
|
||||
self.set_total_incoming_outgoing_value()
|
||||
@ -84,16 +72,13 @@ class StockEntry(StockController):
|
||||
|
||||
def validate_purpose(self):
|
||||
valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer", "Material Transfer for Manufacture",
|
||||
"Manufacture", "Repack", "Subcontract", "Sales Return", "Purchase Return"]
|
||||
"Manufacture", "Repack", "Subcontract"]
|
||||
if self.purpose not in valid_purposes:
|
||||
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
|
||||
|
||||
if self.purpose in ("Manufacture", "Repack", "Sales Return") and not self.difference_account:
|
||||
if self.purpose in ("Manufacture", "Repack") and not self.difference_account:
|
||||
self.difference_account = frappe.db.get_value("Company", self.company, "default_expense_account")
|
||||
|
||||
if self.purpose in ("Purchase Return") and not self.difference_account:
|
||||
frappe.throw(_("Difference Account mandatory for purpose '{0}'").format(self.purpose))
|
||||
|
||||
def set_transfer_qty(self):
|
||||
for item in self.get("items"):
|
||||
if not flt(item.qty):
|
||||
@ -122,7 +107,7 @@ class StockEntry(StockController):
|
||||
if not item.transfer_qty:
|
||||
item.transfer_qty = item.qty * item.conversion_factor
|
||||
|
||||
if (self.purpose in ("Material Transfer", "Sales Return", "Purchase Return", "Material Transfer for Manufacture")
|
||||
if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
|
||||
and not item.serial_no
|
||||
and item.item_code in serialized_items):
|
||||
frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
|
||||
@ -131,8 +116,8 @@ class StockEntry(StockController):
|
||||
def validate_warehouse(self):
|
||||
"""perform various (sometimes conditional) validations on warehouse"""
|
||||
|
||||
source_mandatory = ["Material Issue", "Material Transfer", "Purchase Return", "Subcontract", "Material Transfer for Manufacture"]
|
||||
target_mandatory = ["Material Receipt", "Material Transfer", "Sales Return", "Subcontract", "Material Transfer for Manufacture"]
|
||||
source_mandatory = ["Material Issue", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"]
|
||||
target_mandatory = ["Material Receipt", "Material Transfer", "Subcontract", "Material Transfer for Manufacture"]
|
||||
|
||||
validate_for_manufacture_repack = any([d.bom_no for d in self.get("items")])
|
||||
|
||||
@ -201,9 +186,7 @@ class StockEntry(StockController):
|
||||
def check_if_operations_completed(self):
|
||||
"""Check if Time Logs are completed against before manufacturing to capture operating costs."""
|
||||
prod_order = frappe.get_doc("Production Order", self.production_order)
|
||||
if not prod_order.track_operations:
|
||||
return
|
||||
|
||||
|
||||
for d in prod_order.get("operations"):
|
||||
total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty)
|
||||
if total_completed_qty > flt(d.completed_qty):
|
||||
@ -291,8 +274,8 @@ class StockEntry(StockController):
|
||||
|
||||
# get incoming rate
|
||||
if not d.bom_no:
|
||||
if not flt(d.incoming_rate) or d.s_warehouse or self.purpose == "Sales Return" or force:
|
||||
incoming_rate = flt(self.get_incoming_rate(args), self.precision("incoming_rate", d))
|
||||
if not flt(d.incoming_rate) or d.s_warehouse or force:
|
||||
incoming_rate = flt(get_incoming_rate(args), self.precision("incoming_rate", d))
|
||||
if incoming_rate > 0:
|
||||
d.incoming_rate = incoming_rate
|
||||
|
||||
@ -336,27 +319,6 @@ class StockEntry(StockController):
|
||||
|
||||
return operation_cost_per_unit + (flt(self.additional_operating_cost) / flt(qty))
|
||||
|
||||
def get_incoming_rate(self, args):
|
||||
incoming_rate = 0
|
||||
if self.purpose == "Sales Return":
|
||||
incoming_rate = self.get_incoming_rate_for_sales_return(args)
|
||||
else:
|
||||
incoming_rate = get_incoming_rate(args)
|
||||
|
||||
return incoming_rate
|
||||
|
||||
def get_incoming_rate_for_sales_return(self, args):
|
||||
incoming_rate = 0.0
|
||||
if (self.delivery_note_no or self.sales_invoice_no) and args.get("item_code"):
|
||||
incoming_rate = frappe.db.sql("""select abs(ifnull(stock_value_difference, 0) / actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where voucher_type = %s and voucher_no = %s and item_code = %s limit 1""",
|
||||
((self.delivery_note_no and "Delivery Note" or "Sales Invoice"),
|
||||
self.delivery_note_no or self.sales_invoice_no, args.item_code))
|
||||
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
|
||||
|
||||
return incoming_rate
|
||||
|
||||
def validate_purchase_order(self):
|
||||
"""Throw exception if more raw material is transferred against Purchase Order than in
|
||||
the raw materials supplied table"""
|
||||
@ -403,55 +365,6 @@ class StockEntry(StockController):
|
||||
frappe.throw(_("Finished Item {0} must be entered for Manufacture type entry")
|
||||
.format(production_item))
|
||||
|
||||
def validate_return_reference_doc(self):
|
||||
"""validate item with reference doc"""
|
||||
ref = get_return_doc_and_details(self)
|
||||
|
||||
if ref.doc:
|
||||
# validate docstatus
|
||||
if ref.doc.docstatus != 1:
|
||||
frappe.throw(_("{0} {1} must be submitted").format(ref.doc.doctype, ref.doc.name),
|
||||
frappe.InvalidStatusError)
|
||||
|
||||
# update stock check
|
||||
if ref.doc.doctype == "Sales Invoice" and cint(ref.doc.update_stock) != 1:
|
||||
frappe.throw(_("'Update Stock' for Sales Invoice {0} must be set").format(ref.doc.name), NotUpdateStockError)
|
||||
|
||||
# posting date check
|
||||
ref_posting_datetime = "%s %s" % (ref.doc.posting_date, ref.doc.posting_time or "00:00:00")
|
||||
|
||||
if get_datetime(ref_posting_datetime) < get_datetime(ref_posting_datetime):
|
||||
from frappe.utils.dateutils import datetime_in_user_format
|
||||
frappe.throw(_("Posting timestamp must be after {0}")
|
||||
.format(datetime_in_user_format(ref_posting_datetime)))
|
||||
|
||||
stock_items = get_stock_items_for_return(ref.doc, ref.parentfields)
|
||||
already_returned_item_qty = self.get_already_returned_item_qty(ref.fieldname)
|
||||
|
||||
for item in self.get("items"):
|
||||
# validate if item exists in the ref doc and that it is a stock item
|
||||
if item.item_code not in stock_items:
|
||||
frappe.throw(_("Item {0} does not exist in {1} {2}").format(item.item_code, ref.doc.doctype, ref.doc.name),
|
||||
frappe.DoesNotExistError)
|
||||
|
||||
# validate quantity <= ref item's qty - qty already returned
|
||||
if self.purpose == "Purchase Return":
|
||||
ref_item_qty = sum([flt(d.qty)*flt(d.conversion_factor) for d in ref.doc.get({"item_code": item.item_code})])
|
||||
elif self.purpose == "Sales Return":
|
||||
ref_item_qty = sum([flt(d.qty) for d in ref.doc.get({"item_code": item.item_code})])
|
||||
returnable_qty = ref_item_qty - flt(already_returned_item_qty.get(item.item_code))
|
||||
if not returnable_qty:
|
||||
frappe.throw(_("Item {0} has already been returned").format(item.item_code), StockOverReturnError)
|
||||
elif item.transfer_qty > returnable_qty:
|
||||
frappe.throw(_("Cannot return more than {0} for Item {1}").format(returnable_qty, item.item_code),
|
||||
StockOverReturnError)
|
||||
|
||||
def get_already_returned_item_qty(self, ref_fieldname):
|
||||
return dict(frappe.db.sql("""select item_code, sum(transfer_qty) as qty
|
||||
from `tabStock Entry Detail` where parent in (
|
||||
select name from `tabStock Entry` where `%s`=%s and docstatus=1)
|
||||
group by item_code""" % (ref_fieldname, "%s"), (self.get(ref_fieldname),)))
|
||||
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
for d in self.get('items'):
|
||||
@ -514,6 +427,7 @@ class StockEntry(StockController):
|
||||
(args.get('item_code')), as_dict = 1)
|
||||
if not item:
|
||||
frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code")))
|
||||
|
||||
item = item[0]
|
||||
|
||||
ret = {
|
||||
@ -561,7 +475,7 @@ class StockEntry(StockController):
|
||||
|
||||
ret = {
|
||||
"actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
|
||||
"incoming_rate" : self.get_incoming_rate(args)
|
||||
"incoming_rate" : get_incoming_rate(args)
|
||||
}
|
||||
return ret
|
||||
|
||||
@ -738,15 +652,6 @@ class StockEntry(StockController):
|
||||
if getdate(self.posting_date) > getdate(expiry_date):
|
||||
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(ref_dt, ref_dn):
|
||||
if ref_dt in ["Delivery Note", "Sales Invoice"]:
|
||||
res = frappe.db.get_value(ref_dt, ref_dn,
|
||||
["customer", "customer_name", "address_display as customer_address"], as_dict=1)
|
||||
else:
|
||||
res = frappe.db.get_value(ref_dt, ref_dn,
|
||||
["supplier", "supplier_name", "address_display as supplier_address"], as_dict=1)
|
||||
return res or {}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_production_order_details(production_order):
|
||||
@ -756,264 +661,3 @@ def get_production_order_details(production_order):
|
||||
from `tabProduction Order` where name = %s""", production_order, as_dict=1)
|
||||
|
||||
return res and res[0] or {}
|
||||
|
||||
def query_sales_return_doc(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = ""
|
||||
if doctype == "Sales Invoice":
|
||||
conditions = "and update_stock=1"
|
||||
|
||||
return frappe.db.sql("""select name, customer, customer_name
|
||||
from `tab%s` where docstatus = 1
|
||||
and (`%s` like %%(txt)s
|
||||
or `customer` like %%(txt)s) %s %s
|
||||
order by name, customer, customer_name
|
||||
limit %s""" % (doctype, searchfield, conditions,
|
||||
get_match_cond(doctype), "%(start)s, %(page_len)s"),
|
||||
{"txt": "%%%s%%" % txt, "start": start, "page_len": page_len},
|
||||
as_list=True)
|
||||
|
||||
def query_purchase_return_doc(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select name, supplier, supplier_name
|
||||
from `tab%s` where docstatus = 1
|
||||
and (`%s` like %%(txt)s
|
||||
or `supplier` like %%(txt)s) %s
|
||||
order by name, supplier, supplier_name
|
||||
limit %s""" % (doctype, searchfield, get_match_cond(doctype),
|
||||
"%(start)s, %(page_len)s"), {"txt": "%%%s%%" % txt, "start":
|
||||
start, "page_len": page_len}, as_list=True)
|
||||
|
||||
def query_return_item(doctype, txt, searchfield, start, page_len, filters):
|
||||
txt = txt.replace("%", "")
|
||||
|
||||
ref = get_return_doc_and_details(filters)
|
||||
|
||||
stock_items = get_stock_items_for_return(ref.doc, ref.parentfields)
|
||||
|
||||
result = []
|
||||
for item in ref.doc.get_all_children():
|
||||
if getattr(item, "item_code", None) in stock_items:
|
||||
item.item_name = cstr(item.item_name)
|
||||
item.description = cstr(item.description)
|
||||
if (txt in item.item_code) or (txt in item.item_name) or (txt in item.description):
|
||||
val = [
|
||||
item.item_code,
|
||||
(len(item.item_name) > 40) and (item.item_name[:40] + "...") or item.item_name,
|
||||
(len(item.description) > 40) and (item.description[:40] + "...") or \
|
||||
item.description
|
||||
]
|
||||
if val not in result:
|
||||
result.append(val)
|
||||
|
||||
return result[start:start+page_len]
|
||||
|
||||
def get_stock_items_for_return(ref_doc, parentfields):
|
||||
"""return item codes filtered from doc, which are stock items"""
|
||||
if isinstance(parentfields, basestring):
|
||||
parentfields = [parentfields]
|
||||
|
||||
all_items = list(set([d.item_code for d in
|
||||
ref_doc.get_all_children() if d.get("item_code")]))
|
||||
stock_items = frappe.db.sql_list("""select name from `tabItem`
|
||||
where is_stock_item='Yes' and name in (%s)""" % (", ".join(["%s"] * len(all_items))),
|
||||
tuple(all_items))
|
||||
|
||||
return stock_items
|
||||
|
||||
def get_return_doc_and_details(args):
|
||||
ref = frappe._dict()
|
||||
|
||||
# get ref_doc
|
||||
if args.get("purpose") in return_map:
|
||||
for fieldname, val in return_map[args.get("purpose")].items():
|
||||
if args.get(fieldname):
|
||||
ref.fieldname = fieldname
|
||||
ref.doc = frappe.get_doc(val[0], args.get(fieldname))
|
||||
ref.parentfields = val[1]
|
||||
break
|
||||
|
||||
return ref
|
||||
|
||||
return_map = {
|
||||
"Sales Return": {
|
||||
# [Ref DocType, [Item tables' parentfields]]
|
||||
"delivery_note_no": ["Delivery Note", ["items", "packed_items"]],
|
||||
"sales_invoice_no": ["Sales Invoice", ["items", "packed_items"]]
|
||||
},
|
||||
"Purchase Return": {
|
||||
"purchase_receipt_no": ["Purchase Receipt", ["items"]]
|
||||
}
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_return_jv(stock_entry):
|
||||
se = frappe.get_doc("Stock Entry", stock_entry)
|
||||
if not se.purpose in ["Sales Return", "Purchase Return"]:
|
||||
return
|
||||
|
||||
ref = get_return_doc_and_details(se)
|
||||
|
||||
if ref.doc.doctype == "Delivery Note":
|
||||
result = make_return_jv_from_delivery_note(se, ref)
|
||||
elif ref.doc.doctype == "Sales Invoice":
|
||||
result = make_return_jv_from_sales_invoice(se, ref)
|
||||
elif ref.doc.doctype == "Purchase Receipt":
|
||||
result = make_return_jv_from_purchase_receipt(se, ref)
|
||||
|
||||
# create jv doc and fetch balance for each unique row item
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.update({
|
||||
"posting_date": se.posting_date,
|
||||
"voucher_type": se.purpose == "Sales Return" and "Credit Note" or "Debit Note",
|
||||
"fiscal_year": se.fiscal_year,
|
||||
"company": se.company,
|
||||
"stock_entry": se.name
|
||||
})
|
||||
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
for r in result:
|
||||
jv.append("accounts", {
|
||||
"account": r.get("account"),
|
||||
"party_type": r.get("party_type"),
|
||||
"party": r.get("party"),
|
||||
"balance": get_balance_on(r.get("account"), se.posting_date) if r.get("account") else 0
|
||||
})
|
||||
|
||||
return jv
|
||||
|
||||
def make_return_jv_from_sales_invoice(se, ref):
|
||||
# customer account entry
|
||||
parent = {
|
||||
"account": ref.doc.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": ref.doc.customer
|
||||
}
|
||||
|
||||
# income account entries
|
||||
children = []
|
||||
for se_item in se.get("items"):
|
||||
# find item in ref.doc
|
||||
ref_item = ref.doc.get({"item_code": se_item.item_code})[0]
|
||||
|
||||
account = get_sales_account_from_item(ref.doc, ref_item)
|
||||
|
||||
if account not in children:
|
||||
children.append(account)
|
||||
|
||||
return [parent] + [{"account": account} for account in children]
|
||||
|
||||
def get_sales_account_from_item(doc, ref_item):
|
||||
account = None
|
||||
if not getattr(ref_item, "income_account", None):
|
||||
if ref_item.parent_item:
|
||||
parent_item = doc.get("items", {"item_code": ref_item.parent_item})[0]
|
||||
account = parent_item.income_account
|
||||
else:
|
||||
account = ref_item.income_account
|
||||
|
||||
return account
|
||||
|
||||
def make_return_jv_from_delivery_note(se, ref):
|
||||
invoices_against_delivery = get_invoice_list("Sales Invoice Item", "delivery_note",
|
||||
ref.doc.name)
|
||||
|
||||
if not invoices_against_delivery:
|
||||
sales_orders_against_delivery = [d.against_sales_order for d in ref.doc.get_all_children() if getattr(d, "against_sales_order", None)]
|
||||
|
||||
if sales_orders_against_delivery:
|
||||
invoices_against_delivery = get_invoice_list("Sales Invoice Item", "sales_order",
|
||||
sales_orders_against_delivery)
|
||||
|
||||
if not invoices_against_delivery:
|
||||
return []
|
||||
|
||||
packing_item_parent_map = dict([[d.item_code, d.parent_item] for d in ref.doc.get(ref.parentfields[1])])
|
||||
|
||||
parent = {}
|
||||
children = []
|
||||
|
||||
for se_item in se.get("items"):
|
||||
for sales_invoice in invoices_against_delivery:
|
||||
si = frappe.get_doc("Sales Invoice", sales_invoice)
|
||||
|
||||
if se_item.item_code in packing_item_parent_map:
|
||||
ref_item = si.get({"item_code": packing_item_parent_map[se_item.item_code]})
|
||||
else:
|
||||
ref_item = si.get({"item_code": se_item.item_code})
|
||||
|
||||
if not ref_item:
|
||||
continue
|
||||
|
||||
ref_item = ref_item[0]
|
||||
|
||||
account = get_sales_account_from_item(si, ref_item)
|
||||
|
||||
if account not in children:
|
||||
children.append(account)
|
||||
|
||||
if not parent:
|
||||
parent = {
|
||||
"account": si.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": si.customer
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
result = [parent] + [{"account": account} for account in children]
|
||||
|
||||
return result
|
||||
|
||||
def get_invoice_list(doctype, link_field, value):
|
||||
if isinstance(value, basestring):
|
||||
value = [value]
|
||||
|
||||
return frappe.db.sql_list("""select distinct parent from `tab%s`
|
||||
where docstatus = 1 and `%s` in (%s)""" % (doctype, link_field,
|
||||
", ".join(["%s"]*len(value))), tuple(value))
|
||||
|
||||
def make_return_jv_from_purchase_receipt(se, ref):
|
||||
invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_receipt",
|
||||
ref.doc.name)
|
||||
|
||||
if not invoice_against_receipt:
|
||||
purchase_orders_against_receipt = [d.prevdoc_docname for d in
|
||||
ref.doc.get("items", {"prevdoc_doctype": "Purchase Order"})
|
||||
if getattr(d, "prevdoc_docname", None)]
|
||||
|
||||
if purchase_orders_against_receipt:
|
||||
invoice_against_receipt = get_invoice_list("Purchase Invoice Item", "purchase_order",
|
||||
purchase_orders_against_receipt)
|
||||
|
||||
if not invoice_against_receipt:
|
||||
return []
|
||||
|
||||
parent = {}
|
||||
children = []
|
||||
|
||||
for se_item in se.get("items"):
|
||||
for purchase_invoice in invoice_against_receipt:
|
||||
pi = frappe.get_doc("Purchase Invoice", purchase_invoice)
|
||||
ref_item = pi.get({"item_code": se_item.item_code})
|
||||
|
||||
if not ref_item:
|
||||
continue
|
||||
|
||||
ref_item = ref_item[0]
|
||||
|
||||
account = ref_item.expense_account
|
||||
|
||||
if account not in children:
|
||||
children.append(account)
|
||||
|
||||
if not parent:
|
||||
parent = {
|
||||
"account": pi.credit_to,
|
||||
"party_type": "Supplier",
|
||||
"party": pi.supplier
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
result = [parent] + [{"account": account} for account in children]
|
||||
|
||||
return result
|
||||
|
@ -4,15 +4,12 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe, unittest
|
||||
import frappe.defaults
|
||||
from frappe.utils import flt, nowdate, nowtime, getdate
|
||||
from frappe.utils import flt, nowdate, nowtime
|
||||
from erpnext.stock.doctype.serial_no.serial_no import *
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
|
||||
import set_perpetual_inventory, make_purchase_receipt
|
||||
import set_perpetual_inventory
|
||||
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import make_return_jv, NotUpdateStockError
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
|
||||
|
||||
def get_sle(**args):
|
||||
@ -303,263 +300,6 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertEquals(expected_gl_entries[i][1], gle[1])
|
||||
self.assertEquals(expected_gl_entries[i][2], gle[2])
|
||||
|
||||
def _test_sales_invoice_return(self, item_code, delivered_qty, returned_qty):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
si = create_sales_invoice(item_code=item_code, qty=delivered_qty)
|
||||
|
||||
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=returned_qty,
|
||||
purpose="Sales Return", sales_invoice_no=si.name, do_not_save=True)
|
||||
self.assertRaises(NotUpdateStockError, se.insert)
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=200, incoming_rate=100)
|
||||
|
||||
# check currency available qty in bin
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
# insert a pos invoice with update stock
|
||||
si = create_sales_invoice(update_stock=1, item_code=item_code, qty=5)
|
||||
|
||||
# check available bin qty after invoice submission
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_0 - delivered_qty, actual_qty_1)
|
||||
|
||||
# check if item is validated
|
||||
se = make_stock_entry(item_code="_Test Item Home Desktop 200", target="_Test Warehouse - _TC",
|
||||
qty=returned_qty, purpose="Sales Return", sales_invoice_no=si.name, do_not_save=True)
|
||||
|
||||
self.assertRaises(frappe.DoesNotExistError, se.insert)
|
||||
|
||||
# try again
|
||||
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
|
||||
qty=returned_qty, purpose="Sales Return", sales_invoice_no=si.name)
|
||||
|
||||
# check if available qty is increased
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_1 + returned_qty, actual_qty_2)
|
||||
|
||||
return se
|
||||
|
||||
def test_sales_invoice_return_of_non_packing_item(self):
|
||||
self._test_sales_invoice_return("_Test Item", 5, 2)
|
||||
|
||||
def test_sales_invoice_return_of_packing_item(self):
|
||||
self._test_sales_invoice_return("_Test Product Bundle Item", 25, 20)
|
||||
|
||||
def _test_delivery_note_return(self, item_code, delivered_qty, returned_qty):
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, incoming_rate=100)
|
||||
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
# make a delivery note based on this invoice
|
||||
dn = create_delivery_note(item_code="_Test Item",
|
||||
warehouse="_Test Warehouse - _TC", qty=delivered_qty)
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_0 - delivered_qty, actual_qty_1)
|
||||
|
||||
si = make_sales_invoice(dn.name)
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
# insert and submit stock entry for sales return
|
||||
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
|
||||
qty=returned_qty, purpose="Sales Return", delivery_note_no=dn.name)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_1 + returned_qty, actual_qty_2)
|
||||
|
||||
return se
|
||||
|
||||
def test_delivery_note_return_of_non_packing_item(self):
|
||||
self._test_delivery_note_return("_Test Item", 5, 2)
|
||||
|
||||
def test_delivery_note_return_of_packing_item(self):
|
||||
self._test_delivery_note_return("_Test Product Bundle Item", 25, 20)
|
||||
|
||||
def _test_sales_return_jv(self, se):
|
||||
jv = make_return_jv(se.name)
|
||||
|
||||
self.assertEqual(len(jv.get("accounts")), 2)
|
||||
self.assertEqual(jv.get("voucher_type"), "Credit Note")
|
||||
self.assertEqual(jv.get("posting_date"), getdate(se.posting_date))
|
||||
self.assertEqual(jv.get("accounts")[0].get("account"), "Debtors - _TC")
|
||||
self.assertEqual(jv.get("accounts")[0].get("party_type"), "Customer")
|
||||
self.assertEqual(jv.get("accounts")[0].get("party"), "_Test Customer")
|
||||
self.assertEqual(jv.get("accounts")[1].get("account"), "Sales - _TC")
|
||||
|
||||
def test_make_return_jv_for_sales_invoice_non_packing_item(self):
|
||||
se = self._test_sales_invoice_return("_Test Item", 5, 2)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def test_make_return_jv_for_sales_invoice_packing_item(self):
|
||||
se = self._test_sales_invoice_return("_Test Product Bundle Item", 25, 20)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def test_make_return_jv_for_delivery_note_non_packing_item(self):
|
||||
se = self._test_delivery_note_return("_Test Item", 5, 2)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
se = self._test_delivery_note_return_against_sales_order("_Test Item", 5, 2)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def test_make_return_jv_for_delivery_note_packing_item(self):
|
||||
se = self._test_delivery_note_return("_Test Product Bundle Item", 25, 20)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
se = self._test_delivery_note_return_against_sales_order("_Test Product Bundle Item", 25, 20)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def _test_delivery_note_return_against_sales_order(self, item_code, delivered_qty, returned_qty):
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
so = make_sales_order(qty=50)
|
||||
|
||||
dn = create_dn_against_so(so.name, delivered_qty)
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_0 - delivered_qty, actual_qty_1)
|
||||
|
||||
si = make_sales_invoice(so.name)
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
# insert and submit stock entry for sales return
|
||||
se = make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC",
|
||||
qty=returned_qty, purpose="Sales Return", delivery_note_no=dn.name)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
self.assertEquals(actual_qty_1 + returned_qty, actual_qty_2)
|
||||
|
||||
return se
|
||||
|
||||
def test_purchase_receipt_return(self):
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
# submit purchase receipt
|
||||
pr = make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=5)
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_0 + 5, actual_qty_1)
|
||||
|
||||
pi_doc = make_purchase_invoice(pr.name)
|
||||
|
||||
pi = frappe.get_doc(pi_doc)
|
||||
pi.posting_date = pr.posting_date
|
||||
pi.credit_to = "_Test Payable - _TC"
|
||||
for d in pi.get("items"):
|
||||
d.expense_account = "_Test Account Cost for Goods Sold - _TC"
|
||||
d.cost_center = "_Test Cost Center - _TC"
|
||||
|
||||
for d in pi.get("taxes"):
|
||||
d.cost_center = "_Test Cost Center - _TC"
|
||||
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
# submit purchase return
|
||||
se = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC",
|
||||
qty=5, purpose="Purchase Return", purchase_receipt_no=pr.name)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_1 - 5, actual_qty_2)
|
||||
|
||||
return se, pr.name
|
||||
|
||||
def test_over_stock_return(self):
|
||||
from erpnext.stock.doctype.stock_entry.stock_entry import StockOverReturnError
|
||||
|
||||
# out of 10, 5 gets returned
|
||||
prev_se, pr_docname = self.test_purchase_receipt_return()
|
||||
|
||||
se = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC",
|
||||
qty=6, purpose="Purchase Return", purchase_receipt_no=pr_docname, do_not_save=True)
|
||||
|
||||
self.assertRaises(StockOverReturnError, se.insert)
|
||||
|
||||
def _test_purchase_return_jv(self, se):
|
||||
jv = make_return_jv(se.name)
|
||||
|
||||
self.assertEqual(len(jv.get("accounts")), 2)
|
||||
self.assertEqual(jv.get("voucher_type"), "Debit Note")
|
||||
self.assertEqual(jv.get("posting_date"), getdate(se.posting_date))
|
||||
self.assertEqual(jv.get("accounts")[0].get("account"), "_Test Payable - _TC")
|
||||
self.assertEqual(jv.get("accounts")[0].get("party"), "_Test Supplier")
|
||||
self.assertEqual(jv.get("accounts")[1].get("account"), "_Test Account Cost for Goods Sold - _TC")
|
||||
|
||||
def test_make_return_jv_for_purchase_receipt(self):
|
||||
se, pr_name = self.test_purchase_receipt_return()
|
||||
self._test_purchase_return_jv(se)
|
||||
|
||||
se, pr_name = self._test_purchase_return_return_against_purchase_order()
|
||||
self._test_purchase_return_jv(se)
|
||||
|
||||
def _test_purchase_return_return_against_purchase_order(self):
|
||||
|
||||
actual_qty_0 = get_qty_after_transaction()
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order \
|
||||
import test_records as purchase_order_test_records
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import \
|
||||
make_purchase_receipt, make_purchase_invoice
|
||||
|
||||
# submit purchase receipt
|
||||
po = frappe.copy_doc(purchase_order_test_records[0])
|
||||
po.transaction_date = nowdate()
|
||||
po.is_subcontracted = None
|
||||
po.get("items")[0].item_code = "_Test Item"
|
||||
po.get("items")[0].rate = 50
|
||||
po.insert()
|
||||
po.submit()
|
||||
|
||||
pr_doc = make_purchase_receipt(po.name)
|
||||
|
||||
pr = frappe.get_doc(pr_doc)
|
||||
pr.posting_date = po.transaction_date
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
actual_qty_1 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_0 + 10, actual_qty_1)
|
||||
|
||||
pi_doc = make_purchase_invoice(po.name)
|
||||
|
||||
pi = frappe.get_doc(pi_doc)
|
||||
pi.posting_date = pr.posting_date
|
||||
pi.credit_to = "_Test Payable - _TC"
|
||||
for d in pi.get("items"):
|
||||
d.expense_account = "_Test Account Cost for Goods Sold - _TC"
|
||||
d.cost_center = "_Test Cost Center - _TC"
|
||||
for d in pi.get("taxes"):
|
||||
d.cost_center = "_Test Cost Center - _TC"
|
||||
|
||||
pi.run_method("calculate_taxes_and_totals")
|
||||
pi.bill_no = "NA"
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
|
||||
# submit purchase return
|
||||
se = make_stock_entry(item_code="_Test Item", source="_Test Warehouse - _TC",
|
||||
qty=5, purpose="Purchase Return", purchase_receipt_no=pr.name)
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
|
||||
self.assertEquals(actual_qty_1 - 5, actual_qty_2)
|
||||
|
||||
return se, pr.name
|
||||
|
||||
def test_serial_no_not_reqd(self):
|
||||
se = frappe.copy_doc(test_records[0])
|
||||
se.get("items")[0].serial_no = "ABCD"
|
||||
|
@ -150,6 +150,15 @@
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "outgoing_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Outgoing Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
@ -266,7 +275,7 @@
|
||||
"icon": "icon-list",
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"modified": "2015-07-13 05:28:27.826340",
|
||||
"modified": "2015-07-16 16:37:54.452944",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Ledger Entry",
|
||||
|
@ -98,3 +98,11 @@ cur_frm.cscript.company = function(doc, cdt, cdn) {
|
||||
cur_frm.cscript.posting_date = function(doc, cdt, cdn){
|
||||
erpnext.get_fiscal_year(doc.company, doc.posting_date);
|
||||
}
|
||||
|
||||
cur_frm.fields_dict.items.grid.get_field('item_code').get_query = function(doc, cdt, cdn) {
|
||||
return {
|
||||
filters:[
|
||||
['Item', 'end_of_life', '>=', frappe.datetime.nowdate()]
|
||||
]
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.stock.utils import get_stock_balance
|
||||
|
||||
class OpeningEntryAccountError(frappe.ValidationError): pass
|
||||
class EmptyStockReconciliationItemsError(frappe.ValidationError): pass
|
||||
|
||||
class StockReconciliation(StockController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
@ -51,7 +52,11 @@ class StockReconciliation(StockController):
|
||||
|
||||
items = filter(lambda d: _changed(d), self.items)
|
||||
|
||||
if len(items) != len(self.items):
|
||||
if not items:
|
||||
frappe.throw(_("None of the items have any change in quantity or value."),
|
||||
EmptyStockReconciliationItemsError)
|
||||
|
||||
elif len(items) != len(self.items):
|
||||
self.items = items
|
||||
for i, item in enumerate(self.items):
|
||||
item.idx = i + 1
|
||||
|
@ -107,8 +107,6 @@ def create_stock_reconciliation(**args):
|
||||
"valuation_rate": args.rate
|
||||
})
|
||||
|
||||
sr.insert()
|
||||
|
||||
sr.submit()
|
||||
return sr
|
||||
|
||||
|
@ -9,8 +9,13 @@ def execute(filters=None):
|
||||
columns = get_columns()
|
||||
sl_entries = get_stock_ledger_entries(filters)
|
||||
item_details = get_item_details(filters)
|
||||
|
||||
opening_row = get_opening_balance(filters, columns)
|
||||
|
||||
data = []
|
||||
|
||||
if opening_row:
|
||||
data.append(opening_row)
|
||||
|
||||
for sle in sl_entries:
|
||||
item_detail = item_details[sle.item_code]
|
||||
|
||||
@ -20,7 +25,7 @@ def execute(filters=None):
|
||||
(sle.incoming_rate if sle.actual_qty > 0 else 0.0),
|
||||
sle.valuation_rate, sle.stock_value, sle.voucher_type, sle.voucher_no,
|
||||
sle.batch_no, sle.serial_no, sle.company])
|
||||
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_columns():
|
||||
@ -40,7 +45,7 @@ def get_stock_ledger_entries(filters):
|
||||
where company = %(company)s and
|
||||
posting_date between %(from_date)s and %(to_date)s
|
||||
{sle_conditions}
|
||||
order by posting_date desc, posting_time desc, name desc"""\
|
||||
order by posting_date asc, posting_time asc, name asc"""\
|
||||
.format(sle_conditions=get_sle_conditions(filters)), filters, as_dict=1)
|
||||
|
||||
def get_item_details(filters):
|
||||
@ -73,3 +78,22 @@ def get_sle_conditions(filters):
|
||||
conditions.append("voucher_no=%(voucher_no)s")
|
||||
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
def get_opening_balance(filters, columns):
|
||||
if not (filters.item_code and filters.warehouse and filters.from_date):
|
||||
return
|
||||
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
last_entry = get_previous_sle({
|
||||
"item_code": filters.item_code,
|
||||
"warehouse": filters.warehouse,
|
||||
"posting_date": filters.from_date,
|
||||
"posting_time": "00:00:00"
|
||||
})
|
||||
|
||||
row = [""]*len(columns)
|
||||
row[1] = _("'Opening'")
|
||||
for i, v in ((9, 'qty_after_transaction'), (11, 'valuation_rate'), (12, 'stock_value')):
|
||||
row[i] = last_entry.get(v, 0)
|
||||
|
||||
return row
|
@ -109,7 +109,7 @@ class update_entries_after(object):
|
||||
def build(self):
|
||||
# includes current entry!
|
||||
entries_to_fix = self.get_sle_after_datetime()
|
||||
|
||||
|
||||
for sle in entries_to_fix:
|
||||
self.process_sle(sle)
|
||||
|
||||
@ -230,19 +230,21 @@ class update_entries_after(object):
|
||||
self.valuation_rate = new_stock_value / new_stock_qty
|
||||
|
||||
def get_moving_average_values(self, sle):
|
||||
incoming_rate = flt(sle.incoming_rate)
|
||||
actual_qty = flt(sle.actual_qty)
|
||||
|
||||
if flt(sle.actual_qty) > 0:
|
||||
|
||||
if actual_qty > 0 or flt(sle.outgoing_rate) > 0:
|
||||
rate = flt(sle.incoming_rate) if actual_qty > 0 else flt(sle.outgoing_rate)
|
||||
|
||||
if self.qty_after_transaction < 0 and not self.valuation_rate:
|
||||
# if negative stock, take current valuation rate as incoming rate
|
||||
self.valuation_rate = incoming_rate
|
||||
self.valuation_rate = rate
|
||||
|
||||
new_stock_qty = abs(self.qty_after_transaction) + actual_qty
|
||||
new_stock_value = (abs(self.qty_after_transaction) * self.valuation_rate) + (actual_qty * incoming_rate)
|
||||
new_stock_value = (abs(self.qty_after_transaction) * self.valuation_rate) + (actual_qty * rate)
|
||||
|
||||
if new_stock_qty:
|
||||
self.valuation_rate = new_stock_value / flt(new_stock_qty)
|
||||
|
||||
elif not self.valuation_rate and self.qty_after_transaction <= 0:
|
||||
self.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, self.allow_zero_rate)
|
||||
|
||||
@ -251,6 +253,7 @@ class update_entries_after(object):
|
||||
def get_fifo_values(self, sle):
|
||||
incoming_rate = flt(sle.incoming_rate)
|
||||
actual_qty = flt(sle.actual_qty)
|
||||
outgoing_rate = flt(sle.outgoing_rate)
|
||||
|
||||
if actual_qty > 0:
|
||||
if not self.stock_queue:
|
||||
@ -278,16 +281,34 @@ class update_entries_after(object):
|
||||
_rate = 0
|
||||
self.stock_queue.append([0, _rate])
|
||||
|
||||
batch = self.stock_queue[0]
|
||||
index = None
|
||||
if outgoing_rate > 0:
|
||||
# Find the entry where rate matched with outgoing rate
|
||||
for i, v in enumerate(self.stock_queue):
|
||||
if v[1] == outgoing_rate:
|
||||
index = i
|
||||
break
|
||||
|
||||
# If no entry found with outgoing rate, collapse stack
|
||||
if index == None:
|
||||
new_stock_value = sum((d[0]*d[1] for d in self.stock_queue)) - qty_to_pop*outgoing_rate
|
||||
new_stock_qty = sum((d[0] for d in self.stock_queue)) - qty_to_pop
|
||||
self.stock_queue = [[new_stock_qty, new_stock_value/new_stock_qty if new_stock_qty > 0 else outgoing_rate]]
|
||||
break
|
||||
else:
|
||||
index = 0
|
||||
|
||||
# select first batch or the batch with same rate
|
||||
batch = self.stock_queue[index]
|
||||
|
||||
if qty_to_pop >= batch[0]:
|
||||
# consume current batch
|
||||
qty_to_pop = qty_to_pop - batch[0]
|
||||
self.stock_queue.pop(0)
|
||||
self.stock_queue.pop(index)
|
||||
if not self.stock_queue and qty_to_pop:
|
||||
# stock finished, qty still remains to be withdrawn
|
||||
# negative stock, keep in as a negative batch
|
||||
self.stock_queue.append([-qty_to_pop, batch[1]])
|
||||
self.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]])
|
||||
break
|
||||
|
||||
else:
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% macro product_image_square(website_image, css_class="") %}
|
||||
<div class="product-image product-image-square {% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
|
||||
{% if website_image -%} style="background-image: url('{{ frappe.utils.quoted(website_image) }}');" {%- endif %}>
|
||||
{% if website_image -%} style="background-image: url('{{ frappe.utils.quoted(website_image) | abs_url }}');" {%- endif %}>
|
||||
{% if not website_image -%}<i class="centered octicon octicon-device-camera"></i>{%- endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@ -8,7 +8,7 @@
|
||||
{% macro product_image(website_image, css_class="") %}
|
||||
<div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
|
||||
{% if website_image -%}
|
||||
<img src="{{ frappe.utils.quoted(website_image) }}" class="img-responsive">
|
||||
<img src="{{ frappe.utils.quoted(website_image) | abs_url }}" class="img-responsive">
|
||||
{%- else -%}
|
||||
<i class="centered octicon octicon-device-camera"></i>
|
||||
{%- endif %}
|
||||
|
@ -1,32 +1,58 @@
|
||||
{
|
||||
"autoname": "SMSLOG/.########",
|
||||
"creation": "2012-03-27 14:36:47.000000",
|
||||
"creation": "2012-03-27 14:36:47",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "sender_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Sender Name",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sent_on",
|
||||
"fieldtype": "Date",
|
||||
"label": "Sent On",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "receiver_list",
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Receiver List",
|
||||
"permlevel": 0
|
||||
"label": "Message",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sec_break1",
|
||||
"fieldtype": "Section Break",
|
||||
"options": "Simple",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_requested_sms",
|
||||
"fieldtype": "Int",
|
||||
"label": "No of Requested SMS",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "requested_numbers",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Requested Numbers",
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
@ -34,28 +60,25 @@
|
||||
"permlevel": 0,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_requested_sms",
|
||||
"fieldtype": "Int",
|
||||
"label": "No of Requested SMS",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_sent_sms",
|
||||
"fieldtype": "Int",
|
||||
"label": "No of Sent SMS",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "message",
|
||||
"fieldname": "sent_to",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Message",
|
||||
"permlevel": 0
|
||||
"label": "Sent To",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "icon-mobile-phone",
|
||||
"idx": 1,
|
||||
"modified": "2013-12-20 19:24:35.000000",
|
||||
"modified": "2015-07-22 11:53:25.998578",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Utilities",
|
||||
"name": "SMS Log",
|
||||
|
@ -3,12 +3,12 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.share
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, now_datetime, cint, flt
|
||||
import frappe.share
|
||||
|
||||
from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class UOMMustBeIntegerError(frappe.ValidationError): pass
|
||||
|
||||
class TransactionBase(StatusUpdater):
|
||||
def load_notification_message(self):
|
||||
@ -109,8 +109,6 @@ def delete_events(ref_type, ref_name):
|
||||
frappe.delete_doc("Event", frappe.db.sql_list("""select name from `tabEvent`
|
||||
where ref_type=%s and ref_name=%s""", (ref_type, ref_name)), for_reload=True)
|
||||
|
||||
class UOMMustBeIntegerError(frappe.ValidationError): pass
|
||||
|
||||
def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
|
||||
if isinstance(qty_fields, basestring):
|
||||
qty_fields = [qty_fields]
|
||||
|
Loading…
x
Reference in New Issue
Block a user