mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-07 07:02:54 +00:00
Merge branch 'develop'
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Questions
|
## Questions
|
||||||
|
|
||||||
If you have questions on how to use ERPNext or want help in customization or debugging of your scripts, please post on https://discuss.frappe.io. This is only for bug reports and feature requests.
|
If you have questions on how to use ERPNext or want help in customization or debugging of your scripts, please post on https://discuss.erpnext.com. This is only for bug reports and feature requests.
|
||||||
|
|
||||||
## Reporting issues
|
## Reporting issues
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a
|
|||||||
|
|
||||||
- [User Guide](https://manual.erpnext.com)
|
- [User Guide](https://manual.erpnext.com)
|
||||||
- [Getting Help](http://erpnext.org/getting-help.html)
|
- [Getting Help](http://erpnext.org/getting-help.html)
|
||||||
- [Discussion Forum](https://discuss.frappe.io/)
|
- [Discussion Forum](https://discuss.erpnext.com/)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
__version__ = '5.4.2'
|
__version__ = '5.5.0'
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class Account(Document):
|
|||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_parent()
|
self.validate_parent()
|
||||||
self.validate_root_details()
|
self.validate_root_details()
|
||||||
|
self.set_root_and_report_type()
|
||||||
self.validate_mandatory()
|
self.validate_mandatory()
|
||||||
self.validate_warehouse_account()
|
self.validate_warehouse_account()
|
||||||
self.validate_frozen_accounts_modifier()
|
self.validate_frozen_accounts_modifier()
|
||||||
@@ -32,7 +33,7 @@ class Account(Document):
|
|||||||
"""Fetch Parent Details and validate parent account"""
|
"""Fetch Parent Details and validate parent account"""
|
||||||
if self.parent_account:
|
if self.parent_account:
|
||||||
par = frappe.db.get_value("Account", self.parent_account,
|
par = frappe.db.get_value("Account", self.parent_account,
|
||||||
["name", "is_group", "report_type", "root_type", "company"], as_dict=1)
|
["name", "is_group", "company"], as_dict=1)
|
||||||
if not par:
|
if not par:
|
||||||
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
|
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
|
||||||
elif par.name == self.name:
|
elif par.name == self.name:
|
||||||
@@ -43,11 +44,25 @@ class Account(Document):
|
|||||||
throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
|
throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
|
||||||
.format(self.name, self.parent_account, self.company))
|
.format(self.name, self.parent_account, self.company))
|
||||||
|
|
||||||
|
def set_root_and_report_type(self):
|
||||||
|
if self.parent_account:
|
||||||
|
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1)
|
||||||
|
|
||||||
if par.report_type:
|
if par.report_type:
|
||||||
self.report_type = par.report_type
|
self.report_type = par.report_type
|
||||||
if par.root_type:
|
if par.root_type:
|
||||||
self.root_type = par.root_type
|
self.root_type = par.root_type
|
||||||
|
|
||||||
|
if self.is_group:
|
||||||
|
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
|
||||||
|
if db_value:
|
||||||
|
if self.report_type != db_value.report_type:
|
||||||
|
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
|
||||||
|
(self.report_type, self.lft, self.rgt))
|
||||||
|
if self.root_type != db_value.root_type:
|
||||||
|
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
|
||||||
|
(self.root_type, self.lft, self.rgt))
|
||||||
|
|
||||||
def validate_root_details(self):
|
def validate_root_details(self):
|
||||||
# does not exists parent
|
# does not exists parent
|
||||||
if frappe.db.exists("Account", self.name):
|
if frappe.db.exists("Account", self.name):
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
|
|||||||
filters: [
|
filters: [
|
||||||
[opts[1], opts[2], "=", jvd.party],
|
[opts[1], opts[2], "=", jvd.party],
|
||||||
[opts[1], "docstatus", "=", 1],
|
[opts[1], "docstatus", "=", 1],
|
||||||
[opts[1], "outstanding_amount", ">", 0]
|
[opts[1], "outstanding_amount", "!=", 0]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -156,12 +156,10 @@ class JournalEntry(AccountsController):
|
|||||||
.format(d.against_jv, dr_or_cr))
|
.format(d.against_jv, dr_or_cr))
|
||||||
|
|
||||||
def validate_against_sales_invoice(self):
|
def validate_against_sales_invoice(self):
|
||||||
payment_against_voucher = self.validate_account_in_against_voucher("against_invoice", "Sales Invoice")
|
self.validate_account_in_against_voucher("against_invoice", "Sales Invoice")
|
||||||
self.validate_against_invoice_fields("Sales Invoice", payment_against_voucher)
|
|
||||||
|
|
||||||
def validate_against_purchase_invoice(self):
|
def validate_against_purchase_invoice(self):
|
||||||
payment_against_voucher = self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice")
|
self.validate_account_in_against_voucher("against_voucher", "Purchase Invoice")
|
||||||
self.validate_against_invoice_fields("Purchase Invoice", payment_against_voucher)
|
|
||||||
|
|
||||||
def validate_against_sales_order(self):
|
def validate_against_sales_order(self):
|
||||||
payment_against_voucher = self.validate_account_in_against_voucher("against_sales_order", "Sales Order")
|
payment_against_voucher = self.validate_account_in_against_voucher("against_sales_order", "Sales Order")
|
||||||
@@ -183,10 +181,10 @@ class JournalEntry(AccountsController):
|
|||||||
if d.get(against_field):
|
if d.get(against_field):
|
||||||
dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \
|
dr_or_cr = "credit" if against_field in ["against_invoice", "against_sales_order"] \
|
||||||
else "debit"
|
else "debit"
|
||||||
if against_field in ["against_invoice", "against_sales_order"] and flt(d.debit) > 0:
|
if against_field == "against_sales_order" and flt(d.debit) > 0:
|
||||||
frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, doctype))
|
frappe.throw(_("Row {0}: Debit entry can not be linked with a {1}").format(d.idx, doctype))
|
||||||
|
|
||||||
if against_field in ["against_voucher", "against_purchase_order"] and flt(d.credit) > 0:
|
if against_field == "against_purchase_order" and flt(d.credit) > 0:
|
||||||
frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, doctype))
|
frappe.throw(_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, doctype))
|
||||||
|
|
||||||
against_voucher = frappe.db.get_value(doctype, d.get(against_field),
|
against_voucher = frappe.db.get_value(doctype, d.get(against_field),
|
||||||
@@ -555,18 +553,18 @@ def get_outstanding(args):
|
|||||||
and ifnull(against_jv, '')=''""".format(condition), args)
|
and ifnull(against_jv, '')=''""".format(condition), args)
|
||||||
|
|
||||||
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
|
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
|
||||||
if against_jv_amount > 0:
|
|
||||||
return {"credit": against_jv_amount}
|
|
||||||
else:
|
|
||||||
return {"debit": -1* against_jv_amount}
|
|
||||||
|
|
||||||
elif args.get("doctype") == "Sales Invoice":
|
|
||||||
return {
|
return {
|
||||||
"credit": flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount"))
|
("credit" if against_jv_amount > 0 else "debit"): abs(against_jv_amount)
|
||||||
|
}
|
||||||
|
elif args.get("doctype") == "Sales Invoice":
|
||||||
|
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", args["docname"], "outstanding_amount"))
|
||||||
|
return {
|
||||||
|
("credit" if outstanding_amount > 0 else "debit"): abs(outstanding_amount)
|
||||||
}
|
}
|
||||||
elif args.get("doctype") == "Purchase Invoice":
|
elif args.get("doctype") == "Purchase Invoice":
|
||||||
|
outstanding_amount = flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount"))
|
||||||
return {
|
return {
|
||||||
"debit": flt(frappe.db.get_value("Purchase Invoice", args["docname"], "outstanding_amount"))
|
("debit" if outstanding_amount > 0 else "credit"): abs(outstanding_amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -192,9 +192,9 @@ class PaymentReconciliation(Document):
|
|||||||
.format(p.idx, p.allocated_amount, p.amount))
|
.format(p.idx, p.allocated_amount, p.amount))
|
||||||
|
|
||||||
invoice_outstanding = unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)
|
invoice_outstanding = unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)
|
||||||
if abs(flt(p.allocated_amount) - invoice_outstanding) > 0.009:
|
if flt(p.allocated_amount) - invoice_outstanding > 0.009:
|
||||||
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
|
frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
|
||||||
.format(p.idx, p.allocated_amount, unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)))
|
.format(p.idx, p.allocated_amount, invoice_outstanding))
|
||||||
|
|
||||||
if not invoices_to_reconcile:
|
if not invoices_to_reconcile:
|
||||||
frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
|
frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ class SalesInvoice(SellingController):
|
|||||||
if gl_entries:
|
if gl_entries:
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
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
|
# if POS and amount is written off, updating outstanding amt after posting all gl entries
|
||||||
update_outstanding = "No" if (cint(self.is_pos) or self.write_off_account) else "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),
|
make_gl_entries(gl_entries, cancel=(self.docstatus == 2),
|
||||||
@@ -447,7 +447,8 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if update_outstanding == "No":
|
if update_outstanding == "No":
|
||||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||||
update_outstanding_amt(self.debit_to, "Customer", self.customer, self.doctype, self.name)
|
update_outstanding_amt(self.debit_to, "Customer", self.customer,
|
||||||
|
self.doctype, self.return_against if cint(self.is_return) else self.name)
|
||||||
|
|
||||||
if repost_future_gle and cint(self.update_stock) \
|
if repost_future_gle and cint(self.update_stock) \
|
||||||
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
|
and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
|
||||||
|
|||||||
@@ -790,6 +790,58 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
|
|
||||||
set_perpetual_inventory(0)
|
set_perpetual_inventory(0)
|
||||||
|
|
||||||
|
def test_discount_on_net_total(self):
|
||||||
|
si = frappe.copy_doc(test_records[2])
|
||||||
|
si.apply_discount_on = "Net Total"
|
||||||
|
si.discount_amount = 625
|
||||||
|
si.insert()
|
||||||
|
|
||||||
|
expected_values = {
|
||||||
|
"keys": ["price_list_rate", "discount_percentage", "rate", "amount",
|
||||||
|
"base_price_list_rate", "base_rate", "base_amount",
|
||||||
|
"net_rate", "base_net_rate", "net_amount", "base_net_amount"],
|
||||||
|
"_Test Item Home Desktop 100": [50, 0, 50, 500, 50, 50, 500, 25, 25, 250, 250],
|
||||||
|
"_Test Item Home Desktop 200": [150, 0, 150, 750, 150, 150, 750, 75, 75, 375, 375],
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if children are saved
|
||||||
|
self.assertEquals(len(si.get("items")),
|
||||||
|
len(expected_values)-1)
|
||||||
|
|
||||||
|
# check if item values are calculated
|
||||||
|
for d in si.get("items"):
|
||||||
|
for i, k in enumerate(expected_values["keys"]):
|
||||||
|
self.assertEquals(d.get(k), expected_values[d.item_code][i])
|
||||||
|
|
||||||
|
# check net total
|
||||||
|
self.assertEquals(si.base_total, 1250)
|
||||||
|
self.assertEquals(si.total, 1250)
|
||||||
|
self.assertEquals(si.base_net_total, 625)
|
||||||
|
self.assertEquals(si.net_total, 625)
|
||||||
|
|
||||||
|
# check tax calculation
|
||||||
|
expected_values = {
|
||||||
|
"keys": ["tax_amount", "tax_amount_after_discount_amount",
|
||||||
|
"base_tax_amount_after_discount_amount"],
|
||||||
|
"_Test Account Shipping Charges - _TC": [100, 100, 100],
|
||||||
|
"_Test Account Customs Duty - _TC": [62.5, 62.5, 62.5],
|
||||||
|
"_Test Account Excise Duty - _TC": [70, 70, 70],
|
||||||
|
"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
|
||||||
|
"_Test Account S&H Education Cess - _TC": [.7, 0.7, 0.7],
|
||||||
|
"_Test Account CST - _TC": [17.2, 17.2, 17.2],
|
||||||
|
"_Test Account VAT - _TC": [78.13, 78.13, 78.13],
|
||||||
|
"_Test Account Discount - _TC": [-95.49, -95.49, -95.49]
|
||||||
|
}
|
||||||
|
|
||||||
|
for d in si.get("taxes"):
|
||||||
|
for i, k in enumerate(expected_values["keys"]):
|
||||||
|
self.assertEquals(d.get(k), expected_values[d.account_head][i])
|
||||||
|
|
||||||
|
|
||||||
|
self.assertEquals(si.total_taxes_and_charges, 234.44)
|
||||||
|
self.assertEquals(si.base_grand_total, 859.44)
|
||||||
|
self.assertEquals(si.grand_total, 859.44)
|
||||||
|
|
||||||
|
|
||||||
def create_sales_invoice(**args):
|
def create_sales_invoice(**args):
|
||||||
si = frappe.new_doc("Sales Invoice")
|
si = frappe.new_doc("Sales Invoice")
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ def validate_filters(filters):
|
|||||||
|
|
||||||
def get_columns(filters):
|
def get_columns(filters):
|
||||||
return [_("Journal Entry") + ":Link/Journal Entry:140",
|
return [_("Journal Entry") + ":Link/Journal Entry:140",
|
||||||
_("Party Type") + ":Link/DocType:100", _("Party") + ":Dynamic Link/Party Type:140",
|
_("Party Type") + "::100", _("Party") + ":Dynamic Link/Party Type:140",
|
||||||
_("Posting Date") + ":Date:100",
|
_("Posting Date") + ":Date:100",
|
||||||
_("Against Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"),
|
_("Against Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"),
|
||||||
_("Against Invoice Posting Date") + ":Date:130", _("Debit") + ":Currency:120", _("Credit") + ":Currency:120",
|
_("Against Invoice Posting Date") + ":Date:130", _("Debit") + ":Currency:120", _("Credit") + ":Currency:120",
|
||||||
|
|||||||
2
erpnext/change_log/v5/v5_5_0.md
Normal file
2
erpnext/change_log/v5/v5_5_0.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
- Automatically insert Price List Rate in Price List if added in transaction if permission exists and allowed from Stock Settings
|
||||||
|
- Product Bundle now allowed for all Items (stock or non-stock)
|
||||||
@@ -11,6 +11,8 @@ from erpnext.utilities.transaction_base import TransactionBase
|
|||||||
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
||||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||||
|
|
||||||
|
force_item_fields = ("item_name", "item_group", "barcode", "brand", "stock_uom")
|
||||||
|
|
||||||
class CustomerFrozen(frappe.ValidationError): pass
|
class CustomerFrozen(frappe.ValidationError): pass
|
||||||
|
|
||||||
class AccountsController(TransactionBase):
|
class AccountsController(TransactionBase):
|
||||||
@@ -142,7 +144,8 @@ class AccountsController(TransactionBase):
|
|||||||
|
|
||||||
for fieldname, value in ret.items():
|
for fieldname, value in ret.items():
|
||||||
if item.meta.get_field(fieldname) and \
|
if item.meta.get_field(fieldname) and \
|
||||||
item.get(fieldname) is None and value is not None:
|
(item.get(fieldname) is None or fieldname in force_item_fields) \
|
||||||
|
and value is not None:
|
||||||
item.set(fieldname, value)
|
item.set(fieldname, value)
|
||||||
|
|
||||||
if fieldname == "cost_center" and item.meta.get_field("cost_center") \
|
if fieldname == "cost_center" and item.meta.get_field("cost_center") \
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from frappe.utils import flt, get_datetime, format_datetime
|
|||||||
|
|
||||||
class StockOverReturnError(frappe.ValidationError): pass
|
class StockOverReturnError(frappe.ValidationError): pass
|
||||||
|
|
||||||
|
|
||||||
def validate_return(doc):
|
def validate_return(doc):
|
||||||
if not doc.meta.get_field("is_return") or not doc.is_return:
|
if not doc.meta.get_field("is_return") or not doc.is_return:
|
||||||
return
|
return
|
||||||
@@ -50,13 +49,19 @@ def validate_return_against(doc):
|
|||||||
.format(doc.return_against))
|
.format(doc.return_against))
|
||||||
|
|
||||||
def validate_returned_items(doc):
|
def validate_returned_items(doc):
|
||||||
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
valid_items = frappe._dict()
|
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):
|
select_fields = "item_code, sum(qty) as qty, rate" if doc.doctype=="Purchase Invoice" \
|
||||||
|
else "item_code, sum(qty) as qty, rate, serial_no, batch_no"
|
||||||
|
|
||||||
|
for d in frappe.db.sql("""select {0} from `tab{1} Item` where parent = %s
|
||||||
|
group by item_code""".format(select_fields, doc.doctype), doc.return_against, as_dict=1):
|
||||||
valid_items.setdefault(d.item_code, d)
|
valid_items.setdefault(d.item_code, d)
|
||||||
|
|
||||||
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
if doc.doctype in ("Delivery Note", "Sales Invoice"):
|
||||||
for d in frappe.db.sql("""select item_code, sum(qty) as qty from `tabPacked Item`
|
for d in frappe.db.sql("""select item_code, sum(qty) as qty, serial_no, batch_no from `tabPacked Item`
|
||||||
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
where parent = %s group by item_code""".format(doc.doctype), doc.return_against, as_dict=1):
|
||||||
valid_items.setdefault(d.item_code, d)
|
valid_items.setdefault(d.item_code, d)
|
||||||
|
|
||||||
@@ -81,7 +86,19 @@ def validate_returned_items(doc):
|
|||||||
elif ref.rate and flt(d.rate) != ref.rate:
|
elif ref.rate and flt(d.rate) != ref.rate:
|
||||||
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
|
frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
|
||||||
.format(d.idx, doc.doctype, doc.return_against))
|
.format(d.idx, doc.doctype, doc.return_against))
|
||||||
|
elif ref.batch_no and d.batch_no != ref.batch_no:
|
||||||
|
frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
|
||||||
|
.format(d.idx, doc.doctype, doc.return_against))
|
||||||
|
elif ref.serial_no:
|
||||||
|
if not d.serial_no:
|
||||||
|
frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
|
||||||
|
else:
|
||||||
|
serial_nos = get_serial_nos(d.serial_no)
|
||||||
|
ref_serial_nos = get_serial_nos(ref.serial_no)
|
||||||
|
for s in serial_nos:
|
||||||
|
if s not in ref_serial_nos:
|
||||||
|
frappe.throw(_("Row # {0}: Serial No {1} does not match with {2} {3}")
|
||||||
|
.format(d.idx, s, doc.doctype, doc.return_against))
|
||||||
|
|
||||||
items_returned = True
|
items_returned = True
|
||||||
|
|
||||||
@@ -134,9 +151,11 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
},
|
},
|
||||||
doctype +" Item": {
|
doctype +" Item": {
|
||||||
"doctype": doctype + " Item",
|
"doctype": doctype + " Item",
|
||||||
"fields": {
|
"field_map": {
|
||||||
"purchase_order": "purchase_order",
|
"purchase_order": "purchase_order",
|
||||||
"purchase_receipt": "purchase_receipt"
|
"purchase_receipt": "purchase_receipt",
|
||||||
|
"serial_no": "serial_no",
|
||||||
|
"batch_no": "batch_no"
|
||||||
},
|
},
|
||||||
"postprocess": update_item
|
"postprocess": update_item
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ class StockController(AccountsController):
|
|||||||
if d['reserved_qty'] < 0 :
|
if d['reserved_qty'] < 0 :
|
||||||
# Reduce reserved qty from reserved warehouse mentioned in so
|
# Reduce reserved qty from reserved warehouse mentioned in so
|
||||||
if not d["reserved_warehouse"]:
|
if not d["reserved_warehouse"]:
|
||||||
frappe.throw(_("Reserved Warehouse is missing in Sales Order"))
|
frappe.throw(_("Delivery Warehouse is missing in Sales Order"))
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
"item_code": d['item_code'],
|
"item_code": d['item_code'],
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ class calculate_taxes_and_totals(object):
|
|||||||
|
|
||||||
# adjust Discount Amount loss in last tax iteration
|
# adjust Discount Amount loss in last tax iteration
|
||||||
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
|
||||||
and self.doc.discount_amount:
|
and self.doc.discount_amount and self.doc.apply_discount_on == "Grand Total":
|
||||||
self.adjust_discount_amount_loss(tax)
|
self.adjust_discount_amount_loss(tax)
|
||||||
|
|
||||||
|
|
||||||
@@ -303,9 +303,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
for tax in self.doc.get("taxes"):
|
for tax in self.doc.get("taxes"):
|
||||||
if tax.category in ["Valuation and Total", "Total"]:
|
if tax.category in ["Valuation and Total", "Total"]:
|
||||||
if tax.add_deduct_tax == "Add":
|
if tax.add_deduct_tax == "Add":
|
||||||
self.doc.taxes_and_charges_added += flt(tax.tax_amount)
|
self.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount)
|
||||||
else:
|
else:
|
||||||
self.doc.taxes_and_charges_deducted += flt(tax.tax_amount)
|
self.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount)
|
||||||
|
|
||||||
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
self.doc.round_floats_in(self.doc, ["taxes_and_charges_added", "taxes_and_charges_deducted"])
|
||||||
|
|
||||||
|
|||||||
@@ -34,10 +34,8 @@ class NewsletterList(Document):
|
|||||||
}).insert(ignore_permissions=True)
|
}).insert(ignore_permissions=True)
|
||||||
|
|
||||||
added += 1
|
added += 1
|
||||||
except Exception, e:
|
except frappe.UniqueValidationError:
|
||||||
# already added, ignore
|
pass
|
||||||
if e.args[0]!=1062:
|
|
||||||
raise
|
|
||||||
|
|
||||||
frappe.msgprint(_("{0} subscribers added").format(added))
|
frappe.msgprint(_("{0} subscribers added").format(added))
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ blogs.
|
|||||||
"""
|
"""
|
||||||
app_icon = "icon-th"
|
app_icon = "icon-th"
|
||||||
app_color = "#e74c3c"
|
app_color = "#e74c3c"
|
||||||
app_version = "5.4.2"
|
app_version = "5.5.0"
|
||||||
github_link = "https://github.com/frappe/erpnext"
|
github_link = "https://github.com/frappe/erpnext"
|
||||||
|
|
||||||
error_report_email = "support@erpnext.com"
|
error_report_email = "support@erpnext.com"
|
||||||
|
|||||||
@@ -44,31 +44,35 @@ class ProductionPlanningTool(Document):
|
|||||||
""" Pull sales orders which are pending to deliver based on criteria selected"""
|
""" Pull sales orders which are pending to deliver based on criteria selected"""
|
||||||
so_filter = item_filter = ""
|
so_filter = item_filter = ""
|
||||||
if self.from_date:
|
if self.from_date:
|
||||||
so_filter += ' and so.transaction_date >= "' + self.from_date + '"'
|
so_filter += " and so.transaction_date >= %(from_date)s"
|
||||||
if self.to_date:
|
if self.to_date:
|
||||||
so_filter += ' and so.transaction_date <= "' + self.to_date + '"'
|
so_filter += " and so.transaction_date <= %(to_date)s"
|
||||||
if self.customer:
|
if self.customer:
|
||||||
so_filter += ' and so.customer = "' + self.customer + '"'
|
so_filter += " and so.customer = %(customer)s"
|
||||||
|
|
||||||
if self.fg_item:
|
if self.fg_item:
|
||||||
item_filter += ' and item.name = "' + self.fg_item + '"'
|
item_filter += " and item.name = %(item)s"
|
||||||
|
|
||||||
open_so = frappe.db.sql("""
|
open_so = frappe.db.sql("""
|
||||||
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
|
select distinct so.name, so.transaction_date, so.customer, so.base_grand_total
|
||||||
from `tabSales Order` so, `tabSales Order Item` so_item
|
from `tabSales Order` so, `tabSales Order Item` so_item
|
||||||
where so_item.parent = so.name
|
where so_item.parent = so.name
|
||||||
and so.docstatus = 1 and so.status != "Stopped"
|
and so.docstatus = 1 and so.status != "Stopped"
|
||||||
and so.company = %s
|
and so.company = %(company)s
|
||||||
and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) %s
|
and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) {0}
|
||||||
and (exists (select name from `tabItem` item where item.name=so_item.item_code
|
and (exists (select name from `tabItem` item where item.name=so_item.item_code
|
||||||
and (item.is_pro_applicable = 1
|
and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1 {1}))
|
||||||
or item.is_sub_contracted_item = 1 %s)
|
|
||||||
or exists (select name from `tabPacked Item` pi
|
or exists (select name from `tabPacked Item` pi
|
||||||
where pi.parent = so.name and pi.parent_item = so_item.item_code
|
where pi.parent = so.name and pi.parent_item = so_item.item_code
|
||||||
and exists (select name from `tabItem` item where item.name=pi.item_code
|
and exists (select name from `tabItem` item where item.name=pi.item_code
|
||||||
and (item.is_pro_applicable = 1
|
and (item.is_pro_applicable = 1 or item.is_sub_contracted_item = 1) {2})))
|
||||||
or item.is_sub_contracted_item = 1) %s)))
|
""".format(so_filter, item_filter, item_filter), {
|
||||||
""" % ('%s', so_filter, item_filter, item_filter), self.company, as_dict=1)
|
"from_date": self.from_date,
|
||||||
|
"to_date": self.to_date,
|
||||||
|
"customer": self.customer,
|
||||||
|
"item": self.fg_item,
|
||||||
|
"company": self.company
|
||||||
|
}, as_dict=1)
|
||||||
|
|
||||||
self.add_so_in_table(open_so)
|
self.add_so_in_table(open_so)
|
||||||
|
|
||||||
|
|||||||
@@ -184,3 +184,7 @@ execute:frappe.delete_doc("Module Def", "Contacts")
|
|||||||
erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015
|
erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items # 30-07-2015
|
||||||
execute:frappe.reload_doctype("Leave Type")
|
execute:frappe.reload_doctype("Leave Type")
|
||||||
execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0")
|
execute:frappe.db.sql("update `tabLeave Type` set include_holiday=0")
|
||||||
|
erpnext.patches.v5_4.set_root_and_report_type
|
||||||
|
erpnext.patches.v5_4.notify_system_managers_regarding_wrong_tax_calculation
|
||||||
|
erpnext.patches.v5_4.fix_invoice_outstanding
|
||||||
|
execute:frappe.db.sql("update `tabStock Ledger Entry` set stock_queue = '[]' where voucher_type = 'Stock Reconciliation' and ifnull(qty_after_transaction, 0) = 0")
|
||||||
|
|||||||
13
erpnext/patches/v5_4/fix_invoice_outstanding.py
Normal file
13
erpnext/patches/v5_4/fix_invoice_outstanding.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
frappe.reload_doctype("Sales Invoice")
|
||||||
|
return_entries = frappe.get_list("Sales Invoice", filters={"is_return": 1, "docstatus": 1},
|
||||||
|
fields=["debit_to", "customer", "return_against"])
|
||||||
|
for d in return_entries:
|
||||||
|
update_outstanding_amt(d.debit_to, "Customer", d.customer, "Sales Invoice", d.return_against)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.email import sendmail_to_system_managers
|
||||||
|
from frappe.utils import get_url_to_form
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
wrong_records = []
|
||||||
|
for dt in ("Quotation", "Sales Order", "Delivery Note", "Sales Invoice",
|
||||||
|
"Purchase Order", "Purchase Receipt", "Purchase Invoice"):
|
||||||
|
records = frappe.db.sql_list("""select name from `tab{0}`
|
||||||
|
where apply_discount_on = 'Net Total' and ifnull(discount_amount, 0) != 0
|
||||||
|
and modified >= '2015-02-17' and docstatus=1""".format(dt))
|
||||||
|
|
||||||
|
if records:
|
||||||
|
records = [get_url_to_form(dt, d) for d in records]
|
||||||
|
wrong_records.append([dt, records])
|
||||||
|
|
||||||
|
if wrong_records:
|
||||||
|
content = """Dear System Manager,
|
||||||
|
|
||||||
|
Due to an error related to Discount Amount on Net Total, tax calculation might be wrong in the following records. We did not fix the tax amount automatically because it can corrupt the entries, so we request you to check these records and amend if you found the calculation wrong.
|
||||||
|
|
||||||
|
Please check following Entries:
|
||||||
|
|
||||||
|
%s
|
||||||
|
|
||||||
|
|
||||||
|
Regards,
|
||||||
|
|
||||||
|
Administrator""" % "\n".join([(d[0] + ": " + ", ".join(d[1])) for d in wrong_records])
|
||||||
|
try:
|
||||||
|
sendmail_to_system_managers("[Important] [ERPNext] Tax calculation might be wrong, please check.", content)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print "="*50
|
||||||
|
print content
|
||||||
|
print "="*50
|
||||||
12
erpnext/patches/v5_4/set_root_and_report_type.py
Normal file
12
erpnext/patches/v5_4/set_root_and_report_type.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
|
||||||
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
roots = frappe.db.sql("""select lft, rgt, report_type, root_type
|
||||||
|
from `tabAccount` where ifnull(parent_account, '')=''""", as_dict=1)
|
||||||
|
for d in roots:
|
||||||
|
frappe.db.sql("update `tabAccount` set report_type=%s, root_type=%s where lft > %s and rgt < %s",
|
||||||
|
(d.report_type, d.root_type, d.lft, d.rgt))
|
||||||
@@ -256,7 +256,8 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
|||||||
me.round_off_totals(tax);
|
me.round_off_totals(tax);
|
||||||
|
|
||||||
// adjust Discount Amount loss in last tax iteration
|
// adjust Discount Amount loss in last tax iteration
|
||||||
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied && me.frm.doc.apply_discount_on == "Grand Total")
|
if ((i == me.frm.doc["taxes"].length - 1) && me.discount_amount_applied
|
||||||
|
&& me.frm.doc.apply_discount_on == "Grand Total" && me.frm.doc.discount_amount)
|
||||||
me.adjust_discount_amount_loss(tax);
|
me.adjust_discount_amount_loss(tax);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -365,9 +366,9 @@ erpnext.taxes_and_totals = erpnext.stock.StockController.extend({
|
|||||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||||
if (in_list(["Valuation and Total", "Total"], tax.category)) {
|
if (in_list(["Valuation and Total", "Total"], tax.category)) {
|
||||||
if(tax.add_deduct_tax == "Add") {
|
if(tax.add_deduct_tax == "Add") {
|
||||||
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount);
|
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
|
||||||
} else {
|
} else {
|
||||||
me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount);
|
me.frm.doc.taxes_and_charges_deducted += flt(tax.tax_amount_after_discount_amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -218,7 +218,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
item.serial_no += sr_no[x] + '\n';
|
item.serial_no += sr_no[x] + '\n';
|
||||||
|
|
||||||
refresh_field("serial_no", item.name, item.parentfield);
|
refresh_field("serial_no", item.name, item.parentfield);
|
||||||
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
|
if(!doc.is_return) {
|
||||||
|
frappe.model.set_value(item.doctype, item.name, "qty", sr_no.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ $.extend(erpnext, {
|
|||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
item_code:grid_row.doc.item_code ,
|
item_code:grid_row.doc.item_code ,
|
||||||
warehouse:grid_row.doc.warehouse
|
warehouse:cur_frm.doc.is_return ? null : grid_row.doc.warehouse
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,41 @@
|
|||||||
{
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
"allow_import": 1,
|
"allow_import": 1,
|
||||||
|
"allow_rename": 0,
|
||||||
"creation": "2013-06-20 11:53:21",
|
"creation": "2013-06-20 11:53:21",
|
||||||
|
"custom": 0,
|
||||||
"description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
|
"description": "Aggregate group of **Items** into another **Item**. This is useful if you are bundling a certain **Items** into a package and you maintain stock of the packed **Items** and not the aggregate **Item**. \n\nThe package **Item** will have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\".\n\nFor Example: If you are selling Laptops and Backpacks separately and have a special price if the customer buys both, then the Laptop + Backpack will be a new Product Bundle Item.\n\nNote: BOM = Bill of Materials",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Master",
|
"document_type": "",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "basic_section",
|
"fieldname": "basic_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "",
|
"label": "",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "The Item that represents the Package. This Item must have \"Is Stock Item\" as \"No\" and \"Is Sales Item\" as \"Yes\"",
|
"allow_on_submit": 0,
|
||||||
|
"description": "",
|
||||||
"fieldname": "new_item_code",
|
"fieldname": "new_item_code",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Parent Item",
|
"label": "Parent Item",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
@@ -23,30 +43,67 @@
|
|||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"options": "Item",
|
"options": "Item",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"reqd": 1
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"description": "List items that form the package.",
|
"description": "List items that form the package.",
|
||||||
"fieldname": "item_section",
|
"fieldname": "item_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "",
|
"label": "",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "items",
|
"fieldname": "items",
|
||||||
"fieldtype": "Table",
|
"fieldtype": "Table",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Items",
|
"label": "Items",
|
||||||
|
"no_copy": 0,
|
||||||
"oldfieldname": "sales_bom_items",
|
"oldfieldname": "sales_bom_items",
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"options": "Product Bundle Item",
|
"options": "Product Bundle Item",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"reqd": 1
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 1,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
"icon": "icon-sitemap",
|
"icon": "icon-sitemap",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"in_create": 0,
|
||||||
|
"in_dialog": 0,
|
||||||
"is_submittable": 0,
|
"is_submittable": 0,
|
||||||
"modified": "2015-07-13 05:28:28.140327",
|
"issingle": 0,
|
||||||
|
"istable": 0,
|
||||||
|
"modified": "2015-08-03 11:23:26.263254",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Product Bundle",
|
"name": "Product Bundle",
|
||||||
@@ -54,14 +111,20 @@
|
|||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
|
"apply_user_permissions": 0,
|
||||||
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
"export": 0,
|
||||||
|
"if_owner": 0,
|
||||||
|
"import": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Stock Manager",
|
"role": "Stock Manager",
|
||||||
|
"set_user_permissions": 0,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
"submit": 0,
|
||||||
"write": 1
|
"write": 1
|
||||||
@@ -69,31 +132,44 @@
|
|||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 1,
|
"apply_user_permissions": 1,
|
||||||
|
"cancel": 0,
|
||||||
"create": 0,
|
"create": 0,
|
||||||
"delete": 0,
|
"delete": 0,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
"export": 0,
|
||||||
|
"if_owner": 0,
|
||||||
|
"import": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Stock User",
|
"role": "Stock User",
|
||||||
|
"set_user_permissions": 0,
|
||||||
|
"share": 0,
|
||||||
"submit": 0,
|
"submit": 0,
|
||||||
"write": 0
|
"write": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"amend": 0,
|
"amend": 0,
|
||||||
"apply_user_permissions": 1,
|
"apply_user_permissions": 1,
|
||||||
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
"delete": 1,
|
"delete": 1,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
"export": 0,
|
||||||
|
"if_owner": 0,
|
||||||
|
"import": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
"report": 1,
|
"report": 1,
|
||||||
"role": "Sales User",
|
"role": "Sales User",
|
||||||
|
"set_user_permissions": 0,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
"submit": 0,
|
"submit": 0,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_onload": 0
|
||||||
}
|
}
|
||||||
@@ -14,15 +14,13 @@ class ProductBundle(Document):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_main_item()
|
self.validate_main_item()
|
||||||
|
|
||||||
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
from erpnext.utilities.transaction_base import validate_uom_is_integer
|
||||||
validate_uom_is_integer(self, "uom", "qty")
|
validate_uom_is_integer(self, "uom", "qty")
|
||||||
|
|
||||||
def validate_main_item(self):
|
def validate_main_item(self):
|
||||||
"""main item must have Is Stock Item as No and Is Sales Item as Yes"""
|
"""Validates, main Item is not a stock item"""
|
||||||
if not frappe.db.sql("""select name from tabItem where name=%s and
|
if frappe.db.get_value("Item", self.new_item_code, "is_stock_item"):
|
||||||
is_stock_item = 0 and is_sales_item = 1""", self.new_item_code):
|
frappe.throw(_("Parent Item {0} must not be a Stock Item").format(self.new_item_code))
|
||||||
frappe.throw(_("Parent Item {0} must be not Stock Item and must be a Sales Item").format(self.new_item_code))
|
|
||||||
|
|
||||||
def get_item_details(self, name):
|
def get_item_details(self, name):
|
||||||
det = frappe.db.sql("""select description, stock_uom from `tabItem`
|
det = frappe.db.sql("""select description, stock_uom from `tabItem`
|
||||||
@@ -36,8 +34,7 @@ def get_new_item_code(doctype, txt, searchfield, start, page_len, filters):
|
|||||||
from erpnext.controllers.queries import get_match_cond
|
from erpnext.controllers.queries import get_match_cond
|
||||||
|
|
||||||
return frappe.db.sql("""select name, item_name, description from tabItem
|
return frappe.db.sql("""select name, item_name, description from tabItem
|
||||||
where is_stock_item=0 and is_sales_item=1
|
where is_stock_item=0 and name not in (select name from `tabProduct Bundle`)
|
||||||
and name not in (select name from `tabProduct Bundle`) and %s like %s
|
and %s like %s %s limit %s, %s""" % (searchfield, "%s",
|
||||||
%s limit %s, %s""" % (searchfield, "%s",
|
|
||||||
get_match_cond(doctype),"%s", "%s"),
|
get_match_cond(doctype),"%s", "%s"),
|
||||||
("%%%s%%" % txt, start, page_len))
|
("%%%s%%" % txt, start, page_len))
|
||||||
|
|||||||
@@ -6,3 +6,19 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
test_records = frappe.get_test_records('Product Bundle')
|
test_records = frappe.get_test_records('Product Bundle')
|
||||||
|
|
||||||
|
def make_product_bundle(parent, items):
|
||||||
|
if frappe.db.exists("Product Bundle", parent):
|
||||||
|
return frappe.get_doc("Product Bundle", parent)
|
||||||
|
|
||||||
|
product_bundle = frappe.get_doc({
|
||||||
|
"doctype": "Product Bundle",
|
||||||
|
"new_item_code": parent
|
||||||
|
})
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
product_bundle.append("items", {"item_code": item, "qty": 1})
|
||||||
|
|
||||||
|
product_bundle.insert()
|
||||||
|
|
||||||
|
return product_bundle
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ form_grid_templates = {
|
|||||||
"items": "templates/form_grid/item_grid.html"
|
"items": "templates/form_grid/item_grid.html"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WarehouseRequired(frappe.ValidationError): pass
|
||||||
|
|
||||||
class SalesOrder(SellingController):
|
class SalesOrder(SellingController):
|
||||||
def validate_mandatory(self):
|
def validate_mandatory(self):
|
||||||
# validate transaction date v/s delivery date
|
# validate transaction date v/s delivery date
|
||||||
@@ -40,8 +42,10 @@ class SalesOrder(SellingController):
|
|||||||
check_list.append(cstr(d.item_code))
|
check_list.append(cstr(d.item_code))
|
||||||
|
|
||||||
if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or
|
if (frappe.db.get_value("Item", d.item_code, "is_stock_item")==1 or
|
||||||
self.has_product_bundle(d.item_code)) and not d.warehouse:
|
(self.has_product_bundle(d.item_code) and self.product_bundle_has_stock_item(d.item_code))) \
|
||||||
frappe.throw(_("Reserved warehouse required for stock item {0}").format(d.item_code))
|
and not d.warehouse:
|
||||||
|
frappe.throw(_("Delivery warehouse required for stock item {0}").format(d.item_code),
|
||||||
|
WarehouseRequired)
|
||||||
|
|
||||||
# used for production plan
|
# used for production plan
|
||||||
d.transaction_date = self.transaction_date
|
d.transaction_date = self.transaction_date
|
||||||
@@ -53,6 +57,12 @@ class SalesOrder(SellingController):
|
|||||||
if len(unique_chk_list) != len(check_list):
|
if len(unique_chk_list) != len(check_list):
|
||||||
frappe.msgprint(_("Warning: Same item has been entered multiple times."))
|
frappe.msgprint(_("Warning: Same item has been entered multiple times."))
|
||||||
|
|
||||||
|
def product_bundle_has_stock_item(self, product_bundle):
|
||||||
|
"""Returns true if product bundle has stock item"""
|
||||||
|
ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi
|
||||||
|
where pbi.parent = %s and pbi.item_code = i.name and i.is_stock_item = 1""", product_bundle))
|
||||||
|
return ret
|
||||||
|
|
||||||
def validate_sales_mntc_quotation(self):
|
def validate_sales_mntc_quotation(self):
|
||||||
for d in self.get('items'):
|
for d in self.get('items'):
|
||||||
if d.prevdoc_docname:
|
if d.prevdoc_docname:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from frappe.utils import flt, add_days
|
|||||||
import frappe.permissions
|
import frappe.permissions
|
||||||
import unittest
|
import unittest
|
||||||
from erpnext.selling.doctype.sales_order.sales_order \
|
from erpnext.selling.doctype.sales_order.sales_order \
|
||||||
import make_material_request, make_delivery_note, make_sales_invoice
|
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
|
||||||
|
|
||||||
class TestSalesOrder(unittest.TestCase):
|
class TestSalesOrder(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
@@ -224,6 +224,66 @@ class TestSalesOrder(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(frappe.CancelledLinkError, dn.submit)
|
self.assertRaises(frappe.CancelledLinkError, dn.submit)
|
||||||
|
|
||||||
|
def test_service_type_product_bundle(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
|
|
||||||
|
make_item("_Test Service Product Bundle", {"is_stock_item": 0, "is_sales_item": 1})
|
||||||
|
make_item("_Test Service Product Bundle Item 1", {"is_stock_item": 0, "is_sales_item": 1})
|
||||||
|
make_item("_Test Service Product Bundle Item 2", {"is_stock_item": 0, "is_sales_item": 1})
|
||||||
|
|
||||||
|
make_product_bundle("_Test Service Product Bundle",
|
||||||
|
["_Test Service Product Bundle Item 1", "_Test Service Product Bundle Item 2"])
|
||||||
|
|
||||||
|
so = make_sales_order(item_code = "_Test Service Product Bundle", warehouse=None)
|
||||||
|
|
||||||
|
self.assertTrue("_Test Service Product Bundle Item 1" in [d.item_code for d in so.packed_items])
|
||||||
|
self.assertTrue("_Test Service Product Bundle Item 2" in [d.item_code for d in so.packed_items])
|
||||||
|
|
||||||
|
def test_mix_type_product_bundle(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
|
||||||
|
|
||||||
|
make_item("_Test Mix Product Bundle", {"is_stock_item": 0, "is_sales_item": 1})
|
||||||
|
make_item("_Test Mix Product Bundle Item 1", {"is_stock_item": 1, "is_sales_item": 1})
|
||||||
|
make_item("_Test Mix Product Bundle Item 2", {"is_stock_item": 0, "is_sales_item": 1})
|
||||||
|
|
||||||
|
make_product_bundle("_Test Mix Product Bundle",
|
||||||
|
["_Test Mix Product Bundle Item 1", "_Test Mix Product Bundle Item 2"])
|
||||||
|
|
||||||
|
self.assertRaises(WarehouseRequired, make_sales_order, item_code = "_Test Mix Product Bundle", warehouse="")
|
||||||
|
|
||||||
|
def test_auto_insert_price(self):
|
||||||
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
|
make_item("_Test Item for Auto Price List", {"is_stock_item": 0, "is_sales_item": 1})
|
||||||
|
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
|
||||||
|
|
||||||
|
item_price = frappe.db.get_value("Item Price", {"price_list": "_Test Price List",
|
||||||
|
"item_code": "_Test Item for Auto Price List"})
|
||||||
|
if item_price:
|
||||||
|
frappe.delete_doc("Item Price", item_price)
|
||||||
|
|
||||||
|
make_sales_order(item_code = "_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100)
|
||||||
|
|
||||||
|
self.assertEquals(frappe.db.get_value("Item Price",
|
||||||
|
{"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}, "price_list_rate"), 100)
|
||||||
|
|
||||||
|
|
||||||
|
# do not update price list
|
||||||
|
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
|
||||||
|
|
||||||
|
item_price = frappe.db.get_value("Item Price", {"price_list": "_Test Price List",
|
||||||
|
"item_code": "_Test Item for Auto Price List"})
|
||||||
|
if item_price:
|
||||||
|
frappe.delete_doc("Item Price", item_price)
|
||||||
|
|
||||||
|
make_sales_order(item_code = "_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100)
|
||||||
|
|
||||||
|
self.assertEquals(frappe.db.get_value("Item Price",
|
||||||
|
{"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}, "price_list_rate"), None)
|
||||||
|
|
||||||
|
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
|
||||||
|
|
||||||
def make_sales_order(**args):
|
def make_sales_order(**args):
|
||||||
so = frappe.new_doc("Sales Order")
|
so = frappe.new_doc("Sales Order")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
@@ -234,14 +294,20 @@ def make_sales_order(**args):
|
|||||||
so.customer = args.customer or "_Test Customer"
|
so.customer = args.customer or "_Test Customer"
|
||||||
so.delivery_date = add_days(so.transaction_date, 10)
|
so.delivery_date = add_days(so.transaction_date, 10)
|
||||||
so.currency = args.currency or "INR"
|
so.currency = args.currency or "INR"
|
||||||
|
if args.selling_price_list:
|
||||||
|
so.selling_price_list = args.selling_price_list
|
||||||
|
|
||||||
|
if "warehouse" not in args:
|
||||||
|
args.warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
so.append("items", {
|
so.append("items", {
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"item_code": args.item or args.item_code or "_Test Item",
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"warehouse": args.warehouse,
|
||||||
"qty": args.qty or 10,
|
"qty": args.qty or 10,
|
||||||
"rate": args.rate or 100,
|
"rate": args.rate or 100,
|
||||||
"conversion_factor": 1.0,
|
"conversion_factor": 1.0,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not args.do_not_save:
|
if not args.do_not_save:
|
||||||
so.insert()
|
so.insert()
|
||||||
if not args.do_not_submit:
|
if not args.do_not_submit:
|
||||||
|
|||||||
@@ -1,103 +1,250 @@
|
|||||||
{
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
"creation": "2013-06-25 10:25:16",
|
"creation": "2013-06-25 10:25:16",
|
||||||
|
"custom": 0,
|
||||||
"description": "Settings for Selling Module",
|
"description": "Settings for Selling Module",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"document_type": "Other",
|
"document_type": "Other",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"default": "Customer Name",
|
"default": "Customer Name",
|
||||||
"fieldname": "cust_master_name",
|
"fieldname": "cust_master_name",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Customer Naming By",
|
"label": "Customer Naming By",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Customer Name\nNaming Series",
|
"options": "Customer Name\nNaming Series",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "campaign_naming_by",
|
"fieldname": "campaign_naming_by",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Campaign Naming By",
|
"label": "Campaign Naming By",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Campaign Name\nNaming Series",
|
"options": "Campaign Name\nNaming Series",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"description": "",
|
"description": "",
|
||||||
"fieldname": "customer_group",
|
"fieldname": "customer_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Customer Group",
|
"label": "Default Customer Group",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Customer Group",
|
"options": "Customer Group",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"description": "",
|
"description": "",
|
||||||
"fieldname": "territory",
|
"fieldname": "territory",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Territory",
|
"label": "Default Territory",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Territory",
|
"options": "Territory",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "selling_price_list",
|
"fieldname": "selling_price_list",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Price List",
|
"label": "Default Price List",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Price List",
|
"options": "Price List",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "column_break_5",
|
"fieldname": "column_break_5",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"permlevel": 0
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "so_required",
|
"fieldname": "so_required",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Sales Order Required",
|
"label": "Sales Order Required",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "No\nYes",
|
"options": "No\nYes",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "dn_required",
|
"fieldname": "dn_required",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Delivery Note Required",
|
"label": "Delivery Note Required",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "No\nYes",
|
"options": "No\nYes",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "maintain_same_sales_rate",
|
"fieldname": "maintain_same_sales_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Maintain Same Rate Throughout Sales Cycle",
|
"label": "Maintain Same Rate Throughout Sales Cycle",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "editable_price_list_rate",
|
"fieldname": "editable_price_list_rate",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Allow user to edit Price List Rate in transactions",
|
"label": "Allow user to edit Price List Rate in transactions",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"in_create": 0,
|
||||||
|
"in_dialog": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"modified": "2015-02-05 05:11:46.384538",
|
"istable": 0,
|
||||||
|
"modified": "2015-08-03 12:59:51.829458",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Selling",
|
"module": "Selling",
|
||||||
"name": "Selling Settings",
|
"name": "Selling Settings",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
"amend": 0,
|
||||||
|
"apply_user_permissions": 0,
|
||||||
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
"delete": 0,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
"export": 0,
|
||||||
|
"if_owner": 0,
|
||||||
|
"import": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
|
"report": 0,
|
||||||
"role": "System Manager",
|
"role": "System Manager",
|
||||||
|
"set_user_permissions": 0,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
|
"submit": 0,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_onload": 0
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,8 @@ class Company(Document):
|
|||||||
frappe.clear_cache()
|
frappe.clear_cache()
|
||||||
|
|
||||||
def install_country_fixtures(self):
|
def install_country_fixtures(self):
|
||||||
if os.path.exists(os.path.join(os.path.dirname(__file__), "fixtures", self.country.lower())):
|
path = os.path.join(os.path.dirname(__file__), "fixtures", self.country.lower())
|
||||||
|
if os.path.exists(path.encode("utf-8")):
|
||||||
frappe.get_attr("erpnext.setup.doctype.company.fixtures.{0}.install".format(self.country.lower()))(self)
|
frappe.get_attr("erpnext.setup.doctype.company.fixtures.{0}.install".format(self.country.lower()))(self)
|
||||||
|
|
||||||
def create_default_warehouses(self):
|
def create_default_warehouses(self):
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ def set_defaults(args):
|
|||||||
stock_settings.valuation_method = "FIFO"
|
stock_settings.valuation_method = "FIFO"
|
||||||
stock_settings.stock_uom = _("Nos")
|
stock_settings.stock_uom = _("Nos")
|
||||||
stock_settings.auto_indent = 1
|
stock_settings.auto_indent = 1
|
||||||
|
stock_settings.auto_insert_price_list_rate_if_missing = 1
|
||||||
stock_settings.save()
|
stock_settings.save()
|
||||||
|
|
||||||
selling_settings = frappe.get_doc("Selling Settings")
|
selling_settings = frappe.get_doc("Selling Settings")
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ def before_tests():
|
|||||||
frappe.db.sql("delete from `tabLeave Application`")
|
frappe.db.sql("delete from `tabLeave Application`")
|
||||||
frappe.db.sql("delete from `tabSalary Slip`")
|
frappe.db.sql("delete from `tabSalary Slip`")
|
||||||
frappe.db.sql("delete from `tabItem Price`")
|
frappe.db.sql("delete from `tabItem Price`")
|
||||||
|
|
||||||
|
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
|
||||||
|
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -108,9 +108,13 @@ class Item(WebsiteGenerator):
|
|||||||
|
|
||||||
def check_stock_uom_with_bin(self):
|
def check_stock_uom_with_bin(self):
|
||||||
if not self.get("__islocal"):
|
if not self.get("__islocal"):
|
||||||
|
if self.stock_uom == frappe.db.get_value("Item", self.name, "stock_uom"):
|
||||||
|
return
|
||||||
|
|
||||||
matched=True
|
matched=True
|
||||||
ref_uom = frappe.db.get_value("Stock Ledger Entry",
|
ref_uom = frappe.db.get_value("Stock Ledger Entry",
|
||||||
{"item_code": self.name}, "stock_uom")
|
{"item_code": self.name}, "stock_uom")
|
||||||
|
|
||||||
if ref_uom:
|
if ref_uom:
|
||||||
if cstr(ref_uom) != cstr(self.stock_uom):
|
if cstr(ref_uom) != cstr(self.stock_uom):
|
||||||
matched = False
|
matched = False
|
||||||
@@ -128,7 +132,7 @@ class Item(WebsiteGenerator):
|
|||||||
(self.stock_uom, self.name))
|
(self.stock_uom, self.name))
|
||||||
|
|
||||||
if not matched:
|
if not matched:
|
||||||
frappe.throw(_("Default Unit of Measure can not be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module."))
|
frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. To change default UOM, use 'UOM Replace Utility' tool under Stock module.").format(self.name))
|
||||||
|
|
||||||
def update_template_tables(self):
|
def update_template_tables(self):
|
||||||
template = frappe.get_doc("Item", self.variant_of)
|
template = frappe.get_doc("Item", self.variant_of)
|
||||||
|
|||||||
@@ -12,6 +12,29 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
|||||||
test_ignore = ["BOM"]
|
test_ignore = ["BOM"]
|
||||||
test_dependencies = ["Warehouse"]
|
test_dependencies = ["Warehouse"]
|
||||||
|
|
||||||
|
def make_item(item_code, properties=None):
|
||||||
|
if frappe.db.exists("Item", item_code):
|
||||||
|
return frappe.get_doc("Item", item_code)
|
||||||
|
|
||||||
|
item = frappe.get_doc({
|
||||||
|
"doctype": "Item",
|
||||||
|
"item_code": item_code,
|
||||||
|
"item_name": item_code,
|
||||||
|
"description": item_code,
|
||||||
|
"item_group": "Products"
|
||||||
|
})
|
||||||
|
|
||||||
|
if properties:
|
||||||
|
item.update(properties)
|
||||||
|
|
||||||
|
|
||||||
|
if item.is_stock_item and not item.default_warehouse:
|
||||||
|
item.default_warehouse = "_Test Warehouse - _TC"
|
||||||
|
|
||||||
|
item.insert()
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
class TestItem(unittest.TestCase):
|
class TestItem(unittest.TestCase):
|
||||||
def get_item(self, idx):
|
def get_item(self, idx):
|
||||||
item_code = test_records[idx].get("item_code")
|
item_code = test_records[idx].get("item_code")
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
|
|||||||
d.get_input("fetch").on("click", function() {
|
d.get_input("fetch").on("click", function() {
|
||||||
var values = d.get_values();
|
var values = d.get_values();
|
||||||
if(!values) return;
|
if(!values) return;
|
||||||
|
values["company"] = cur_frm.doc.company;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items",
|
method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items",
|
||||||
args: values,
|
args: values,
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class StockEntry(StockController):
|
|||||||
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
|
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
|
||||||
|
|
||||||
item_details = self.get_item_details(frappe._dict({"item_code": item.item_code,
|
item_details = self.get_item_details(frappe._dict({"item_code": item.item_code,
|
||||||
"company": self.company, "project_name": self.project_name}))
|
"company": self.company, "project_name": self.project_name, "uom": item.uom}), for_update=True)
|
||||||
|
|
||||||
for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
|
for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
|
||||||
"cost_center", "conversion_factor"):
|
"cost_center", "conversion_factor"):
|
||||||
@@ -419,7 +419,7 @@ class StockEntry(StockController):
|
|||||||
"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty)
|
"planned_qty": (self.docstatus==1 and -1 or 1 ) * flt(self.fg_completed_qty)
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_item_details(self, args=None):
|
def get_item_details(self, args=None, for_update=False):
|
||||||
item = frappe.db.sql("""select stock_uom, description, image, item_name,
|
item = frappe.db.sql("""select stock_uom, description, image, item_name,
|
||||||
expense_account, buying_cost_center, item_group from `tabItem`
|
expense_account, buying_cost_center, item_group from `tabItem`
|
||||||
where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""",
|
where name = %s and (ifnull(end_of_life,'0000-00-00')='0000-00-00' or end_of_life > now())""",
|
||||||
@@ -450,14 +450,22 @@ class StockEntry(StockController):
|
|||||||
if not ret[d[1]] or (company and self.company != company):
|
if not ret[d[1]] or (company and self.company != company):
|
||||||
ret[d[1]] = frappe.db.get_value("Company", self.company, d[2]) if d[2] else None
|
ret[d[1]] = frappe.db.get_value("Company", self.company, d[2]) if d[2] else None
|
||||||
|
|
||||||
|
# update uom
|
||||||
|
if args.get("uom") and for_update:
|
||||||
|
ret.update(self.get_uom_details(args))
|
||||||
|
|
||||||
if not ret["expense_account"]:
|
if not ret["expense_account"]:
|
||||||
ret["expense_account"] = frappe.db.get_value("Company", self.company, "stock_adjustment_account")
|
ret["expense_account"] = frappe.db.get_value("Company", self.company, "stock_adjustment_account")
|
||||||
|
|
||||||
stock_and_rate = args.get('warehouse') and self.get_warehouse_details(args) or {}
|
stock_and_rate = args.get('warehouse') and self.get_warehouse_details(args) or {}
|
||||||
ret.update(stock_and_rate)
|
ret.update(stock_and_rate)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_uom_details(self, args):
|
def get_uom_details(self, args):
|
||||||
|
"""Returns dict `{"conversion_factor": [value], "transfer_qty": qty * [value]}`
|
||||||
|
|
||||||
|
:param args: dict with `item_code`, `uom` and `qty`"""
|
||||||
conversion_factor = get_conversion_factor(args.get("item_code"), args.get("uom")).get("conversion_factor")
|
conversion_factor = get_conversion_factor(args.get("item_code"), args.get("uom")).get("conversion_factor")
|
||||||
|
|
||||||
if not conversion_factor:
|
if not conversion_factor:
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ class StockLedgerEntry(Document):
|
|||||||
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
|
frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
|
||||||
ItemTemplateCannotHaveStock)
|
ItemTemplateCannotHaveStock)
|
||||||
|
|
||||||
if not self.stock_uom:
|
self.stock_uom = item_det.stock_uom
|
||||||
self.stock_uom = item_det.stock_uom
|
|
||||||
|
|
||||||
def check_stock_frozen_date(self):
|
def check_stock_frozen_date(self):
|
||||||
stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
|
stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
|
||||||
|
|||||||
@@ -1,123 +1,343 @@
|
|||||||
{
|
{
|
||||||
|
"allow_copy": 0,
|
||||||
|
"allow_import": 0,
|
||||||
|
"allow_rename": 0,
|
||||||
"creation": "2013-06-24 16:37:54",
|
"creation": "2013-06-24 16:37:54",
|
||||||
|
"custom": 0,
|
||||||
"description": "Settings",
|
"description": "Settings",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"default": "Item Code",
|
"default": "Item Code",
|
||||||
"fieldname": "item_naming_by",
|
"fieldname": "item_naming_by",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Item Naming By",
|
"label": "Item Naming By",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Item Code\nNaming Series",
|
"options": "Item Code\nNaming Series",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"description": "",
|
"description": "",
|
||||||
"fieldname": "item_group",
|
"fieldname": "item_group",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Item Group",
|
"label": "Default Item Group",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Item Group",
|
"options": "Item Group",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "stock_uom",
|
"fieldname": "stock_uom",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Default Stock UOM",
|
"label": "Default Stock UOM",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "UOM",
|
"options": "UOM",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
|
"fieldname": "auto_insert_price_list_rate_if_missing",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"label": "Auto insert Price List rate if missing",
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"precision": "",
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "column_break_4",
|
"fieldname": "column_break_4",
|
||||||
"fieldtype": "Column Break",
|
"fieldtype": "Column Break",
|
||||||
"permlevel": 0
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "valuation_method",
|
"fieldname": "valuation_method",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Default Valuation Method",
|
"label": "Default Valuation Method",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "FIFO\nMoving Average",
|
"options": "FIFO\nMoving Average",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.",
|
"description": "Percentage you are allowed to receive or deliver more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to receive 110 units.",
|
||||||
"fieldname": "tolerance",
|
"fieldname": "tolerance",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Allowance Percent",
|
"label": "Allowance Percent",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "allow_negative_stock",
|
"fieldname": "allow_negative_stock",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Allow Negative Stock",
|
"label": "Allow Negative Stock",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "auto_material_request",
|
"fieldname": "auto_material_request",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Auto Material Request",
|
"label": "Auto Material Request",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "auto_indent",
|
"fieldname": "auto_indent",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Raise Material Request when stock reaches re-order level",
|
"label": "Raise Material Request when stock reaches re-order level",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "reorder_email_notify",
|
"fieldname": "reorder_email_notify",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Notify by Email on creation of automatic Material Request",
|
"label": "Notify by Email on creation of automatic Material Request",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "freeze_stock_entries",
|
"fieldname": "freeze_stock_entries",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Freeze Stock Entries",
|
"label": "Freeze Stock Entries",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "stock_frozen_upto",
|
"fieldname": "stock_frozen_upto",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Stock Frozen Upto",
|
"label": "Stock Frozen Upto",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "stock_frozen_upto_days",
|
"fieldname": "stock_frozen_upto_days",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Freeze Stocks Older Than [Days]",
|
"label": "Freeze Stocks Older Than [Days]",
|
||||||
"permlevel": 0
|
"no_copy": 0,
|
||||||
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"allow_on_submit": 0,
|
||||||
"fieldname": "stock_auth_role",
|
"fieldname": "stock_auth_role",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"hidden": 0,
|
||||||
|
"ignore_user_permissions": 0,
|
||||||
|
"in_filter": 0,
|
||||||
|
"in_list_view": 0,
|
||||||
"label": "Role Allowed to edit frozen stock",
|
"label": "Role Allowed to edit frozen stock",
|
||||||
|
"no_copy": 0,
|
||||||
"options": "Role",
|
"options": "Role",
|
||||||
"permlevel": 0
|
"permlevel": 0,
|
||||||
|
"print_hide": 0,
|
||||||
|
"read_only": 0,
|
||||||
|
"report_hide": 0,
|
||||||
|
"reqd": 0,
|
||||||
|
"search_index": 0,
|
||||||
|
"set_only_once": 0,
|
||||||
|
"unique": 0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"hide_heading": 0,
|
||||||
|
"hide_toolbar": 0,
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
|
"in_create": 0,
|
||||||
|
"in_dialog": 0,
|
||||||
|
"is_submittable": 0,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"modified": "2015-07-13 05:28:23.839277",
|
"istable": 0,
|
||||||
|
"modified": "2015-08-03 13:00:36.082986",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Stock Settings",
|
"name": "Stock Settings",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
|
"amend": 0,
|
||||||
|
"apply_user_permissions": 0,
|
||||||
|
"cancel": 0,
|
||||||
"create": 1,
|
"create": 1,
|
||||||
|
"delete": 0,
|
||||||
"email": 1,
|
"email": 1,
|
||||||
|
"export": 0,
|
||||||
|
"if_owner": 0,
|
||||||
|
"import": 0,
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"print": 1,
|
"print": 1,
|
||||||
"read": 1,
|
"read": 1,
|
||||||
|
"report": 0,
|
||||||
"role": "Stock Manager",
|
"role": "Stock Manager",
|
||||||
|
"set_user_permissions": 0,
|
||||||
"share": 1,
|
"share": 1,
|
||||||
|
"submit": 0,
|
||||||
"write": 1
|
"write": 1
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"read_only": 0,
|
||||||
|
"read_only_onload": 0
|
||||||
}
|
}
|
||||||
@@ -214,6 +214,8 @@ def get_price_list_rate(args, item_doc, out):
|
|||||||
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
|
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
|
||||||
|
|
||||||
if not price_list_rate:
|
if not price_list_rate:
|
||||||
|
if args.price_list and args.rate:
|
||||||
|
insert_item_price(args)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
|
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
|
||||||
@@ -224,6 +226,22 @@ def get_price_list_rate(args, item_doc, out):
|
|||||||
out.update(get_last_purchase_details(item_doc.name,
|
out.update(get_last_purchase_details(item_doc.name,
|
||||||
args.parent, args.conversion_rate))
|
args.parent, args.conversion_rate))
|
||||||
|
|
||||||
|
def insert_item_price(args):
|
||||||
|
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
|
||||||
|
if frappe.db.get_value("Price List", args.price_list, "currency") == args.currency \
|
||||||
|
and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")):
|
||||||
|
if frappe.has_permission("Item Price", "write"):
|
||||||
|
item_price = frappe.get_doc({
|
||||||
|
"doctype": "Item Price",
|
||||||
|
"price_list": args.price_list,
|
||||||
|
"item_code": args.item_code,
|
||||||
|
"currency": args.currency,
|
||||||
|
"price_list_rate": args.rate
|
||||||
|
})
|
||||||
|
item_price.insert()
|
||||||
|
frappe.msgprint("Item Price added for {0} in Price List {1}".format(args.item_code,
|
||||||
|
args.price_list))
|
||||||
|
|
||||||
def get_price_list_rate_for(args, item_code):
|
def get_price_list_rate_for(args, item_code):
|
||||||
return frappe.db.get_value("Item Price",
|
return frappe.db.get_value("Item Price",
|
||||||
{"price_list": args.price_list, "item_code": item_code}, "price_list_rate")
|
{"price_list": args.price_list, "item_code": item_code}, "price_list_rate")
|
||||||
|
|||||||
@@ -300,22 +300,22 @@ class update_entries_after(object):
|
|||||||
|
|
||||||
# select first batch or the batch with same rate
|
# select first batch or the batch with same rate
|
||||||
batch = self.stock_queue[index]
|
batch = self.stock_queue[index]
|
||||||
|
if batch[0]:
|
||||||
|
if qty_to_pop >= batch[0]:
|
||||||
|
# consume current batch
|
||||||
|
qty_to_pop = qty_to_pop - batch[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, outgoing_rate or batch[1]])
|
||||||
|
break
|
||||||
|
|
||||||
if qty_to_pop >= batch[0]:
|
else:
|
||||||
# consume current batch
|
# qty found in current batch
|
||||||
qty_to_pop = qty_to_pop - batch[0]
|
# consume it and exit
|
||||||
self.stock_queue.pop(index)
|
batch[0] = batch[0] - qty_to_pop
|
||||||
if not self.stock_queue and qty_to_pop:
|
qty_to_pop = 0
|
||||||
# stock finished, qty still remains to be withdrawn
|
|
||||||
# negative stock, keep in as a negative batch
|
|
||||||
self.stock_queue.append([-qty_to_pop, outgoing_rate or batch[1]])
|
|
||||||
break
|
|
||||||
|
|
||||||
else:
|
|
||||||
# qty found in current batch
|
|
||||||
# consume it and exit
|
|
||||||
batch[0] = batch[0] - qty_to_pop
|
|
||||||
qty_to_pop = 0
|
|
||||||
|
|
||||||
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue))
|
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in self.stock_queue))
|
||||||
stock_qty = sum((flt(batch[0]) for batch in self.stock_queue))
|
stock_qty = sum((flt(batch[0]) for batch in self.stock_queue))
|
||||||
|
|||||||
@@ -133,19 +133,20 @@ def get_fifo_rate(previous_stock_queue, qty):
|
|||||||
qty_to_pop = abs(qty)
|
qty_to_pop = abs(qty)
|
||||||
while qty_to_pop and previous_stock_queue:
|
while qty_to_pop and previous_stock_queue:
|
||||||
batch = previous_stock_queue[0]
|
batch = previous_stock_queue[0]
|
||||||
if 0 < batch[0] <= qty_to_pop:
|
if batch[0]:
|
||||||
# if batch qty > 0
|
if 0 < batch[0] <= qty_to_pop:
|
||||||
# not enough or exactly same qty in current batch, clear batch
|
# if batch qty > 0
|
||||||
available_qty_for_outgoing += flt(batch[0])
|
# not enough or exactly same qty in current batch, clear batch
|
||||||
outgoing_cost += flt(batch[0]) * flt(batch[1])
|
available_qty_for_outgoing += flt(batch[0])
|
||||||
qty_to_pop -= batch[0]
|
outgoing_cost += flt(batch[0]) * flt(batch[1])
|
||||||
previous_stock_queue.pop(0)
|
qty_to_pop -= batch[0]
|
||||||
else:
|
previous_stock_queue.pop(0)
|
||||||
# all from current batch
|
else:
|
||||||
available_qty_for_outgoing += flt(qty_to_pop)
|
# all from current batch
|
||||||
outgoing_cost += flt(qty_to_pop) * flt(batch[1])
|
available_qty_for_outgoing += flt(qty_to_pop)
|
||||||
batch[0] -= qty_to_pop
|
outgoing_cost += flt(qty_to_pop) * flt(batch[1])
|
||||||
qty_to_pop = 0
|
batch[0] -= qty_to_pop
|
||||||
|
qty_to_pop = 0
|
||||||
|
|
||||||
return outgoing_cost / available_qty_for_outgoing
|
return outgoing_cost / available_qty_for_outgoing
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3904
erpnext/translations/fi.csv
Normal file
3904
erpnext/translations/fi.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3480
erpnext/translations/km.csv
Normal file
3480
erpnext/translations/km.csv
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3901
erpnext/translations/mk.csv
Normal file
3901
erpnext/translations/mk.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3903
erpnext/translations/my.csv
Normal file
3903
erpnext/translations/my.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3904
erpnext/translations/no.csv
Normal file
3904
erpnext/translations/no.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3902
erpnext/translations/sq.csv
Normal file
3902
erpnext/translations/sq.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3904
erpnext/translations/sv.csv
Normal file
3904
erpnext/translations/sv.csv
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2
setup.py
2
setup.py
@@ -1,6 +1,6 @@
|
|||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
version = "5.4.2"
|
version = "5.5.0"
|
||||||
|
|
||||||
with open("requirements.txt", "r") as f:
|
with open("requirements.txt", "r") as f:
|
||||||
install_requires = f.readlines()
|
install_requires = f.readlines()
|
||||||
|
|||||||
Reference in New Issue
Block a user