Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7219dc698 | ||
|
|
37c6ce7b1e | ||
|
|
7028ba507e | ||
|
|
fcee17fd16 | ||
|
|
5906f02e3a | ||
|
|
721dcb1870 | ||
|
|
6385967ccd | ||
|
|
81a977cacf | ||
|
|
34058ded0e | ||
|
|
e98120b716 | ||
|
|
50e363b24e | ||
|
|
bc176b6bf8 | ||
|
|
784b49e54e | ||
|
|
04fb6e1a45 | ||
|
|
dffd7c3889 | ||
|
|
0c7594e5ed | ||
|
|
5105f909d6 | ||
|
|
2ab9d6e92c | ||
|
|
7580723ab3 | ||
|
|
52fef71c14 | ||
|
|
2cd02af80a | ||
|
|
0b0ec3536c | ||
|
|
6b35ea873b | ||
|
|
ecf220a721 | ||
|
|
04fb1c7fe4 | ||
|
|
53bd62eafc | ||
|
|
ff68bf2609 | ||
|
|
fdd0db3459 | ||
|
|
f92465981c | ||
|
|
885c70984d | ||
|
|
9c15ef903d | ||
|
|
bc799885d0 | ||
|
|
cb129264cb | ||
|
|
fe9cd1d875 | ||
|
|
64fbe955c7 | ||
|
|
0988440fd9 | ||
|
|
274dd4ada0 | ||
|
|
724fd82419 | ||
|
|
3f2604eff6 | ||
|
|
c761fefb78 | ||
|
|
203cb10ef7 | ||
|
|
90c66b1998 | ||
|
|
fed431f908 | ||
|
|
bd9745ba72 | ||
|
|
8fb123b20e | ||
|
|
333ccd212b | ||
|
|
c8cc8b7115 | ||
|
|
ec44fa95ce | ||
|
|
a25e8ea0bc | ||
|
|
ea02b9a5c3 | ||
|
|
74a63bf003 | ||
|
|
a0f2510b01 | ||
|
|
0820161157 | ||
|
|
56bc215855 | ||
|
|
8687d25f7e | ||
|
|
9cea01fa8b | ||
|
|
e06d01e3ed | ||
|
|
11243a4fb4 | ||
|
|
b9e5cd0df4 | ||
|
|
a1f1edc786 | ||
|
|
e2d8dd0663 | ||
|
|
d1605c5cb2 | ||
|
|
64ca52fb77 | ||
|
|
2f11a3bdaf | ||
|
|
943dc1f59c | ||
|
|
6d1c994bc9 | ||
|
|
93cdee4503 | ||
|
|
dfac6848cc | ||
|
|
8ad0b4e0b9 | ||
|
|
b8cd92f1aa | ||
|
|
1fe48cb820 | ||
|
|
723b046c5b | ||
|
|
035160c9a6 | ||
|
|
01c7ce1da3 | ||
|
|
9cf2910ba1 | ||
|
|
c8da7b7d32 | ||
|
|
9679d31397 | ||
|
|
38bebe1b83 | ||
|
|
ffbc11e8de | ||
|
|
818d9674d9 | ||
|
|
086f8942eb | ||
|
|
c3b492b237 | ||
|
|
9d0dd5066a | ||
|
|
e7f479b26a | ||
|
|
7ee45b4af2 | ||
|
|
5cce1e0929 | ||
|
|
be496bc91c | ||
|
|
59f063e5c3 | ||
|
|
af5820874c | ||
|
|
495db99719 | ||
|
|
123beb5a07 | ||
|
|
7ed4e080a2 | ||
|
|
e91025c0aa | ||
|
|
1ac9f2f50d | ||
|
|
bd9aa13db3 | ||
|
|
ed9d5cfdaf | ||
|
|
bf4547ca5f | ||
|
|
b45a6bcb88 | ||
|
|
ac59c2b300 | ||
|
|
6a8d7a1b91 | ||
|
|
5d71a28e97 | ||
|
|
7d100a1ee7 | ||
|
|
81dca110eb | ||
|
|
9c852108d0 | ||
|
|
e3ac032696 | ||
|
|
64cacfb077 | ||
|
|
ae4cc078ea | ||
|
|
0ee543e932 | ||
|
|
a123638d37 | ||
|
|
58996985ed | ||
|
|
5d0ce7939f | ||
|
|
4abf552d7b | ||
|
|
7c5ba957ac | ||
|
|
77f04e293a | ||
|
|
d1d3237784 | ||
|
|
76f0d26f1e | ||
|
|
65922d3079 | ||
|
|
810041cbe3 | ||
|
|
b9626659ea | ||
|
|
e3d13bee36 | ||
|
|
13bd538aca | ||
|
|
d72a24965b | ||
|
|
dc633fe360 | ||
|
|
39046d663d | ||
|
|
6b01abe9ad | ||
|
|
63e4d31aa6 | ||
|
|
f2a0161709 | ||
|
|
3c1a4a0b9b | ||
|
|
82cc2921d1 |
@@ -1,2 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
__version__ = '5.0.22'
|
||||
__version__ = '5.1.2'
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -44,7 +44,7 @@
|
||||
"label": "Is Group",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"search_index": 1
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@@ -57,7 +57,7 @@
|
||||
"permlevel": 0,
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "root_type",
|
||||
@@ -147,7 +147,8 @@
|
||||
"label": "Lft",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rgt",
|
||||
@@ -156,7 +157,8 @@
|
||||
"label": "Rgt",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "old_parent",
|
||||
@@ -171,7 +173,7 @@
|
||||
"icon": "icon-money",
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"modified": "2015-05-28 14:10:40.606010",
|
||||
"modified": "2015-06-14 20:57:55.471334",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Account",
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Party",
|
||||
"options": "party_type",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
@@ -192,7 +193,7 @@
|
||||
"icon": "icon-list",
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"modified": "2015-04-27 20:32:48.246818",
|
||||
"modified": "2015-06-14 20:57:19.800276",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GL Entry",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate
|
||||
from frappe.utils import flt, fmt_money, getdate, formatdate, cstr
|
||||
from frappe import _
|
||||
|
||||
from frappe.model.document import Document
|
||||
@@ -118,7 +118,7 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
||||
bal = flt(frappe.db.sql("""select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
from `tabGL Entry`
|
||||
where against_voucher_type=%s and against_voucher=%s
|
||||
and account = %s and party_type=%s and party=%s""",
|
||||
and account = %s and ifnull(party_type, '')=%s and ifnull(party, '')=%s""",
|
||||
(against_voucher_type, against_voucher, account, party_type, party))[0][0] or 0.0)
|
||||
|
||||
if against_voucher_type == 'Purchase Invoice':
|
||||
@@ -127,8 +127,9 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
|
||||
against_voucher_amount = flt(frappe.db.sql("""
|
||||
select sum(ifnull(debit, 0)) - sum(ifnull(credit, 0))
|
||||
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
|
||||
and account = %s and party_type=%s and party=%s and ifnull(against_voucher, '') = ''""",
|
||||
(against_voucher, account, party_type, party))[0][0])
|
||||
and account = %s and ifnull(party_type, '')=%s and ifnull(party, '')=%s
|
||||
and ifnull(against_voucher, '') = ''""",
|
||||
(against_voucher, account, cstr(party_type), cstr(party)))[0][0])
|
||||
|
||||
if not against_voucher_amount:
|
||||
frappe.throw(_("Against Journal Entry {0} is already adjusted against some other voucher")
|
||||
@@ -157,3 +158,22 @@ def validate_frozen_account(account, adv_adj=None):
|
||||
frappe.throw(_("Account {0} is frozen").format(account))
|
||||
elif frozen_accounts_modifier not in frappe.get_roles():
|
||||
frappe.throw(_("Not authorized to edit frozen Account {0}").format(account))
|
||||
|
||||
def update_against_account(voucher_type, voucher_no):
|
||||
entries = frappe.db.get_all("GL Entry",
|
||||
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
|
||||
fields=["name", "party", "against", "debit", "credit", "account"])
|
||||
|
||||
accounts_debited, accounts_credited = [], []
|
||||
for d in entries:
|
||||
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
|
||||
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
|
||||
|
||||
for d in entries:
|
||||
if flt(d.debit > 0):
|
||||
new_against = ", ".join(list(set(accounts_credited)))
|
||||
if flt(d.credit > 0):
|
||||
new_against = ", ".join(list(set(accounts_debited)))
|
||||
|
||||
if d.against != new_against:
|
||||
frappe.db.set_value("GL Entry", d.name, "against", new_against)
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "Posting Date",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "posting_date",
|
||||
@@ -445,7 +445,7 @@
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-04-27 20:32:31.655580",
|
||||
"modified": "2015-06-29 15:28:12.529019",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -81,7 +81,7 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("Row {0}: Party Type and Party is only applicable against Receivable / Payable account").format(d.idx))
|
||||
|
||||
def check_credit_limit(self):
|
||||
customers = list(set([d.party for d in self.get("accounts")
|
||||
customers = list(set([d.party for d in self.get("accounts")
|
||||
if d.party_type=="Customer" and d.party and flt(d.debit) > 0]))
|
||||
if customers:
|
||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||
@@ -243,8 +243,8 @@ class JournalEntry(AccountsController):
|
||||
def set_against_account(self):
|
||||
accounts_debited, accounts_credited = [], []
|
||||
for d in self.get("accounts"):
|
||||
if flt(d.debit > 0): accounts_debited.append(d.account)
|
||||
if flt(d.credit) > 0: accounts_credited.append(d.account)
|
||||
if flt(d.debit > 0): accounts_debited.append(d.party or d.account)
|
||||
if flt(d.credit) > 0: accounts_credited.append(d.party or d.account)
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if flt(d.debit > 0): d.against_account = ", ".join(list(set(accounts_credited)))
|
||||
@@ -275,29 +275,27 @@ class JournalEntry(AccountsController):
|
||||
else:
|
||||
msgprint(_("Please enter Reference date"), raise_exception=frappe.MandatoryError)
|
||||
|
||||
company_currency = get_company_currency(self.company)
|
||||
|
||||
for d in self.get('accounts'):
|
||||
if d.against_invoice and d.credit:
|
||||
currency = frappe.db.get_value("Sales Invoice", d.against_invoice, "currency")
|
||||
|
||||
r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = currency), \
|
||||
r.append(_("{0} against Sales Invoice {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
|
||||
d.against_invoice))
|
||||
|
||||
if d.against_sales_order and d.credit:
|
||||
currency = frappe.db.get_value("Sales Order", d.against_sales_order, "currency")
|
||||
r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = currency), \
|
||||
r.append(_("{0} against Sales Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
|
||||
d.against_sales_order))
|
||||
|
||||
if d.against_voucher and d.debit:
|
||||
bill_no = frappe.db.sql("""select bill_no, bill_date, currency
|
||||
bill_no = frappe.db.sql("""select bill_no, bill_date
|
||||
from `tabPurchase Invoice` where name=%s""", d.against_voucher)
|
||||
if bill_no and bill_no[0][0] and bill_no[0][0].lower().strip() \
|
||||
not in ['na', 'not applicable', 'none']:
|
||||
r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=bill_no[0][2]), bill_no[0][0],
|
||||
r.append(_('{0} against Bill {1} dated {2}').format(fmt_money(flt(d.debit), currency=company_currency), bill_no[0][0],
|
||||
bill_no[0][1] and formatdate(bill_no[0][1].strftime('%Y-%m-%d'))))
|
||||
|
||||
if d.against_purchase_order and d.debit:
|
||||
currency = frappe.db.get_value("Purchase Order", d.against_purchase_order, "currency")
|
||||
r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = currency), \
|
||||
r.append(_("{0} against Purchase Order {1}").format(fmt_money(flt(d.credit), currency = company_currency), \
|
||||
d.against_purchase_order))
|
||||
|
||||
if self.user_remark:
|
||||
@@ -428,7 +426,7 @@ class JournalEntry(AccountsController):
|
||||
def validate_expense_claim(self):
|
||||
for d in self.accounts:
|
||||
if d.against_expense_claim:
|
||||
sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim",
|
||||
sanctioned_amount, reimbursed_amount = frappe.db.get_value("Expense Claim",
|
||||
d.against_expense_claim, ("total_sanctioned_amount", "total_amount_reimbursed"))
|
||||
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
|
||||
if d.debit > pending_amount:
|
||||
@@ -544,7 +542,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
and (ifnull(jv_detail.against_invoice, '') = '' and ifnull(jv_detail.against_voucher, '') = ''
|
||||
and ifnull(jv_detail.against_jv, '') = '' )
|
||||
and jv.docstatus = 1 and jv.{0} like %s order by jv.name desc limit %s, %s""".format(searchfield),
|
||||
(filters["account"], cstr(filters["party"]), "%{0}%".format(txt), start, page_len))
|
||||
(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding(args):
|
||||
|
||||
@@ -224,3 +224,4 @@ cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
|
||||
else
|
||||
cur_frm.pformat.print_heading = __("Purchase Invoice");
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import cint, formatdate, flt
|
||||
from frappe.utils import cint, formatdate, flt, getdate
|
||||
from frappe import msgprint, _, throw
|
||||
from erpnext.setup.utils import get_company_currency
|
||||
import frappe.defaults
|
||||
@@ -88,9 +88,7 @@ class PurchaseInvoice(BuyingController):
|
||||
throw(_("Conversion rate cannot be 0 or 1"))
|
||||
|
||||
def validate_credit_to_acc(self):
|
||||
root_type, account_type = frappe.db.get_value("Account", self.credit_to, ["root_type", "account_type"])
|
||||
if root_type != "Liability":
|
||||
frappe.throw(_("Credit To account must be a liability account"))
|
||||
account_type = frappe.db.get_value("Account", self.credit_to, "account_type")
|
||||
if account_type != "Payable":
|
||||
frappe.throw(_("Credit To account must be a Payable account"))
|
||||
|
||||
@@ -166,7 +164,7 @@ class PurchaseInvoice(BuyingController):
|
||||
elif item.expense_account not in against_accounts:
|
||||
# if no auto_accounting_for_stock or not a stock item
|
||||
against_accounts.append(item.expense_account)
|
||||
|
||||
|
||||
self.against_expense_account = ",".join(against_accounts)
|
||||
|
||||
def po_required(self):
|
||||
@@ -273,7 +271,7 @@ class PurchaseInvoice(BuyingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
"against": self.credit_to,
|
||||
"against": self.supplier,
|
||||
"debit": tax.add_deduct_tax == "Add" and tax.base_tax_amount_after_discount_amount or 0,
|
||||
"credit": tax.add_deduct_tax == "Deduct" and tax.base_tax_amount_after_discount_amount or 0,
|
||||
"remarks": self.remarks,
|
||||
@@ -297,7 +295,7 @@ class PurchaseInvoice(BuyingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.expense_account,
|
||||
"against": self.credit_to,
|
||||
"against": self.supplier,
|
||||
"debit": item.base_net_amount,
|
||||
"remarks": self.remarks,
|
||||
"cost_center": item.cost_center
|
||||
@@ -317,7 +315,7 @@ class PurchaseInvoice(BuyingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": stock_received_but_not_billed,
|
||||
"against": self.credit_to,
|
||||
"against": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, self.precision("item_tax_amount", item)),
|
||||
"remarks": self.remarks or "Accounting Entry for Stock"
|
||||
})
|
||||
@@ -343,7 +341,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict({
|
||||
"account": expenses_included_in_valuation,
|
||||
"cost_center": cost_center,
|
||||
"against": self.credit_to,
|
||||
"against": self.supplier,
|
||||
"credit": applicable_amount,
|
||||
"remarks": self.remarks or "Accounting Entry for Stock"
|
||||
})
|
||||
@@ -357,7 +355,7 @@ class PurchaseInvoice(BuyingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.write_off_account,
|
||||
"against": self.credit_to,
|
||||
"against": self.supplier,
|
||||
"credit": flt(self.write_off_amount),
|
||||
"remarks": self.remarks,
|
||||
"cost_center": self.write_off_cost_center
|
||||
@@ -376,7 +374,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.update_billing_status_for_zero_amount_refdoc("Purchase Order")
|
||||
self.make_gl_entries_on_cancel()
|
||||
self.update_project()
|
||||
|
||||
|
||||
def update_project(self):
|
||||
project_list = []
|
||||
for d in self.items:
|
||||
@@ -386,10 +384,10 @@ class PurchaseInvoice(BuyingController):
|
||||
project.update_purchase_costing()
|
||||
project.save()
|
||||
project_list.append(d.project_name)
|
||||
|
||||
|
||||
def validate_supplier_invoice(self):
|
||||
if self.bill_date:
|
||||
if self.bill_date > self.posting_date:
|
||||
if getdate(self.bill_date) > getdate(self.posting_date):
|
||||
frappe.throw("Supplier Invoice Date cannot be greater than Posting Date")
|
||||
if self.bill_no:
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
|
||||
@@ -412,4 +410,4 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabAccount.company = '%(company)s'
|
||||
and tabAccount.%(key)s LIKE '%(txt)s'
|
||||
%(mcond)s""" % {'company': filters['company'], 'key': searchfield,
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)})
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype)})
|
||||
|
||||
@@ -49,6 +49,23 @@
|
||||
"read_only": 0,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
"label": "Image",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "image_view",
|
||||
"fieldtype": "Image",
|
||||
"label": "Image View",
|
||||
"options": "image",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_and_rate",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -452,7 +469,7 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2015-06-02 14:18:56.294949",
|
||||
"modified": "2015-07-02 03:00:44.496683",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -36,7 +36,7 @@ def get_items(price_list, sales_or_purchase, item=None):
|
||||
if(locate(%(_name)s, i.item_name), locate(%(_name)s, i.item_name), 99999),
|
||||
if(locate(%(_name)s, i.variant_of), locate(%(_name)s, i.variant_of), 99999),
|
||||
if(locate(%(_name)s, i.item_group), locate(%(_name)s, i.item_group), 99999),"""
|
||||
args["name"] = "%%%s%%" % item
|
||||
args["name"] = "%%%s%%" % frappe.db.escape(item)
|
||||
args["_name"] = item.replace("%", "")
|
||||
|
||||
# locate function is used to sort by closest match from the beginning of the value
|
||||
|
||||
@@ -392,8 +392,6 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
cur_frm.set_query("debit_to", function(doc) {
|
||||
return{
|
||||
filters: [
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"options": "Customer",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"read_only": 0
|
||||
"read_only": 0,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "customer",
|
||||
@@ -502,7 +503,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -880,7 +881,7 @@
|
||||
"options": "Project",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"search_index": 1
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.source == 'Campaign'",
|
||||
@@ -1226,6 +1227,15 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_recurring==1",
|
||||
"fieldname": "recurring_print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Recurring Print Format",
|
||||
"options": "Print Format",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "against_income_account",
|
||||
"fieldtype": "Small Text",
|
||||
@@ -1243,7 +1253,7 @@
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-05-27 02:48:02.897865",
|
||||
"modified": "2015-06-22 06:39:22.072544",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -65,8 +65,7 @@ class SalesInvoice(SellingController):
|
||||
self.set_against_income_account()
|
||||
self.validate_c_form()
|
||||
self.validate_time_logs_are_submitted()
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount",
|
||||
"items")
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
||||
|
||||
def on_submit(self):
|
||||
super(SalesInvoice, self).on_submit()
|
||||
@@ -236,9 +235,7 @@ class SalesInvoice(SellingController):
|
||||
reconcile_against_document(lst)
|
||||
|
||||
def validate_debit_to_acc(self):
|
||||
root_type, account_type = frappe.db.get_value("Account", self.debit_to, ["root_type", "account_type"])
|
||||
if root_type != "Asset":
|
||||
frappe.throw(_("Debit To account must be a liability account"))
|
||||
account_type = frappe.db.get_value("Account", self.debit_to, "account_type")
|
||||
if account_type != "Receivable":
|
||||
frappe.throw(_("Debit To account must be a Receivable account"))
|
||||
|
||||
@@ -506,7 +503,7 @@ class SalesInvoice(SellingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": tax.account_head,
|
||||
"against": self.debit_to,
|
||||
"against": self.customer,
|
||||
"credit": flt(tax.base_tax_amount_after_discount_amount),
|
||||
"remarks": self.remarks,
|
||||
"cost_center": tax.cost_center
|
||||
@@ -520,7 +517,7 @@ class SalesInvoice(SellingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": item.income_account,
|
||||
"against": self.debit_to,
|
||||
"against": self.customer,
|
||||
"credit": item.base_net_amount,
|
||||
"remarks": self.remarks,
|
||||
"cost_center": item.cost_center
|
||||
@@ -551,7 +548,7 @@ class SalesInvoice(SellingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.cash_bank_account,
|
||||
"against": self.debit_to,
|
||||
"against": self.customer,
|
||||
"debit": self.paid_amount,
|
||||
"remarks": self.remarks,
|
||||
})
|
||||
@@ -575,7 +572,7 @@ class SalesInvoice(SellingController):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": self.write_off_account,
|
||||
"against": self.debit_to,
|
||||
"against": self.customer,
|
||||
"debit": self.write_off_amount,
|
||||
"remarks": self.remarks,
|
||||
"cost_center": self.write_off_cost_center
|
||||
@@ -590,7 +587,7 @@ def get_list_context(context=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_cash_account(mode_of_payment, company):
|
||||
account = frappe.db.get_value("Mode of Payment Account",
|
||||
account = frappe.db.get_value("Mode of Payment Account",
|
||||
{"parent": mode_of_payment, "company": company}, "default_account")
|
||||
if not account:
|
||||
frappe.msgprint(_("Please set default Cash or Bank account in Mode of Payment {0}").format(mode_of_payment))
|
||||
@@ -614,7 +611,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabAccount.company = '%(company)s'
|
||||
and tabAccount.%(key)s LIKE '%(txt)s'
|
||||
%(mcond)s""" % {'company': filters['company'], 'key': searchfield,
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype)})
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype)})
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_note(source_name, target_doc=None):
|
||||
|
||||
@@ -68,6 +68,23 @@
|
||||
"reqd": 1,
|
||||
"width": "200px"
|
||||
},
|
||||
{
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
"label": "Image",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "image_view",
|
||||
"fieldtype": "Image",
|
||||
"label": "Image View",
|
||||
"options": "image",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_and_rate",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -505,7 +522,7 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2015-06-02 14:18:45.176726",
|
||||
"modified": "2015-07-02 02:59:08.413213",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import flt, cstr
|
||||
from frappe.utils import flt, cstr, cint
|
||||
from frappe import _
|
||||
from frappe.model.meta import get_field_precision
|
||||
from erpnext.accounts.utils import validate_expense_against_budget
|
||||
@@ -82,14 +82,15 @@ def make_entry(args, adv_adj, update_outstanding):
|
||||
gle.submit()
|
||||
|
||||
def validate_account_for_auto_accounting_for_stock(gl_map):
|
||||
if gl_map[0].voucher_type=="Journal Entry":
|
||||
aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||
where account_type = 'Warehouse' and ifnull(warehouse, '')!=''""")]
|
||||
if cint(frappe.db.get_single_value("Accounts Settings", "auto_accounting_for_stock")) \
|
||||
and gl_map[0].voucher_type=="Journal Entry":
|
||||
aii_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||
where account_type = 'Warehouse' and ifnull(warehouse, '')!=''""")]
|
||||
|
||||
for entry in gl_map:
|
||||
if entry.account in aii_accounts:
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||
.format(entry.account), StockAccountInvalidTransaction)
|
||||
for entry in gl_map:
|
||||
if entry.account in aii_accounts:
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
|
||||
.format(entry.account), StockAccountInvalidTransaction)
|
||||
|
||||
def round_off_debit_credit(gl_map):
|
||||
precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
|
||||
|
||||
@@ -51,7 +51,7 @@ class ReceivablePayableReport(object):
|
||||
currency_precision = get_currency_precision() or 2
|
||||
dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit"
|
||||
|
||||
voucher_details = self.get_voucher_details()
|
||||
voucher_details = self.get_voucher_details(args.get("party_type"))
|
||||
|
||||
future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type"))
|
||||
|
||||
@@ -153,23 +153,26 @@ class ReceivablePayableReport(object):
|
||||
|
||||
return self.party_map
|
||||
|
||||
def get_voucher_details(self):
|
||||
def get_voucher_details(self, party_type):
|
||||
voucher_details = frappe._dict()
|
||||
|
||||
if party_type == "Customer":
|
||||
for si in frappe.db.sql("""select name, due_date
|
||||
from `tabSales Invoice` where docstatus=1""", as_dict=1):
|
||||
voucher_details.setdefault(si.name, si)
|
||||
|
||||
for si in frappe.db.sql("""select name, due_date
|
||||
from `tabSales Invoice` where docstatus=1""", as_dict=1):
|
||||
voucher_details.setdefault(si.name, si)
|
||||
|
||||
for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date
|
||||
from `tabPurchase Invoice` where docstatus=1""", as_dict=1):
|
||||
voucher_details.setdefault(pi.name, pi)
|
||||
if party_type == "Supplier":
|
||||
for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date
|
||||
from `tabPurchase Invoice` where docstatus=1""", as_dict=1):
|
||||
voucher_details.setdefault(pi.name, pi)
|
||||
|
||||
return voucher_details
|
||||
|
||||
def get_gl_entries(self, party_type):
|
||||
if not hasattr(self, "gl_entries"):
|
||||
conditions, values = self.prepare_conditions(party_type)
|
||||
self.gl_entries = frappe.db.sql("""select * from `tabGL Entry`
|
||||
self.gl_entries = frappe.db.sql("""select name, posting_date, account, party_type, party, debit, credit,
|
||||
voucher_type, voucher_no, against_voucher_type, against_voucher from `tabGL Entry`
|
||||
where docstatus < 2 and party_type=%s {0} order by posting_date, party"""
|
||||
.format(conditions), values, as_dict=True)
|
||||
|
||||
@@ -187,7 +190,7 @@ class ReceivablePayableReport(object):
|
||||
|
||||
if self.filters.get(party_type_field):
|
||||
conditions.append("party=%s")
|
||||
values.append(self.filters.get(party_type_field))
|
||||
values.append(self.filters.get(party_type_field))
|
||||
|
||||
return " and ".join(conditions), values
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ def get_data(company, root_type, balance_must_be, period_list, ignore_closing_en
|
||||
gl_entries_by_account = get_gl_entries(company, period_list[0]["from_date"], period_list[-1]["to_date"],
|
||||
accounts[0].lft, accounts[0].rgt, ignore_closing_entries=ignore_closing_entries)
|
||||
|
||||
calculate_values(accounts, gl_entries_by_account, period_list)
|
||||
calculate_values(accounts_by_name, gl_entries_by_account, period_list)
|
||||
accumulate_values_into_parents(accounts, accounts_by_name, period_list)
|
||||
out = prepare_data(accounts, balance_must_be, period_list)
|
||||
|
||||
@@ -92,16 +92,14 @@ def get_data(company, root_type, balance_must_be, period_list, ignore_closing_en
|
||||
|
||||
return out
|
||||
|
||||
def calculate_values(accounts, gl_entries_by_account, period_list):
|
||||
for d in accounts:
|
||||
for name in ([d.name] + (d.collapsed_children or [])):
|
||||
for entry in gl_entries_by_account.get(name, []):
|
||||
for period in period_list:
|
||||
entry.posting_date = getdate(entry.posting_date)
|
||||
|
||||
# check if posting date is within the period
|
||||
if entry.posting_date <= period.to_date:
|
||||
d[period.key] = d.get(period.key, 0.0) + flt(entry.debit) - flt(entry.credit)
|
||||
def calculate_values(accounts_by_name, gl_entries_by_account, period_list):
|
||||
for entries in gl_entries_by_account.values():
|
||||
for entry in entries:
|
||||
d = accounts_by_name.get(entry.account)
|
||||
for period in period_list:
|
||||
# check if posting date is within the period
|
||||
if entry.posting_date <= period.to_date:
|
||||
d[period.key] = d.get(period.key, 0.0) + flt(entry.debit) - flt(entry.credit)
|
||||
|
||||
|
||||
def accumulate_values_into_parents(accounts, accounts_by_name, period_list):
|
||||
@@ -159,22 +157,8 @@ def add_total_row(out, balance_must_be, period_list):
|
||||
out.append({})
|
||||
|
||||
def get_accounts(company, root_type):
|
||||
# root lft, rgt
|
||||
root_account = frappe.db.sql("""select lft, rgt from `tabAccount`
|
||||
where company=%s and root_type=%s and ifnull(parent_account, '') = ''
|
||||
order by lft limit 1""",
|
||||
(company, root_type), as_dict=True)
|
||||
|
||||
if not root_account:
|
||||
return None
|
||||
|
||||
lft, rgt = root_account[0].lft, root_account[0].rgt
|
||||
|
||||
accounts = frappe.db.sql("""select * from `tabAccount`
|
||||
where company=%(company)s and lft >= %(lft)s and rgt <= %(rgt)s order by lft""",
|
||||
{ "company": company, "lft": lft, "rgt": rgt }, as_dict=True)
|
||||
|
||||
return accounts
|
||||
return frappe.db.sql("""select name, parent_account, lft, rgt, root_type, report_type, account_name from `tabAccount`
|
||||
where company=%s and root_type=%s order by lft""", (company, root_type), as_dict=True)
|
||||
|
||||
def filter_accounts(accounts, depth=10):
|
||||
parent_children_map = {}
|
||||
@@ -196,14 +180,6 @@ def filter_accounts(accounts, depth=10):
|
||||
filtered_accounts.append(child)
|
||||
add_to_list(child.name, level + 1)
|
||||
|
||||
else:
|
||||
# include all children at level lower than the depth
|
||||
parent_account = accounts_by_name[parent]
|
||||
parent_account["collapsed_children"] = []
|
||||
for d in accounts:
|
||||
if d.lft > parent_account.lft and d.rgt < parent_account.rgt:
|
||||
parent_account["collapsed_children"].append(d.name)
|
||||
|
||||
add_to_list(None, 0)
|
||||
|
||||
return filtered_accounts, accounts_by_name
|
||||
@@ -234,7 +210,7 @@ def get_gl_entries(company, from_date, to_date, root_lft, root_rgt, ignore_closi
|
||||
if from_date:
|
||||
additional_conditions.append("and posting_date >= %(from_date)s")
|
||||
|
||||
gl_entries = frappe.db.sql("""select * from `tabGL Entry`
|
||||
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
{additional_conditions}
|
||||
and posting_date <= %(to_date)s
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.utils import flt, getdate
|
||||
from frappe.utils import flt, getdate, cstr
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -66,7 +66,7 @@ def get_gl_entries(filters):
|
||||
|
||||
gl_entries = frappe.db.sql("""select posting_date, account, party_type, party,
|
||||
sum(ifnull(debit, 0)) as debit, sum(ifnull(credit, 0)) as credit,
|
||||
voucher_type, voucher_no, cost_center, remarks, is_opening, against
|
||||
voucher_type, voucher_no, cost_center, remarks, against, is_opening
|
||||
from `tabGL Entry`
|
||||
where company=%(company)s {conditions}
|
||||
{group_by_condition}
|
||||
@@ -91,6 +91,9 @@ def get_conditions(filters):
|
||||
|
||||
if filters.get("party"):
|
||||
conditions.append("party=%(party)s")
|
||||
|
||||
if not (filters.get("account") or filters.get("party") or filters.get("group_by_account")):
|
||||
conditions.append("posting_date >=%(from_date)s")
|
||||
|
||||
from frappe.desk.reportview import build_match_conditions
|
||||
match_conditions = build_match_conditions("GL Entry")
|
||||
@@ -148,14 +151,15 @@ def initialize_gle_map(gl_entries):
|
||||
|
||||
def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
opening, total_debit, total_credit = 0, 0, 0
|
||||
|
||||
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
|
||||
for gle in gl_entries:
|
||||
amount = flt(gle.debit, 3) - flt(gle.credit, 3)
|
||||
if gle.posting_date < getdate(filters.from_date):
|
||||
if (filters.get("account") or filters.get("party") or filters.get("group_by_account")) \
|
||||
and (gle.posting_date < from_date or cstr(gle.is_opening) == "Yes"):
|
||||
gle_map[gle.account].opening += amount
|
||||
if filters.get("account") or filters.get("party"):
|
||||
opening += amount
|
||||
elif gle.posting_date <= getdate(filters.to_date):
|
||||
elif gle.posting_date <= to_date:
|
||||
gle_map[gle.account].entries.append(gle)
|
||||
gle_map[gle.account].total_debit += flt(gle.debit, 3)
|
||||
gle_map[gle.account].total_credit += flt(gle.credit, 3)
|
||||
|
||||
@@ -175,16 +175,15 @@ class GrossProfitGenerator(object):
|
||||
|
||||
else:
|
||||
if row.update_stock or row.dn_detail:
|
||||
parenttype, parent, item_row = row.parenttype, row.parent, row.item_row
|
||||
if row.dn_detail:
|
||||
row.parenttype = "Delivery Note"
|
||||
row.parent = row.delivery_note
|
||||
row.item_row = row.dn_detail
|
||||
parenttype, parent, item_row = "Delivery Note", row.delivery_note, row.dn_detail
|
||||
|
||||
my_sle = self.sle.get((item_code, row.warehouse))
|
||||
for i, sle in enumerate(my_sle):
|
||||
# find the stock valution rate from stock ledger entry
|
||||
if sle.voucher_type == row.parenttype and row.parent == sle.voucher_no and \
|
||||
sle.voucher_detail_no == row.item_row:
|
||||
if sle.voucher_type == parenttype and parent == sle.voucher_no and \
|
||||
sle.voucher_detail_no == item_row:
|
||||
previous_stock_value = len(my_sle) > i+1 and \
|
||||
flt(my_sle[i+1].stock_value) or 0.0
|
||||
return previous_stock_value - flt(sle.stock_value)
|
||||
|
||||
@@ -9,8 +9,9 @@ from frappe.utils import flt
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
|
||||
columns = get_columns()
|
||||
validate_filters(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
entries = get_entries(filters)
|
||||
invoice_posting_date_map = get_invoice_posting_date_map(filters)
|
||||
against_date = ""
|
||||
@@ -36,11 +37,18 @@ def execute(filters=None):
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
|
||||
def validate_filters(filters):
|
||||
if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \
|
||||
(filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"):
|
||||
frappe.throw(_("{0} payment entries can not be filtered by {1}")\
|
||||
.format(filters.payment_type, filters.party_type))
|
||||
|
||||
def get_columns():
|
||||
def get_columns(filters):
|
||||
return [_("Journal Entry") + ":Link/Journal Entry:140",
|
||||
_("Party Type") + ":Link/DocType:100", _("Party") + ":Dynamic Link/party_type:140",
|
||||
_("Posting Date") + ":Date:100", _("Against Invoice") + ":Link/Purchase Invoice:130",
|
||||
_("Party Type") + ":Link/DocType:100", _("Party") + ":Dynamic Link/Party Type:140",
|
||||
_("Posting Date") + ":Date:100",
|
||||
_("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",
|
||||
_("Reference No") + "::100", _("Reference Date") + ":Date:100", _("Remarks") + "::150", _("Age") +":Int:40",
|
||||
"0-30:Currency:100", "30-60:Currency:100", "60-90:Currency:100", _("90-Above") + ":Currency:100"
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, getdate, formatdate
|
||||
from frappe.utils import cint, flt, getdate, formatdate, cstr
|
||||
from erpnext.accounts.report.financial_statements import filter_accounts, get_gl_entries
|
||||
|
||||
value_fields = ("opening_debit", "opening_credit", "debit", "credit", "closing_debit", "closing_credit")
|
||||
|
||||
def execute(filters):
|
||||
def execute(filters=None):
|
||||
validate_filters(filters)
|
||||
data = get_data(filters)
|
||||
columns = get_columns()
|
||||
@@ -45,8 +45,8 @@ def validate_filters(filters):
|
||||
filters.to_date = filters.year_end_date
|
||||
|
||||
def get_data(filters):
|
||||
accounts = frappe.db.sql("""select * from `tabAccount` where company=%s order by lft""",
|
||||
filters.company, as_dict=True)
|
||||
accounts = frappe.db.sql("""select name, parent_account, account_name, root_type, report_type, lft, rgt
|
||||
from `tabAccount` where company=%s order by lft""", filters.company, as_dict=True)
|
||||
|
||||
if not accounts:
|
||||
return None
|
||||
@@ -56,17 +56,58 @@ def get_data(filters):
|
||||
min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount`
|
||||
where company=%s""", (filters.company,))[0]
|
||||
|
||||
gl_entries_by_account = get_gl_entries(filters.company, None, filters.to_date, min_lft, max_rgt,
|
||||
gl_entries_by_account = get_gl_entries(filters.company, filters.from_date, filters.to_date, min_lft, max_rgt,
|
||||
ignore_closing_entries=not flt(filters.with_period_closing_entry))
|
||||
|
||||
total_row = calculate_values(accounts, gl_entries_by_account, filters)
|
||||
opening_balances = get_opening_balances(filters)
|
||||
|
||||
total_row = calculate_values(accounts, gl_entries_by_account, opening_balances, filters)
|
||||
accumulate_values_into_parents(accounts, accounts_by_name)
|
||||
|
||||
data = prepare_data(accounts, filters, total_row)
|
||||
|
||||
return data
|
||||
|
||||
def get_opening_balances(filters):
|
||||
balance_sheet_opening = get_rootwise_opening_balances(filters, "Balance Sheet")
|
||||
pl_opening = get_rootwise_opening_balances(filters, "Profit and Loss")
|
||||
|
||||
balance_sheet_opening.update(pl_opening)
|
||||
return balance_sheet_opening
|
||||
|
||||
|
||||
def get_rootwise_opening_balances(filters, report_type):
|
||||
additional_conditions = " and posting_date >= %(year_start_date)s" \
|
||||
if report_type == "Profit and Loss" else ""
|
||||
|
||||
if not flt(filters.with_period_closing_entry):
|
||||
additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'"
|
||||
|
||||
gle = frappe.db.sql("""
|
||||
select
|
||||
account, sum(ifnull(debit, 0)) as opening_debit, sum(ifnull(credit, 0)) as opening_credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
company=%(company)s
|
||||
{additional_conditions}
|
||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
||||
and account in (select name from `tabAccount` where report_type=%(report_type)s)
|
||||
group by account""".format(additional_conditions=additional_conditions),
|
||||
{
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"report_type": report_type,
|
||||
"year_start_date": filters.year_start_date
|
||||
},
|
||||
as_dict=True)
|
||||
|
||||
opening = frappe._dict()
|
||||
for d in gle:
|
||||
opening.setdefault(d.account, d)
|
||||
|
||||
return opening
|
||||
|
||||
def calculate_values(accounts, gl_entries_by_account, filters):
|
||||
def calculate_values(accounts, gl_entries_by_account, opening_balances, filters):
|
||||
init = {
|
||||
"opening_debit": 0.0,
|
||||
"opening_credit": 0.0,
|
||||
@@ -87,30 +128,18 @@ def calculate_values(accounts, gl_entries_by_account, filters):
|
||||
for d in accounts:
|
||||
d.update(init.copy())
|
||||
|
||||
# add opening
|
||||
d["opening_debit"] = opening_balances.get(d.name, {}).get("opening_debit", 0)
|
||||
d["opening_credit"] = opening_balances.get(d.name, {}).get("opening_credit", 0)
|
||||
|
||||
for entry in gl_entries_by_account.get(d.name, []):
|
||||
posting_date = getdate(entry.posting_date)
|
||||
|
||||
# opening
|
||||
if posting_date < filters.from_date:
|
||||
is_valid_opening = (d.root_type in ("Asset", "Liability", "Equity") or
|
||||
(filters.year_start_date <= posting_date < filters.from_date))
|
||||
|
||||
if is_valid_opening:
|
||||
d["opening_debit"] += flt(entry.debit)
|
||||
d["opening_credit"] += flt(entry.credit)
|
||||
|
||||
elif posting_date <= filters.to_date:
|
||||
|
||||
if entry.is_opening == "Yes" and d.root_type in ("Asset", "Liability", "Equity"):
|
||||
d["opening_debit"] += flt(entry.debit)
|
||||
d["opening_credit"] += flt(entry.credit)
|
||||
|
||||
else:
|
||||
d["debit"] += flt(entry.debit)
|
||||
d["credit"] += flt(entry.credit)
|
||||
if cstr(entry.is_opening) != "Yes":
|
||||
d["debit"] += flt(entry.debit)
|
||||
d["credit"] += flt(entry.credit)
|
||||
|
||||
total_row["debit"] += d["debit"]
|
||||
total_row["credit"] += d["credit"]
|
||||
|
||||
|
||||
return total_row
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ def get_balance_on(account=None, date=None, party_type=None, party=None):
|
||||
# different filter for group and ledger - improved performance
|
||||
if acc.is_group:
|
||||
cond.append("""exists (
|
||||
select * from `tabAccount` ac where ac.name = gle.account
|
||||
select name from `tabAccount` ac where ac.name = gle.account
|
||||
and ac.lft >= %s and ac.rgt <= %s
|
||||
)""" % (acc.lft, acc.rgt))
|
||||
else:
|
||||
@@ -397,7 +397,7 @@ def get_outstanding_invoices(amount_query, account, party_type, party):
|
||||
|
||||
for d in outstanding_voucher_list:
|
||||
payment_amount = frappe.db.sql("""
|
||||
select ifnull(sum(ifnull({amount_query}, 0)), 0)
|
||||
select ifnull(sum({amount_query}), 0)
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
|
||||
@@ -457,7 +457,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -868,12 +868,21 @@
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_recurring==1",
|
||||
"fieldname": "recurring_print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Recurring Print Format",
|
||||
"options": "Print Format",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-06-02 17:15:44.711032",
|
||||
"modified": "2015-06-22 07:30:36.259753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -439,7 +439,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -660,7 +660,7 @@
|
||||
"icon": "icon-shopping-cart",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-06-02 17:15:57.283516",
|
||||
"modified": "2015-06-15 15:39:08.954248",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
||||
17
erpnext/change_log/v5/v5_0_25.md
Normal file
17
erpnext/change_log/v5/v5_0_25.md
Normal file
@@ -0,0 +1,17 @@
|
||||
- Performance upgrade in Trial Balance, General Ledger, AR/AP, Balance Sheet and P&L Statement reports
|
||||
- Add index on Account and GL Entry, Sales Invoice and Purchase Invoice table
|
||||
- Don't create Time Logs against Production Order if Workstation is not specified in Operations
|
||||
- Task should be mandatory in Time Log only when Project is mentioned but Production Order is not
|
||||
- Supplier invoice no unique validation and supplier invoice date can not be after posting date
|
||||
- Removed BOM No from mandatory from Stock Entry against Production Order
|
||||
- Load tasks in project for printing purpose
|
||||
- Added Customers Not Buying Since Long Time against Sales Invoice
|
||||
- POS - search by Item Group
|
||||
- Payment period based on invoice date: show party columns and filter based on party
|
||||
- Barcode added to Purchase Receipt
|
||||
- Fetch item name and desc on change of item code in Quality Inspection
|
||||
- Show item name in item grid view based 'In List View' property
|
||||
- Validate and update manufactured qty in Stock Entry
|
||||
- Show only users with Expense Approver role in Expense Claim Approver field
|
||||
- Over Production Allowance Percentage Setting added to Manufacturing Settings
|
||||
- Activity Cost - Mandatory removed for Employee
|
||||
1
erpnext/change_log/v5/v5_0_28.md
Normal file
1
erpnext/change_log/v5/v5_0_28.md
Normal file
@@ -0,0 +1 @@
|
||||
- Open notification of Sales Order and Purchase Order based on whether Invoice is created against them. For eg. If a Sales Order is not Invoiced, it will be considered as open. Previously it was considered open if Delivery Note was created against Sales Order.
|
||||
8
erpnext/change_log/v5/v5_1_0.md
Normal file
8
erpnext/change_log/v5/v5_1_0.md
Normal file
@@ -0,0 +1,8 @@
|
||||
- Item variants is now manageable via dedicated tool **Manage Variants**. To learn about it, check [Manual Page for Item variants](https://manual.erpnext.com/contents/stock/item/item-variants)
|
||||
- Against account in General Ledger will show Party instead of Account (which is not useful)
|
||||
- Print format for recurring documents can be set by the users
|
||||
- Recurring documents won't be created for Stopped Sales / Purchase Orders.
|
||||
- Lead status will be changed to 'Opportunity' when Lead converted to Opportunity
|
||||
- Amount in Journal Entry list view
|
||||
- Currency exchange rate is now automatically fetched from fixer.io, instead of jsonrates.com
|
||||
- Item image is now available in Sales / Purchase Invoice
|
||||
@@ -103,6 +103,12 @@ def get_data():
|
||||
"name": "Completed Production Orders",
|
||||
"doctype": "Production Order"
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "BOM Search",
|
||||
"doctype": "BOM"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -237,6 +237,12 @@ def get_data():
|
||||
"route": "query-report/Sales Person Target Variance Item Group-Wise",
|
||||
"doctype": "Sales Person",
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "BOM Search",
|
||||
"doctype": "BOM"
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
|
||||
@@ -194,7 +194,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabBOM.is_active=1
|
||||
and tabBOM.%(key)s like "%(txt)s"
|
||||
%(fcond)s %(mcond)s
|
||||
limit %(start)s, %(page_len)s """ % {'key': searchfield, 'txt': "%%%s%%" % txt,
|
||||
limit %(start)s, %(page_len)s """ % {'key': searchfield, 'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond':get_match_cond(doctype), 'start': start, 'page_len': page_len})
|
||||
|
||||
@@ -207,7 +207,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
where `tabProject`.status not in ("Completed", "Cancelled")
|
||||
and %(cond)s `tabProject`.name like "%(txt)s" %(mcond)s
|
||||
order by `tabProject`.name asc
|
||||
limit %(start)s, %(page_len)s """ % {'cond': cond,'txt': "%%%s%%" % txt,
|
||||
limit %(start)s, %(page_len)s """ % {'cond': cond,'txt': "%%%s%%" % frappe.db.escape(txt),
|
||||
'mcond':get_match_cond(doctype),'start': start, 'page_len': page_len})
|
||||
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
@@ -33,11 +33,13 @@ def manage_recurring_documents(doctype, next_date=None, commit=True):
|
||||
next_date = next_date or nowdate()
|
||||
|
||||
date_field = date_field_map[doctype]
|
||||
|
||||
condition = " and ifnull(status, '') != 'Stopped'" if doctype in ("Sales Order", "Purchase Order") else ""
|
||||
|
||||
recurring_documents = frappe.db.sql("""select name, recurring_id
|
||||
from `tab{}` where ifnull(is_recurring, 0)=1
|
||||
and docstatus=1 and next_date='{}'
|
||||
and next_date <= ifnull(end_date, '2199-12-31')""".format(doctype, next_date))
|
||||
from `tab{0}` where ifnull(is_recurring, 0)=1
|
||||
and docstatus=1 and next_date=%s
|
||||
and next_date <= ifnull(end_date, '2199-12-31') {1}""".format(doctype, condition), next_date)
|
||||
|
||||
exception_list = []
|
||||
for ref_document, recurring_id in recurring_documents:
|
||||
@@ -124,7 +126,7 @@ def send_notification(new_rv):
|
||||
frappe.sendmail(new_rv.notification_email_address,
|
||||
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name)])
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=new_rv.recurring_print_format)])
|
||||
|
||||
def notify_errors(doc, doctype, party, owner):
|
||||
from frappe.utils.user import get_system_managers
|
||||
|
||||
@@ -75,8 +75,7 @@ class Lead(SellingController):
|
||||
return frappe.db.get_value("Customer", {"lead_name": self.name})
|
||||
|
||||
def has_opportunity(self):
|
||||
return frappe.db.get_value("Opportunity", {"lead": self.name, "docstatus": 1,
|
||||
"status": ["!=", "Lost"]})
|
||||
return frappe.db.get_value("Opportunity", {"lead": self.name, "status": ["!=", "Lost"]})
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_customer(source_name, target_doc=None):
|
||||
|
||||
@@ -5,7 +5,7 @@ app_publisher = "Frappe Technologies Pvt. Ltd. and Contributors"
|
||||
app_description = "Open Source Enterprise Resource Planning for Small and Midsized Organizations"
|
||||
app_icon = "icon-th"
|
||||
app_color = "#e74c3c"
|
||||
app_version = "5.0.22"
|
||||
app_version = "5.1.2"
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
|
||||
@@ -78,8 +78,10 @@ doc_events = {
|
||||
}
|
||||
|
||||
scheduler_events = {
|
||||
"hourly": [
|
||||
"erpnext.controllers.recurring_document.create_recurring_documents"
|
||||
],
|
||||
"daily": [
|
||||
"erpnext.controllers.recurring_document.create_recurring_documents",
|
||||
"erpnext.stock.reorder_item.reorder_item",
|
||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||
"erpnext.support.doctype.issue.issue.auto_close_tickets",
|
||||
|
||||
@@ -63,7 +63,7 @@ cur_frm.cscript.onload = function(doc,cdt,cdn) {
|
||||
|
||||
cur_frm.set_query("exp_approver", function() {
|
||||
return {
|
||||
filters: [["UserRole", "role", "=", "Expense Approver"]]
|
||||
query: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_approver"
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,4 +58,13 @@ class ExpenseClaim(Document):
|
||||
def validate_sanctioned_amount(self):
|
||||
for d in self.get('expenses'):
|
||||
if flt(d.sanctioned_amount) > flt(d.claim_amount):
|
||||
frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx))
|
||||
frappe.throw(_("Sanctioned Amount cannot be greater than Claim Amount in Row {0}.").format(d.idx))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_expense_approver(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""
|
||||
select u.name, concat(u.first_name, ' ', u.last_name)
|
||||
from tabUser u, tabUserRole r
|
||||
where u.name = r.parent and r.role = 'Expense Approver' and u.name like %s
|
||||
""", ("%" + txt + "%"))
|
||||
@@ -339,6 +339,7 @@ def add_block_dates(events, start, end, employee, company):
|
||||
events.append({
|
||||
"doctype": "Leave Block List Date",
|
||||
"from_date": block_date.block_date,
|
||||
"to_date": block_date.block_date,
|
||||
"title": _("Leave Blocked") + ": " + block_date.reason,
|
||||
"name": "_" + str(cnt),
|
||||
})
|
||||
@@ -355,6 +356,7 @@ def add_holidays(events, start, end, employee, company):
|
||||
events.append({
|
||||
"doctype": "Holiday",
|
||||
"from_date": holiday.holiday_date,
|
||||
"to_date": holiday.holiday_date,
|
||||
"title": _("Holiday") + ": " + cstr(holiday.description),
|
||||
"name": holiday.name
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import cstr, flt
|
||||
from frappe.utils import cstr, flt, getdate
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@@ -14,6 +14,13 @@ from erpnext.hr.utils import set_employee_name
|
||||
class SalaryStructure(Document):
|
||||
def autoname(self):
|
||||
self.name = make_autoname(self.employee + '/.SST' + '/.#####')
|
||||
|
||||
def validate(self):
|
||||
self.check_existing()
|
||||
self.validate_amount()
|
||||
self.validate_employee()
|
||||
self.validate_joining_date()
|
||||
set_employee_name(self)
|
||||
|
||||
def get_employee_details(self):
|
||||
ret = {}
|
||||
@@ -77,14 +84,11 @@ class SalaryStructure(Document):
|
||||
old_employee = frappe.db.get_value("Salary Structure", self.name, "employee")
|
||||
if old_employee and self.employee != old_employee:
|
||||
frappe.throw(_("Employee can not be changed"))
|
||||
|
||||
|
||||
def validate(self):
|
||||
self.check_existing()
|
||||
self.validate_amount()
|
||||
self.validate_employee()
|
||||
set_employee_name(self)
|
||||
|
||||
|
||||
def validate_joining_date(self):
|
||||
joining_date = getdate(frappe.db.get_value("Employee", self.employee, "date_of_joining"))
|
||||
if getdate(self.from_date) < joining_date:
|
||||
frappe.throw(_("From Date in Salary Structure cannot be lesser than Employee Joining Date."))
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_salary_slip(source_name, target_doc=None):
|
||||
|
||||
@@ -61,7 +61,7 @@ var get_bom_material_detail= function(doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
if (d.item_code) {
|
||||
return frappe.call({
|
||||
doc: cur_frm.doc,
|
||||
doc: doc,
|
||||
method: "get_bom_material_detail",
|
||||
args: {
|
||||
'item_code': d.item_code,
|
||||
@@ -234,5 +234,3 @@ frappe.ui.form.on("BOM", "with_operations", function(frm) {
|
||||
cur_frm.cscript.image = function() {
|
||||
refresh_field("image_view");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"fieldname": "item",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"oldfieldname": "item",
|
||||
"oldfieldtype": "Link",
|
||||
@@ -54,7 +54,7 @@
|
||||
"fieldname": "is_active",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Is Active",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "is_active",
|
||||
@@ -67,7 +67,7 @@
|
||||
"default": "1",
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Is Default",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "is_default",
|
||||
@@ -279,7 +279,7 @@
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-03-03 14:22:44.725097",
|
||||
"modified": "2015-06-26 02:02:30.705279",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM",
|
||||
|
||||
@@ -55,6 +55,13 @@
|
||||
"label": "Time Between Operations (in mins)",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "over_production_allowance_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Over Production Allowance Percentage",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
@@ -65,7 +72,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-04-21 07:57:40.260862",
|
||||
"modified": "2015-06-15 05:52:22.986958",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing Settings",
|
||||
|
||||
@@ -90,8 +90,9 @@ class ProductionOrder(Document):
|
||||
(self.sales_order, self.production_item))[0][0]
|
||||
# total qty in SO
|
||||
so_qty = flt(so_item_qty) + flt(dnpi_qty)
|
||||
|
||||
if total_qty > so_qty:
|
||||
|
||||
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", "over_production_allowance_percentage"))
|
||||
if total_qty > so_qty + (allowance_percentage/100 * so_qty):
|
||||
frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}").format(self.production_item,
|
||||
so_qty), OverProductionError)
|
||||
|
||||
@@ -217,12 +218,14 @@ class ProductionOrder(Document):
|
||||
for i, d in enumerate(self.operations):
|
||||
self.set_operation_start_end_time(i, d)
|
||||
|
||||
if not d.workstation:
|
||||
continue
|
||||
|
||||
time_log = make_time_log(self.name, d.operation, d.planned_start_time, d.planned_end_time,
|
||||
flt(self.qty) - flt(d.completed_qty), self.project_name, d.workstation, operation_id=d.name)
|
||||
|
||||
if d.workstation:
|
||||
# validate operating hours if workstation [not mandatory] is specified
|
||||
self.check_operation_fits_in_working_hours(d)
|
||||
# validate operating hours if workstation [not mandatory] is specified
|
||||
self.check_operation_fits_in_working_hours(d)
|
||||
|
||||
original_start_time = time_log.from_time
|
||||
while True:
|
||||
|
||||
@@ -165,4 +165,9 @@ erpnext.patches.v5_0.repost_gle_for_jv_with_multiple_party
|
||||
erpnext.patches.v5_0.portal_fixes
|
||||
erpnext.patches.v5_0.reset_values_in_tools
|
||||
execute:frappe.delete_doc("Page", "users")
|
||||
erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again
|
||||
erpnext.patches.v5_0.update_material_transferred_for_manufacturing_again
|
||||
erpnext.patches.v5_0.index_on_account_and_gl_entry
|
||||
execute:frappe.db.sql("""delete from `tabProject Task`""")
|
||||
erpnext.patches.v5_0.item_variants
|
||||
erpnext.patches.v5_0.update_item_desc_in_invoice
|
||||
erpnext.patches.v5_1.fix_against_account
|
||||
|
||||
@@ -30,8 +30,8 @@ def create_receivable_payable_account():
|
||||
|
||||
account_id = account.name
|
||||
|
||||
frappe.db.set_value("Company", args["company"], ("default_receivable_account"
|
||||
if args["account_type"]=="Receivable" else "default_payable_account"), account_id)
|
||||
frappe.db.set_value("Company", args["company"], ("default_receivable_account"
|
||||
if args["account_type"]=="Receivable" else "default_payable_account"), account_id)
|
||||
|
||||
receivable_payable_accounts.setdefault(args["company"], {}).setdefault(args["account_type"], account_id)
|
||||
|
||||
|
||||
30
erpnext/patches/v5_0/index_on_account_and_gl_entry.py
Normal file
30
erpnext/patches/v5_0/index_on_account_and_gl_entry.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
index_map = {
|
||||
"Account": ["parent_account", "lft", "rgt"],
|
||||
"GL Entry": ["posting_date", "account", 'party', "voucher_no"],
|
||||
"Sales Invoice": ["posting_date", "debit_to", "customer"],
|
||||
"Purchase Invoice": ["posting_date", "credit_to", "supplier"]
|
||||
}
|
||||
|
||||
for dt, indexes in index_map.items():
|
||||
existing_indexes = [(d.Key_name, d.Column_name) for d in frappe.db.sql("""show index from `tab{0}`
|
||||
where Column_name != 'name'""".format(dt), as_dict=1)]
|
||||
|
||||
for old, column in existing_indexes:
|
||||
if column in ("parent", "group_or_ledger", "is_group", "is_pl_account", "debit_or_credit",
|
||||
"account_name", "company", "project_name", "voucher_date", "due_date", "bill_no",
|
||||
"bill_date", "is_opening", "fiscal_year", "outstanding_amount"):
|
||||
frappe.db.sql("alter table `tab{0}` drop index {1}".format(dt, old))
|
||||
|
||||
existing_indexes = [(d.Key_name, d.Column_name) for d in frappe.db.sql("""show index from `tab{0}`
|
||||
where Column_name != 'name'""".format(dt), as_dict=1)]
|
||||
|
||||
existing_indexed_columns = list(set([x[1] for x in existing_indexes]))
|
||||
|
||||
for new in indexes:
|
||||
if new not in existing_indexed_columns:
|
||||
frappe.db.sql("alter table `tab{0}` add index ({1})".format(dt, new))
|
||||
16
erpnext/patches/v5_0/item_variants.py
Normal file
16
erpnext/patches/v5_0/item_variants.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype("Item")
|
||||
for dt in ["manage_variants", "manage_variants_item", "variant_attribute"]:
|
||||
frappe.reload_doc("stock", "doctype", dt)
|
||||
|
||||
for d in frappe.get_list("Item", filters={"has_variants":1}):
|
||||
manage_variant = frappe.new_doc("Manage Variants")
|
||||
manage_variant.item_code = d.name
|
||||
manage_variant.attributes = frappe.db.sql("select item_attribute as attribute, item_attribute_value as attribute_value \
|
||||
from `tabItem Variant` where parent = %s", d.name, as_dict=1)
|
||||
if manage_variant.attributes:
|
||||
manage_variant.generate_combinations()
|
||||
manage_variant.create_variants()
|
||||
frappe.delete_doc("DocType", "Item Variant")
|
||||
@@ -7,6 +7,6 @@ def execute():
|
||||
account_settings = frappe.get_doc("Accounts Settings")
|
||||
|
||||
if not account_settings.frozen_accounts_modifier and account_settings.bde_auth_role:
|
||||
frappe.db.set_value("Account Settings", None,
|
||||
frappe.db.set_value("Accounts Settings", None,
|
||||
"frozen_accounts_modifier", account_settings.bde_auth_role)
|
||||
|
||||
|
||||
51
erpnext/patches/v5_0/update_item_desc_in_invoice.py
Normal file
51
erpnext/patches/v5_0/update_item_desc_in_invoice.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.website.utils import find_first_image
|
||||
from frappe.utils import cstr
|
||||
import re
|
||||
|
||||
def execute():
|
||||
item_details = frappe._dict()
|
||||
for d in frappe.db.sql("select name, description, image from `tabItem`", as_dict=1):
|
||||
description = cstr(d.description).strip()
|
||||
item_details.setdefault(d.name, frappe._dict({
|
||||
"description": description,
|
||||
"image": d.image
|
||||
}))
|
||||
|
||||
|
||||
dt_list= ["Sales Invoice Item","Purchase Invoice Item"]
|
||||
for dt in dt_list:
|
||||
frappe.reload_doctype(dt)
|
||||
records = frappe.db.sql("""select name, item_code, description from `tab{0}`
|
||||
where ifnull(item_code, '') != '' and description is not null """.format(dt), as_dict=1)
|
||||
|
||||
count = 1
|
||||
for d in records:
|
||||
if item_details.get(d.item_code) and cstr(d.description) == item_details.get(d.item_code).description:
|
||||
desc = item_details.get(d.item_code).description
|
||||
image = item_details.get(d.item_code).image
|
||||
else:
|
||||
desc, image = extract_image_and_description(cstr(d.description))
|
||||
|
||||
if not image:
|
||||
item_detail = item_details.get(d.item_code)
|
||||
if item_detail:
|
||||
image = item_detail.image
|
||||
|
||||
frappe.db.sql("""update `tab{0}` set description = %s, image = %s
|
||||
where name = %s """.format(dt), (desc, image, d.name))
|
||||
|
||||
count += 1
|
||||
if count % 500 == 0:
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def extract_image_and_description(data):
|
||||
image_url = find_first_image(data)
|
||||
desc = data
|
||||
for tag in ("img", "table", "tr", "td"):
|
||||
desc = re.sub("\</*{0}[^>]*\>".format(tag), "", desc)
|
||||
return desc, image_url
|
||||
0
erpnext/patches/v5_1/__init__.py
Normal file
0
erpnext/patches/v5_1/__init__.py
Normal file
37
erpnext/patches/v5_1/fix_against_account.py
Normal file
37
erpnext/patches/v5_1/fix_against_account.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_against_account
|
||||
|
||||
def execute():
|
||||
from_date = "2015-05-01"
|
||||
|
||||
for doc in frappe.get_all("Journal Entry",
|
||||
filters={"creation": (">", from_date), "docstatus": "1"}):
|
||||
|
||||
# update in gl_entry
|
||||
update_against_account("Journal Entry", doc.name)
|
||||
|
||||
# update in jv
|
||||
doc = frappe.get_doc("Journal Entry", doc.name)
|
||||
doc.set_against_account()
|
||||
doc.db_update()
|
||||
|
||||
for doc in frappe.get_all("Sales Invoice",
|
||||
filters={"creation": (">", from_date), "docstatus": "1"},
|
||||
fields=["name", "customer"]):
|
||||
|
||||
frappe.db.sql("""update `tabGL Entry` set against=%s
|
||||
where voucher_type='Sales Invoice' and voucher_no=%s
|
||||
and credit > 0 and ifnull(party, '')=''""",
|
||||
(doc.customer, doc.name))
|
||||
|
||||
for doc in frappe.get_all("Purchase Invoice",
|
||||
filters={"creation": (">", from_date), "docstatus": "1"},
|
||||
fields=["name", "supplier"]):
|
||||
|
||||
frappe.db.sql("""update `tabGL Entry` set against=%s
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
and debit > 0 and ifnull(party, '')=''""",
|
||||
(doc.supplier, doc.name))
|
||||
@@ -9,41 +9,6 @@
|
||||
"doctype": "DocType",
|
||||
"document_type": "Master",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Employee",
|
||||
"no_copy": 0,
|
||||
"options": "Employee",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "activity_type",
|
||||
@@ -64,6 +29,41 @@
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Employee",
|
||||
"no_copy": 0,
|
||||
"options": "Employee",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Employee Name",
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -136,7 +136,7 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"modified": "2015-06-11 06:50:47.999788",
|
||||
"modified": "2015-06-16 03:12:25.644839",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Activity Cost",
|
||||
|
||||
@@ -15,12 +15,21 @@ class ActivityCost(Document):
|
||||
self.check_unique()
|
||||
|
||||
def set_title(self):
|
||||
if not self.employee_name:
|
||||
self.employee_name = frappe.db.get_value("Employee", self.employee, "employee_name")
|
||||
self.title = _("{0} for {1}").format(self.employee_name, self.activity_type)
|
||||
|
||||
if self.employee:
|
||||
if not self.employee_name:
|
||||
self.employee_name = frappe.db.get_value("Employee", self.employee, "employee_name")
|
||||
self.title = _("{0} for {1}").format(self.employee_name, self.activity_type)
|
||||
else:
|
||||
self.title = self.activity_type
|
||||
|
||||
def check_unique(self):
|
||||
if frappe.db.sql("""select name from `tabActivity Cost` where employee_name= %s and activity_type= %s and name != %s""",
|
||||
(self.employee_name, self.activity_type, self.name)):
|
||||
frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type - {1}")
|
||||
.format(self.employee, self.activity_type), DuplicationError)
|
||||
if self.employee:
|
||||
if frappe.db.sql("""select name from `tabActivity Cost` where employee_name= %s and activity_type= %s and name != %s""",
|
||||
(self.employee_name, self.activity_type, self.name)):
|
||||
frappe.throw(_("Activity Cost exists for Employee {0} against Activity Type - {1}")
|
||||
.format(self.employee, self.activity_type), DuplicationError)
|
||||
else:
|
||||
if frappe.db.sql("""select name from `tabActivity Cost` where ifnull(employee, '')='' and activity_type= %s and name != %s""",
|
||||
(self.activity_type, self.name)):
|
||||
frappe.throw(_("Default Activity Cost exists for Activity Type - {0}")
|
||||
.format(self.activity_type), DuplicationError)
|
||||
|
||||
@@ -163,6 +163,7 @@
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 0,
|
||||
"label": "% Tasks Completed",
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -356,7 +357,7 @@
|
||||
"icon": "icon-puzzle-piece",
|
||||
"idx": 1,
|
||||
"max_attachments": 4,
|
||||
"modified": "2015-04-27 07:37:44.239930",
|
||||
"modified": "2015-06-12 09:00:54.080220",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Projects",
|
||||
"name": "Project",
|
||||
|
||||
@@ -35,6 +35,7 @@ class Project(Document):
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.sync_tasks()
|
||||
self.tasks = []
|
||||
|
||||
def validate_dates(self):
|
||||
if self.expected_start_date and self.expected_end_date:
|
||||
@@ -45,6 +46,8 @@ class Project(Document):
|
||||
"""sync tasks and remove table"""
|
||||
if self.flags.dont_sync_tasks: return
|
||||
|
||||
|
||||
task_added_or_deleted = False
|
||||
task_names = []
|
||||
for t in self.tasks:
|
||||
if t.task_id:
|
||||
@@ -52,6 +55,7 @@ class Project(Document):
|
||||
else:
|
||||
task = frappe.new_doc("Task")
|
||||
task.project = self.name
|
||||
task_added_or_deleted = True
|
||||
|
||||
task.update({
|
||||
"subject": t.title,
|
||||
@@ -69,17 +73,22 @@ class Project(Document):
|
||||
# delete
|
||||
for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
|
||||
frappe.delete_doc("Task", t.name)
|
||||
task_added_or_deleted = True
|
||||
|
||||
if task_added_or_deleted:
|
||||
self.update_project()
|
||||
|
||||
self.tasks = []
|
||||
def update_project(self):
|
||||
self.update_percent_complete()
|
||||
self.update_costing()
|
||||
|
||||
def update_percent_complete(self):
|
||||
total = frappe.db.sql("""select count(*) from tabTask where project=%s""",
|
||||
self.name)[0][0]
|
||||
total = frappe.db.sql("""select count(*) from tabTask where project=%s""", self.name)[0][0]
|
||||
if total:
|
||||
completed = frappe.db.sql("""select count(*) from tabTask where
|
||||
project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0]
|
||||
frappe.db.set_value("Project", self.name, "percent_complete",
|
||||
int(float(completed) / total * 100))
|
||||
|
||||
self.percent_complete = flt(completed) / total * 100
|
||||
|
||||
def update_costing(self):
|
||||
total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount,
|
||||
|
||||
@@ -5,3 +5,4 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
test_records = frappe.get_test_records('Project')
|
||||
test_ignore = ["Sales Order"]
|
||||
|
||||
@@ -43,15 +43,8 @@ class Task(Document):
|
||||
def on_update(self):
|
||||
self.check_recursion()
|
||||
self.reschedule_dependent_tasks()
|
||||
self.update_percentage()
|
||||
self.update_project()
|
||||
|
||||
def update_percentage(self):
|
||||
"""update percent complete in project"""
|
||||
if self.project and not self.flags.from_project:
|
||||
project = frappe.get_doc("Project", self.project)
|
||||
project.run_method("update_percent_complete")
|
||||
|
||||
def update_total_expense_claim(self):
|
||||
self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim`
|
||||
where project = %s and task = %s and approval_status = "Approved" and docstatus=1""",(self.project, self.name))
|
||||
@@ -70,10 +63,10 @@ class Task(Document):
|
||||
self.act_end_date= tl.end_date
|
||||
|
||||
def update_project(self):
|
||||
if self.project and frappe.db.exists("Project", self.project):
|
||||
if self.project and not self.flags.from_project:
|
||||
project = frappe.get_doc("Project", self.project)
|
||||
project.flags.dont_sync_tasks = True
|
||||
project.update_costing()
|
||||
project.update_project()
|
||||
project.save()
|
||||
|
||||
def check_recursion(self):
|
||||
@@ -141,7 +134,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
%(mcond)s
|
||||
order by name
|
||||
limit %(start)s, %(page_len)s """ % {'key': searchfield,
|
||||
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype),
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype),
|
||||
'start': start, 'page_len': page_len})
|
||||
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class TimeLog(Document):
|
||||
|
||||
def update_production_order(self):
|
||||
"""Updates `start_date`, `end_date`, `status` for operation in Production Order."""
|
||||
|
||||
|
||||
if self.production_order and self.for_manufacturing:
|
||||
if not self.operation_id:
|
||||
frappe.throw(_("Operation ID not set"))
|
||||
@@ -208,22 +208,23 @@ class TimeLog(Document):
|
||||
self.production_order = None
|
||||
self.operation = None
|
||||
self.quantity = None
|
||||
|
||||
|
||||
def update_cost(self):
|
||||
rate = get_activity_cost(self.employee, self.activity_type)
|
||||
if rate:
|
||||
self.costing_rate = rate.get('costing_rate')
|
||||
self.billing_rate = rate.get('billing_rate')
|
||||
self.billing_rate = rate.get('billing_rate')
|
||||
self.costing_amount = self.costing_rate * self.hours
|
||||
if self.billable:
|
||||
self.billing_amount = self.billing_rate * self.hours
|
||||
else:
|
||||
self.billing_amount = 0
|
||||
|
||||
|
||||
def validate_task(self):
|
||||
if self.project and not self.task:
|
||||
# if a time log is being created against a project without production order
|
||||
if (self.project and not self.production_order) and not self.task:
|
||||
frappe.throw(_("Task is Mandatory if Time Log is against a project"))
|
||||
|
||||
|
||||
def update_task(self):
|
||||
if self.task and frappe.db.exists("Task", self.task):
|
||||
task = frappe.get_doc("Task", self.task)
|
||||
@@ -266,9 +267,12 @@ def get_events(start, end, filters=None):
|
||||
d.title += " for Project: " + d.project
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_activity_cost(employee=None, activity_type=None):
|
||||
rate = frappe.db.sql("""select costing_rate, billing_rate from `tabActivity Cost` where employee= %s
|
||||
and activity_type= %s""", (employee, activity_type), as_dict=1)
|
||||
if not rate:
|
||||
rate = frappe.db.sql("""select costing_rate, billing_rate from `tabActivity Cost` where ifnull(employee, '')=''
|
||||
and activity_type= %s""", (activity_type), as_dict=1)
|
||||
return rate[0] if rate else {}
|
||||
|
||||
@@ -37,6 +37,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(this.frm.fields_dict["items"]) {
|
||||
this["items_remove"] = this.calculate_taxes_and_totals;
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["recurring_print_format"]) {
|
||||
this.frm.set_query("recurring_print_format", function(doc) {
|
||||
return{
|
||||
filters: [
|
||||
['Print Format', 'doc_type', '=', cur_frm.doctype],
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
@@ -782,3 +792,5 @@ frappe.ui.form.on(cur_frm.doctype, "discount_amount", function(frm) {
|
||||
cur_frm.cscript.set_dynamic_labels();
|
||||
cur_frm.cscript.calculate_taxes_and_totals();
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -488,7 +488,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -858,7 +858,7 @@
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"max_attachments": 1,
|
||||
"modified": "2015-05-27 02:48:00.388847",
|
||||
"modified": "2015-06-15 15:37:39.199814",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
@@ -183,5 +183,3 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
|
||||
cur_frm.email_doc(frappe.boot.notification_settings.sales_order_message);
|
||||
}
|
||||
};
|
||||
|
||||
;
|
||||
|
||||
@@ -493,7 +493,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -1074,13 +1074,22 @@
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_recurring==1",
|
||||
"fieldname": "recurring_print_format",
|
||||
"fieldtype": "Link",
|
||||
"label": "Recurring Print Format",
|
||||
"options": "Print Format",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"icon": "icon-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"modified": "2015-05-27 02:48:01.160307",
|
||||
"modified": "2015-06-22 07:29:24.379272",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
@@ -498,7 +498,7 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2015-05-27 02:47:15.134435",
|
||||
"modified": "2015-07-02 05:37:29.289574",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
||||
@@ -122,7 +122,7 @@ erpnext.SalesChart = Class.extend({
|
||||
|
||||
if(me.ctype == "Sales Person") {
|
||||
fields.splice(-1, 0, {fieldtype:'Link', fieldname:'employee', label:__('Employee'),
|
||||
options:'Employee', description: __("Please enter Employee Id of this sales parson")});
|
||||
options:'Employee', description: __("Please enter Employee Id of this sales person")});
|
||||
}
|
||||
|
||||
// the dialog
|
||||
|
||||
@@ -66,20 +66,6 @@
|
||||
"label": "Disable Rounded Total",
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"description": "For automatic exchange rates go to jsonrates.com and signup for an API key",
|
||||
"fieldname": "jsonrates_api_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "jsonrates.com API Key",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 0,
|
||||
@@ -87,7 +73,7 @@
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"modified": "2015-05-07 05:43:49.760061",
|
||||
"modified": "2015-06-30 03:00:26.420003",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Global Defaults",
|
||||
|
||||
@@ -17,7 +17,6 @@ keydict = {
|
||||
'hide_currency_symbol':'hide_currency_symbol',
|
||||
'account_url':'account_url',
|
||||
'disable_rounded_total': 'disable_rounded_total',
|
||||
'jsonrates_api_key': 'jsonrates_api_key'
|
||||
}
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
@@ -26,7 +26,8 @@ class NamingSeries(Document):
|
||||
except frappe.DoesNotExistError:
|
||||
continue
|
||||
|
||||
prefixes = prefixes + "\n" + options
|
||||
if options:
|
||||
prefixes = prefixes + "\n" + options
|
||||
|
||||
prefixes.replace("\n\n", "\n")
|
||||
prefixes = "\n".join(sorted(prefixes.split()))
|
||||
|
||||
@@ -60,20 +60,21 @@ def before_tests():
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_exchange_rate(from_currency, to_currency):
|
||||
jsonrates_api_key = frappe.conf.jsonrates_api_key or frappe.db.get_default("jsonrates_api_key")
|
||||
|
||||
if jsonrates_api_key:
|
||||
try:
|
||||
cache = frappe.cache()
|
||||
key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency)
|
||||
value = cache.get(key)
|
||||
if not value:
|
||||
import requests
|
||||
response = requests.get("http://jsonrates.com/get/?from={0}&to={1}&apiKey={2}".format(from_currency,
|
||||
to_currency, jsonrates_api_key))
|
||||
response = requests.get("http://api.fixer.io/latest", params={
|
||||
"base": from_currency,
|
||||
"symbols": to_currency
|
||||
})
|
||||
# expire in 24 hours
|
||||
value = response.json().get("rate")
|
||||
response.raise_for_status()
|
||||
value = response.json()["rates"][to_currency]
|
||||
cache.setex(key, value, 24 * 60 * 60)
|
||||
return flt(value)
|
||||
else:
|
||||
except:
|
||||
exchange = "%s-%s" % (from_currency, to_currency)
|
||||
return flt(frappe.db.get_value("Currency Exchange", exchange, "exchange_rate"))
|
||||
|
||||
@@ -14,7 +14,7 @@ def get_notification_config():
|
||||
"Contact": {"status": "Open"},
|
||||
"Opportunity": {"status": "Open"},
|
||||
"Quotation": {"docstatus": 0},
|
||||
"Sales Order": { "per_delivered": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
|
||||
"Sales Order": { "per_billed": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
|
||||
"Journal Entry": {"docstatus": 0},
|
||||
"Sales Invoice": { "outstanding_amount": (">", 0), "docstatus": ("<", 2) },
|
||||
"Purchase Invoice": {"docstatus": 0},
|
||||
@@ -25,7 +25,7 @@ def get_notification_config():
|
||||
"Delivery Note": {"docstatus": 0},
|
||||
"Stock Entry": {"docstatus": 0},
|
||||
"Material Request": {"docstatus": 0},
|
||||
"Purchase Order": { "per_received": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
|
||||
"Purchase Order": { "per_billed": ("<", 100), "status": ("!=", "Stopped"), "docstatus": ("<", 2) },
|
||||
"Production Order": { "status": "In Process" },
|
||||
"BOM": {"docstatus": 0},
|
||||
"Timesheet": {"docstatus": 0},
|
||||
|
||||
@@ -517,7 +517,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -1070,7 +1070,7 @@
|
||||
"idx": 1,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-05-27 02:47:59.778147",
|
||||
"modified": "2015-06-15 15:37:54.699371",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -5,39 +5,6 @@ frappe.provide("erpnext.item");
|
||||
|
||||
frappe.ui.form.on("Item", {
|
||||
onload: function(frm) {
|
||||
var df = frappe.meta.get_docfield("Item Variant", "item_attribute_value");
|
||||
df.on_make = function(field) {
|
||||
field.$input.autocomplete({
|
||||
minLength: 0,
|
||||
minChars: 0,
|
||||
source: function(request, response) {
|
||||
frappe.call({
|
||||
method:"frappe.client.get_list",
|
||||
args:{
|
||||
doctype:"Item Attribute Value",
|
||||
filters: [
|
||||
["parent","=", field.doc.item_attribute],
|
||||
["attribute_value", "like", request.term + "%"]
|
||||
],
|
||||
fields: ["attribute_value"]
|
||||
},
|
||||
callback: function(r) {
|
||||
response($.map(r.message, function(d) { return d.attribute_value; }));
|
||||
}
|
||||
});
|
||||
},
|
||||
select: function(event, ui) {
|
||||
field.$input.val(ui.item.value);
|
||||
field.$input.trigger("change");
|
||||
},
|
||||
focus: function( event, ui ) {
|
||||
if(ui.item.action) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
erpnext.item.setup_queries(frm);
|
||||
},
|
||||
|
||||
@@ -55,6 +22,9 @@ frappe.ui.form.on("Item", {
|
||||
// read only if any stock ledger entry exists
|
||||
erpnext.item.make_dashboard(frm);
|
||||
|
||||
// clear intro
|
||||
frm.set_intro();
|
||||
|
||||
if (frm.doc.has_variants) {
|
||||
frm.set_intro(__("This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set"), true);
|
||||
frm.add_custom_button(__("Show Variants"), function() {
|
||||
@@ -85,7 +55,7 @@ frappe.ui.form.on("Item", {
|
||||
erpnext.item.weight_to_validate(frm);
|
||||
erpnext.item.variants_can_not_be_created_manually(frm);
|
||||
},
|
||||
|
||||
|
||||
image: function(frm) {
|
||||
refresh_field("image_view");
|
||||
},
|
||||
@@ -110,8 +80,14 @@ frappe.ui.form.on("Item", {
|
||||
method: "copy_specification_from_item_group"
|
||||
});
|
||||
},
|
||||
|
||||
is_stock_item: function(frm) {
|
||||
erpnext.item.toggle_reqd(frm);
|
||||
},
|
||||
|
||||
manage_variants: function(frm) {
|
||||
frappe.route_options = {"item_code": frm.doc.name };
|
||||
frappe.set_route("List", "Manage Variants");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -209,11 +185,11 @@ $.extend(erpnext.item, {
|
||||
validated = 0;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
variants_can_not_be_created_manually: function(frm) {
|
||||
if (frm.doc.__islocal && frm.doc.variant_of)
|
||||
frappe.throw(__("Variants can not be created manually, add item attributes in the template item"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
{
|
||||
"fieldname": "name_and_description_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Name and Description",
|
||||
"label": "",
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "icon-flag",
|
||||
@@ -167,16 +167,17 @@
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!!!doc.variant_of",
|
||||
"depends_on": "eval:!doc.variant_of",
|
||||
"fieldname": "variants_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Variants",
|
||||
"label": "Variant",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Automatically set. If this item has variants, then it cannot be selected in sales orders etc.",
|
||||
"depends_on": "",
|
||||
"description": "If this item has variants, then it cannot be selected in sales orders etc.",
|
||||
"fieldname": "has_variants",
|
||||
"fieldtype": "Check",
|
||||
"label": "Has Variants",
|
||||
@@ -186,15 +187,37 @@
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "has_variants",
|
||||
"description": "A new variant (Item) will be created for each attribute value combination",
|
||||
"fieldname": "variants",
|
||||
"fieldtype": "Table",
|
||||
"label": "Variants",
|
||||
"options": "Item Variant",
|
||||
"fieldname": "column_break_18",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "has_variants",
|
||||
"fieldname": "manage_variants",
|
||||
"fieldtype": "Button",
|
||||
"label": "Manage Variants",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_20",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"depends_on": "variant_of",
|
||||
"fieldname": "attributes",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"label": "Attributes",
|
||||
"no_copy": 1,
|
||||
"options": "Variant Attribute",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "inventory",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -707,7 +730,7 @@
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Default BOM",
|
||||
"no_copy": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "default_bom",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "BOM",
|
||||
@@ -877,9 +900,9 @@
|
||||
}
|
||||
],
|
||||
"icon": "icon-tag",
|
||||
"idx": 1,
|
||||
"idx": 1,
|
||||
"max_attachments": 1,
|
||||
"modified": "2015-05-22 02:16:57.435105",
|
||||
"modified": "2015-07-01 17:20:18.204558",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -9,10 +9,9 @@ from frappe.website.website_generator import WebsiteGenerator
|
||||
from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for, get_parent_item_groups
|
||||
from frappe.website.render import clear_cache
|
||||
from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
|
||||
import copy
|
||||
from erpnext.stock.doctype.manage_variants.manage_variants import update_variant
|
||||
|
||||
class WarehouseNotSet(frappe.ValidationError): pass
|
||||
class DuplicateVariant(frappe.ValidationError): pass
|
||||
class ItemTemplateCannotHaveStock(frappe.ValidationError): pass
|
||||
|
||||
class Item(WebsiteGenerator):
|
||||
@@ -48,9 +47,6 @@ class Item(WebsiteGenerator):
|
||||
if self.image and not self.website_image:
|
||||
self.website_image = self.image
|
||||
|
||||
if self.variant_of:
|
||||
self.copy_attributes_to_variant(frappe.get_doc("Item", self.variant_of), self)
|
||||
|
||||
self.check_warehouse_is_set_for_stock_item()
|
||||
self.check_stock_uom_with_bin()
|
||||
self.add_default_uom_in_conversion_factor_table()
|
||||
@@ -63,9 +59,10 @@ class Item(WebsiteGenerator):
|
||||
self.cant_change()
|
||||
self.validate_reorder_level()
|
||||
self.validate_warehouse_for_reorder()
|
||||
self.validate_variants()
|
||||
self.update_item_desc()
|
||||
self.synced_with_hub = 0
|
||||
self.validate_has_variants()
|
||||
self.validate_stock_for_template_must_be_zero()
|
||||
|
||||
if not self.get("__islocal"):
|
||||
self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
|
||||
@@ -77,7 +74,7 @@ class Item(WebsiteGenerator):
|
||||
invalidate_cache_for_item(self)
|
||||
self.validate_name_with_item_group()
|
||||
self.update_item_price()
|
||||
self.sync_variants()
|
||||
self.update_variants()
|
||||
|
||||
def get_context(self, context):
|
||||
context["parent_groups"] = get_parent_item_groups(self.item_group) + \
|
||||
@@ -133,142 +130,6 @@ class Item(WebsiteGenerator):
|
||||
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."))
|
||||
|
||||
def validate_variants(self):
|
||||
self.validate_variants_are_unique()
|
||||
self.validate_stock_for_template_must_be_zero()
|
||||
|
||||
def validate_stock_for_template_must_be_zero(self):
|
||||
if self.has_variants:
|
||||
stock_in = frappe.db.sql_list("""select warehouse from tabBin
|
||||
where item_code=%s and ifnull(actual_qty, 0) > 0""", self.name)
|
||||
if stock_in:
|
||||
frappe.throw(_("Item Template cannot have stock and varaiants. Please remove stock from warehouses {0}").format(", ".join(stock_in)),
|
||||
ItemTemplateCannotHaveStock)
|
||||
|
||||
def validate_variants_are_unique(self):
|
||||
if not self.has_variants:
|
||||
self.variants = []
|
||||
return
|
||||
|
||||
if self.variants:
|
||||
if self.variant_of:
|
||||
frappe.throw(_("Item cannot be a variant of a variant"))
|
||||
|
||||
variants, attributes = [], {}
|
||||
for d in self.variants:
|
||||
key = (d.item_attribute, d.item_attribute_value)
|
||||
if key in variants:
|
||||
frappe.throw(_("{0} {1} is entered more than once in Item Variants table")
|
||||
.format(d.item_attribute, d.item_attribute_value), DuplicateVariant)
|
||||
variants.append(key)
|
||||
|
||||
attributes.setdefault(d.item_attribute, [t.attribute_value for t in frappe.db.get_all("Item Attribute Value",
|
||||
fields=["attribute_value"], filters={"parent": d.item_attribute })])
|
||||
|
||||
if d.item_attribute_value not in attributes.get(d.item_attribute):
|
||||
frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.item_attribute_value))
|
||||
else:
|
||||
frappe.throw(_("Please enter atleast one attribute row in Item Variants table"))
|
||||
|
||||
def sync_variants(self):
|
||||
variant_item_codes = self.get_variant_item_codes()
|
||||
|
||||
# delete missing variants
|
||||
existing_variants = [d.name for d in frappe.get_all("Item",
|
||||
filters={"variant_of":self.name})]
|
||||
|
||||
updated, deleted = [], []
|
||||
for existing_variant in existing_variants:
|
||||
if existing_variant not in variant_item_codes:
|
||||
frappe.delete_doc("Item", existing_variant)
|
||||
deleted.append(existing_variant)
|
||||
else:
|
||||
self.update_variant(existing_variant)
|
||||
updated.append(existing_variant)
|
||||
|
||||
inserted = []
|
||||
for item_code in variant_item_codes:
|
||||
if item_code not in existing_variants:
|
||||
self.make_variant(item_code)
|
||||
inserted.append(item_code)
|
||||
|
||||
if inserted:
|
||||
frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted)))
|
||||
|
||||
if updated:
|
||||
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
|
||||
|
||||
if deleted:
|
||||
frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted)))
|
||||
|
||||
def get_variant_item_codes(self):
|
||||
"""Get all possible suffixes for variants"""
|
||||
if not self.variants:
|
||||
return []
|
||||
|
||||
self.variant_attributes = {}
|
||||
variant_dict = {}
|
||||
variant_item_codes = []
|
||||
|
||||
for d in self.variants:
|
||||
variant_dict.setdefault(d.item_attribute, []).append(d.item_attribute_value)
|
||||
|
||||
all_attributes = [d.name for d in frappe.get_all("Item Attribute", order_by = "priority asc")]
|
||||
|
||||
# sort attributes by their priority
|
||||
attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes))
|
||||
|
||||
def add_attribute_suffixes(item_code, my_attributes, attributes):
|
||||
attr = frappe.get_doc("Item Attribute", attributes[0])
|
||||
for value in attr.item_attribute_values:
|
||||
if value.attribute_value in variant_dict[attr.name]:
|
||||
_my_attributes = copy.deepcopy(my_attributes)
|
||||
_my_attributes.append([attr.name, value.attribute_value])
|
||||
if len(attributes) > 1:
|
||||
add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:])
|
||||
else:
|
||||
variant_item_codes.append(item_code + "-" + value.abbr)
|
||||
self.variant_attributes[item_code + "-" + value.abbr] = _my_attributes
|
||||
|
||||
add_attribute_suffixes(self.name, [], attributes)
|
||||
|
||||
return variant_item_codes
|
||||
|
||||
def make_variant(self, item_code):
|
||||
item = frappe.new_doc("Item")
|
||||
item.item_code = item_code
|
||||
self.copy_attributes_to_variant(self, item, insert=True)
|
||||
item.insert()
|
||||
|
||||
def update_variant(self, item_code):
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
item.item_code = item_code
|
||||
self.copy_attributes_to_variant(self, item)
|
||||
item.save()
|
||||
|
||||
def copy_attributes_to_variant(self, template, variant, insert=False):
|
||||
from frappe.model import no_value_fields
|
||||
for field in self.meta.fields:
|
||||
if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\
|
||||
and field.fieldname not in ("item_code", "item_name"):
|
||||
if variant.get(field.fieldname) != template.get(field.fieldname):
|
||||
variant.set(field.fieldname, template.get(field.fieldname))
|
||||
variant.__dirty = True
|
||||
|
||||
variant.description += "\n"
|
||||
|
||||
if not getattr(template, "variant_attributes", None):
|
||||
template.get_variant_item_codes()
|
||||
|
||||
for attr in template.variant_attributes[variant.item_code]:
|
||||
variant.description += "<p>" + attr[0] + ": " + attr[1] + "</p>"
|
||||
|
||||
variant.item_name = self.item_name + variant.item_code[len(self.name):]
|
||||
|
||||
variant.variant_of = template.name
|
||||
variant.has_variants = 0
|
||||
variant.show_in_website = 0
|
||||
|
||||
def update_template_tables(self):
|
||||
template = frappe.get_doc("Item", self.variant_of)
|
||||
|
||||
@@ -313,15 +174,6 @@ class Item(WebsiteGenerator):
|
||||
if bom_item not in (self.name, self.variant_of):
|
||||
frappe.throw(_("Default BOM ({0}) must be active for this item or its template").format(bom_item))
|
||||
|
||||
if self.is_purchase_item != "Yes":
|
||||
bom_mat = frappe.db.sql("""select distinct t1.parent
|
||||
from `tabBOM Item` t1, `tabBOM` t2 where t2.name = t1.parent
|
||||
and t1.item_code =%s and ifnull(t1.bom_no, '') = '' and t2.is_active = 1
|
||||
and t2.docstatus = 1 and t1.docstatus =1 """, self.name)
|
||||
|
||||
if bom_mat and bom_mat[0][0]:
|
||||
frappe.throw(_("Item must be a purchase item, as it is present in one or many Active BOMs"))
|
||||
|
||||
def fill_customer_code(self):
|
||||
""" Append all the customer codes and insert into "customer_code" field of item table """
|
||||
cust_code=[]
|
||||
@@ -361,7 +213,8 @@ class Item(WebsiteGenerator):
|
||||
vals.has_batch_no != self.has_batch_no or
|
||||
cstr(vals.valuation_method) != cstr(self.valuation_method)):
|
||||
if self.check_if_sle_exists() == "exists":
|
||||
frappe.throw(_("As there are existing stock transactions for this item, you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'"))
|
||||
frappe.throw(_("As there are existing stock transactions for this item, \
|
||||
you can not change the values of 'Has Serial No', 'Has Batch No', 'Is Stock Item' and 'Valuation Method'"))
|
||||
|
||||
def validate_reorder_level(self):
|
||||
if cint(self.apply_warehouse_wise_reorder_level):
|
||||
@@ -460,9 +313,33 @@ class Item(WebsiteGenerator):
|
||||
def update_item_desc(self):
|
||||
if frappe.db.get_value('BOM',self.name, 'description') != self.description:
|
||||
frappe.db.sql("""update `tabBOM` set description = %s where item = %s and docstatus < 2""",(self.description, self.name))
|
||||
frappe.db.sql("""update `tabBOM Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name))
|
||||
frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where item_code = %s and docstatus < 2""",(self.description, self.name))
|
||||
frappe.db.sql("""update `tabBOM Item` set description = %s where
|
||||
item_code = %s and docstatus < 2""",(self.description, self.name))
|
||||
frappe.db.sql("""update `tabBOM Explosion Item` set description = %s where
|
||||
item_code = %s and docstatus < 2""",(self.description, self.name))
|
||||
|
||||
def update_variants(self):
|
||||
if self.has_variants:
|
||||
updated = []
|
||||
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
|
||||
for d in variants:
|
||||
update_variant(self.name, d)
|
||||
updated.append(d.item_code)
|
||||
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
|
||||
|
||||
def validate_has_variants(self):
|
||||
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
|
||||
if frappe.db.exists("Item", {"variant_of": self.name}):
|
||||
frappe.throw(_("Item has variants."))
|
||||
|
||||
def validate_stock_for_template_must_be_zero(self):
|
||||
if self.has_variants:
|
||||
stock_in = frappe.db.sql_list("""select warehouse from tabBin
|
||||
where item_code=%s and (ifnull(actual_qty, 0) > 0 or ifnull(ordered_qty, 0) > 0
|
||||
or ifnull(reserved_qty, 0) > 0 or ifnull(indented_qty, 0) > 0 or ifnull(planned_qty, 0) > 0)""", self.name)
|
||||
if stock_in:
|
||||
frappe.throw(_("Item Template cannot have stock or Open Sales/Purchase/Production Orders."), ItemTemplateCannotHaveStock)
|
||||
|
||||
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")
|
||||
@@ -567,3 +444,4 @@ def invalidate_cache_for_item(doc):
|
||||
|
||||
if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
|
||||
invalidate_cache_for(doc, doc.old_item_group)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import unittest
|
||||
import frappe
|
||||
|
||||
from frappe.test_runner import make_test_records
|
||||
from erpnext.stock.doctype.item.item import WarehouseNotSet, DuplicateVariant, ItemTemplateCannotHaveStock
|
||||
from erpnext.stock.doctype.item.item import WarehouseNotSet, ItemTemplateCannotHaveStock
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
test_ignore = ["BOM"]
|
||||
@@ -20,60 +20,14 @@ class TestItem(unittest.TestCase):
|
||||
item.insert()
|
||||
else:
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
|
||||
return item
|
||||
|
||||
def test_duplicate_variant(self):
|
||||
item = frappe.copy_doc(test_records[11])
|
||||
item.append("variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"})
|
||||
self.assertRaises(DuplicateVariant, item.insert)
|
||||
|
||||
|
||||
def test_template_cannot_have_stock(self):
|
||||
item = self.get_item(10)
|
||||
|
||||
se = make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1)
|
||||
|
||||
item.has_variants = 1
|
||||
item.append("variants", {"item_attribute": "Test Size", "item_attribute_value": "Small"})
|
||||
|
||||
self.assertRaises(ItemTemplateCannotHaveStock, item.save)
|
||||
|
||||
def test_variant_item_codes(self):
|
||||
item = self.get_item(11)
|
||||
|
||||
variants = ['_Test Variant Item-S', '_Test Variant Item-M', '_Test Variant Item-L']
|
||||
self.assertEqual(item.get_variant_item_codes(), variants)
|
||||
for v in variants:
|
||||
self.assertTrue(frappe.db.get_value("Item", {"variant_of": item.name, "name": v}))
|
||||
|
||||
item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Red"})
|
||||
item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Blue"})
|
||||
item.append("variants", {"item_attribute": "Test Colour", "item_attribute_value": "Green"})
|
||||
|
||||
self.assertEqual(item.get_variant_item_codes(), ['_Test Variant Item-S-R',
|
||||
'_Test Variant Item-S-G', '_Test Variant Item-S-B',
|
||||
'_Test Variant Item-M-R', '_Test Variant Item-M-G',
|
||||
'_Test Variant Item-M-B', '_Test Variant Item-L-R',
|
||||
'_Test Variant Item-L-G', '_Test Variant Item-L-B'])
|
||||
|
||||
self.assertEqual(item.variant_attributes['_Test Variant Item-L-R'], [['Test Size', 'Large'], ['Test Colour', 'Red']])
|
||||
self.assertEqual(item.variant_attributes['_Test Variant Item-S-G'], [['Test Size', 'Small'], ['Test Colour', 'Green']])
|
||||
|
||||
# check stock entry cannot be made
|
||||
def test_stock_entry_cannot_be_made_for_template(self):
|
||||
item = self.get_item(11)
|
||||
|
||||
se = frappe.new_doc("Stock Entry")
|
||||
se.purpose = "Material Receipt"
|
||||
se.append("items", {
|
||||
"item_code": item.name,
|
||||
"t_warehouse": "Stores - _TC",
|
||||
"qty": 1,
|
||||
"incoming_rate": 1
|
||||
})
|
||||
se.insert()
|
||||
self.assertRaises(ItemTemplateCannotHaveStock, se.submit)
|
||||
|
||||
item = self.get_item(10)
|
||||
se = make_stock_entry(item_code=item.name, target="Stores - _TC", qty=1, incoming_rate=1)
|
||||
item.has_variants = 1
|
||||
self.assertRaises(ItemTemplateCannotHaveStock, item.save)
|
||||
|
||||
def test_default_warehouse(self):
|
||||
item = frappe.copy_doc(test_records[0])
|
||||
item.is_stock_item = "Yes"
|
||||
|
||||
@@ -273,11 +273,6 @@
|
||||
"item_name": "_Test Variant Item",
|
||||
"stock_uom": "_Test UOM",
|
||||
"has_variants": 1,
|
||||
"variants": [
|
||||
{"item_attribute": "Test Size", "item_attribute_value": "Small"},
|
||||
{"item_attribute": "Test Size", "item_attribute_value": "Medium"},
|
||||
{"item_attribute": "Test Size", "item_attribute_value": "Large"}
|
||||
],
|
||||
"apply_warehouse_wise_reorder_level": 1,
|
||||
"reorder_levels": [
|
||||
{
|
||||
|
||||
0
erpnext/stock/doctype/manage_variants/__init__.py
Normal file
0
erpnext/stock/doctype/manage_variants/__init__.py
Normal file
56
erpnext/stock/doctype/manage_variants/manage_variants.js
Normal file
56
erpnext/stock/doctype/manage_variants/manage_variants.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Manage Variants", {
|
||||
onload: function(frm) {
|
||||
var df = frappe.meta.get_docfield("Variant Attribute", "attribute_value");
|
||||
df.on_make = function(field) {
|
||||
$(field.input_area).addClass("ui-front");
|
||||
field.$input.autocomplete({
|
||||
minLength: 0,
|
||||
minChars: 0,
|
||||
source: function(request, response) {
|
||||
frappe.call({
|
||||
method:"frappe.client.get_list",
|
||||
args:{
|
||||
doctype:"Item Attribute Value",
|
||||
filters: [
|
||||
["parent","=", field.doc.attribute],
|
||||
["attribute_value", "like", request.term + "%"]
|
||||
],
|
||||
fields: ["attribute_value"]
|
||||
},
|
||||
callback: function(r) {
|
||||
response($.map(r.message, function(d) { return d.attribute_value; }));
|
||||
}
|
||||
});
|
||||
},
|
||||
select: function(event, ui) {
|
||||
field.$input.val(ui.item.value);
|
||||
field.$input.trigger("change");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.disable_save();
|
||||
frm.page.set_primary_action(__("Create Variants"), function() {
|
||||
frappe.call({
|
||||
method: "create_variants",
|
||||
doc:frm.doc
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
item_code:function(frm) {
|
||||
return frappe.call({
|
||||
method: "get_item_details",
|
||||
doc:frm.doc,
|
||||
callback: function(r) {
|
||||
refresh_field('attributes');
|
||||
refresh_field('variants');
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
103
erpnext/stock/doctype/manage_variants/manage_variants.json
Normal file
103
erpnext/stock/doctype/manage_variants/manage_variants.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"creation": "2015-05-19 05:39:59.345901",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Item Variant Attributes",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "attributes",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"label": "Attributes",
|
||||
"no_copy": 0,
|
||||
"options": "Variant Attribute",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "generate_combinations",
|
||||
"fieldtype": "Button",
|
||||
"label": "Generate Combinations",
|
||||
"options": "generate_combinations",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Item Variants",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "variants",
|
||||
"fieldtype": "Table",
|
||||
"label": "Variants",
|
||||
"options": "Manage Variants Item",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"in_create": 1,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"modified": "2015-06-30 13:40:59.946655",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Manage Variants",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Material Master Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
207
erpnext/stock/doctype/manage_variants/manage_variants.py
Normal file
207
erpnext/stock/doctype/manage_variants/manage_variants.py
Normal file
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
import copy
|
||||
import json
|
||||
|
||||
class DuplicateAttribute(frappe.ValidationError): pass
|
||||
|
||||
class ManageVariants(Document):
|
||||
|
||||
def get_item_details(self):
|
||||
self.clear_tables()
|
||||
if self.item_code:
|
||||
self.get_attributes()
|
||||
self.get_variants()
|
||||
|
||||
def generate_combinations(self):
|
||||
self.validate_attributes()
|
||||
self.validate_template_item()
|
||||
self.validate_attribute_values()
|
||||
self.validate_attributes_are_unique()
|
||||
self.get_variant_item_codes()
|
||||
|
||||
def create_variants(self):
|
||||
self.sync_variants()
|
||||
|
||||
def clear_tables(self):
|
||||
self.set('attributes', [])
|
||||
self.set('variants', [])
|
||||
|
||||
def get_attributes(self):
|
||||
attributes = {}
|
||||
self.set('attributes', [])
|
||||
for d in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` as attribute,
|
||||
`tabItem` as item where attribute.parent= item.name and item.variant_of = %s""", self.item_code, as_dict=1):
|
||||
attributes.setdefault(d.attribute, []).append(d.attribute_value)
|
||||
for d in attributes:
|
||||
attribute_values = set(attributes[d])
|
||||
for value in attribute_values:
|
||||
self.append('attributes',{"attribute": d, "attribute_value": value})
|
||||
|
||||
def get_variants(self):
|
||||
variants = [d.name for d in frappe.get_all("Item",
|
||||
filters={"variant_of":self.item_code})]
|
||||
data = frappe.db.sql("""select parent, attribute, attribute_value from `tabVariant Attribute`""", as_dict=1)
|
||||
for d in variants:
|
||||
variant_attributes, attributes = "", []
|
||||
for attribute in data:
|
||||
if attribute.parent == d:
|
||||
variant_attributes += attribute.attribute_value + " | "
|
||||
attributes.append([attribute.attribute, attribute.attribute_value])
|
||||
self.append('variants',{"variant": d, "variant_attributes": variant_attributes[: -3], "attributes": json.dumps(attributes)})
|
||||
|
||||
def validate_attributes(self):
|
||||
if not self.attributes:
|
||||
frappe.throw(_("Enter atleast one Attribute & its Value in Attribute table."))
|
||||
|
||||
def validate_template_item(self):
|
||||
if not frappe.db.get_value("Item", self.item_code, "has_variants"):
|
||||
frappe.throw(_("Selected Item cannot have Variants."))
|
||||
|
||||
if frappe.db.get_value("Item", self.item_code, "variant_of"):
|
||||
frappe.throw(_("Item cannot be a variant of a variant"))
|
||||
|
||||
def validate_attribute_values(self):
|
||||
attributes = {}
|
||||
for t in frappe.db.get_all("Item Attribute Value", fields=["parent", "attribute_value"]):
|
||||
attributes.setdefault(t.parent, []).append(t.attribute_value)
|
||||
|
||||
for d in self.attributes:
|
||||
if d.attribute_value not in attributes.get(d.attribute):
|
||||
frappe.throw(_("Attribute value {0} does not exist in Item Attribute Master.").format(d.attribute_value))
|
||||
|
||||
def validate_attributes_are_unique(self):
|
||||
attributes = []
|
||||
for d in self.attributes:
|
||||
key = (d.attribute, d.attribute_value)
|
||||
if key in attributes:
|
||||
frappe.throw(_("{0} {1} is entered more than once in Attributes table")
|
||||
.format(d.attribute, d.attribute_value), DuplicateAttribute)
|
||||
attributes.append(key)
|
||||
|
||||
def get_variant_item_codes(self):
|
||||
"""Get all possible suffixes for variants"""
|
||||
variant_dict = {}
|
||||
self.set('variants', [])
|
||||
|
||||
for d in self.attributes:
|
||||
variant_dict.setdefault(d.attribute, []).append(d.attribute_value)
|
||||
|
||||
all_attributes = [d.name for d in frappe.get_all("Item Attribute", order_by = "priority asc")]
|
||||
|
||||
# sort attributes by their priority
|
||||
attributes = filter(None, map(lambda d: d if d in variant_dict else None, all_attributes))
|
||||
|
||||
def add_attribute_suffixes(item_code, my_attributes, attributes):
|
||||
attr = frappe.get_doc("Item Attribute", attributes[0])
|
||||
for value in attr.item_attribute_values:
|
||||
if value.attribute_value in variant_dict[attr.name]:
|
||||
_my_attributes = copy.deepcopy(my_attributes)
|
||||
_my_attributes.append([attr.name, value.attribute_value])
|
||||
if len(attributes) > 1:
|
||||
add_attribute_suffixes(item_code + "-" + value.abbr, _my_attributes, attributes[1:])
|
||||
else:
|
||||
variant_attributes = ""
|
||||
for d in _my_attributes:
|
||||
variant_attributes += d[1] + " | "
|
||||
self.append('variants', {"variant": item_code + "-" + value.abbr,
|
||||
"attributes": json.dumps(_my_attributes), "variant_attributes": variant_attributes[: -3]})
|
||||
add_attribute_suffixes(self.item_code, [], attributes)
|
||||
|
||||
def sync_variants(self):
|
||||
variant_item_codes = []
|
||||
item_variants_attributes = {}
|
||||
inserted, updated, old_variant_name, new_variant_name, deleted = [], [], [], [], []
|
||||
|
||||
for v in self.variants:
|
||||
variant_item_codes.append(v.variant)
|
||||
|
||||
existing_variants = [d.name for d in frappe.get_all("Item",
|
||||
filters={"variant_of":self.item_code})]
|
||||
|
||||
for d in existing_variants:
|
||||
attributes = []
|
||||
for attribute in frappe.db.sql("""select attribute, attribute_value from `tabVariant Attribute` where parent = %s""", d):
|
||||
attributes.append([attribute[0], attribute[1]])
|
||||
item_variants_attributes.setdefault(d, []).append(attributes)
|
||||
|
||||
for existing_variant in existing_variants:
|
||||
if existing_variant not in variant_item_codes:
|
||||
att = item_variants_attributes[existing_variant][0]
|
||||
for variant in self.variants:
|
||||
if sorted(json.loads(variant.attributes) ,key=lambda x: x[0]) == \
|
||||
sorted(att ,key=lambda x: x[0]):
|
||||
rename_variant(existing_variant, variant.variant)
|
||||
old_variant_name.append(existing_variant)
|
||||
new_variant_name.append(variant.variant)
|
||||
|
||||
if existing_variant not in old_variant_name:
|
||||
delete_variant(existing_variant)
|
||||
deleted.append(existing_variant)
|
||||
|
||||
for item_code in variant_item_codes:
|
||||
if item_code not in existing_variants:
|
||||
if item_code not in new_variant_name:
|
||||
make_variant(self.item_code, item_code, self.variants)
|
||||
inserted.append(item_code)
|
||||
else:
|
||||
update_variant(self.item_code, item_code, self.variants)
|
||||
updated.append(item_code)
|
||||
|
||||
if inserted:
|
||||
frappe.msgprint(_("Item Variants {0} created").format(", ".join(inserted)))
|
||||
|
||||
if updated:
|
||||
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
|
||||
|
||||
if old_variant_name:
|
||||
frappe.msgprint(_("Item Variants {0} renamed").format(", ".join(old_variant_name)))
|
||||
|
||||
if deleted:
|
||||
frappe.msgprint(_("Item Variants {0} deleted").format(", ".join(deleted)))
|
||||
|
||||
def make_variant(item, variant_code, variant_attribute):
|
||||
variant = frappe.new_doc("Item")
|
||||
variant.item_code = variant_code
|
||||
copy_attributes_to_variant(item, variant, variant_attribute, insert=True)
|
||||
variant.insert()
|
||||
|
||||
def update_variant(item, variant_code, variant_attribute=None):
|
||||
variant = frappe.get_doc("Item", variant_code)
|
||||
copy_attributes_to_variant(item, variant, variant_attribute, insert=True)
|
||||
variant.save()
|
||||
|
||||
def rename_variant(old_variant_code, new_variant_code):
|
||||
frappe.rename_doc("Item", old_variant_code, new_variant_code)
|
||||
|
||||
def delete_variant(variant_code):
|
||||
frappe.delete_doc("Item", variant_code)
|
||||
|
||||
def copy_attributes_to_variant(item, variant, variant_attribute=None, insert=False):
|
||||
template = frappe.get_doc("Item", item)
|
||||
from frappe.model import no_value_fields
|
||||
for field in template.meta.fields:
|
||||
if field.fieldtype not in no_value_fields and (insert or not field.no_copy)\
|
||||
and field.fieldname not in ("item_code", "item_name"):
|
||||
if variant.get(field.fieldname) != template.get(field.fieldname):
|
||||
variant.set(field.fieldname, template.get(field.fieldname))
|
||||
variant.item_name = template.item_name + variant.item_code[len(template.name):]
|
||||
variant.variant_of = template.name
|
||||
variant.has_variants = 0
|
||||
variant.show_in_website = 0
|
||||
if variant_attribute:
|
||||
for d in variant_attribute:
|
||||
if d.variant == variant.item_code:
|
||||
variant.attributes= []
|
||||
for a in json.loads(d.attributes):
|
||||
variant.append('attributes', {"attribute": a[0], "attribute_value": a[1]})
|
||||
if variant.attributes:
|
||||
variant.description += "\n"
|
||||
for d in variant.attributes:
|
||||
variant.description += "<p>" + d.attribute + ": " + d.attribute_value + "</p>"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import unittest
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.manage_variants.manage_variants import DuplicateAttribute
|
||||
|
||||
class TestManageVariants(unittest.TestCase):
|
||||
def test_variant_item_codes(self):
|
||||
manage_variant = frappe.new_doc("Manage Variants")
|
||||
manage_variant.update({
|
||||
"item": "_Test Variant Item",
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Test Size",
|
||||
"attribute_value": "Small"
|
||||
},
|
||||
{
|
||||
"attribute": "Test Size",
|
||||
"attribute_value": "Large"
|
||||
}
|
||||
]
|
||||
})
|
||||
manage_variant.generate_combinations()
|
||||
self.assertEqual(manage_variant.variants[0].variant, "_Test Variant Item-S")
|
||||
self.assertEqual(manage_variant.variants[1].variant, "_Test Variant Item-L")
|
||||
|
||||
self.assertEqual(manage_variant.variants[0].variant_attributes, "Small")
|
||||
self.assertEqual(manage_variant.variants[1].variant_attributes, "Large")
|
||||
manage_variant.create_variants()
|
||||
|
||||
def test_attributes_are_unique(self):
|
||||
manage_variant = frappe.new_doc("Manage Variants")
|
||||
manage_variant.update({
|
||||
"item": "_Test Variant Item",
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Test Size",
|
||||
"attribute_value": "Small"
|
||||
},
|
||||
{
|
||||
"attribute": "Test Size",
|
||||
"attribute_value": "Small"
|
||||
}
|
||||
]
|
||||
})
|
||||
self.assertRaises(DuplicateAttribute, manage_variant.generate_combinations)
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"creation": "2015-05-19 05:55:31.155672",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "variant",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Variant",
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"fieldname": "variant_attributes",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Variant Attributes",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "attributes",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"label": "attributes",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "",
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-06-30 03:19:07.548196",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Manage Variants Item",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ManageVariantsItem(Document):
|
||||
pass
|
||||
@@ -443,7 +443,7 @@
|
||||
{
|
||||
"fieldname": "base_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discount Amount (Company Currency)",
|
||||
"label": "Additional Discount Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
@@ -854,7 +854,7 @@
|
||||
"icon": "icon-truck",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"modified": "2015-05-27 02:48:00.763945",
|
||||
"modified": "2015-06-15 15:38:43.754869",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -180,7 +180,7 @@ class SerialNo(StockController):
|
||||
where fieldname='serial_no' and fieldtype='Text'"""):
|
||||
|
||||
for item in frappe.db.sql("""select name, serial_no from `tab%s`
|
||||
where serial_no like '%%%s%%'""" % (dt[0], old)):
|
||||
where serial_no like '%%%s%%'""" % (dt[0], frappe.db.escape(old))):
|
||||
|
||||
serial_nos = map(lambda i: i==old and new or i, item[1].split('\n'))
|
||||
frappe.db.sql("""update `tab%s` set serial_no = %s
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
import frappe.defaults
|
||||
|
||||
from frappe.utils import cstr, cint, flt, comma_or, get_datetime
|
||||
from frappe.utils import cstr, cint, flt, comma_or, get_datetime, getdate
|
||||
|
||||
from frappe import _
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
@@ -66,6 +66,7 @@ class StockEntry(StockController):
|
||||
self.validate_valuation_rate()
|
||||
self.set_total_incoming_outgoing_value()
|
||||
self.set_total_amount()
|
||||
self.validate_batch()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
@@ -359,8 +360,11 @@ class StockEntry(StockController):
|
||||
if self.purpose == "Subcontract" and self.purchase_order:
|
||||
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
|
||||
for se_item in self.items:
|
||||
total_allowed = [d.required_qty for d in purchase_order.supplied_items \
|
||||
if d.rm_item_code == se_item.item_code][0]
|
||||
total_allowed = sum([flt(d.required_qty) for d in purchase_order.supplied_items \
|
||||
if d.rm_item_code == se_item.item_code])
|
||||
if not total_allowed:
|
||||
frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}")
|
||||
.format(se_item.item_code, self.purchase_order))
|
||||
total_supplied = frappe.db.sql("""select sum(qty)
|
||||
from `tabStock Entry Detail`, `tabStock Entry`
|
||||
where `tabStock Entry`.purchase_order = %s
|
||||
@@ -721,6 +725,13 @@ class StockEntry(StockController):
|
||||
mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse):
|
||||
frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx),
|
||||
frappe.MappingMismatchError)
|
||||
|
||||
def validate_batch(self):
|
||||
if self.purpose == "Material Transfer for Manufacture":
|
||||
for item in self.get("items"):
|
||||
if item.batch_no:
|
||||
if getdate(self.posting_date) > getdate(frappe.db.get_value("Batch", item.batch_no, "expiry_date")):
|
||||
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(ref_dt, ref_dn):
|
||||
|
||||
@@ -72,6 +72,18 @@ class TestStockEntry(unittest.TestCase):
|
||||
self._test_auto_material_request("_Test Item")
|
||||
|
||||
def test_auto_material_request_for_variant(self):
|
||||
manage_variant = frappe.new_doc("Manage Variants")
|
||||
manage_variant.update({
|
||||
"item": "_Test Variant Item",
|
||||
"attributes": [
|
||||
{
|
||||
"attribute": "Test Size",
|
||||
"attribute_value": "Small"
|
||||
}
|
||||
]
|
||||
})
|
||||
manage_variant.generate_combinations()
|
||||
manage_variant.create_variants()
|
||||
self._test_auto_material_request("_Test Variant Item-S")
|
||||
|
||||
def _test_auto_material_request(self, item_code):
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
"in_list_view": 0,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Text",
|
||||
@@ -344,7 +344,7 @@
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"modified": "2015-02-25 04:31:21.801200",
|
||||
"modified": "2015-07-02 05:32:56.511570",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry Detail",
|
||||
|
||||
0
erpnext/stock/doctype/variant_attribute/__init__.py
Normal file
0
erpnext/stock/doctype/variant_attribute/__init__.py
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"creation": "2015-05-19 05:12:30.344797",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "attribute",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Attribute",
|
||||
"no_copy": 0,
|
||||
"options": "Item Attribute",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"precision": ""
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"fieldname": "attribute_value",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Attribute Value",
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "",
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"modified": "2015-05-20 06:16:16.803578",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Variant Attribute",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
10
erpnext/stock/doctype/variant_attribute/variant_attribute.py
Normal file
10
erpnext/stock/doctype/variant_attribute/variant_attribute.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class VariantAttribute(Document):
|
||||
pass
|
||||
@@ -54,7 +54,7 @@ def get_stock_ledger_entries(filters):
|
||||
return frappe.db.sql("""select item_code, batch_no, warehouse,
|
||||
posting_date, actual_qty
|
||||
from `tabStock Ledger Entry`
|
||||
where docstatus < 2 %s order by item_code, warehouse""" %
|
||||
where docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
|
||||
conditions, as_dict=1)
|
||||
|
||||
def get_item_warehouse_batch_map(filters, float_precision):
|
||||
|
||||
0
erpnext/stock/report/bom_search/__init__.py
Normal file
0
erpnext/stock/report/bom_search/__init__.py
Normal file
42
erpnext/stock/report/bom_search/bom_search.js
Normal file
42
erpnext/stock/report/bom_search/bom_search.js
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["BOM Search"] = {
|
||||
"filters": [
|
||||
{
|
||||
fieldname: "item1",
|
||||
label: __("Item 1"),
|
||||
fieldtype: "Link",
|
||||
options: "Item"
|
||||
},
|
||||
{
|
||||
fieldname: "item2",
|
||||
label: __("Item 2"),
|
||||
fieldtype: "Link",
|
||||
options: "Item"
|
||||
},
|
||||
{
|
||||
fieldname: "item3",
|
||||
label: __("Item 3"),
|
||||
fieldtype: "Link",
|
||||
options: "Item"
|
||||
},
|
||||
{
|
||||
fieldname: "item4",
|
||||
label: __("Item 4"),
|
||||
fieldtype: "Link",
|
||||
options: "Item"
|
||||
},
|
||||
{
|
||||
fieldname: "item5",
|
||||
label: __("Item 5"),
|
||||
fieldtype: "Link",
|
||||
options: "Item"
|
||||
},
|
||||
{
|
||||
fieldname: "search_sub_assemblies",
|
||||
label: __("Search Sub Assemblies"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
]
|
||||
}
|
||||
17
erpnext/stock/report/bom_search/bom_search.json
Normal file
17
erpnext/stock/report/bom_search/bom_search.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"creation": "2015-06-16 15:16:11.930954",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"modified": "2015-06-16 15:16:29.850834",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "BOM Search",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "BOM",
|
||||
"report_name": "BOM Search",
|
||||
"report_type": "Script Report"
|
||||
}
|
||||
43
erpnext/stock/report/bom_search/bom_search.py
Normal file
43
erpnext/stock/report/bom_search/bom_search.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
|
||||
def execute(filters=None):
|
||||
data = []
|
||||
parents = {
|
||||
"Sales BOM Item": "Sales BOM",
|
||||
"BOM Explosion Item": "BOM",
|
||||
"BOM Item": "BOM"
|
||||
}
|
||||
|
||||
for doctype in ("Sales BOM Item",
|
||||
"BOM Explosion Item" if filters.search_sub_assemblies else "BOM Item"):
|
||||
all_boms = {}
|
||||
for d in frappe.get_all(doctype, fields=["parent", "item_code"]):
|
||||
all_boms.setdefault(d.parent, []).append(d.item_code)
|
||||
|
||||
for parent, items in all_boms.iteritems():
|
||||
valid = True
|
||||
for key, item in filters.iteritems():
|
||||
if key != "search_sub_assemblies":
|
||||
if item and item not in items:
|
||||
valid = False
|
||||
|
||||
if valid:
|
||||
data.append((parent, parents[doctype]))
|
||||
|
||||
return [{
|
||||
"fieldname": "parent",
|
||||
"label": "BOM",
|
||||
"width": 200,
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "doctype"
|
||||
},
|
||||
{
|
||||
"fieldname": "doctype",
|
||||
"label": "Type",
|
||||
"width": 200,
|
||||
"fieldtype": "Data"
|
||||
}], data
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
from frappe.utils import add_days, getdate, cint
|
||||
from frappe.utils import add_days, getdate, cint, cstr
|
||||
|
||||
from frappe import throw, _
|
||||
from erpnext.utilities.transaction_base import TransactionBase, delete_events
|
||||
@@ -73,7 +73,7 @@ class MaintenanceSchedule(TransactionBase):
|
||||
"owner": email_map[d.sales_person] or self.owner,
|
||||
"subject": description,
|
||||
"description": description,
|
||||
"starts_on": key["scheduled_date"] + " 10:00:00",
|
||||
"starts_on": cstr(key["scheduled_date"]) + " 10:00:00",
|
||||
"event_type": "Private",
|
||||
"ref_type": self.doctype,
|
||||
"ref_name": self.name
|
||||
|
||||
@@ -9,5 +9,7 @@
|
||||
{%= doc.get_formatted(df.fieldname) %}
|
||||
</div>
|
||||
</div>
|
||||
{% } else if (df.fieldname==="description") { %}
|
||||
<p>{{ doc.description }}</p>
|
||||
{% } %}
|
||||
{% }); %}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{% var visible_columns = row.get_visible_columns(["item_code", "qty", "rate", "amount", "stock_uom", "uom", "discount_percentage", "schedule_date", "warehouse", "against_sales_order", "sales_order"]); %}
|
||||
{% var visible_columns = row.get_visible_columns(["item_code", "qty", "rate", "amount",
|
||||
"stock_uom", "uom", "discount_percentage", "schedule_date", "warehouse",
|
||||
"against_sales_order", "sales_order"]); %}
|
||||
|
||||
{% if(!doc) { %}
|
||||
<div class="row">
|
||||
@@ -48,12 +50,7 @@
|
||||
|
||||
{% if(doc.item_name != doc.item_code && in_list(visible_column_fieldnames, "item_name")) { %}
|
||||
<br>{%= doc.item_name %}{% } %}
|
||||
|
||||
{% if((doc.description != doc.item_code != doc.item_name) &&
|
||||
in_list(visible_column_fieldnames, "description")) { %}
|
||||
<br>
|
||||
<strong>Description: </strong>
|
||||
{%= doc.get_formatted("description") %}{% } %}
|
||||
|
||||
{% include "templates/form_grid/includes/visible_cols.html" %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="web-list-item">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="col-sm-6">
|
||||
<a class="no-decoration" href="/issues?name={{ doc.name }}" no-pjax>
|
||||
{{ doc.subject }}
|
||||
</a>
|
||||
@@ -9,6 +9,11 @@
|
||||
<span class="indicator {{ "red" if doc.status=="Open" else "blue" }}">
|
||||
{{ doc.status }}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<a class="text-muted text-right" href="/issues?name={{ doc.name }}" no-pjax>
|
||||
{{ doc.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-2 text-muted text-right small">
|
||||
{{ frappe.format_date(doc.creation) }}
|
||||
</div>
|
||||
|
||||
@@ -22,16 +22,21 @@ class TransactionBase(StatusUpdater):
|
||||
self.posting_time = now_datetime().strftime('%H:%M:%S')
|
||||
|
||||
def add_calendar_event(self, opts, force=False):
|
||||
if self.contact_by != cstr(self._prev.contact_by) or \
|
||||
self.contact_date != cstr(self._prev.contact_date) or force:
|
||||
if cstr(self.contact_by) != cstr(self._prev.contact_by) or \
|
||||
cstr(self.contact_date) != cstr(self._prev.contact_date) or force:
|
||||
|
||||
self.delete_events()
|
||||
self._add_calendar_event(opts)
|
||||
|
||||
def delete_events(self):
|
||||
frappe.delete_doc("Event", frappe.db.sql_list("""select name from `tabEvent`
|
||||
where ref_type=%s and ref_name=%s""", (self.doctype, self.name)),
|
||||
ignore_permissions=True)
|
||||
events = frappe.db.sql_list("""select name from `tabEvent`
|
||||
where ref_type=%s and ref_name=%s""", (self.doctype, self.name))
|
||||
if events:
|
||||
frappe.db.sql("delete from `tabEvent` where name in (%s)"
|
||||
.format(", ".join(['%s']*len(events))), tuple(events))
|
||||
|
||||
frappe.db.sql("delete from `tabEvent Role` where parent in (%s)"
|
||||
.format(", ".join(['%s']*len(events))), tuple(events))
|
||||
|
||||
def _add_calendar_event(self, opts):
|
||||
opts = frappe._dict(opts)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user