Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baefec4498 | ||
|
|
02a56b4e1a | ||
|
|
9a6df0341f | ||
|
|
5a90e3b2e9 | ||
|
|
7dab3c1f85 | ||
|
|
f9a974385a | ||
|
|
32e48bb568 | ||
|
|
21e09a2bd8 | ||
|
|
1aa6e98136 | ||
|
|
023c036afa | ||
|
|
8372c44262 | ||
|
|
6d69ca6bac | ||
|
|
283b55f88c | ||
|
|
4757d0634a | ||
|
|
dc8ce7f7e9 | ||
|
|
d02375e89d | ||
|
|
a90a0528aa | ||
|
|
350f9592d3 | ||
|
|
43e50de6ef | ||
|
|
a530f410e3 | ||
|
|
bdfb070ed6 | ||
|
|
caa9fc033f | ||
|
|
15bf4e5599 | ||
|
|
6d490e530a | ||
|
|
9b363fe5f1 | ||
|
|
3c5df9f64c | ||
|
|
fd53991dfa | ||
|
|
c794ca53fb | ||
|
|
fa0adafa82 | ||
|
|
99f4b43641 | ||
|
|
fdeab29e94 | ||
|
|
31755b485f | ||
|
|
3f3696d1eb | ||
|
|
e1a478779c | ||
|
|
6c6f3789d0 | ||
|
|
044c43a5cb | ||
|
|
9ce9c052e4 | ||
|
|
83e68bb837 | ||
|
|
415df04834 | ||
|
|
6d2d6862d6 | ||
|
|
13a65d52dd | ||
|
|
6485d4a749 | ||
|
|
2e63c80523 | ||
|
|
23bd21778e | ||
|
|
a3f490890d | ||
|
|
8e3ea32d6d | ||
|
|
c4a1a943ef | ||
|
|
abc0b64b68 | ||
|
|
00818bfa90 | ||
|
|
b9bfe6117e | ||
|
|
7a9f46d9d1 | ||
|
|
a10b52c6e6 | ||
|
|
5033e7b431 | ||
|
|
e6791ee78e | ||
|
|
b84ba868e6 | ||
|
|
1c501b6aac | ||
|
|
9a2a6d8fcb | ||
|
|
0b59d1c78b | ||
|
|
0fbf10797c | ||
|
|
58344cbb81 | ||
|
|
c6e2c8f79e | ||
|
|
b64b461d53 | ||
|
|
0b93bdcf40 | ||
|
|
e3910d02a5 | ||
|
|
0af146cea6 | ||
|
|
683f756d0f | ||
|
|
d905204e49 | ||
|
|
d8bc40d7f0 |
@@ -6,7 +6,7 @@
|
||||
|
||||
Includes: Accounting, Inventory, CRM, Sales, Purchase, Projects, HRMS. Requires MariaDB.
|
||||
|
||||
ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & Javascript.
|
||||
ERPNext is built on the [Frappe](https://github.com/frappe/frappe) Framework, a full-stack web app framework in Python & JavaScript.
|
||||
|
||||
- [User Guide](https://manual.erpnext.com)
|
||||
- [Getting Help](http://erpnext.org/getting-help.html)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
__version__ = '6.6.0'
|
||||
__version__ = '6.6.7'
|
||||
|
||||
@@ -147,6 +147,8 @@ class Account(Document):
|
||||
self.validate_warehouse(old_warehouse)
|
||||
if self.warehouse:
|
||||
self.validate_warehouse(self.warehouse)
|
||||
elif self.warehouse:
|
||||
self.warehouse = None
|
||||
|
||||
def validate_warehouse(self, warehouse):
|
||||
if frappe.db.get_value("Stock Ledger Entry", {"warehouse": warehouse}):
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.party import validate_party_gle_currency, get_party_account_currency
|
||||
from erpnext.accounts.party import validate_party_gle_currency
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.setup.doctype.company.company import get_company_currency
|
||||
from erpnext.exceptions import InvalidAccountCurrency, CustomerFrozen
|
||||
@@ -114,13 +114,7 @@ class GLEntry(Document):
|
||||
.format(self.account, (account_currency or company_currency)), InvalidAccountCurrency)
|
||||
|
||||
if self.party_type and self.party:
|
||||
party_account_currency = get_party_account_currency(self.party_type, self.party, self.company)
|
||||
|
||||
if party_account_currency != self.account_currency:
|
||||
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
|
||||
.format(self.party_type, self.party, party_account_currency), InvalidAccountCurrency)
|
||||
|
||||
validate_party_gle_currency(self.party_type, self.party, self.company)
|
||||
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
|
||||
|
||||
def validate_balance_type(account, adv_adj=False):
|
||||
if not adv_adj and account:
|
||||
|
||||
@@ -498,14 +498,17 @@ def get_default_bank_cash_account(company, voucher_type, mode_of_payment=None):
|
||||
if voucher_type=="Bank Entry":
|
||||
account = frappe.db.get_value("Company", company, "default_bank_account")
|
||||
if not account:
|
||||
account = frappe.db.get_value("Account", {"company": company, "account_type": "Bank", "is_group": 0})
|
||||
account = frappe.db.get_value("Account",
|
||||
{"company": company, "account_type": "Bank", "is_group": 0})
|
||||
elif voucher_type=="Cash Entry":
|
||||
account = frappe.db.get_value("Company", company, "default_cash_account")
|
||||
if not account:
|
||||
account = frappe.db.get_value("Account", {"company": company, "account_type": "Cash", "is_group": 0})
|
||||
account = frappe.db.get_value("Account",
|
||||
{"company": company, "account_type": "Cash", "is_group": 0})
|
||||
|
||||
if account:
|
||||
account_details = frappe.db.get_value("Account", account, ["account_currency", "account_type"], as_dict=1)
|
||||
account_details = frappe.db.get_value("Account", account,
|
||||
["account_currency", "account_type"], as_dict=1)
|
||||
return {
|
||||
"account": account,
|
||||
"balance": get_balance_on(account),
|
||||
@@ -696,13 +699,15 @@ def get_payment_entry_from_purchase_order(purchase_order):
|
||||
|
||||
def get_payment_entry(doc):
|
||||
bank_account = get_default_bank_cash_account(doc.company, "Bank Entry")
|
||||
cost_center = frappe.db.get_value("Company", doc.company, "cost_center")
|
||||
|
||||
jv = frappe.new_doc('Journal Entry')
|
||||
jv.voucher_type = 'Bank Entry'
|
||||
jv.company = doc.company
|
||||
jv.fiscal_year = doc.fiscal_year
|
||||
|
||||
jv.append("accounts")
|
||||
d1 = jv.append("accounts")
|
||||
d1.cost_center = cost_center
|
||||
d2 = jv.append("accounts")
|
||||
|
||||
if bank_account:
|
||||
@@ -712,6 +717,7 @@ def get_payment_entry(doc):
|
||||
d2.account_type = bank_account["account_type"]
|
||||
d2.exchange_rate = get_exchange_rate(bank_account["account"],
|
||||
bank_account["account_currency"], doc.company)
|
||||
d2.cost_center = cost_center
|
||||
|
||||
return jv
|
||||
|
||||
@@ -814,11 +820,19 @@ def get_account_balance_and_party_type(account, date, company, debit=None, credi
|
||||
return grid_values
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_exchange_rate(account, account_currency, company,
|
||||
def get_exchange_rate(account, account_currency=None, company=None,
|
||||
reference_type=None, reference_name=None, debit=None, credit=None, exchange_rate=None):
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
account_details = frappe.db.get_value("Account", account,
|
||||
["account_type", "root_type", "account_currency", "company"], as_dict=1)
|
||||
|
||||
if not company:
|
||||
company = account_details.company
|
||||
|
||||
if not account_currency:
|
||||
account_currency = account_details.account_currency
|
||||
|
||||
company_currency = get_company_currency(company)
|
||||
account_details = frappe.db.get_value("Account", account, ["account_type", "root_type"], as_dict=1)
|
||||
|
||||
if account_currency != company_currency:
|
||||
if reference_type in ("Sales Invoice", "Purchase Invoice") and reference_name:
|
||||
|
||||
@@ -160,6 +160,9 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
me.set_dynamic_labels();
|
||||
me.calculate_taxes_and_totals();
|
||||
if(callback_fn) callback_fn();
|
||||
frappe.after_ajax(function() {
|
||||
cur_frm.doc.__missing_values_set = false;
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -109,7 +109,8 @@ def get_party_details(party, party_type, args=None):
|
||||
def get_tax_template(posting_date, args):
|
||||
"""Get matching tax rule"""
|
||||
args = frappe._dict(args)
|
||||
conditions = []
|
||||
conditions = ["""(from_date is null or from_date = '' or from_date <= '{0}')
|
||||
and (to_date is null or to_date = '' or to_date >= '{0}')""".format(posting_date)]
|
||||
|
||||
for key, value in args.iteritems():
|
||||
if key in "use_for_shopping_cart":
|
||||
@@ -117,16 +118,16 @@ def get_tax_template(posting_date, args):
|
||||
else:
|
||||
conditions.append("ifnull({0}, '') in ('', '{1}')".format(key, frappe.db.escape(cstr(value))))
|
||||
|
||||
matching = frappe.db.sql("""select * from `tabTax Rule`
|
||||
tax_rule = frappe.db.sql("""select * from `tabTax Rule`
|
||||
where {0}""".format(" and ".join(conditions)), as_dict = True)
|
||||
|
||||
if not matching:
|
||||
if not tax_rule:
|
||||
return None
|
||||
|
||||
for rule in matching:
|
||||
for rule in tax_rule:
|
||||
rule.no_of_keys_matched = 0
|
||||
for key in args:
|
||||
if rule.get(key): rule.no_of_keys_matched += 1
|
||||
|
||||
rule = sorted(matching, lambda b, a: cmp(a.no_of_keys_matched, b.no_of_keys_matched) or cmp(a.priority, b.priority))[0]
|
||||
rule = sorted(tax_rule, lambda b, a: cmp(a.no_of_keys_matched, b.no_of_keys_matched) or cmp(a.priority, b.priority))[0]
|
||||
return rule.sales_tax_template or rule.purchase_tax_template
|
||||
|
||||
@@ -209,13 +209,12 @@ erpnext.AccountsChart = Class.extend({
|
||||
{fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
|
||||
description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
|
||||
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
|
||||
options: ['', 'Bank', 'Cash', 'Warehouse', 'Receivable', 'Payable',
|
||||
'Equity', 'Cost of Goods Sold', 'Fixed Asset', 'Expense Account',
|
||||
'Income Account', 'Tax', 'Chargeable', 'Temporary'].join('\n'),
|
||||
options: ['', 'Bank', 'Cash', 'Warehouse', 'Tax', 'Chargeable'].join('\n'),
|
||||
description: __("Optional. This setting will be used to filter in various transactions.") },
|
||||
{fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate')},
|
||||
{fieldtype:'Link', fieldname:'warehouse', label:__('Warehouse'), options:"Warehouse"},
|
||||
{fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency"}
|
||||
{fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
|
||||
description: __("Optional. Sets company's default currency, if not specified.")}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -201,11 +201,14 @@ def get_party_gle_currency(party_type, party, company):
|
||||
|
||||
return existing_gle_currency[0][0] if existing_gle_currency else None
|
||||
|
||||
return frappe.local_cache("party_gle_currency", (party_type, party, company), generator)
|
||||
return frappe.local_cache("party_gle_currency", (party_type, party, company), generator,
|
||||
regenerate_if_none=True)
|
||||
|
||||
def validate_party_gle_currency(party_type, party, company):
|
||||
def validate_party_gle_currency(party_type, party, company, party_account_currency=None):
|
||||
"""Validate party account currency with existing GL Entry's currency"""
|
||||
party_account_currency = get_party_account_currency(party_type, party, company)
|
||||
if not party_account_currency:
|
||||
party_account_currency = get_party_account_currency(party_type, party, company)
|
||||
|
||||
existing_gle_currency = get_party_gle_currency(party_type, party, company)
|
||||
|
||||
if existing_gle_currency and party_account_currency != existing_gle_currency:
|
||||
@@ -221,10 +224,10 @@ def validate_party_accounts(doc):
|
||||
.format(doc.doctype, doc.name), DuplicatePartyAccountError)
|
||||
else:
|
||||
companies.append(account.company)
|
||||
|
||||
|
||||
party_account_currency = frappe.db.get_value("Account", account.account, "account_currency")
|
||||
existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company)
|
||||
|
||||
|
||||
if existing_gle_currency and party_account_currency != existing_gle_currency:
|
||||
frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company))
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ class PurchaseCommon(BuyingController):
|
||||
items = []
|
||||
for d in obj.get("items"):
|
||||
if not d.qty:
|
||||
if obj.doctype == "Purchase Receipt" and d.rejected_qty:
|
||||
continue
|
||||
frappe.throw(_("Please enter quantity for Item {0}").format(d.item_code))
|
||||
|
||||
# udpate with latest quantities
|
||||
@@ -56,11 +58,11 @@ class PurchaseCommon(BuyingController):
|
||||
d.set(x, f_lst[x])
|
||||
|
||||
item = frappe.db.sql("""select is_stock_item, is_purchase_item,
|
||||
is_sub_contracted_item, end_of_life from `tabItem` where name=%s""",
|
||||
is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""",
|
||||
d.item_code, as_dict=1)[0]
|
||||
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
validate_end_of_life(d.item_code, item.end_of_life)
|
||||
validate_end_of_life(d.item_code, item.end_of_life, item.disabled)
|
||||
|
||||
# validate stock item
|
||||
if item.is_stock_item==1 and d.qty and not d.warehouse:
|
||||
@@ -72,6 +74,7 @@ class PurchaseCommon(BuyingController):
|
||||
frappe.throw(_("{0} must be a Purchased or Sub-Contracted Item in row {1}").format(d.item_code, d.idx))
|
||||
|
||||
items.append(cstr(d.item_code))
|
||||
|
||||
if items and len(items) != len(set(items)) and \
|
||||
not cint(frappe.db.get_single_value("Buying Settings", "allow_multiple_items") or 0):
|
||||
frappe.msgprint(_("Warning: Same item has been entered multiple times."))
|
||||
|
||||
@@ -164,6 +164,21 @@ def get_data():
|
||||
"label": _("Customer and Supplier"),
|
||||
"youtube_id": "anoGi_RpQ20"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Material Request to Purchase Order"),
|
||||
"youtube_id": "4TN9kPyfIqM"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Purchase Order to Payment"),
|
||||
"youtube_id": "EK65tLdVUDk"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Managing Subcontracting"),
|
||||
"youtube_id": "ThiMCC2DtKo"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -146,6 +146,11 @@ def get_data():
|
||||
"label": _("Lead to Quotation"),
|
||||
"youtube_id": "TxYX4r4JAKA"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Newsletters"),
|
||||
"youtube_id": "muLKsCrrDRo"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -196,4 +196,30 @@ def get_data():
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Help"),
|
||||
"icon": "icon-facetime-video",
|
||||
"items": [
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Setting up Employees"),
|
||||
"youtube_id": "USfIUdZlUhw"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Leave Management"),
|
||||
"youtube_id": "fc0p_AXebc8"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Expense Claims"),
|
||||
"youtube_id": "5SZHJF--ZFY"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Processing Payroll"),
|
||||
"youtube_id": "apgE-f25Rm0"
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -122,6 +122,11 @@ def get_data():
|
||||
"label": _("Items and Pricing"),
|
||||
"youtube_id": "qXaEwld4_Ps"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Item Variants"),
|
||||
"youtube_id": "OGBETlCzU5o"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Opening Stock Balance"),
|
||||
|
||||
@@ -120,6 +120,16 @@ def get_data():
|
||||
"label": _("Bill of Materials"),
|
||||
"youtube_id": "hDV0c1OeWLo"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Production Planning Tool"),
|
||||
"youtube_id": "CzatSl4zJ2Y"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Production Order"),
|
||||
"youtube_id": "ZotgLyp2YFY"
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -70,4 +70,15 @@ def get_data():
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": _("Help"),
|
||||
"icon": "icon-facetime-video",
|
||||
"items": [
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Managing Projects"),
|
||||
"youtube_id": "egxIGwtoKI4"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -294,6 +294,16 @@ def get_data():
|
||||
"label": _("Customer and Supplier"),
|
||||
"youtube_id": "anoGi_RpQ20"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Sales Order to Payment"),
|
||||
"youtube_id": "7AMq4lqkN4A"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Point-of-Sale"),
|
||||
"youtube_id": "4WkelWkbP_c"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@@ -263,6 +263,11 @@ def get_data():
|
||||
"label": _("Items and Pricing"),
|
||||
"youtube_id": "qXaEwld4_Ps"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Item Variants"),
|
||||
"youtube_id": "OGBETlCzU5o"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Opening Stock Balance"),
|
||||
@@ -270,8 +275,23 @@ def get_data():
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Item Variants"),
|
||||
"youtube_id": "OGBETlCzU5o"
|
||||
"label": _("Making Stock Entries"),
|
||||
"youtube_id": "Njt107hlY3I"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Serialized Inventory"),
|
||||
"youtube_id": "gvOVlEwFDAk"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Batch Inventory"),
|
||||
"youtube_id": "J0QKl7ABPKM"
|
||||
},
|
||||
{
|
||||
"type": "help",
|
||||
"label": _("Managing Subcontracting"),
|
||||
"youtube_id": "ThiMCC2DtKo"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year, get_ac
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
from erpnext.controllers.recurring_document import convert_to_recurring, validate_recurring_document
|
||||
from erpnext.controllers.sales_and_purchase_return import validate_return
|
||||
from erpnext.accounts.party import get_party_account_currency, validate_party_gle_currency
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.exceptions import CustomerFrozen, InvalidCurrency
|
||||
|
||||
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
|
||||
@@ -435,6 +435,8 @@ class AccountsController(TransactionBase):
|
||||
frappe.throw(_("Accounting Entry for {0}: {1} can only be made in currency: {2}")
|
||||
.format(party_type, party, party_account_currency), InvalidCurrency)
|
||||
|
||||
# Note: not validating with gle account because we don't have the account at quotation / sales order level and we shouldn't stop someone from creating a sales invoice if sales order is already created
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tax_rate(account_head):
|
||||
return frappe.db.get_value("Account", account_head, "tax_rate")
|
||||
|
||||
@@ -166,6 +166,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
from tabItem
|
||||
where tabItem.docstatus < 2
|
||||
and ifnull(tabItem.has_variants, 0)=0
|
||||
and tabItem.disabled=0
|
||||
and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00')
|
||||
and (tabItem.`{key}` LIKE %(txt)s
|
||||
or tabItem.item_name LIKE %(txt)s
|
||||
@@ -303,10 +304,10 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
# Hence the first condition is an "OR"
|
||||
if not filters: filters = {}
|
||||
|
||||
condition = ""
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
|
||||
return frappe.db.sql("""select tabAccount.name from `tabAccount`
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Income Account", "Temporary"))
|
||||
@@ -314,6 +315,6 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabAccount.`{key}` LIKE %(txt)s
|
||||
{condition} {match_condition}"""
|
||||
.format(condition=condition, match_condition=get_match_cond(doctype), key=searchfield), {
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'company': filters.get("company", "")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -29,7 +29,7 @@ blogs.
|
||||
"""
|
||||
app_icon = "icon-th"
|
||||
app_color = "#e74c3c"
|
||||
app_version = "6.6.0"
|
||||
app_version = "6.6.7"
|
||||
github_link = "https://github.com/frappe/erpnext"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
|
||||
@@ -18,24 +18,25 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
|
||||
jv.voucher_type = 'Bank Entry';
|
||||
jv.company = cur_frm.doc.company;
|
||||
jv.remark = 'Payment against Expense Claim: ' + cur_frm.doc.name;
|
||||
jv.fiscal_year = cur_frm.doc.fiscal_year;
|
||||
var expense = cur_frm.doc.expenses || [];
|
||||
for(var i = 0; i < expense.length; i++){
|
||||
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
|
||||
d1.debit = expense[i].sanctioned_amount;
|
||||
d1.account = expense[i].default_account;
|
||||
d1.debit_in_account_currency = expense[i].sanctioned_amount;
|
||||
d1.reference_type = cur_frm.doc.doctype;
|
||||
d1.reference_name = cur_frm.doc.name;
|
||||
}
|
||||
|
||||
// credit to bank
|
||||
var d1 = frappe.model.add_child(jv, 'Journal Entry Account', 'accounts');
|
||||
d1.credit = cur_frm.doc.total_sanctioned_amount;
|
||||
d1.credit_in_account_currency = cur_frm.doc.total_sanctioned_amount;
|
||||
d1.reference_type = cur_frm.doc.doctype;
|
||||
d1.reference_name = cur_frm.doc.name;
|
||||
if(r.message) {
|
||||
d1.account = r.message.account;
|
||||
d1.balance = r.message.balance;
|
||||
d1.account_currency = r.message.account_currency;
|
||||
d1.account_type = r.message.account_type;
|
||||
}
|
||||
|
||||
loaddoc('Journal Entry', jv.name);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.add_fetch('employee','employee_name','employee_name');
|
||||
cur_frm.add_fetch('employee','company','company');
|
||||
|
||||
frappe.ui.form.on("Leave Application", {
|
||||
onload: function(frm) {
|
||||
|
||||
@@ -140,7 +140,7 @@ erpnext.production_order = {
|
||||
} else msgprint(__("Please enter Production Item first"));
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
set_default_warehouse: function(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.production_order.production_order.get_default_warehouse",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt, get_datetime, getdate, date_diff, cint
|
||||
from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
@@ -159,22 +159,22 @@ class ProductionOrder(Document):
|
||||
|
||||
def on_cancel(self):
|
||||
self.validate_cancel()
|
||||
|
||||
|
||||
frappe.db.set(self,'status', 'Cancelled')
|
||||
self.update_planned_qty()
|
||||
self.delete_time_logs()
|
||||
|
||||
|
||||
def validate_cancel(self):
|
||||
if self.status == "Stopped":
|
||||
frappe.throw(_("Stopped Production Order cannot be cancelled, Unstop it first to cancel"))
|
||||
|
||||
|
||||
# Check whether any stock entry exists against this Production Order
|
||||
stock_entry = frappe.db.sql("""select name from `tabStock Entry`
|
||||
where production_order = %s and docstatus = 1""", self.name)
|
||||
if stock_entry:
|
||||
frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0]))
|
||||
|
||||
def update_planned_qty(self):
|
||||
def update_planned_qty(self):
|
||||
update_bin_qty(self.production_item, self.fg_warehouse, {
|
||||
"planned_qty": get_planned_qty(self.production_item, self.fg_warehouse)
|
||||
})
|
||||
@@ -342,8 +342,8 @@ class ProductionOrder(Document):
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item):
|
||||
res = frappe.db.sql("""select stock_uom, description
|
||||
from `tabItem` where (ifnull(end_of_life, "0000-00-00")="0000-00-00" or end_of_life > now())
|
||||
and name=%s""", item, as_dict=1)
|
||||
from `tabItem` where disabled=0 and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
|
||||
and name=%s""", (nowdate(), item), as_dict=1)
|
||||
if not res:
|
||||
return {}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ class TestProductionOrder(unittest.TestCase):
|
||||
self.assertEqual(prod_order.name, time_log.production_order)
|
||||
self.assertEqual((prod_order.qty - d.completed_qty), time_log.completed_qty)
|
||||
self.assertEqual(time_diff_in_hours(d.planned_end_time, d.planned_start_time),time_log.hours)
|
||||
|
||||
|
||||
manufacturing_settings = frappe.get_doc({
|
||||
"doctype": "Manufacturing Settings",
|
||||
"allow_production_on_holidays": 0
|
||||
@@ -136,6 +136,11 @@ class TestProductionOrder(unittest.TestCase):
|
||||
self.assertRaises(frappe.ValidationError, prod_order.save)
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "end_of_life", None)
|
||||
frappe.db.set_value("Item", "_Test FG Item", "disabled", 1)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, prod_order.save)
|
||||
|
||||
frappe.db.set_value("Item", "_Test FG Item", "disabled", 0)
|
||||
|
||||
prod_order = make_prod_order_test_record(item="_Test Variant Item", qty=1, do_not_save=True)
|
||||
self.assertRaises(ItemHasVariantError, prod_order.save)
|
||||
|
||||
@@ -228,3 +228,5 @@ erpnext.patches.v6_4.set_user_in_contact
|
||||
erpnext.patches.v6_4.make_image_thumbnail #2015-10-20
|
||||
erpnext.patches.v6_5.show_in_website_for_template_item
|
||||
erpnext.patches.v6_4.fix_expense_included_in_valuation
|
||||
execute:frappe.delete_doc_if_exists("Report", "Item-wise Last Purchase Rate")
|
||||
erpnext.patches.v6_6.fix_website_image
|
||||
|
||||
@@ -8,13 +8,13 @@ from frappe.utils import cstr
|
||||
def execute():
|
||||
for company in frappe.db.sql("select name, expenses_included_in_valuation from tabCompany", as_dict=1):
|
||||
frozen_date = get_frozen_date(company.name, company.expenses_included_in_valuation)
|
||||
|
||||
# Purchase Invoices after frozen date
|
||||
|
||||
# Purchase Invoices after frozen date
|
||||
# which are not against Receipt, but valuation related tax is there
|
||||
pi_list = frappe.db.sql("""
|
||||
select distinct pi.name
|
||||
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item
|
||||
where
|
||||
where
|
||||
pi.name = pi_item.parent
|
||||
and pi.company = %s
|
||||
and pi.posting_date > %s
|
||||
@@ -25,40 +25,50 @@ def execute():
|
||||
and (pi_item.item_code is not null and pi_item.item_code != '')
|
||||
and exists(select name from `tabItem` where name=pi_item.item_code and is_stock_item=1)
|
||||
""", (company.name, frozen_date), as_dict=1)
|
||||
|
||||
|
||||
for pi in pi_list:
|
||||
# Check whether gle exists for Expenses Included in Valuation account against the PI
|
||||
gle_for_expenses_included_in_valuation = frappe.db.sql("""select name from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s and account=%s""",
|
||||
gle_for_expenses_included_in_valuation = frappe.db.sql("""select name from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s and account=%s""",
|
||||
(pi.name, company.expenses_included_in_valuation))
|
||||
|
||||
|
||||
if gle_for_expenses_included_in_valuation:
|
||||
print pi.name
|
||||
|
||||
frappe.db.sql("""delete from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s""", pi.name)
|
||||
|
||||
purchase_invoice = frappe.get_doc("Purchase Invoice", pi.name)
|
||||
|
||||
# some old entries have missing expense accounts
|
||||
if purchase_invoice.against_expense_account:
|
||||
expense_account = purchase_invoice.against_expense_account.split(",")
|
||||
if len(expense_account) == 1:
|
||||
expense_account = expense_account[0]
|
||||
for item in purchase_invoice.items:
|
||||
if not item.expense_account:
|
||||
item.db_set("expense_account", expense_account, update_modified=False)
|
||||
|
||||
purchase_invoice.make_gl_entries()
|
||||
|
||||
print pi.name
|
||||
|
||||
|
||||
def get_frozen_date(company, account):
|
||||
# Accounting frozen upto
|
||||
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||
|
||||
|
||||
# Last adjustment entry to correct Expenses Included in Valuation account balance
|
||||
last_adjustment_entry = frappe.db.sql("""select posting_date from `tabGL Entry`
|
||||
last_adjustment_entry = frappe.db.sql("""select posting_date from `tabGL Entry`
|
||||
where account=%s and company=%s and voucher_type = 'Journal Entry'
|
||||
order by posting_date desc limit 1""", (account, company))
|
||||
|
||||
|
||||
last_adjustment_date = cstr(last_adjustment_entry[0][0]) if last_adjustment_entry else None
|
||||
|
||||
|
||||
# Last period closing voucher
|
||||
last_closing_entry = frappe.db.sql("""select posting_date from `tabGL Entry`
|
||||
last_closing_entry = frappe.db.sql("""select posting_date from `tabGL Entry`
|
||||
where company=%s and voucher_type = 'Period Closing Voucher'
|
||||
order by posting_date desc limit 1""", company)
|
||||
|
||||
|
||||
last_closing_date = cstr(last_closing_entry[0][0]) if last_closing_entry else None
|
||||
|
||||
frozen_date = max([accounts_frozen_upto, last_adjustment_date, last_closing_date])
|
||||
|
||||
return frozen_date or '1900-01-01'
|
||||
|
||||
return frozen_date or '1900-01-01'
|
||||
|
||||
1
erpnext/patches/v6_6/__init__.py
Normal file
1
erpnext/patches/v6_6/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from __future__ import unicode_literals
|
||||
32
erpnext/patches/v6_6/fix_website_image.py
Normal file
32
erpnext/patches/v6_6/fix_website_image.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import encode
|
||||
|
||||
def execute():
|
||||
"""Fix the File records created via item.py even if the website_image file didn't exist"""
|
||||
for item in frappe.db.sql_list("""select name from `tabItem`
|
||||
where website_image is not null and website_image != ''
|
||||
and website_image like '/files/%'
|
||||
and exists (
|
||||
select name from `tabFile`
|
||||
where attached_to_doctype='Item'
|
||||
and attached_to_name=`tabItem`.name
|
||||
and file_url=`tabItem`.website_image
|
||||
and (file_name is null or file_name = '')
|
||||
)"""):
|
||||
|
||||
item = frappe.get_doc("Item", item)
|
||||
file = frappe.get_doc("File", {
|
||||
"attached_to_doctype": "Item",
|
||||
"attached_to_name": item.name,
|
||||
"file_url": item.website_image
|
||||
})
|
||||
|
||||
try:
|
||||
file.validate_file()
|
||||
except IOError:
|
||||
print encode(item.website_image), "does not exist"
|
||||
file.delete()
|
||||
item.db_set("website_image", None, update_modified=False)
|
||||
|
||||
|
||||
BIN
erpnext/public/images/YouTube-icon-full_color.png
Normal file
BIN
erpnext/public/images/YouTube-icon-full_color.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
erpnext/public/images/erpnext-video-placeholder.jpg
Normal file
BIN
erpnext/public/images/erpnext-video-placeholder.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 794 KiB |
@@ -76,10 +76,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
|
||||
&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
|
||||
this.apply_default_taxes();
|
||||
}
|
||||
|
||||
if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"] && !this.frm.doc.is_pos) {
|
||||
this.calculate_taxes_and_totals();
|
||||
} else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
|
||||
&& !this.frm.doc.is_pos) {
|
||||
me.calculate_taxes_and_totals();
|
||||
}
|
||||
if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
|
||||
cur_frm.get_field("items").grid.set_multiple_add("item_code", "qty");
|
||||
@@ -102,7 +101,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
me.frm.doc.name);
|
||||
|
||||
if(taxes_and_charges_field) {
|
||||
frappe.call({
|
||||
return frappe.call({
|
||||
method: "erpnext.controllers.accounts_controller.get_default_taxes_and_charges",
|
||||
args: {
|
||||
"master_doctype": taxes_and_charges_field.options
|
||||
@@ -110,6 +109,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
me.frm.set_value("taxes", r.message);
|
||||
me.calculate_taxes_and_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -362,7 +362,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
|
||||
get_exchange_rate: function(from_currency, to_currency, callback) {
|
||||
frappe.call({
|
||||
return frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: from_currency,
|
||||
|
||||
@@ -22,11 +22,16 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
|
||||
}
|
||||
|
||||
if (args) {
|
||||
args.posting_date = frm.doc.transaction_date;
|
||||
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
|
||||
}
|
||||
}
|
||||
if(!args) return;
|
||||
|
||||
|
||||
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
|
||||
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
|
||||
args.posting_date, args.party_type=="Customer" ? "customer": "supplier")) return;
|
||||
}
|
||||
|
||||
args.currency = frm.doc.currency;
|
||||
args.company = frm.doc.company;
|
||||
args.doctype = frm.doc.doctype;
|
||||
@@ -64,6 +69,15 @@ erpnext.utils.get_address_display = function(frm, address_field, display_field)
|
||||
if(r.message){
|
||||
frm.set_value(display_field, r.message)
|
||||
}
|
||||
|
||||
if(frappe.meta.get_docfield(frm.doc.doctype, "taxes")) {
|
||||
if(!erpnext.utils.validate_mandatory(frm, "Customer/Supplier",
|
||||
frm.doc.customer || frm.doc.supplier, address_field)) return;
|
||||
|
||||
if(!erpnext.utils.validate_mandatory(frm, "Posting/Transaction Date",
|
||||
frm.doc.posting_date || frm.doc.transaction_date, address_field)) return;
|
||||
} else return;
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.party.set_taxes",
|
||||
args: {
|
||||
@@ -99,3 +113,13 @@ erpnext.utils.get_contact_details = function(frm) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.utils.validate_mandatory = function(frm, label, value, trigger_on) {
|
||||
if(!value) {
|
||||
frm.doc[trigger_on] = "";
|
||||
refresh_field(trigger_on);
|
||||
frappe.msgprint(__("Please enter {0} first", [label]));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -22,8 +22,10 @@ def delete_company_transactions(company_name):
|
||||
|
||||
for doctype in frappe.db.sql_list("""select parent from
|
||||
tabDocField where fieldtype='Link' and options='Company'"""):
|
||||
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail", "Party Account", "Employee"):
|
||||
delete_for_doctype(doctype, company_name)
|
||||
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail",
|
||||
"Party Account", "Employee", "Sales Taxes and Charges Template",
|
||||
"Purchase Taxes and Charges Template", "POS Profile"):
|
||||
delete_for_doctype(doctype, company_name)
|
||||
|
||||
# Clear notification counts
|
||||
clear_notifications()
|
||||
|
||||
@@ -139,7 +139,7 @@ class EmailDigest(Document):
|
||||
|
||||
for i, e in enumerate(events):
|
||||
e.starts_on_label = format_time(e.starts_on)
|
||||
e.ends_on_label = format_time(e.ends_on)
|
||||
e.ends_on_label = format_time(e.ends_on) if e.ends_on else None
|
||||
e.date = formatdate(e.starts)
|
||||
e.link = get_url_to_form("Event", e.name)
|
||||
|
||||
@@ -346,7 +346,7 @@ class EmailDigest(Document):
|
||||
self.get_next_sending()
|
||||
|
||||
def fmt_money(self, value):
|
||||
return fmt_money(value, currency = self.currency)
|
||||
return fmt_money(abs(value), currency = self.currency)
|
||||
|
||||
def send():
|
||||
now_date = now_datetime().date()
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
<span style="{{ label_css }}">
|
||||
{% if e.all_day %}
|
||||
{{ _("All Day") }}
|
||||
{% elif (not e.ends_on_label or e.starts_on_label == e.ends_on_label)%}
|
||||
{{ e.starts_on_label }}
|
||||
{% else %}
|
||||
{{ e.starts_on_label }} - {{ e.ends_on_label }}
|
||||
{% endif %}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import urllib
|
||||
from frappe.utils import nowdate
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
from frappe.website.render import clear_cache
|
||||
@@ -71,14 +72,16 @@ def get_product_list_for_group(product_group=None, start=0, limit=10):
|
||||
concat(parent_website_route, "/", page_name) as route
|
||||
from `tabItem`
|
||||
where show_in_website = 1
|
||||
and disabled=0
|
||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
|
||||
and (variant_of = '' or variant_of is null)
|
||||
and (item_group in (%s)
|
||||
or name in (select parent from `tabWebsite Item Group` where item_group in (%s)))
|
||||
""" % (child_groups, child_groups)
|
||||
and (item_group in ({child_groups})
|
||||
or name in (select parent from `tabWebsite Item Group` where item_group in ({child_groups})))
|
||||
""".format(child_groups=child_groups)
|
||||
|
||||
query += """order by weightage desc, modified desc limit %s, %s""" % (start, limit)
|
||||
|
||||
data = frappe.db.sql(query, {"product_group": product_group}, as_dict=1)
|
||||
data = frappe.db.sql(query, {"product_group": product_group, "today": nowdate()}, as_dict=1)
|
||||
|
||||
return [get_item_for_list_in_html(r) for r in data]
|
||||
|
||||
|
||||
@@ -658,7 +658,7 @@ $.extend(erpnext.wiz, {
|
||||
return frappe.render_template("setup_wizard_message", {
|
||||
image: "/assets/frappe/images/ui/bubble-tea-happy.svg",
|
||||
title: __('Setup Complete'),
|
||||
message: __('Your setup is complete. Refreshing.') + ".."
|
||||
message: ""
|
||||
});
|
||||
},
|
||||
|
||||
@@ -670,6 +670,7 @@ $.extend(erpnext.wiz, {
|
||||
args: values,
|
||||
callback: function(r) {
|
||||
wiz.show_complete();
|
||||
localStorage.setItem("session_last_route", "#welcome-to-erpnext");
|
||||
setTimeout(function() {
|
||||
window.location = "/desk";
|
||||
}, 2000);
|
||||
|
||||
@@ -374,6 +374,7 @@ def create_items(args):
|
||||
is_sales_item = args.get("is_sales_item_" + str(i))
|
||||
is_purchase_item = args.get("is_purchase_item_" + str(i))
|
||||
is_stock_item = item_group!=_("Services")
|
||||
is_pro_applicable = item_group!=_("Services")
|
||||
default_warehouse = ""
|
||||
if is_stock_item:
|
||||
default_warehouse = frappe.db.get_value("Warehouse", filters={
|
||||
@@ -391,6 +392,7 @@ def create_items(args):
|
||||
"is_purchase_item": 1 if is_purchase_item else 0,
|
||||
"show_in_website": 1,
|
||||
"is_stock_item": is_stock_item and 1 or 0,
|
||||
"is_pro_applicable": is_pro_applicable and 1 or 0,
|
||||
"item_group": item_group,
|
||||
"stock_uom": args.get("item_uom_" + str(i)),
|
||||
"default_warehouse": default_warehouse
|
||||
|
||||
0
erpnext/setup/page/welcome_to_erpnext/__init__.py
Normal file
0
erpnext/setup/page/welcome_to_erpnext/__init__.py
Normal file
13
erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.css
Normal file
13
erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.css
Normal file
@@ -0,0 +1,13 @@
|
||||
#page-welcome-to-erpnext ul li {
|
||||
margin: 7px 0px;
|
||||
}
|
||||
|
||||
#page-welcome-to-erpnext .video-placeholder-image {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#page-welcome-to-erpnext .youtube-icon {
|
||||
width: 10%;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="container welcome-to-erpnext text-center" style="padding: 30px 0px;">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-md-push-2 col-sm-12">
|
||||
<h1>{%= __("Welcome to ERPNext") %}</h1>
|
||||
<p class="text-muted">
|
||||
{%= __("To get the best out of ERPNext, we recommend that you take some time and watch these help videos.") %}
|
||||
<br><br>
|
||||
</p>
|
||||
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<div class="video-placeholder embed-responsive-item">
|
||||
<img class="video-placeholder-image"
|
||||
src="/assets/erpnext/images/erpnext-video-placeholder.jpg">
|
||||
<img class="centered youtube-icon"
|
||||
src="/assets/erpnext/images/YouTube-icon-full_color.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<h3>Next Steps</h3>
|
||||
<ul class="list-unstyled">
|
||||
<li><a class="text-muted" href="#">{%= __("Go to the Desktop and start using ERPNext") %}</a></li>
|
||||
<li><a class="text-muted" href="#Module/Learn">{%= __("View a list of all the help videos") %}</a></li>
|
||||
<li><a class="text-muted" href="https://manual.erpnext.com" target="_blank">{%= __("Read the ERPNext Manual") %}</a></li>
|
||||
<li><a class="text-muted" href="https://discuss.erpnext.com" target="_blank">{%= __("Community Forum") %}</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
10
erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.js
Normal file
10
erpnext/setup/page/welcome_to_erpnext/welcome_to_erpnext.js
Normal file
@@ -0,0 +1,10 @@
|
||||
frappe.pages['welcome-to-erpnext'].on_page_load = function(wrapper) {
|
||||
var parent = $('<div class="welcome-to-erpnext"></div>').appendTo(wrapper);
|
||||
|
||||
parent.html(frappe.render_template("welcome_to_erpnext", {}));
|
||||
|
||||
parent.find(".video-placeholder").on("click", function() {
|
||||
parent.find(".video-placeholder").addClass("hidden");
|
||||
parent.find(".embed-responsive").append('<iframe class="embed-responsive-item video-playlist" src="https://www.youtube.com/embed/videoseries?list=PL3lFfCEoMxvxDHtYyQFJeUYkWzQpXwFM9&color=white&autoplay=1" allowfullscreen></iframe>')
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"content": null,
|
||||
"creation": "2015-10-28 16:27:02.197707",
|
||||
"docstatus": 0,
|
||||
"doctype": "Page",
|
||||
"modified": "2015-10-28 16:27:02.197707",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "welcome-to-erpnext",
|
||||
"owner": "Administrator",
|
||||
"page_name": "welcome-to-erpnext",
|
||||
"roles": [],
|
||||
"script": null,
|
||||
"standard": "Yes",
|
||||
"style": null,
|
||||
"title": "Welcome to ERPNext"
|
||||
}
|
||||
@@ -735,7 +735,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"label": "From Warehouse",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
@@ -755,13 +755,15 @@
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"depends_on": "",
|
||||
"description": "",
|
||||
"fieldname": "target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Target Warehouse",
|
||||
"label": "To Warehouse (Optional)",
|
||||
"no_copy": 0,
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
@@ -831,7 +833,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Available Qty at Warehouse",
|
||||
"label": "Available Qty at From Warehouse",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "actual_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
@@ -857,7 +859,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Available Batch Qty at Warehouse",
|
||||
"label": "Available Batch Qty at From Warehouse",
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -1160,7 +1162,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-10-19 03:04:50.887288",
|
||||
"modified": "2015-10-28 12:41:53.738462",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
||||
@@ -178,31 +178,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "2099-12-31",
|
||||
"depends_on": "is_stock_item",
|
||||
"fieldname": "end_of_life",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "End of Life",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "end_of_life",
|
||||
"oldfieldtype": "Date",
|
||||
"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,
|
||||
"bold": 0,
|
||||
@@ -249,6 +224,28 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Disabled",
|
||||
"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,
|
||||
"bold": 0,
|
||||
@@ -436,6 +433,31 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"default": "2099-12-31",
|
||||
"depends_on": "is_stock_item",
|
||||
"fieldname": "end_of_life",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "End of Life",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "end_of_life",
|
||||
"oldfieldtype": "Date",
|
||||
"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,
|
||||
"bold": 0,
|
||||
@@ -2113,7 +2135,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2015-10-20 12:14:43.315827",
|
||||
"modified": "2015-10-29 02:25:26.256373",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
import urllib
|
||||
import itertools
|
||||
from frappe import msgprint, _
|
||||
from frappe.utils import cstr, flt, cint, getdate, now_datetime, formatdate
|
||||
from frappe.website.website_generator import WebsiteGenerator
|
||||
@@ -103,12 +104,16 @@ class Item(WebsiteGenerator):
|
||||
|
||||
# for CSV import
|
||||
if not file_doc:
|
||||
file_doc = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_url": self.website_image,
|
||||
"attached_to_doctype": "Item",
|
||||
"attached_to_name": self.name
|
||||
}).insert()
|
||||
try:
|
||||
file_doc = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_url": self.website_image,
|
||||
"attached_to_doctype": "Item",
|
||||
"attached_to_name": self.name
|
||||
}).insert()
|
||||
|
||||
except IOError:
|
||||
self.website_image = None
|
||||
|
||||
if file_doc:
|
||||
if not file_doc.thumbnail_url:
|
||||
@@ -130,6 +135,8 @@ class Item(WebsiteGenerator):
|
||||
|
||||
self.set_attribute_context(context)
|
||||
|
||||
self.set_disabled_attributes(context)
|
||||
|
||||
context.parents = self.get_parents(context)
|
||||
|
||||
return context
|
||||
@@ -189,15 +196,63 @@ class Item(WebsiteGenerator):
|
||||
for attr in self.attributes:
|
||||
values = context.attribute_values.setdefault(attr.attribute, [])
|
||||
|
||||
# get list of values defined (for sequence)
|
||||
for attr_value in frappe.db.get_all("Item Attribute Value",
|
||||
fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
|
||||
if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
|
||||
for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
|
||||
values.append(val)
|
||||
|
||||
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
|
||||
values.append(attr_value.attribute_value)
|
||||
else:
|
||||
# get list of values defined (for sequence)
|
||||
for attr_value in frappe.db.get_all("Item Attribute Value",
|
||||
fields=["attribute_value"], filters={"parent": attr.attribute}, order_by="idx asc"):
|
||||
|
||||
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
|
||||
values.append(attr_value.attribute_value)
|
||||
|
||||
context.variant_info = json.dumps(context.variants)
|
||||
|
||||
def set_disabled_attributes(self, context):
|
||||
"""Disable selection options of attribute combinations that do not result in a variant"""
|
||||
if not self.attributes:
|
||||
return
|
||||
|
||||
context.disabled_attributes = {}
|
||||
attributes = [attr.attribute for attr in self.attributes]
|
||||
|
||||
def find_variant(combination):
|
||||
for variant in context.variants:
|
||||
if len(variant.attributes) < len(attributes):
|
||||
continue
|
||||
|
||||
if "combination" not in variant:
|
||||
ref_combination = []
|
||||
|
||||
for attr in variant.attributes:
|
||||
idx = attributes.index(attr.attribute)
|
||||
ref_combination.insert(idx, attr.attribute_value)
|
||||
|
||||
variant["combination"] = ref_combination
|
||||
|
||||
if not (set(combination) - set(variant["combination"])):
|
||||
# check if the combination is a subset of a variant combination
|
||||
# eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
|
||||
return True
|
||||
|
||||
for i, attr in enumerate(self.attributes):
|
||||
if i==0:
|
||||
continue
|
||||
|
||||
combination_source = []
|
||||
|
||||
# loop through previous attributes
|
||||
for prev_attr in self.attributes[:i]:
|
||||
combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
|
||||
|
||||
combination_source.append(context.attribute_values[attr.attribute])
|
||||
|
||||
for combination in itertools.product(*combination_source):
|
||||
if not find_variant(combination):
|
||||
context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
|
||||
|
||||
def check_warehouse_is_set_for_stock_item(self):
|
||||
if self.is_stock_item==1 and not self.default_warehouse and frappe.get_all("Warehouse"):
|
||||
frappe.msgprint(_("Default Warehouse is mandatory for stock Item."),
|
||||
@@ -469,14 +524,17 @@ class Item(WebsiteGenerator):
|
||||
if variant and self.get("__islocal"):
|
||||
frappe.throw(_("Item variant {0} exists with same attributes").format(variant), ItemVariantExistsError)
|
||||
|
||||
def validate_end_of_life(item_code, end_of_life=None, verbose=1):
|
||||
if not end_of_life:
|
||||
end_of_life = frappe.db.get_value("Item", item_code, "end_of_life")
|
||||
def validate_end_of_life(item_code, end_of_life=None, disabled=None, verbose=1):
|
||||
if (not end_of_life) or (disabled is None):
|
||||
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
|
||||
|
||||
if end_of_life and end_of_life!="0000-00-00" and getdate(end_of_life) <= now_datetime().date():
|
||||
msg = _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
|
||||
_msgprint(msg, verbose)
|
||||
|
||||
if disabled:
|
||||
_msgprint(_("Item {0} is disabled").format(item_code), verbose)
|
||||
|
||||
def validate_is_stock_item(item_code, is_stock_item=None, verbose=1):
|
||||
if not is_stock_item:
|
||||
is_stock_item = frappe.db.get_value("Item", item_code, "is_stock_item")
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
frappe.listview_settings['Item'] = {
|
||||
add_fields: ["item_name", "stock_uom", "item_group", "image", "variant_of",
|
||||
"has_variants", "end_of_life", "is_sales_item"],
|
||||
"has_variants", "end_of_life", "disabled", "is_sales_item"],
|
||||
|
||||
get_indicator: function(doc) {
|
||||
if(doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
|
||||
return [__("Expired"), "grey", "end_of_life,<,Today"]
|
||||
} else if(doc.has_variants) {
|
||||
return [__("Template"), "blue", "has_variants,=,Yes"]
|
||||
} else if(doc.variant_of) {
|
||||
return [__("Variant"), "green", "variant_of,=," + doc.variant_of]
|
||||
if (doc.disabled) {
|
||||
return [__("Disabled"), "grey", "disabled,=,Yes"];
|
||||
} else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
|
||||
return [__("Expired"), "grey", "end_of_life,<,Today"];
|
||||
} else if (doc.has_variants) {
|
||||
return [__("Template"), "blue", "has_variants,=,Yes"];
|
||||
} else if (doc.variant_of) {
|
||||
return [__("Variant"), "green", "variant_of,=," + doc.variant_of];
|
||||
} else {
|
||||
return [__("Active"), "blue", "end_of_life,>=,Today"]
|
||||
return [__("Active"), "blue", "end_of_life,>=,Today"];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Warehouse",
|
||||
"label": "From Warehouse",
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "warehouse",
|
||||
"oldfieldtype": "Link",
|
||||
@@ -179,7 +179,7 @@
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Target Warehouse",
|
||||
"label": "To Warehouse (Optional)",
|
||||
"no_copy": 0,
|
||||
"options": "Warehouse",
|
||||
"permlevel": 0,
|
||||
@@ -511,7 +511,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-10-12 07:38:58.896987",
|
||||
"modified": "2015-10-26 02:25:47.718911",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Packed Item",
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, cint, flt, comma_or, getdate
|
||||
from frappe.utils import cstr, cint, flt, comma_or, getdate, nowdate
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError
|
||||
from erpnext.stock.get_item_details import get_available_qty, get_default_cost_center, get_conversion_factor
|
||||
@@ -359,7 +359,7 @@ class StockEntry(StockController):
|
||||
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
|
||||
|
||||
# make sl entries for source warehouse first, then do for target warehouse
|
||||
for d in self.get('items'):
|
||||
if cstr(d.s_warehouse):
|
||||
@@ -368,7 +368,7 @@ class StockEntry(StockController):
|
||||
"actual_qty": -flt(d.transfer_qty),
|
||||
"incoming_rate": 0
|
||||
}))
|
||||
|
||||
|
||||
for d in self.get('items'):
|
||||
if cstr(d.t_warehouse):
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
@@ -438,8 +438,10 @@ class StockEntry(StockController):
|
||||
def get_item_details(self, args=None, for_update=False):
|
||||
item = frappe.db.sql("""select stock_uom, description, image, item_name,
|
||||
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())""",
|
||||
(args.get('item_code')), as_dict = 1)
|
||||
where name = %s
|
||||
and disabled=0
|
||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)""",
|
||||
(args.get('item_code'), nowdate()), as_dict = 1)
|
||||
if not item:
|
||||
frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code")))
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ class StockReconciliation(StockController):
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
|
||||
# end of life and stock item
|
||||
validate_end_of_life(item_code, item.end_of_life, verbose=0)
|
||||
validate_end_of_life(item_code, item.end_of_life, item.disabled, verbose=0)
|
||||
validate_is_stock_item(item_code, item.is_stock_item, verbose=0)
|
||||
|
||||
# item should not be serialized
|
||||
|
||||
@@ -113,7 +113,7 @@ def validate_item_details(args, item):
|
||||
throw(_("Please specify Company"))
|
||||
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
validate_end_of_life(item.name, item.end_of_life)
|
||||
validate_end_of_life(item.name, item.end_of_life, item.disabled)
|
||||
|
||||
if args.transaction_type == "selling":
|
||||
# validate if sales item or service item
|
||||
|
||||
@@ -23,6 +23,7 @@ def _reorder_item():
|
||||
items_to_consider = frappe.db.sql_list("""select name from `tabItem` item
|
||||
where is_stock_item=1 and has_variants=0
|
||||
and (is_purchase_item=1 or is_sub_contracted_item=1)
|
||||
and disabled=0
|
||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
|
||||
and ((re_order_level is not null and re_order_level > 0)
|
||||
or exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, today
|
||||
|
||||
def execute(filters=None):
|
||||
filters = frappe._dict(filters or {})
|
||||
@@ -18,12 +19,19 @@ def get_columns():
|
||||
_("Shortage Qty") + ":Float:100"]
|
||||
|
||||
def get_data(filters):
|
||||
item_map = {}
|
||||
bin_list = get_bin_list(filters)
|
||||
item_map = get_item_map(filters.get("item_code"))
|
||||
warehouse_company = {}
|
||||
data = []
|
||||
|
||||
for bin in get_bin_list(filters):
|
||||
item = item_map.setdefault(bin.item_code, frappe.get_doc("Item", bin.item_code))
|
||||
for bin in bin_list:
|
||||
item = item_map.get(bin.item_code)
|
||||
|
||||
if not item:
|
||||
# likely an item that has reached its end of life
|
||||
continue
|
||||
|
||||
# item = item_map.setdefault(bin.item_code, get_item(bin.item_code))
|
||||
company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company"))
|
||||
|
||||
if filters.brand and filters.brand != item.brand:
|
||||
@@ -45,7 +53,7 @@ def get_data(filters):
|
||||
|
||||
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
|
||||
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty, bin.reserved_qty,
|
||||
bin.projected_qty, re_order_level, re_order_qty, re_order_level - bin.projected_qty])
|
||||
bin.projected_qty, re_order_level, re_order_qty, re_order_level - flt(bin.projected_qty)])
|
||||
|
||||
return data
|
||||
|
||||
@@ -61,3 +69,36 @@ def get_bin_list(filters):
|
||||
filters=bin_filters, order_by="item_code, warehouse")
|
||||
|
||||
return bin_list
|
||||
|
||||
def get_item_map(item_code):
|
||||
"""Optimization: get only the item doc and re_order_levels table"""
|
||||
|
||||
condition = ""
|
||||
if item_code:
|
||||
condition = 'and item_code = "{0}"'.format(frappe.db.escape(item_code))
|
||||
|
||||
items = frappe.db.sql("""select * from `tabItem` item
|
||||
where is_stock_item = 1
|
||||
and disabled=0
|
||||
{condition}
|
||||
and (end_of_life > %(today)s or end_of_life is null or end_of_life='0000-00-00')
|
||||
and exists (select name from `tabBin` bin where bin.item_code=item.name)"""\
|
||||
.format(condition=condition), {"today": today()}, as_dict=True)
|
||||
|
||||
condition = ""
|
||||
if item_code:
|
||||
condition = 'where parent="{0}"'.format(frappe.db.escape(item_code))
|
||||
|
||||
reorder_levels = frappe._dict()
|
||||
for ir in frappe.db.sql("""select * from `tabItem Reorder` {condition}""".format(condition=condition), as_dict=1):
|
||||
if ir.parent not in reorder_levels:
|
||||
reorder_levels[ir.parent] = []
|
||||
|
||||
reorder_levels[ir.parent].append(ir)
|
||||
|
||||
item_map = frappe._dict()
|
||||
for item in items:
|
||||
item["reorder_levels"] = reorder_levels.get(item.name) or []
|
||||
item_map[item.name] = item
|
||||
|
||||
return item_map
|
||||
|
||||
@@ -51,7 +51,7 @@ cur_frm.fields_dict['serial_no'].get_query = function(doc, cdt, cdn) {
|
||||
var cond = [];
|
||||
var filter = [
|
||||
['Serial No', 'docstatus', '!=', 2],
|
||||
['Serial No', 'status', '=', "Delivered"]
|
||||
['Serial No', 'warehouse', '=', ""]
|
||||
];
|
||||
if(doc.item_code) {
|
||||
cond = ['Serial No', 'item_code', '=', doc.item_code];
|
||||
|
||||
@@ -24,12 +24,13 @@
|
||||
{{ web_long_description or description or _("No description given") }}
|
||||
</div>
|
||||
<p class="text-muted">
|
||||
{{ _("Item Code") }}: <span itemprop="productID">{{ name }}</span></p>
|
||||
{{ _("Item Code") }}: <span itemprop="productID">{{ variant and variant.name or name }}</span></p>
|
||||
<br>
|
||||
<div class="item-attribute-selectors">
|
||||
{% if has_variants %}
|
||||
{% for d in attributes %}
|
||||
<div class="item-view-attribute"
|
||||
{% if attribute_values[d.attribute] -%}
|
||||
<div class="item-view-attribute {% if (attribute_values[d.attribute] | len)==1 -%} hidden {%- endif %}"
|
||||
style="margin-bottom: 10px;">
|
||||
<h6 class="text-muted">{{ _(d.attribute) }}</h6>
|
||||
<select class="form-control"
|
||||
@@ -37,12 +38,17 @@
|
||||
data-attribute="{{ d.attribute }}">
|
||||
{% for value in attribute_values[d.attribute] %}
|
||||
<option value="{{ value }}"
|
||||
{% if selected_attributes and selected_attributes[d.attribute]==value -%} selected {%- endif %}>
|
||||
{% if selected_attributes and selected_attributes[d.attribute]==value -%}
|
||||
selected
|
||||
{%- elif disabled_attributes and value in disabled_attributes.get(d.attribute, []) -%}
|
||||
disabled
|
||||
{%- endif %}>
|
||||
{{ _(value) }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{%- endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -64,8 +64,23 @@ frappe.ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("[itemscope] .item-view-attribute select").on("change", function() {
|
||||
var item_code = encodeURIComponent(get_item_code());
|
||||
$("[itemscope] .item-view-attribute .form-control").on("change", function() {
|
||||
try {
|
||||
var item_code = encodeURIComponent(get_item_code());
|
||||
} catch(e) {
|
||||
// unable to find variant
|
||||
// then chose the closest available one
|
||||
|
||||
var attribute = $(this).attr("data-attribute");
|
||||
var attribute_value = $(this).val()
|
||||
var item_code = update_attribute_selectors(attribute, attribute_value);
|
||||
|
||||
if (!item_code) {
|
||||
msgprint(__("Please select some other value for {0}", [attribute]))
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.search.indexOf(item_code)!==-1) {
|
||||
return;
|
||||
}
|
||||
@@ -83,10 +98,8 @@ var toggle_update_cart = function(qty) {
|
||||
|
||||
function get_item_code() {
|
||||
if(window.variant_info) {
|
||||
attributes = {};
|
||||
$('[itemscope]').find(".item-view-attribute select").each(function() {
|
||||
attributes[$(this).attr('data-attribute')] = $(this).val();
|
||||
});
|
||||
var attributes = get_selected_attributes();
|
||||
|
||||
for(var i in variant_info) {
|
||||
var variant = variant_info[i];
|
||||
var match = true;
|
||||
@@ -106,3 +119,51 @@ function get_item_code() {
|
||||
return item_code;
|
||||
}
|
||||
}
|
||||
|
||||
function update_attribute_selectors(selected_attribute, selected_attribute_value) {
|
||||
// find the closest match keeping the selected attribute in focus and get the item code
|
||||
|
||||
var attributes = get_selected_attributes();
|
||||
|
||||
var previous_match_score = 0;
|
||||
var matched;
|
||||
for(var i in variant_info) {
|
||||
var variant = variant_info[i];
|
||||
var match_score = 0;
|
||||
var has_selected_attribute = false;
|
||||
|
||||
for(var j in variant.attributes) {
|
||||
if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) {
|
||||
match_score = match_score + 1;
|
||||
|
||||
if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) {
|
||||
has_selected_attribute = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (has_selected_attribute && (match_score > previous_match_score)) {
|
||||
previous_match_score = match_score;
|
||||
matched = variant;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched) {
|
||||
for (var j in matched.attributes) {
|
||||
var attr = matched.attributes[j];
|
||||
$('[itemscope]')
|
||||
.find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr))
|
||||
.val(attr.attribute_value);
|
||||
}
|
||||
|
||||
return matched.name;
|
||||
}
|
||||
}
|
||||
|
||||
function get_selected_attributes() {
|
||||
var attributes = {};
|
||||
$('[itemscope]').find(".item-view-attribute .form-control").each(function() {
|
||||
attributes[$(this).attr('data-attribute')] = $(this).val();
|
||||
});
|
||||
return attributes;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, nowdate
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
|
||||
|
||||
no_cache = 1
|
||||
@@ -14,7 +14,11 @@ def get_product_list(search=None, start=0, limit=10):
|
||||
# base query
|
||||
query = """select name, item_name, page_name, website_image, thumbnail, item_group,
|
||||
web_long_description as website_description, parent_website_route
|
||||
from `tabItem` where show_in_website = 1 and (variant_of is null or variant_of = '')"""
|
||||
from `tabItem`
|
||||
where show_in_website = 1
|
||||
and disabled=0
|
||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
|
||||
and (variant_of is null or variant_of = '')"""
|
||||
|
||||
# search term condition
|
||||
if search:
|
||||
@@ -29,6 +33,7 @@ def get_product_list(search=None, start=0, limit=10):
|
||||
|
||||
data = frappe.db.sql(query, {
|
||||
"search": search,
|
||||
"today": nowdate()
|
||||
}, as_dict=1)
|
||||
|
||||
for d in data:
|
||||
|
||||
@@ -674,7 +674,7 @@ The tax rate you define here will be the standard tax rate for all **Items**. If
|
||||
|
||||
#### Description of Columns
|
||||
|
||||
1. Calculation Type:
|
||||
1. Calculation Type:
|
||||
- This can be on **Net Total** (that is the sum of basic amount).
|
||||
- **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.
|
||||
- **Actual** (as mentioned).
|
||||
@@ -685,19 +685,19 @@ The tax rate you define here will be the standard tax rate for all **Items**. If
|
||||
6. Amount: Tax amount.
|
||||
7. Total: Cumulative total to this point.
|
||||
8. Enter Row: If based on ""Previous Row Total"" you can select the row number which will be taken as a base for this calculation (default is the previous row).
|
||||
9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.","Mẫu thuế tiêu chuẩn có thể được áp dụng cho tất cả các giao dịch bán hàng. Mẫu này có thể chứa danh sách của người đứng đầu và thuế cũng khác người đứng đầu chi phí / thu nhập như ""Vận chuyển"", ""bảo hiểm"", ""Xử lý"" vv
|
||||
9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.","Mẫu thuế tiêu chuẩn có thể được áp dụng cho tất cả các giao dịch bán hàng. Mẫu này có thể chứa danh sách của người đứng đầu và thuế cũng khác người đứng đầu chi phí / thu nhập như ""Vận chuyển"", ""bảo hiểm"", ""Xử lý"" vv
|
||||
|
||||
|
||||
|
||||
|
||||
#### Chú ý Các mức thuế suất bạn xác định đây sẽ là mức thuế suất tiêu chuẩn cho tất cả các mục ** **. Nếu có những mục ** ** mà có mức giá khác nhau, chúng phải được thêm vào trong các mục ** ** Thuế bảng trong mục ** ** master.
|
||||
|
||||
#### Mô tả Cột
|
||||
#### Mô tả Cột
|
||||
|
||||
1. Loại tính:
|
||||
1. Loại tính:
|
||||
- Điều này có thể được trên Net ** Tổng số ** (đó là tổng số tiền cơ bản).
|
||||
- ** Mở Row Previous Total / Số tiền ** (cho các loại thuế, phí tích lũy). Nếu bạn chọn tùy chọn này, thuế sẽ được áp dụng như là một tỷ lệ phần trăm của các dòng trước đó (theo Biểu thuế) hoặc tổng số tiền.
|
||||
- ** ** Thực tế (như đã đề cập).
|
||||
2. Tài khoản Head: Các tài khoản sổ cái theo đó thuế này sẽ được đặt
|
||||
2. Tài khoản Head: Các tài khoản sổ cái theo đó thuế này sẽ được đặt
|
||||
3. Trung tâm chi phí: Nếu thuế / phí là một khoản thu nhập (như vận chuyển) hoặc chi phí nó cần phải được đặt trước một Trung tâm chi phí.
|
||||
4. Mô tả: Mô tả về thuế (sẽ được in trên hoá đơn / dấu ngoặc kép).
|
||||
5. Rate: Thuế suất.
|
||||
@@ -2015,7 +2015,7 @@ The tax rate you define here will be the standard tax rate for all **Items**. If
|
||||
|
||||
#### Description of Columns
|
||||
|
||||
1. Calculation Type:
|
||||
1. Calculation Type:
|
||||
- This can be on **Net Total** (that is the sum of basic amount).
|
||||
- **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.
|
||||
- **Actual** (as mentioned).
|
||||
@@ -2027,19 +2027,19 @@ The tax rate you define here will be the standard tax rate for all **Items**. If
|
||||
7. Total: Cumulative total to this point.
|
||||
8. Enter Row: If based on ""Previous Row Total"" you can select the row number which will be taken as a base for this calculation (default is the previous row).
|
||||
9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.
|
||||
10. Add or Deduct: Whether you want to add or deduct the tax.","Mẫu thuế tiêu chuẩn có thể được áp dụng cho tất cả các giao dịch mua hàng. Mẫu này có thể chứa danh sách của người đứng đầu về thuế và cũng đứng đầu chi phí khác như ""Vận chuyển"", ""bảo hiểm"", ""Xử lý"" vv
|
||||
10. Add or Deduct: Whether you want to add or deduct the tax.","Mẫu thuế tiêu chuẩn có thể được áp dụng cho tất cả các giao dịch mua hàng. Mẫu này có thể chứa danh sách của người đứng đầu về thuế và cũng đứng đầu chi phí khác như ""Vận chuyển"", ""bảo hiểm"", ""Xử lý"" vv
|
||||
|
||||
|
||||
|
||||
|
||||
#### Chú ý Các mức thuế bạn xác định ở đây sẽ là mức thuế suất tiêu chuẩn cho tất cả các mục ** **. Nếu có những mục ** ** mà có mức giá khác nhau, chúng phải được thêm vào trong các mục ** ** Thuế bảng trong mục ** ** master.
|
||||
|
||||
#### Mô tả Cột
|
||||
#### Mô tả Cột
|
||||
|
||||
1. Loại tính:
|
||||
1. Loại tính:
|
||||
- Điều này có thể được trên Net ** Tổng số ** (đó là tổng số tiền cơ bản).
|
||||
- ** Mở Row Previous Total / Số tiền ** (cho các loại thuế, phí tích lũy). Nếu bạn chọn tùy chọn này, thuế sẽ được áp dụng như là một tỷ lệ phần trăm của các dòng trước đó (theo Biểu thuế) hoặc tổng số tiền.
|
||||
- ** ** Thực tế (như đã đề cập).
|
||||
2. Tài khoản Head: Các tài khoản sổ cái theo đó thuế này sẽ được đặt
|
||||
2. Tài khoản Head: Các tài khoản sổ cái theo đó thuế này sẽ được đặt
|
||||
3. Trung tâm chi phí: Nếu thuế / phí là một khoản thu nhập (như vận chuyển) hoặc chi phí nó cần phải được đặt trước một Trung tâm chi phí.
|
||||
4. Mô tả: Mô tả về thuế (sẽ được in trên hoá đơn / dấu ngoặc kép).
|
||||
5. Rate: Thuế suất.
|
||||
@@ -2225,7 +2225,7 @@ Examples:
|
||||
1. Ways of addressing disputes, indemnity, liability, etc.
|
||||
1. Address and Contact of your Company.","Điều khoản và Điều kiện có thể được thêm vào để bán hàng và mua tiêu chuẩn.
|
||||
|
||||
Ví dụ:
|
||||
Ví dụ:
|
||||
|
||||
1. Hiệu lực của đề nghị.
|
||||
1. Điều khoản thanh toán (Trong Advance, On Credit, phần trước vv).
|
||||
@@ -2234,7 +2234,7 @@ Examples:
|
||||
1. Bảo hành nếu có.
|
||||
1. Trả Policy.
|
||||
1. Điều khoản vận chuyển, nếu áp dụng.
|
||||
1. Cách giải quyết tranh chấp, bồi thường, trách nhiệm pháp lý, vv
|
||||
1. Cách giải quyết tranh chấp, bồi thường, trách nhiệm pháp lý, vv
|
||||
1. Địa chỉ và liên hệ của công ty của bạn."
|
||||
DocType: Attendance,Leave Type,Loại bỏ
|
||||
apps/erpnext/erpnext/controllers/stock_controller.py +172,Expense / Difference account ({0}) must be a 'Profit or Loss' account,"Chi phí tài khoản / khác biệt ({0}) phải là một ""lợi nhuận hoặc lỗ 'tài khoản"
|
||||
@@ -3125,7 +3125,7 @@ DocType: Accounts Settings,"If enabled, the system will post accounting entries
|
||||
apps/erpnext/erpnext/setup/page/setup_wizard/fixtures/industry_type.py +15,Brokerage,Môi giới
|
||||
DocType: Address,Postal Code,Mã bưu chính
|
||||
DocType: Production Order Operation,"in Minutes
|
||||
Updated via 'Time Log'","trong Minutes
|
||||
Updated via 'Time Log'","trong Minutes
|
||||
Cập nhật qua 'Giờ'"
|
||||
DocType: Customer,From Lead,Từ chì
|
||||
apps/erpnext/erpnext/config/manufacturing.py +19,Orders released for production.,Đơn đặt hàng phát hành để sản xuất.
|
||||
@@ -3302,7 +3302,7 @@ apps/erpnext/erpnext/selling/page/sales_browser/sales_browser.js +121,New {0} Na
|
||||
apps/erpnext/erpnext/controllers/recurring_document.py +128,Please find attached {0} #{1},{0} # Xin vui lòng tìm thấy kèm theo {1}
|
||||
DocType: Job Applicant,Applicant Name,Tên đơn
|
||||
DocType: Authorization Rule,Customer / Item Name,Khách hàng / Item Name
|
||||
DocType: Product Bundle,"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**.
|
||||
DocType: Product Bundle,"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**.
|
||||
|
||||
The package **Item** will have ""Is Stock Item"" as ""No"" and ""Is Sales Item"" as ""Yes"".
|
||||
|
||||
@@ -3444,17 +3444,17 @@ DocType: Address Template,"<h4>Default Template</h4>
|
||||
{% if phone %}Phone: {{ phone }}<br>{% endif -%}
|
||||
{% if fax %}Fax: {{ fax }}<br>{% endif -%}
|
||||
{% if email_id %}Email: {{ email_id }}<br>{% endif -%}
|
||||
</code></pre>","<H4> Default Template </ h4>
|
||||
<p> Sử dụng <a href=""http://jinja.pocoo.org/docs/templates/""> Jinja Templating </a> và tất cả các lĩnh vực của Địa chỉ ( bao gồm Tuỳ chỉnh Fields nếu có) sẽ có sẵn </ p>
|
||||
<pre> <code> {{address_line1}} & lt; br & gt;
|
||||
</code></pre>","<H4> Default Template </ h4>
|
||||
<p> Sử dụng <a href=""http://jinja.pocoo.org/docs/templates/""> Jinja Templating </a> và tất cả các lĩnh vực của Địa chỉ ( bao gồm Tuỳ chỉnh Fields nếu có) sẽ có sẵn </ p>
|
||||
<pre> <code> {{address_line1}} & lt; br & gt;
|
||||
{% nếu address_line2%} {{address_line2}} & lt; br & gt; { endif% -%} {{
|
||||
thành phố}} & lt; br & gt;
|
||||
thành phố}} & lt; br & gt;
|
||||
{% nếu nhà nước%} {{}} nhà nước & lt; br & gt; {% endif -%} {
|
||||
% nếu mã pin%} PIN: {{mã pin}} & lt; br & gt; {% endif -%}
|
||||
{{country}} & lt; br & gt;
|
||||
{% nếu điện thoại%} Điện thoại: {{phone}} & lt; br & gt; { % endif -%}
|
||||
{% nếu fax%} Fax: {{fax}} & lt; br & gt; {% endif -%}
|
||||
{% nếu email_id%} Email: {{email_id}} & lt; br & gt ; {% endif -}%
|
||||
% nếu mã pin%} PIN: {{mã pin}} & lt; br & gt; {% endif -%}
|
||||
{{country}} & lt; br & gt;
|
||||
{% nếu điện thoại%} Điện thoại: {{phone}} & lt; br & gt; { % endif -%}
|
||||
{% nếu fax%} Fax: {{fax}} & lt; br & gt; {% endif -%}
|
||||
{% nếu email_id%} Email: {{email_id}} & lt; br & gt ; {% endif -}%
|
||||
</ code> </ pre>"
|
||||
DocType: Salary Slip Deduction,Default Amount,Số tiền mặc định
|
||||
apps/erpnext/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +89,Warehouse not found in the system,Kho không tìm thấy trong hệ thống
|
||||
@@ -3645,7 +3645,7 @@ apps/erpnext/erpnext/crm/doctype/newsletter_list/newsletter_list.js +40,New News
|
||||
apps/erpnext/erpnext/support/doctype/maintenance_schedule/maintenance_schedule.py +157,Start date should be less than end date for Item {0},Ngày bắt đầu phải nhỏ hơn ngày kết thúc cho hàng {0}
|
||||
apps/erpnext/erpnext/stock/doctype/item/item.js +17,Show Balance,Hiện Balance
|
||||
DocType: Item,"Example: ABCD.#####
|
||||
If series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.","Ví dụ:. ABCD #####
|
||||
If series is set and Serial No is not mentioned in transactions, then automatic serial number will be created based on this series. If you always want to explicitly mention Serial Nos for this item. leave this blank.","Ví dụ:. ABCD #####
|
||||
Nếu series được thiết lập và Serial No không được đề cập trong các giao dịch, số serial sau đó tự động sẽ được tạo ra dựa trên series này. Nếu bạn luôn muốn đề cập đến một cách rõ ràng nối tiếp Nos cho mặt hàng này. để trống này."
|
||||
DocType: Upload Attendance,Upload Attendance,Tải lên tham dự
|
||||
apps/erpnext/erpnext/stock/doctype/stock_entry/stock_entry.js +119,BOM and Manufacturing Quantity are required,BOM và Sản xuất Số lượng được yêu cầu
|
||||
@@ -3776,7 +3776,7 @@ DocType: Batch,Batch,Hàng loạt
|
||||
apps/erpnext/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +53,Balance,Balance
|
||||
DocType: Project,Total Expense Claim (via Expense Claims),Tổng số yêu cầu bồi thường chi phí (thông qua Tuyên bố Expense)
|
||||
DocType: User,Gender,Giới Tính
|
||||
DocType: Journal Entry,Debit Note,"Một lưu ghi nợ là do bên cho mượn, nợ và phục vụ như là một trong hai thông báo về một khoản nợ sẽ sớm nhận được hoá đơn hoặc một lời nhắc nhở đối với khoản nợ mà trước đây được lập hoá đơn và hiện đang nổi bật."
|
||||
DocType: Journal Entry,Debit Note,"nợ Ghi"
|
||||
DocType: Stock Entry,As per Stock UOM,Theo Cổ UOM
|
||||
apps/erpnext/erpnext/stock/doctype/batch/batch_list.js +7,Not Expired,Không hết hạn
|
||||
DocType: Journal Entry,Total Debit,Tổng số Nợ
|
||||
|
||||
|
Reference in New Issue
Block a user