Merge branch 'master' into edge

This commit is contained in:
Anand Doshi 2013-02-27 19:14:16 +05:30
commit 215fc02395
49 changed files with 920 additions and 714 deletions

View File

@ -26,7 +26,6 @@ test_records = [[
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",
"naming_series": "_T-Journal Voucher-", "naming_series": "_T-Journal Voucher-",
"posting_date": "2013-02-14", "posting_date": "2013-02-14",
"tds_applicable": "No",
"user_remark": "test", "user_remark": "test",
"voucher_type": "Bank Voucher", "voucher_type": "Bank Voucher",
"cheque_no": "33", "cheque_no": "33",

View File

@ -35,7 +35,7 @@ class DocType(BuyingController):
self.fname = 'entries' self.fname = 'entries'
def get_credit_to(self): def get_credit_to(self):
acc_head = sql("select name, credit_days from `tabAccount` where (name = %s or (master_name = %s and master_type = 'supplier')) and docstatus != 2", (cstr(self.doc.supplier) + " - " + self.get_company_abbr(),self.doc.supplier)) acc_head = sql("select name, credit_days from `tabAccount` where (name = %s or (master_name = %s and master_type = 'supplier')) and docstatus != 2", (cstr(self.doc.supplier) + " - " + self.company_abbr,self.doc.supplier))
ret = {} ret = {}
if acc_head and acc_head[0][0]: if acc_head and acc_head[0][0]:
@ -55,18 +55,30 @@ class DocType(BuyingController):
return ret return ret
def get_default_values(self,args): def get_default_values(self, args):
import json if isinstance(args, basestring):
args = json.loads(args) import json
ret = {} args = json.loads(args)
if sql("select name from `tabItem` where name = '%s'" % args['item_code']):
if not args['expense_head'] or args['expense_head'] == 'undefined': out = webnotes._dict()
expense_head = sql("select name from `tabAccount` where account_name in (select purchase_account from `tabItem` where name = '%s')" % args['item_code'])
ret['expense_head'] = expense_head and expense_head[0][0] or '' item = webnotes.conn.sql("""select name, purchase_account, cost_center,
if not args['cost_center'] or args['cost_center'] == 'undefined': is_stock_item from `tabItem` where name=%s""", args.get("item_code"), as_dict=1)
cost_center = sql("select cost_center from `tabItem` where name = '%s'" % args['item_code'])
ret['cost_center'] = cost_center and cost_center[0][0] or '' if item and item[0]:
return ret item = item[0]
if cint(webnotes.defaults.get_global_default("auto_inventory_accounting")) and \
item.is_stock_item == "Yes":
# unset expense head for stock item and auto inventory accounting
out.expense_head = out.cost_center = None
else:
if not args.get("expense_head"):
out.expense_head = item.purchase_account
if not args.get("cost_center"):
out.cost_center = item.cost_center
return out
def pull_details(self): def pull_details(self):
if self.doc.purchase_receipt_main: if self.doc.purchase_receipt_main:
@ -86,9 +98,7 @@ class DocType(BuyingController):
def get_expense_account(self, doctype): def get_expense_account(self, doctype):
for d in getlist(self.doclist, doctype): for d in getlist(self.doclist, doctype):
if d.item_code: if d.item_code:
item = webnotes.conn.sql("select purchase_account, cost_center from tabItem where name = '%s'" %(d.item_code), as_dict=1) d.fields.update(self.get_default_values(d.fields))
d.expense_head = item and item[0]['purchase_account'] or ''
d.cost_center = item and item[0]['cost_center'] or ''
def get_advances(self): def get_advances(self):
super(DocType, self).get_advances(self.doc.credit_to, super(DocType, self).get_advances(self.doc.credit_to,
@ -110,9 +120,6 @@ class DocType(BuyingController):
ret={'add_tax_rate' :rate and flt(rate[0][0]) or 0 } ret={'add_tax_rate' :rate and flt(rate[0][0]) or 0 }
return ret return ret
def get_company_abbr(self):
return sql("select abbr from tabCompany where name=%s", self.doc.company)[0][0]
def validate_duplicate_docname(self,doctype): def validate_duplicate_docname(self,doctype):
for d in getlist(self.doclist, 'entries'): for d in getlist(self.doclist, 'entries'):
if doctype == 'purchase_receipt' and cstr(self.doc.purchase_receipt_main) == cstr(d.purchase_receipt): if doctype == 'purchase_receipt' and cstr(self.doc.purchase_receipt_main) == cstr(d.purchase_receipt):
@ -186,7 +193,7 @@ class DocType(BuyingController):
if self.doc.supplier and self.doc.credit_to: if self.doc.supplier and self.doc.credit_to:
acc_head = sql("select master_name from `tabAccount` where name = %s", self.doc.credit_to) acc_head = sql("select master_name from `tabAccount` where name = %s", self.doc.credit_to)
if (acc_head and cstr(acc_head[0][0]) != cstr(self.doc.supplier)) or (not acc_head and (self.doc.credit_to != cstr(self.doc.supplier) + " - " + self.get_company_abbr())): if (acc_head and cstr(acc_head[0][0]) != cstr(self.doc.supplier)) or (not acc_head and (self.doc.credit_to != cstr(self.doc.supplier) + " - " + self.company_abbr)):
msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.doc.credit_to,self.doc.supplier,self.doc.company), raise_exception=1) msgprint("Credit To: %s do not match with Supplier: %s for Company: %s.\n If both correctly entered, please select Master Type and Master Name in account master." %(self.doc.credit_to,self.doc.supplier,self.doc.company), raise_exception=1)
# Check for Stopped PO # Check for Stopped PO
@ -256,11 +263,25 @@ class DocType(BuyingController):
raise Exception raise Exception
def set_against_expense_account(self): def set_against_expense_account(self):
against_acc = [] auto_inventory_accounting = \
for d in getlist(self.doclist, 'entries'): cint(webnotes.defaults.get_global_default("auto_inventory_accounting"))
if d.expense_account not in against_acc: stock_not_billed_account = "Stock Received But Not Billed - %s" % self.company_abbr
against_acc.append(d.expense_account)
self.doc.against_expense_account = ','.join(against_acc) against_accounts = []
for item in self.doclist.get({"parentfield": "entries"}):
if auto_inventory_accounting and item.item_code in self.stock_items:
# in case of auto inventory accounting, against expense account is always
# Stock Received But Not Billed for a stock item
item.expense_head = item.cost_center = None
if stock_not_billed_account not in against_accounts:
against_accounts.append(stock_not_billed_account)
elif item.expense_head not in against_accounts:
# if no auto_inventory_accounting or not a stock item
against_accounts.append(item.expense_head)
self.doc.against_expense_account = ",".join(against_accounts)
def po_required(self): def po_required(self):
res = sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = 'po_required'") res = sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = 'po_required'")
@ -320,6 +341,8 @@ class DocType(BuyingController):
self.doc.posting_date,'Posting Date') self.doc.posting_date,'Posting Date')
self.validate_write_off_account() self.validate_write_off_account()
self.update_raw_material_cost()
self.update_valuation_rate("entries")
def check_prev_docstatus(self): def check_prev_docstatus(self):
for d in getlist(self.doclist,'entries'): for d in getlist(self.doclist,'entries'):
@ -382,11 +405,10 @@ class DocType(BuyingController):
def make_gl_entries(self, is_cancel = 0): def make_gl_entries(self, is_cancel = 0):
from accounts.general_ledger import make_gl_entries from accounts.general_ledger import make_gl_entries
auto_inventory_accounting = \
cint(webnotes.defaults.get_global_default("auto_inventory_accounting"))
gl_entries = [] gl_entries = []
valuation_tax = 0
auto_inventory_accounting = webnotes.conn.get_value("Global Defaults", None,
"auto_inventory_accounting")
abbr = self.get_company_abbr()
# parent's gl entry # parent's gl entry
if self.doc.grand_total: if self.doc.grand_total:
@ -402,11 +424,9 @@ class DocType(BuyingController):
) )
# tax table gl entries # tax table gl entries
for tax in getlist(self.doclist, "purchase_tax_details"): valuation_tax = 0
for tax in self.doclist.get({"parentfield": "purchase_tax_details"}):
if tax.category in ("Total", "Valuation and Total") and flt(tax.tax_amount): if tax.category in ("Total", "Valuation and Total") and flt(tax.tax_amount):
valuation_tax += tax.add_deduct_tax == "Add" \
and flt(tax.tax_amount) or -1 * flt(tax.tax_amount)
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": tax.account_head, "account": tax.account_head,
@ -418,24 +438,30 @@ class DocType(BuyingController):
}, is_cancel) }, is_cancel)
) )
# item gl entries # accumulate valuation tax
stock_item_and_auto_accounting = False if tax.category in ("Valuation", "Valuation and Total") and flt(tax.tax_amount):
for item in self.doclist.get({"parentfield": "entries"}): valuation_tax += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.tax_amount)
if auto_inventory_accounting and flt(item.valuation_rate) and \
webnotes.conn.get_value("Item", item.item_code, "is_stock_item")=="Yes":
# if auto inventory accounting enabled and stock item,
# then do stock related gl entries, expense will be booked in sales invoice
gl_entries.append(
self.get_gl_dict({
"account": "Stock Received But Not Billed - %s" % (abbr,),
"against": self.doc.credit_to,
"debit": flt(item.valuation_rate) * flt(item.conversion_factor) \
* item.qty,
"remarks": self.doc.remarks or "Accounting Entry for Stock"
}, is_cancel)
)
stock_item_and_auto_accounting = True # item gl entries
stock_item_and_auto_inventory_accounting = False
for item in self.doclist.get({"parentfield": "entries"}):
if auto_inventory_accounting and item.item_code in self.stock_items:
if flt(item.valuation_rate):
# if auto inventory accounting enabled and stock item,
# then do stock related gl entries
# expense will be booked in sales invoice
stock_item_and_auto_inventory_accounting = True
gl_entries.append(
self.get_gl_dict({
"account": "Stock Received But Not Billed - %s" % (self.company_abbr,),
"against": self.doc.credit_to,
"debit": flt(item.valuation_rate) * flt(item.conversion_factor) \
* flt(item.qty),
"remarks": self.doc.remarks or "Accounting Entry for Stock"
}, is_cancel)
)
elif flt(item.amount): elif flt(item.amount):
# if not a stock item or auto inventory accounting disabled, book the expense # if not a stock item or auto inventory accounting disabled, book the expense
@ -449,13 +475,13 @@ class DocType(BuyingController):
}, is_cancel) }, is_cancel)
) )
if stock_item_and_auto_accounting and valuation_tax: if stock_item_and_auto_inventory_accounting and valuation_tax:
# credit valuation tax amount in "Expenses Included In Valuation" # credit valuation tax amount in "Expenses Included In Valuation"
# this will balance out valuation amount included in cost of goods sold # this will balance out valuation amount included in cost of goods sold
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": "Expenses Included In Valuation - %s" % (abbr,), "account": "Expenses Included In Valuation - %s" % (self.company_abbr,),
"cost_center": "ERP - %s" % abbr, # to-do "cost_center": "Auto Inventory Accounting - %s" % self.company_abbr,
"against": self.doc.credit_to, "against": self.doc.credit_to,
"credit": valuation_tax, "credit": valuation_tax,
"remarks": self.doc.remarks or "Accounting Entry for Stock" "remarks": self.doc.remarks or "Accounting Entry for Stock"
@ -464,12 +490,12 @@ class DocType(BuyingController):
# writeoff account includes petty difference in the invoice amount # writeoff account includes petty difference in the invoice amount
# and the amount that is paid # and the amount that is paid
if self.doc.write_off_account and self.doc.write_off_amount: if self.doc.write_off_account and flt(self.doc.write_off_amount):
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": self.doc.write_off_account, "account": self.doc.write_off_account,
"against": self.doc.credit_to, "against": self.doc.credit_to,
"credit": self.doc.write_off_amount, "credit": flt(self.doc.write_off_amount),
"remarks": self.doc.remarks, "remarks": self.doc.remarks,
"cost_center": self.doc.write_off_cost_center "cost_center": self.doc.write_off_cost_center
}, is_cancel) }, is_cancel)
@ -478,7 +504,6 @@ class DocType(BuyingController):
if gl_entries: if gl_entries:
make_gl_entries(gl_entries, cancel=is_cancel) make_gl_entries(gl_entries, cancel=is_cancel)
def check_next_docstatus(self): def check_next_docstatus(self):
submit_jv = sql("select t1.name from `tabJournal Voucher` t1,`tabJournal Voucher Detail` t2 where t1.name = t2.parent and t2.against_voucher = '%s' and t1.docstatus = 1" % (self.doc.name)) submit_jv = sql("select t1.name from `tabJournal Voucher` t1,`tabJournal Voucher Detail` t2 where t1.name = t2.parent and t2.against_voucher = '%s' and t1.docstatus = 1" % (self.doc.name))
if submit_jv: if submit_jv:
@ -493,4 +518,16 @@ class DocType(BuyingController):
def on_update(self): def on_update(self):
pass pass
def update_raw_material_cost(self):
if self.sub_contracted_items:
for d in self.doclist.get({"parentfield": "entries"}):
rm_cost = webnotes.conn.sql(""" select raw_material_cost / quantity
from `tabBOM` where item = %s and is_default = 1 and docstatus = 1
and is_active = 1 """, (d.item_code,))
rm_cost = rm_cost and flt(rm_cost[0][0]) or 0
d.conversion_factor = d.conversion_factor or webnotes.conn.get_value(
"UOM Conversion Detail", {"parent": d.item_code, "uom": d.uom},
"conversion_factor") or 1
d.rm_supp_cost = rm_cost * flt(d.qty) * flt(d.conversion_factor)

View File

@ -20,18 +20,18 @@ import unittest
import webnotes import webnotes
import webnotes.model import webnotes.model
import json import json
from webnotes.utils import cint
import webnotes.defaults
test_dependencies = ["Item", "Cost Center"] test_dependencies = ["Item", "Cost Center"]
class TestPurchaseInvoice(unittest.TestCase): class TestPurchaseInvoice(unittest.TestCase):
def test_gl_entries(self): def test_gl_entries_without_auto_inventory_accounting(self):
wrapper = webnotes.bean(self.get_test_doclist()) webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
self.assertTrue(not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")))
# circumvent the disabled calculation call
obj = webnotes.get_obj(doc=wrapper.doc, doclist=wrapper.doclist)
obj.calculate_taxes_and_totals()
wrapper.set_doclist(obj.doclist)
wrapper = webnotes.bean(copy=test_records[0])
wrapper.run_method("calculate_taxes_and_totals")
wrapper.insert() wrapper.insert()
wrapper.submit() wrapper.submit()
wrapper.load_from_db() wrapper.load_from_db()
@ -53,14 +53,38 @@ class TestPurchaseInvoice(unittest.TestCase):
for d in gl_entries: for d in gl_entries:
self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account)) self.assertEqual([d.debit, d.credit], expected_gl_entries.get(d.account))
def test_gl_entries_with_auto_inventory_accounting(self):
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1)
pi = webnotes.bean(copy=test_records[1])
pi.run_method("calculate_taxes_and_totals")
pi.insert()
pi.submit()
gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
order by account asc""", pi.doc.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = sorted([
["_Test Supplier - _TC", 0, 720],
["Stock Received But Not Billed - _TC", 750.0, 0],
["_Test Account Shipping Charges - _TC", 100.0, 0],
["_Test Account VAT - _TC", 120.0, 0],
["Expenses Included In Valuation - _TC", 0, 250.0]
])
for i, gle in enumerate(gl_entries):
self.assertEquals(expected_values[i][0], gle.account)
self.assertEquals(expected_values[i][1], gle.debit)
self.assertEquals(expected_values[i][2], gle.credit)
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
def test_purchase_invoice_calculation(self): def test_purchase_invoice_calculation(self):
wrapper = webnotes.bean(self.get_test_doclist()) wrapper = webnotes.bean(copy=test_records[0])
wrapper.run_method("calculate_taxes_and_totals")
# circumvent the disabled calculation call
obj = webnotes.get_obj(doc=wrapper.doc, doclist=wrapper.doclist)
obj.calculate_taxes_and_totals()
wrapper.set_doclist(obj.doclist)
wrapper.insert() wrapper.insert()
wrapper.load_from_db() wrapper.load_from_db()
@ -92,144 +116,209 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(item.item_code, expected_values[i][0]) self.assertEqual(item.item_code, expected_values[i][0])
self.assertEqual(item.item_tax_amount, expected_values[i][1]) self.assertEqual(item.item_tax_amount, expected_values[i][1])
def get_test_doclist(self): test_records = [
return [ [
# parent # parent
{ {
"doctype": "Purchase Invoice", "doctype": "Purchase Invoice",
"naming_series": "BILL", "naming_series": "BILL",
"supplier_name": "_Test Supplier", "supplier_name": "_Test Supplier",
"credit_to": "_Test Supplier - _TC", "credit_to": "_Test Supplier - _TC",
"bill_no": "NA", "bill_no": "NA",
"posting_date": "2013-02-03", "posting_date": "2013-02-03",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",
"company": "_Test Company", "company": "_Test Company",
"currency": "INR", "currency": "INR",
"conversion_rate": 1, "conversion_rate": 1,
"grand_total_import": 0 # for feed "grand_total_import": 0 # for feed
}, },
# items # items
{ {
"doctype": "Purchase Invoice Item", "doctype": "Purchase Invoice Item",
"parentfield": "entries", "parentfield": "entries",
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item Home Desktop 100",
"item_name": "_Test Item Home Desktop 100", "item_name": "_Test Item Home Desktop 100",
"qty": 10, "qty": 10,
"import_rate": 50, "import_rate": 50,
"import_amount": 500, "import_amount": 500,
"rate": 50, "rate": 50,
"amount": 500, "amount": 500,
"uom": "_Test UOM", "uom": "_Test UOM",
"item_tax_rate": json.dumps({"_Test Account Excise Duty - _TC": 10}), "item_tax_rate": json.dumps({"_Test Account Excise Duty - _TC": 10}),
"expense_head": "_Test Account Cost for Goods Sold - _TC", "expense_head": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC" "cost_center": "_Test Cost Center - _TC",
"conversion_factor": 1.0,
}, },
{ {
"doctype": "Purchase Invoice Item", "doctype": "Purchase Invoice Item",
"parentfield": "entries", "parentfield": "entries",
"item_code": "_Test Item Home Desktop 200", "item_code": "_Test Item Home Desktop 200",
"item_name": "_Test Item Home Desktop 200", "item_name": "_Test Item Home Desktop 200",
"qty": 5, "qty": 5,
"import_rate": 150, "import_rate": 150,
"import_amount": 750, "import_amount": 750,
"rate": 150, "rate": 150,
"amount": 750, "amount": 750,
"uom": "_Test UOM", "uom": "_Test UOM",
"expense_head": "_Test Account Cost for Goods Sold - _TC", "expense_head": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC" "cost_center": "_Test Cost Center - _TC",
}, "conversion_factor": 1.0,
# taxes },
{ # taxes
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "Actual", "parentfield": "purchase_tax_details",
"account_head": "_Test Account Shipping Charges - _TC", "charge_type": "Actual",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account Shipping Charges - _TC",
"description": "Shipping Charges", "cost_center": "_Test Cost Center - _TC",
"category": "Valuation and Total", "description": "Shipping Charges",
"add_deduct_tax": "Add", "category": "Valuation and Total",
"rate": 100 "add_deduct_tax": "Add",
}, "rate": 100
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Net Total", "parentfield": "purchase_tax_details",
"account_head": "_Test Account Customs Duty - _TC", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account Customs Duty - _TC",
"description": "Customs Duty", "cost_center": "_Test Cost Center - _TC",
"category": "Valuation", "description": "Customs Duty",
"add_deduct_tax": "Add", "category": "Valuation",
"rate": 10 "add_deduct_tax": "Add",
}, "rate": 10
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Net Total", "parentfield": "purchase_tax_details",
"account_head": "_Test Account Excise Duty - _TC", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account Excise Duty - _TC",
"description": "Excise Duty", "cost_center": "_Test Cost Center - _TC",
"category": "Total", "description": "Excise Duty",
"add_deduct_tax": "Add", "category": "Total",
"rate": 12 "add_deduct_tax": "Add",
}, "rate": 12
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Previous Row Amount", "parentfield": "purchase_tax_details",
"account_head": "_Test Account Education Cess - _TC", "charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account Education Cess - _TC",
"description": "Education Cess", "cost_center": "_Test Cost Center - _TC",
"category": "Total", "description": "Education Cess",
"add_deduct_tax": "Add", "category": "Total",
"rate": 2, "add_deduct_tax": "Add",
"row_id": 3 "rate": 2,
}, "row_id": 3
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Previous Row Amount", "parentfield": "purchase_tax_details",
"account_head": "_Test Account S&H Education Cess - _TC", "charge_type": "On Previous Row Amount",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account S&H Education Cess - _TC",
"description": "S&H Education Cess", "cost_center": "_Test Cost Center - _TC",
"category": "Total", "description": "S&H Education Cess",
"add_deduct_tax": "Add", "category": "Total",
"rate": 1, "add_deduct_tax": "Add",
"row_id": 3 "rate": 1,
}, "row_id": 3
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Previous Row Total", "parentfield": "purchase_tax_details",
"account_head": "_Test Account CST - _TC", "charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account CST - _TC",
"description": "CST", "cost_center": "_Test Cost Center - _TC",
"category": "Total", "description": "CST",
"add_deduct_tax": "Add", "category": "Total",
"rate": 2, "add_deduct_tax": "Add",
"row_id": 5 "rate": 2,
}, "row_id": 5
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Net Total", "parentfield": "purchase_tax_details",
"account_head": "_Test Account VAT - _TC", "charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account VAT - _TC",
"description": "VAT", "cost_center": "_Test Cost Center - _TC",
"category": "Total", "description": "VAT",
"add_deduct_tax": "Add", "category": "Total",
"rate": 12.5 "add_deduct_tax": "Add",
}, "rate": 12.5
{ },
"doctype": "Purchase Taxes and Charges", {
"parentfield": "purchase_tax_details", "doctype": "Purchase Taxes and Charges",
"charge_type": "On Previous Row Total", "parentfield": "purchase_tax_details",
"account_head": "_Test Account Discount - _TC", "charge_type": "On Previous Row Total",
"cost_center": "_Test Cost Center - _TC", "account_head": "_Test Account Discount - _TC",
"description": "Discount", "cost_center": "_Test Cost Center - _TC",
"category": "Total", "description": "Discount",
"add_deduct_tax": "Deduct", "category": "Total",
"rate": 10, "add_deduct_tax": "Deduct",
"row_id": 7 "rate": 10,
}, "row_id": 7
] },
],
[
# parent
{
"doctype": "Purchase Invoice",
"supplier_name": "_Test Supplier",
"credit_to": "_Test Supplier - _TC",
"bill_no": "NA",
"posting_date": "2013-02-03",
"fiscal_year": "_Test Fiscal Year 2013",
"company": "_Test Company",
"currency": "INR",
"conversion_rate": 1.0,
"grand_total_import": 0 # for feed
},
# items
{
"doctype": "Purchase Invoice Item",
"parentfield": "entries",
"item_code": "_Test Item",
"item_name": "_Test Item",
"qty": 10.0,
"import_rate": 50.0,
"uom": "_Test UOM",
"expense_head": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
"conversion_factor": 1.0,
},
# taxes
{
"doctype": "Purchase Taxes and Charges",
"parentfield": "purchase_tax_details",
"charge_type": "Actual",
"account_head": "_Test Account Shipping Charges - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Shipping Charges",
"category": "Valuation and Total",
"add_deduct_tax": "Add",
"rate": 100.0
},
{
"doctype": "Purchase Taxes and Charges",
"parentfield": "purchase_tax_details",
"charge_type": "Actual",
"account_head": "_Test Account VAT - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"category": "Total",
"add_deduct_tax": "Add",
"rate": 120.0
},
{
"doctype": "Purchase Taxes and Charges",
"parentfield": "purchase_tax_details",
"charge_type": "Actual",
"account_head": "_Test Account Customs Duty - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Customs Duty",
"category": "Valuation",
"add_deduct_tax": "Add",
"rate": 150.0
},
]
]

View File

@ -1,8 +1,8 @@
[ [
{ {
"creation": "2013-01-29 20:53:00", "creation": "2013-02-11 10:54:51",
"docstatus": 0, "docstatus": 0,
"modified": "2013-02-08 14:06:17", "modified": "2013-02-27 18:10:04",
"modified_by": "Administrator", "modified_by": "Administrator",
"owner": "Administrator" "owner": "Administrator"
}, },
@ -282,6 +282,35 @@
"search_index": 0, "search_index": 0,
"width": "150px" "width": "150px"
}, },
{
"doctype": "DocField",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Valuation Rate",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "conversion_factor",
"fieldtype": "Float",
"label": "Conversion Factor",
"print_hide": 1
},
{
"doctype": "DocField",
"fieldname": "rm_supp_cost",
"fieldtype": "Currency",
"hidden": 1,
"label": "Raw Materials Supplied Cost",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"doctype": "DocField", "doctype": "DocField",

View File

@ -87,3 +87,10 @@ class AccountsController(TransactionBase):
(", ".join((["%s"]*len(item_codes))),), item_codes)] (", ".join((["%s"]*len(item_codes))),), item_codes)]
return self._stock_items return self._stock_items
@property
def company_abbr(self):
if not hasattr(self, "_abbr"):
self._abbr = webnotes.conn.get_value("Company", self.doc.company, "abbr")
return self._abbr

View File

@ -232,7 +232,7 @@ class BuyingController(AccountsController):
else: else:
self.doc.grand_total = flt(self.doc.net_total, self.doc.grand_total = flt(self.doc.net_total,
self.precision.main.grand_total) self.precision.main.grand_total)
self.doc.grand_total_print = flt( self.doc.grand_total_import = flt(
self.doc.grand_total / self.doc.conversion_rate, self.doc.grand_total / self.doc.conversion_rate,
self.precision.main.grand_total_import) self.precision.main.grand_total_import)
@ -250,7 +250,9 @@ class BuyingController(AccountsController):
if self.doc.doctype == "Purchase Invoice" and self.doc.docstatus == 0: if self.doc.doctype == "Purchase Invoice" and self.doc.docstatus == 0:
self.doc.total_advance = flt(self.doc.total_advance, self.doc.total_advance = flt(self.doc.total_advance,
self.precision.main.total_advance) self.precision.main.total_advance)
self.doc.outstanding_amount = flt(self.doc.grand_total - self.doc.total_advance, self.doc.total_amount_to_pay = flt(self.doc.grand_total - flt(self.doc.write_off_amount,
self.precision.main.write_off_amount), self.precision.main.total_amount_to_pay)
self.doc.outstanding_amount = flt(self.doc.total_amount_to_pay - self.doc.total_advance,
self.precision.main.outstanding_amount) self.precision.main.outstanding_amount)
def _cleanup(self): def _cleanup(self):
@ -326,6 +328,16 @@ class BuyingController(AccountsController):
item.item_tax_amount += flt(current_tax_amount, item.item_tax_amount += flt(current_tax_amount,
self.precision.item.item_tax_amount) self.precision.item.item_tax_amount)
# update valuation rate
def update_valuation_rate(self, parentfield):
for d in self.doclist.get({"parentfield": parentfield}):
if d.qty:
d.valuation_rate = (flt(d.purchase_rate or d.rate)
+ (flt(d.item_tax_amount) + flt(d.rm_supp_cost)) / flt(d.qty)
) / flt(d.conversion_factor)
else:
d.valuation_rate = 0.0
@property @property
def precision(self): def precision(self):
if not hasattr(self, "_precision"): if not hasattr(self, "_precision"):
@ -336,3 +348,25 @@ class BuyingController(AccountsController):
self._precision.tax = self.meta.get_precision_map(parentfield = \ self._precision.tax = self.meta.get_precision_map(parentfield = \
"purchase_tax_details") "purchase_tax_details")
return self._precision return self._precision
@property
def sub_contracted_items(self):
if not hasattr(self, "_sub_contracted_items"):
item_codes = list(set(item.item_code for item in
self.doclist.get({"parentfield": self.fname})))
self._sub_contracted_items = [r[0] for r in webnotes.conn.sql("""select name
from `tabItem` where name in (%s) and is_sub_contracted_item='Yes'""" % \
(", ".join((["%s"]*len(item_codes))),), item_codes)]
return self._sub_contracted_items
@property
def purchase_items(self):
if not hasattr(self, "_purchase_items"):
item_codes = list(set(item.item_code for item in
self.doclist.get({"parentfield": self.fname})))
self._purchase_items = [r[0] for r in webnotes.conn.sql("""select name
from `tabItem` where name in (%s) and is_purchase_item='Yes'""" % \
(", ".join((["%s"]*len(item_codes))),), item_codes)]
return self._purchase_items

View File

@ -1,8 +1,14 @@
erpnext.updates = [ erpnext.updates = [
["27th February", [
"Time Log: Created Time Log System, with Calendar View."
]],
["26th February", [ ["26th February", [
"Gross Profit: The report has been rewritten and now it is under Accounts module" "Gross Profit: The report has been rewritten and now it is under Accounts module"
] ]],
], ["25th February", [
"Employee Leave Balance: New Report",
"Scripted Reports: Ability to create reports via Script",
]],
["21st February, 2013", [ ["21st February, 2013", [
"Item: Warehouse-wise Re-order Level and Quantity", "Item: Warehouse-wise Re-order Level and Quantity",
"Buying: Purchase Request renamed to Material Request" "Buying: Purchase Request renamed to Material Request"

View File

@ -103,8 +103,10 @@ class DocType:
def get_carry_forwarded_leaves(self): def get_carry_forwarded_leaves(self):
if self.doc.carry_forward: if self.doc.carry_forward:
self.allow_carry_forward() self.allow_carry_forward()
prev_fiscal_year = sql("""select name from `tabFiscal Year` prev_fiscal_year = webnotes.conn.sql("""select name from `tabFiscal Year`
where name < %s order by name desc limit 1""", self.doc.fiscal_year) where year_start_date = (select date_add(year_start_date, interval -1 year)
from `tabFiscal Year` where name=%s)
order by name desc limit 1""", self.doc.fiscal_year)
prev_fiscal_year = prev_fiscal_year and prev_fiscal_year[0][0] or '' prev_fiscal_year = prev_fiscal_year and prev_fiscal_year[0][0] or ''
prev_bal = 0 prev_bal = 0
if prev_fiscal_year and cint(self.doc.carry_forward) == 1: if prev_fiscal_year and cint(self.doc.carry_forward) == 1:

View File

@ -236,13 +236,10 @@ def get_events(start, end):
match_conditions = build_match_conditions("Leave Application") match_conditions = build_match_conditions("Leave Application")
# show department leaves for employee # show department leaves for employee
show_department_leaves = match_conditions and \ if "Employee" in webnotes.get_roles():
len(match_conditions.split("or"))==1 and "employee" in match_conditions
if show_department_leaves:
add_department_leaves(events, start, end, employee, company) add_department_leaves(events, start, end, employee, company)
else:
add_leaves(events, start, end, employee, company, match_conditions) add_leaves(events, start, end, employee, company, match_conditions)
add_block_dates(events, start, end, employee, company) add_block_dates(events, start, end, employee, company)
add_holidays(events, start, end, employee, company) add_holidays(events, start, end, employee, company)
@ -273,7 +270,7 @@ def add_leaves(events, start, end, employee, company, match_conditions=None):
query += " and " + match_conditions query += " and " + match_conditions
for d in webnotes.conn.sql(query, (start, end, start, end), as_dict=True): for d in webnotes.conn.sql(query, (start, end, start, end), as_dict=True):
events.append({ e = {
"name": d.name, "name": d.name,
"doctype": "Leave Application", "doctype": "Leave Application",
"from_date": d.from_date, "from_date": d.from_date,
@ -282,7 +279,9 @@ def add_leaves(events, start, end, employee, company, match_conditions=None):
"title": _("Leave by") + " " + cstr(d.employee_name) + \ "title": _("Leave by") + " " + cstr(d.employee_name) + \
(d.half_day and _(" (Half Day)") or ""), (d.half_day and _(" (Half Day)") or ""),
"docstatus": d.docstatus "docstatus": d.docstatus
}) }
if e not in events:
events.append(e)
def add_block_dates(events, start, end, employee, company): def add_block_dates(events, start, end, employee, company):
# block days # block days

View File

@ -6,6 +6,13 @@ wn.query_reports["Employee Leave Balance"] = {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Fiscal Year", "options": "Fiscal Year",
"default": wn.defaults.get_user_default("fiscal_year") "default": wn.defaults.get_user_default("fiscal_year")
},
{
"fieldname":"company",
"label": "Company",
"fieldtype": "Link",
"options": "Company",
"default": wn.defaults.get_user_default("company")
} }
] ]
} }

View File

@ -4,7 +4,11 @@ from webnotes.widgets.reportview import execute as runreport
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
employees = runreport(doctype="Employee", fields=["name", "employee_name", "department"])
employee_filters = filters.get("company") and \
[["Employee", "company", "=", filters.get("company")]] or None
employees = runreport(doctype="Employee", fields=["name", "employee_name", "department"],
filters=employee_filters)
leave_types = webnotes.conn.sql_list("select name from `tabLeave Type`") leave_types = webnotes.conn.sql_list("select name from `tabLeave Type`")
if filters.get("fiscal_year"): if filters.get("fiscal_year"):
@ -14,8 +18,6 @@ def execute(filters=None):
employee_in = '", "'.join([e.name for e in employees]) employee_in = '", "'.join([e.name for e in employees])
allocations = webnotes.conn.sql("""select employee, fiscal_year, leave_type, total_leaves_allocated allocations = webnotes.conn.sql("""select employee, fiscal_year, leave_type, total_leaves_allocated
from `tabLeave Allocation` from `tabLeave Allocation`
where docstatus=1 and employee in ("%s")""" % employee_in, as_dict=True) where docstatus=1 and employee in ("%s")""" % employee_in, as_dict=True)

View File

@ -18,130 +18,164 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import webnotes import webnotes
import webnotes.model
from webnotes.utils import nowdate, flt
from accounts.utils import get_fiscal_year
from webnotes.model.doclist import DocList
import copy
company = webnotes.conn.get_default("company") test_records = [
[
{
def load_data(): "doctype": "BOM",
"item": "_Test FG Item",
# create default warehouse "quantity": 1.0,
if not webnotes.conn.exists("Warehouse", "Default Warehouse"): "is_active": 1,
webnotes.insert({"doctype": "Warehouse", "is_default": 1,
"warehouse_name": "Default Warehouse", "docstatus": 1
"warehouse_type": "Stores"}) },
{
# create UOM: Nos. "doctype": "BOM Item",
if not webnotes.conn.exists("UOM", "Nos"): "item_code": "_Test Item",
webnotes.insert({"doctype": "UOM", "uom_name": "Nos"}) "parentfield": "bom_materials",
"qty": 1.0,
from webnotes.tests import insert_test_data "rate": 5000.0,
# create item groups and items "amount": 5000.0,
insert_test_data("Item Group", "stock_uom": "No."
sort_fn=lambda ig: (ig[0].get('parent_item_group'), ig[0].get('name'))) },
insert_test_data("Item") {
"doctype": "BOM Item",
base_bom_fg = [ "item_code": "_Test Item Home Desktop 100",
{"doctype": "BOM", "item": "Android Jack D", "quantity": 1, "parentfield": "bom_materials",
"is_active": "Yes", "is_default": 1, "uom": "Nos"}, "qty": 2.0,
{"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations", "rate": 1000.0,
"opn_description": "Development", "hour_rate": 10, "time_in_mins": 90}, "amount": 2000.0,
{"doctype": "BOM Item", "item_code": "Home Desktop 300", "operation_no": 1, "stock_uom": "No."
"qty": 2, "rate": 20, "stock_uom": "Nos", "parentfield": "bom_materials"},
{"doctype": "BOM Item", "item_code": "Home Desktop 100", "operation_no": 1,
"qty": 1, "rate": 300, "stock_uom": "Nos", "parentfield": "bom_materials"},
{"doctype": "BOM Item", "item_code": "Nebula 7", "operation_no": 1,
"qty": 5, "stock_uom": "Nos", "parentfield": "bom_materials"},
]
base_bom_child = [
{"doctype": "BOM", "item": "Nebula 7", "quantity": 5,
"is_active": "Yes", "is_default": 1, "uom": "Nos"},
{"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations",
"opn_description": "Development"},
{"doctype": "BOM Item", "item_code": "Android Jack S", "operation_no": 1,
"qty": 10, "stock_uom": "Nos", "parentfield": "bom_materials"}
]
base_bom_grandchild = [
{"doctype": "BOM", "item": "Android Jack S", "quantity": 1,
"is_active": "Yes", "is_default": 1, "uom": "Nos"},
{"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations",
"opn_description": "Development"},
{"doctype": "BOM Item", "item_code": "Home Desktop 300", "operation_no": 1,
"qty": 3, "rate": 10, "stock_uom": "Nos", "parentfield": "bom_materials"}
]
class TestPurchaseReceipt(unittest.TestCase):
def setUp(self):
webnotes.conn.begin()
load_data()
def test_bom_validation(self):
# show throw error bacause bom no missing for sub-assembly item
bom_fg = copy.deepcopy(base_bom_fg)
self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg))
# main item is not a manufacturing item
bom_fg = copy.deepcopy(base_bom_fg)
bom_fg[0]["item"] = "Home Desktop 200"
bom_fg.pop(4)
self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg))
# operation no mentioed in material table not matching with operation table
bom_fg = copy.deepcopy(base_bom_fg)
bom_fg.pop(4)
bom_fg[2]["operation_no"] = 2
self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg))
def test_bom(self):
gc_wrapper = webnotes.insert(DocList(base_bom_grandchild))
gc_wrapper.submit()
bom_child = copy.deepcopy(base_bom_child)
bom_child[2]["bom_no"] = gc_wrapper.doc.name
child_wrapper = webnotes.insert(DocList(bom_child))
child_wrapper.submit()
bom_fg = copy.deepcopy(base_bom_fg)
bom_fg[4]["bom_no"] = child_wrapper.doc.name
fg_wrapper = webnotes.insert(DocList(bom_fg))
fg_wrapper.load_from_db()
self.check_bom_cost(fg_wrapper)
self.check_flat_bom(fg_wrapper, child_wrapper, gc_wrapper)
def check_bom_cost(self, fg_wrapper):
expected_values = {
"operating_cost": 15,
"raw_material_cost": 640,
"total_cost": 655
} }
]
for key in expected_values: ]
self.assertEqual(flt(expected_values[key]), flt(fg_wrapper.doc.fields.get(key)))
def check_flat_bom(self, fg_wrapper, child_wrapper, gc_wrapper):
expected_flat_bom_items = {
("Home Desktop 300", fg_wrapper.doc.name): (2, 20),
("Home Desktop 100", fg_wrapper.doc.name): (1, 300),
("Home Desktop 300", gc_wrapper.doc.name): (30, 10)
}
self.assertEqual(len(fg_wrapper.doclist.get({"parentfield": "flat_bom_details"})), 3)
for key, val in expected_flat_bom_items.items():
flat_bom = fg_wrapper.doclist.get({"parentfield": "flat_bom_details",
"item_code": key[0], "parent_bom": key[1]})[0]
self.assertEqual(val, (flat_bom.qty, flat_bom.rate))
def tearDown(self):
webnotes.conn.rollback() # import webnotes.model
# from webnotes.utils import nowdate, flt
# from accounts.utils import get_fiscal_year
# from webnotes.model.doclist import DocList
# import copy
#
# company = webnotes.conn.get_default("company")
#
#
# def load_data():
#
# # create default warehouse
# if not webnotes.conn.exists("Warehouse", "Default Warehouse"):
# webnotes.insert({"doctype": "Warehouse",
# "warehouse_name": "Default Warehouse",
# "warehouse_type": "Stores"})
#
# # create UOM: Nos.
# if not webnotes.conn.exists("UOM", "Nos"):
# webnotes.insert({"doctype": "UOM", "uom_name": "Nos"})
#
# from webnotes.tests import insert_test_data
# # create item groups and items
# insert_test_data("Item Group",
# sort_fn=lambda ig: (ig[0].get('parent_item_group'), ig[0].get('name')))
# insert_test_data("Item")
#
# base_bom_fg = [
# {"doctype": "BOM", "item": "Android Jack D", "quantity": 1,
# "is_active": "Yes", "is_default": 1, "uom": "Nos"},
# {"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations",
# "opn_description": "Development", "hour_rate": 10, "time_in_mins": 90},
# {"doctype": "BOM Item", "item_code": "Home Desktop 300", "operation_no": 1,
# "qty": 2, "rate": 20, "stock_uom": "Nos", "parentfield": "bom_materials"},
# {"doctype": "BOM Item", "item_code": "Home Desktop 100", "operation_no": 1,
# "qty": 1, "rate": 300, "stock_uom": "Nos", "parentfield": "bom_materials"},
# {"doctype": "BOM Item", "item_code": "Nebula 7", "operation_no": 1,
# "qty": 5, "stock_uom": "Nos", "parentfield": "bom_materials"},
# ]
#
# base_bom_child = [
# {"doctype": "BOM", "item": "Nebula 7", "quantity": 5,
# "is_active": "Yes", "is_default": 1, "uom": "Nos"},
# {"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations",
# "opn_description": "Development"},
# {"doctype": "BOM Item", "item_code": "Android Jack S", "operation_no": 1,
# "qty": 10, "stock_uom": "Nos", "parentfield": "bom_materials"}
# ]
#
# base_bom_grandchild = [
# {"doctype": "BOM", "item": "Android Jack S", "quantity": 1,
# "is_active": "Yes", "is_default": 1, "uom": "Nos"},
# {"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations",
# "opn_description": "Development"},
# {"doctype": "BOM Item", "item_code": "Home Desktop 300", "operation_no": 1,
# "qty": 3, "rate": 10, "stock_uom": "Nos", "parentfield": "bom_materials"}
# ]
#
#
# class TestPurchaseReceipt(unittest.TestCase):
# def setUp(self):
# webnotes.conn.begin()
# load_data()
#
# def test_bom_validation(self):
# # show throw error bacause bom no missing for sub-assembly item
# bom_fg = copy.deepcopy(base_bom_fg)
# self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg))
#
# # main item is not a manufacturing item
# bom_fg = copy.deepcopy(base_bom_fg)
# bom_fg[0]["item"] = "Home Desktop 200"
# bom_fg.pop(4)
# self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg))
#
# # operation no mentioed in material table not matching with operation table
# bom_fg = copy.deepcopy(base_bom_fg)
# bom_fg.pop(4)
# bom_fg[2]["operation_no"] = 2
# self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg))
#
#
# def test_bom(self):
# gc_wrapper = webnotes.insert(DocList(base_bom_grandchild))
# gc_wrapper.submit()
#
# bom_child = copy.deepcopy(base_bom_child)
# bom_child[2]["bom_no"] = gc_wrapper.doc.name
# child_wrapper = webnotes.insert(DocList(bom_child))
# child_wrapper.submit()
#
# bom_fg = copy.deepcopy(base_bom_fg)
# bom_fg[4]["bom_no"] = child_wrapper.doc.name
# fg_wrapper = webnotes.insert(DocList(bom_fg))
# fg_wrapper.load_from_db()
#
# self.check_bom_cost(fg_wrapper)
#
# self.check_flat_bom(fg_wrapper, child_wrapper, gc_wrapper)
#
# def check_bom_cost(self, fg_wrapper):
# expected_values = {
# "operating_cost": 15,
# "raw_material_cost": 640,
# "total_cost": 655
# }
#
# for key in expected_values:
# self.assertEqual(flt(expected_values[key]), flt(fg_wrapper.doc.fields.get(key)))
#
# def check_flat_bom(self, fg_wrapper, child_wrapper, gc_wrapper):
# expected_flat_bom_items = {
# ("Home Desktop 300", fg_wrapper.doc.name): (2, 20),
# ("Home Desktop 100", fg_wrapper.doc.name): (1, 300),
# ("Home Desktop 300", gc_wrapper.doc.name): (30, 10)
# }
#
# self.assertEqual(len(fg_wrapper.doclist.get({"parentfield": "flat_bom_details"})), 3)
#
# for key, val in expected_flat_bom_items.items():
# flat_bom = fg_wrapper.doclist.get({"parentfield": "flat_bom_details",
# "item_code": key[0], "parent_bom": key[1]})[0]
# self.assertEqual(val, (flat_bom.qty, flat_bom.rate))
#
#
# def tearDown(self):
# webnotes.conn.rollback()

View File

@ -199,4 +199,5 @@ patch_list = [
'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Classic") # 2013-02-26', 'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Classic") # 2013-02-26',
'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Modern") # 2013-02-26', 'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Modern") # 2013-02-26',
'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Spartan") # 2013-02-26', 'execute:webnotes.reload_doc("accounts", "Print Format", "Sales Invoice Spartan") # 2013-02-26',
"execute:(not webnotes.conn.exists('Role', 'Projects Manager')) and webnotes.doc({'doctype':'Role', 'role_name':'Projects Manager'}).insert()",
] ]

View File

@ -1,10 +0,0 @@
[
"BOM Replace Tool",
"The BOM which will be replaced",
"New BOM",
"The new BOM after replacement",
"Production",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM",
"Replace",
"Current BOM"
]

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "BOM \u0627\u0633\u062a\u0628\u062f\u0627\u0644 \u0623\u062f\u0627\u0629",
"Current BOM": "BOM \u0627\u0644\u062d\u0627\u0644\u064a",
"New BOM": "BOM \u062c\u062f\u064a\u062f\u0629",
"Production": "\u0627\u0644\u0625\u0646\u062a\u0627\u062c",
"Replace": "\u0627\u0633\u062a\u0628\u062f\u0644",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "\u0627\u0633\u062a\u0628\u062f\u0627\u0644 BOM \u062e\u0627\u0635\u0629 \u0641\u064a \u062c\u0645\u064a\u0639 BOMs \u0627\u0644\u0623\u062e\u0631\u0649 \u0627\u0644\u062a\u064a \u064a\u0633\u062a\u062e\u062f\u0645 \u0641\u064a\u0647\u0627. \u0648\u0627\u0646\u0647 \u0633\u064a\u062d\u0644 \u0645\u062d\u0644 \u0627\u0644\u0631\u0627\u0628\u0637 BOM \u0627\u0644\u0642\u062f\u064a\u0645\u0629\u060c \u0648\u062a\u062d\u062f\u064a\u062b \u0648\u062a\u062c\u062f\u064a\u062f \u0627\u0644\u062a\u0643\u0644\u0641\u0629 &quot;\u0627\u0644\u0628\u0646\u062f \u0627\u0646\u0641\u062c\u0627\u0631 BOM&quot; \u0627\u0644\u062c\u062f\u0648\u0644 \u0627\u0644\u062c\u062f\u064a\u062f \u0648\u0641\u0642\u0627 BOM",
"The BOM which will be replaced": "\u0648BOM \u0627\u0644\u062a\u064a \u0633\u064a\u062a\u0645 \u0627\u0633\u062a\u0628\u062f\u0627\u0644\u0647\u0627",
"The new BOM after replacement": "\u0648BOM \u0627\u0644\u062c\u062f\u064a\u062f\u0629 \u0628\u0639\u062f \u0627\u0633\u062a\u0628\u062f\u0627\u0644"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "BOM Tool Reemplazar",
"Current BOM": "BOM actual",
"New BOM": "Nueva lista de materiales",
"Production": "Producci\u00f3n",
"Replace": "Reemplazar",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "Reemplazar un BOM particular en todas las listas de materiales de otros en los que se utiliza. Se sustituir\u00e1 el enlace BOM viejo, actualizar el costo y regenerar &quot;Explosi\u00f3n lista de materiales Item&quot; tabla seg\u00fan nueva lista de materiales",
"The BOM which will be replaced": "La lista de materiales que ser\u00e1 sustituido",
"The new BOM after replacement": "La lista de materiales nuevo despu\u00e9s de sustituirlo"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "Outil Remplacer BOM",
"Current BOM": "Nomenclature actuelle",
"New BOM": "Nouvelle nomenclature",
"Production": "Production",
"Replace": "Remplacer",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "Remplacer une nomenclature particuli\u00e8re dans toutes les nomenclatures d&#39;autres o\u00f9 il est utilis\u00e9. Il remplacera le lien de nomenclature ancienne, mettre \u00e0 jour les co\u00fbts et r\u00e9g\u00e9n\u00e9rer &quot;Explosion de nomenclature article&quot; la table comme pour une nouvelle nomenclature",
"The BOM which will be replaced": "La nomenclature qui sera remplac\u00e9",
"The new BOM after replacement": "La nouvelle nomenclature apr\u00e8s le remplacement"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "\u092c\u0940\u0913\u090f\u092e \u092c\u0926\u0932\u0947\u0902 \u0909\u092a\u0915\u0930\u0923",
"Current BOM": "\u0935\u0930\u094d\u0924\u092e\u093e\u0928 \u092c\u0940\u0913\u090f\u092e",
"New BOM": "\u0928\u0908 \u092c\u0940\u0913\u090f\u092e",
"Production": "\u0909\u0924\u094d\u092a\u093e\u0926\u0928",
"Replace": "\u092c\u0926\u0932\u0947\u0902",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "\u0905\u0928\u094d\u092f \u0938\u092d\u0940 BOMs \u091c\u0939\u093e\u0902 \u092f\u0939 \u092a\u094d\u0930\u092f\u094b\u0917 \u0915\u093f\u092f\u093e \u091c\u093e\u0924\u093e \u0939\u0948 \u092e\u0947\u0902 \u090f\u0915 \u0935\u093f\u0936\u0947\u0937 \u092c\u0940\u0913\u090f\u092e \u092c\u0926\u0932\u0947\u0902. \u092f\u0939 \u092a\u0941\u0930\u093e\u0928\u0947 \u092c\u0940\u0913\u090f\u092e \u0932\u093f\u0902\u0915 \u0915\u0940 \u091c\u0917\u0939, \u0932\u093e\u0917\u0924 \u0905\u0926\u094d\u092f\u0924\u0928 \u0914\u0930 \u0928\u092f\u093e \u092c\u0940\u0913\u090f\u092e \u0915\u0947 \u0905\u0928\u0941\u0938\u093e\u0930 &quot;BOM \u0927\u092e\u093e\u0915\u093e \u0906\u0907\u091f\u092e&quot; \u0924\u093e\u0932\u093f\u0915\u093e \u092a\u0941\u0928\u0930\u094d\u091c\u0928\u094d\u092e",
"The BOM which will be replaced": "\u092c\u0940\u0913\u090f\u092e \u091c\u094b \u092a\u094d\u0930\u0924\u093f\u0938\u094d\u0925\u093e\u092a\u093f\u0924 \u0915\u093f\u092f\u093e \u091c\u093e\u090f\u0917\u093e",
"The new BOM after replacement": "\u092c\u0926\u0932\u0928\u0947 \u0915\u0947 \u092c\u093e\u0926 \u0928\u090f \u092c\u0940\u0913\u090f\u092e"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "BOM Zamijenite alat",
"Current BOM": "Trenutni BOM",
"New BOM": "Novi BOM",
"Production": "Proizvodnja",
"Replace": "Zamijeniti",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "Zamijenite odre\u0111eni BOM u svim drugim sastavnicama gdje se koriste. To \u0107e zamijeniti staru vezu BOM, a\u017eurirati tro\u0161kove i regenerirati &quot;BOM eksploziju predmeta&quot; stol kao i po novom BOM",
"The BOM which will be replaced": "BOM koji \u0107e biti zamijenjen",
"The new BOM after replacement": "Novi BOM nakon zamjene"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "BOM Replace Tool",
"Current BOM": "Actueel BOM",
"New BOM": "Nieuwe BOM",
"Production": "Productie",
"Replace": "Vervang",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "Vervang een bepaalde BOM in alle andere BOMs waar het wordt gebruikt. Deze vervangt de oude BOM link, updaten kosten en regenereren &quot;BOM Explosion Item&quot; tafel als per nieuwe BOM",
"The BOM which will be replaced": "De BOM die zal worden vervangen",
"The new BOM after replacement": "De nieuwe BOM na vervanging"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "Ferramenta de Substitui\u00e7\u00e3o da LDM",
"Current BOM": "LDM atual",
"New BOM": "Nova LDM",
"Production": "Produ\u00e7\u00e3o",
"Replace": "Substituir",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "Substituir uma LDM espec\u00edfica em todas as LDMs outros onde ela \u00e9 usada. Isso ir\u00e1 substituir o link da LDM antiga, atualizar o custo e regenerar a tabela &quot;Item de Explos\u00e3o da LDM&quot; com a nova LDM",
"The BOM which will be replaced": "A LDM que ser\u00e1 substitu\u00edda",
"The new BOM after replacement": "A nova LDM ap\u00f3s substitui\u00e7\u00e3o"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "BOM Ferramenta Substituir",
"Current BOM": "BOM atual",
"New BOM": "Novo BOM",
"Production": "Produ\u00e7\u00e3o",
"Replace": "Substituir",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "Substituir um BOM particular em todas as BOMs outros onde ele \u00e9 usado. Ele ir\u00e1 substituir o link BOM antigo, atualizar o custo e regenerar &quot;Explos\u00e3o BOM Item&quot; tabela como por novo BOM",
"The BOM which will be replaced": "O BOM que ser\u00e1 substitu\u00eddo",
"The new BOM after replacement": "O BOM novo ap\u00f3s substitui\u00e7\u00e3o"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "\u0411\u041e\u041c \u0417\u0430\u043c\u0435\u043d\u0430 \u0430\u043b\u0430\u0442\u0430",
"Current BOM": "\u0422\u0440\u0435\u043d\u0443\u0442\u043d\u0438 \u0411\u041e\u041c",
"New BOM": "\u041d\u043e\u0432\u0438 \u0411\u041e\u041c",
"Production": "\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u045a\u0430",
"Replace": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0438",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "\u0417\u0430\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u043e\u0441\u0435\u0431\u043d\u0443 \u0431\u043e\u043c \u0443 \u0441\u0432\u0438\u043c \u043e\u0441\u0442\u0430\u043b\u0438\u043c \u0411\u041e\u041c\u0441 \u0433\u0434\u0435 \u0441\u0435 \u043e\u043d \u043a\u043e\u0440\u0438\u0441\u0442\u0438. \u041e\u043d\u0430 \u045b\u0435 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u0438 \u0441\u0442\u0430\u0440\u0443 \u0432\u0435\u0437\u0443 \u0431\u043e\u043c, \u0430\u0436\u0443\u0440\u0438\u0440\u0430\u045a\u0435 \u0442\u0440\u043e\u0448\u043a\u043e\u0432\u0435 \u0438 \u0440\u0435\u0433\u0435\u043d\u0435\u0440\u0438\u0448\u0443 &quot;\u0431\u043e\u043c \u0435\u043a\u0441\u043f\u043b\u043e\u0437\u0438\u0458\u0435 \u0458\u0435\u0434\u0438\u043d\u0438\u0446\u0435&quot; \u0442\u0430\u0431\u0435\u043b\u0443 \u043f\u043e \u043d\u043e\u0432\u043e\u043c \u0411\u041e\u041c",
"The BOM which will be replaced": "\u0411\u041e\u041c \u043a\u043e\u0458\u0438 \u045b\u0435 \u0431\u0438\u0442\u0438 \u0437\u0430\u043c\u0435\u045a\u0435\u043d",
"The new BOM after replacement": "\u041d\u043e\u0432\u0438 \u0411\u041e\u041c \u043d\u0430\u043a\u043e\u043d \u0437\u0430\u043c\u0435\u043d\u0435"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "BOM \u0baa\u0ba4\u0bbf\u0bb2\u0bbe\u0b95 \u0b95\u0bb0\u0bc1\u0bb5\u0bbf",
"Current BOM": "\u0ba4\u0bb1\u0bcd\u0baa\u0bc7\u0bbe\u0ba4\u0bc8\u0baf BOM",
"New BOM": "\u0baa\u0bc1\u0ba4\u0bbf\u0baf BOM",
"Production": "\u0b89\u0bb1\u0bcd\u0baa\u0ba4\u0bcd\u0ba4\u0bbf",
"Replace": "\u0baa\u0ba4\u0bbf\u0bb2\u0bbe\u0b95",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "\u0b87\u0ba4\u0bc1 \u0baa\u0baf\u0ba9\u0bcd\u0baa\u0b9f\u0bc1\u0ba4\u0bcd\u0ba4\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0b95\u0bbf\u0bb1\u0ba4\u0bc1 \u0b85\u0bae\u0bc8\u0ba8\u0bcd\u0ba4\u0bc1\u0bb3\u0bcd\u0bb3 \u0b85\u0ba9\u0bc8\u0ba4\u0bcd\u0ba4\u0bc1 \u0bae\u0bb1\u0bcd\u0bb1 BOM \u0b95\u0bb3\u0bcd \u0b92\u0bb0\u0bc1 \u0b95\u0bc1\u0bb1\u0bbf\u0baa\u0bcd\u0baa\u0bbf\u0b9f\u0bcd\u0b9f BOM \u0baa\u0ba4\u0bbf\u0bb2\u0bbe\u0b95. \u0b87\u0ba4\u0bc1, \u0baa\u0bb4\u0bc8\u0baf BOM \u0b87\u0ba3\u0bc8\u0baa\u0bcd\u0baa\u0bc1 \u0baa\u0ba4\u0bbf\u0bb2\u0bbe\u0b95 \u0b9a\u0bc6\u0bb2\u0bb5\u0bc1 \u0baa\u0bc1\u0ba4\u0bc1\u0baa\u0bcd\u0baa\u0bbf\u0b95\u0bcd\u0b95 \u0baa\u0bc1\u0ba4\u0bbf\u0baf BOM \u0baa\u0b9f\u0bbf &quot;BOM \u0bb5\u0bc6\u0b9f\u0bbf\u0baa\u0bcd\u0baa\u0bc1 \u0baa\u0bc6\u0bbe\u0bb0\u0bc1\u0bb3\u0bcd&quot; \u0b85\u0b9f\u0bcd\u0b9f\u0bb5\u0ba3\u0bc8 \u0bae\u0bb1\u0bc1\u0b89\u0bb1\u0bcd\u0baa\u0ba4\u0bcd\u0ba4\u0bbf",
"The BOM which will be replaced": "\u0baa\u0ba4\u0bbf\u0bb2\u0bc0\u0b9f\u0bc1 \u0b9a\u0bc6\u0baf\u0bcd\u0baf\u0baa\u0bcd\u0baa\u0b9f\u0bc1\u0bae\u0bcd BOM",
"The new BOM after replacement": "\u0bae\u0bbe\u0bb1\u0bcd\u0bb1\u0bc1 \u0baa\u0bbf\u0ba9\u0bcd\u0ba9\u0bb0\u0bcd \u0baa\u0bc1\u0ba4\u0bbf\u0baf BOM"
}

View File

@ -1,10 +0,0 @@
{
"BOM Replace Tool": "\u0e40\u0e04\u0e23\u0e37\u0e48\u0e2d\u0e07\u0e21\u0e37\u0e2d\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48 BOM",
"Current BOM": "BOM \u0e1b\u0e31\u0e08\u0e08\u0e38\u0e1a\u0e31\u0e19",
"New BOM": "BOM \u0e43\u0e2b\u0e21\u0e48",
"Production": "\u0e01\u0e32\u0e23\u0e1c\u0e25\u0e34\u0e15",
"Replace": "\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48",
"Replace a particular BOM in all other BOMs where it is used. It will replace the old BOM link, update cost and regenerate \"BOM Explosion Item\" table as per new BOM": "\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48 BOM \u0e42\u0e14\u0e22\u0e40\u0e09\u0e1e\u0e32\u0e30\u0e43\u0e19 BOMs \u0e2d\u0e37\u0e48\u0e19 \u0e46 \u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14\u0e17\u0e35\u0e48\u0e16\u0e39\u0e01\u0e19\u0e33\u0e21\u0e32\u0e43\u0e0a\u0e49 \u0e21\u0e31\u0e19\u0e08\u0e30\u0e40\u0e02\u0e49\u0e32\u0e21\u0e32\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48\u0e01\u0e32\u0e23\u0e40\u0e0a\u0e37\u0e48\u0e2d\u0e21\u0e42\u0e22\u0e07 BOM \u0e40\u0e01\u0e48\u0e32\u0e1b\u0e23\u0e31\u0e1a\u0e1b\u0e23\u0e38\u0e07\u0e04\u0e48\u0e32\u0e43\u0e0a\u0e49\u0e08\u0e48\u0e32\u0e22\u0e41\u0e25\u0e30\u0e43\u0e2b\u0e49\u0e0a\u0e35\u0e27\u0e34\u0e15\u0e43\u0e2b\u0e21\u0e48 &quot;\u0e23\u0e30\u0e40\u0e1a\u0e34\u0e14\u0e23\u0e32\u0e22\u0e01\u0e32\u0e23 BOM&quot; \u0e15\u0e32\u0e23\u0e32\u0e07\u0e40\u0e1b\u0e47\u0e19\u0e15\u0e48\u0e2d\u0e43\u0e2b\u0e21\u0e48 BOM",
"The BOM which will be replaced": "BOM \u0e0b\u0e36\u0e48\u0e07\u0e08\u0e30\u0e16\u0e39\u0e01\u0e41\u0e17\u0e19\u0e17\u0e35\u0e48",
"The new BOM after replacement": "BOM \u0e43\u0e2b\u0e21\u0e48\u0e2b\u0e25\u0e31\u0e07\u0e08\u0e32\u0e01\u0e40\u0e1b\u0e25\u0e35\u0e48\u0e22\u0e19"
}

View File

View File

@ -0,0 +1,45 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import webnotes
from webnotes import _
from webnotes.widgets.reportview import build_match_conditions
class DocType:
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def validate(self):
self.validate_overlap()
def validate_overlap(self):
existing = webnotes.conn.sql_list("""select name from `tabTime Log` where owner=%s and
((from_time between %s and %s) or (to_time between %s and %s)) and name!=%s""",
(self.doc.owner, self.doc.from_time, self.doc.to_time, self.doc.from_time,
self.doc.to_time, self.doc.name))
if existing:
webnotes.msgprint(_("This Time Log conflicts with") + ":" + ', '.join(existing),
raise_exception=True)
@webnotes.whitelist()
def get_events(start, end):
match = build_match_conditions("Time Log")
data = webnotes.conn.sql("""select name, from_time, to_time,
activity_type, task, project from `tabTime Log`
where from_time between '%(start)s' and '%(end)s' or to_time between '%(start)s' and '%(end)s'
%(match)s""" % {
"start": start,
"end": end,
"match": match and (" and " + match) or ""
}, as_dict=True, update={"allDay": 0})
for d in data:
d.title = d.name + ": " + d.activity_type
if d.task:
d.title += " for Task: " + d.task
if d.project:
d.title += " for Project: " + d.project
return data

View File

@ -0,0 +1,121 @@
[
{
"creation": "2013-02-26 14:58:28",
"docstatus": 0,
"modified": "2013-02-26 16:09:53",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"autoname": "TL-.######",
"description": "Log of Activities performed by users against Tasks that can be used for tracking time, billing.",
"doctype": "DocType",
"document_type": "Master",
"module": "Projects",
"name": "__common__"
},
{
"doctype": "DocField",
"name": "__common__",
"parent": "Time Log",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0
},
{
"doctype": "DocPerm",
"name": "__common__",
"parent": "Time Log",
"parentfield": "permissions",
"parenttype": "DocType",
"permlevel": 0,
"read": 1,
"write": 1
},
{
"doctype": "DocType",
"name": "Time Log"
},
{
"doctype": "DocField",
"fieldname": "from_time",
"fieldtype": "Datetime",
"label": "From Time",
"reqd": 1
},
{
"doctype": "DocField",
"fieldname": "to_time",
"fieldtype": "Datetime",
"label": "To Time",
"reqd": 1
},
{
"doctype": "DocField",
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"doctype": "DocField",
"fieldname": "activity_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Activity Type",
"options": "Activity Type",
"reqd": 1
},
{
"doctype": "DocField",
"fieldname": "task",
"fieldtype": "Link",
"label": "Task",
"options": "Task"
},
{
"doctype": "DocField",
"fieldname": "billable",
"fieldtype": "Check",
"label": "Billable"
},
{
"doctype": "DocField",
"fieldname": "section_break_7",
"fieldtype": "Section Break"
},
{
"doctype": "DocField",
"fieldname": "note",
"fieldtype": "Text Editor",
"label": "Note"
},
{
"doctype": "DocField",
"fieldname": "section_break_9",
"fieldtype": "Section Break"
},
{
"doctype": "DocField",
"fieldname": "project",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Project",
"options": "Project"
},
{
"doctype": "DocField",
"fieldname": "sales_invoice",
"fieldtype": "Link",
"label": "Sales Invoice",
"options": "Sales Invoice"
},
{
"create": 1,
"doctype": "DocPerm",
"match": "owner",
"role": "Projects User"
},
{
"doctype": "DocPerm",
"role": "Projects Manager"
}
]

View File

@ -0,0 +1,10 @@
wn.views.calendar["Time Log"] = wn.views.Calendar.extend({
field_map: {
"start": "from_time",
"end": "to_time",
"id": "name",
"title": "title",
"allDay": "allDay"
},
get_events_method: "projects.doctype.time_log.time_log.get_events"
})

View File

@ -92,3 +92,4 @@ class DocType:
def on_cancel(self): def on_cancel(self):
webnotes.conn.set(self.doc, 'status', 'Cancelled') webnotes.conn.set(self.doc, 'status', 'Cancelled')

View File

@ -1,4 +0,0 @@
[
"Projects Home",
"Projects"
]

View File

@ -1,4 +0,0 @@
{
"Projects": "\u0645\u0634\u0627\u0631\u064a\u0639",
"Projects Home": "\u0635\u0641\u062d\u0629 \u0627\u0644\u0645\u0634\u0631\u0648\u0639\u0627\u062a"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "Proyectos",
"Projects Home": "Home Proyectos"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "Projets",
"Projects Home": "Accueil Projets"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "\u092a\u0930\u093f\u092f\u094b\u091c\u0928\u093e\u0913\u0902",
"Projects Home": "\u092a\u0930\u093f\u092f\u094b\u091c\u0928\u093e\u0913\u0902 \u0918\u0930"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "Projekti",
"Projects Home": "Projekti Po\u010detna"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "Projecten",
"Projects Home": "Projecten Home"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "Projetos",
"Projects Home": "In\u00edcio de Projetos"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "Projetos",
"Projects Home": "Home Projetos"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "\u041f\u0440\u043e\u0458\u0435\u043a\u0442\u0438",
"Projects Home": "\u041f\u0440\u043e\u0458\u0435\u043a\u0442\u0438"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "\u0ba4\u0bbf\u0b9f\u0bcd\u0b9f\u0b99\u0bcd\u0b95\u0bb3\u0bcd",
"Projects Home": "\u0ba4\u0bbf\u0b9f\u0bcd\u0b9f\u0b99\u0bcd\u0b95\u0bb3\u0bcd \u0bae\u0bc1\u0b95\u0baa\u0bcd\u0baa\u0bc1"
}

View File

@ -1,4 +0,0 @@
{
"Projects": "\u0e42\u0e04\u0e23\u0e07\u0e01\u0e32\u0e23",
"Projects Home": "\u0e2b\u0e19\u0e49\u0e32\u0e41\u0e23\u0e01\u0e42\u0e04\u0e23\u0e07\u0e01\u0e32\u0e23"
}

View File

@ -21,6 +21,11 @@ wn.module_page["Projects"] = [
description: wn._("Timesheet for tasks."), description: wn._("Timesheet for tasks."),
doctype:"Timesheet" doctype:"Timesheet"
}, },
{
label: wn._("Time Log"),
description: wn._("Time Log for tasks."),
doctype:"Time Log"
},
] ]
}, },
{ {

View File

@ -211,6 +211,12 @@ class DocType:
'company_name':self.doc.name, 'company_name':self.doc.name,
'group_or_ledger':'Ledger', 'group_or_ledger':'Ledger',
'parent_cost_center':'Root - ' + self.doc.abbr 'parent_cost_center':'Root - ' + self.doc.abbr
},
{
'cost_center_name': 'Auto Inventory Accounting',
'company_name': self.doc.name,
'group_or_ledger': 'Ledger',
'parent_cost_center': 'Root - ' + self.doc.abbr
} }
] ]
for cc in cc_list: for cc in cc_list:

View File

@ -30,7 +30,7 @@ def make_roles():
"HR Manager", "HR User", "Leave Approver", "Maintenance Manager", "HR Manager", "HR User", "Leave Approver", "Maintenance Manager",
"Maintenance User", "Manufacturing Manager", "Manufacturing User", "Maintenance User", "Manufacturing Manager", "Manufacturing User",
"Material Manager", "Material Master Manager", "Material User", "Material Manager", "Material Master Manager", "Material User",
"Partner", "Projects User", "Purchase Manager", "Purchase Master Manager", "Partner", "Projects User", "Projects Manager", "Purchase Manager", "Purchase Master Manager",
"Purchase User", "Quality Manager", "Sales Manager", "Purchase User", "Quality Manager", "Sales Manager",
"Sales Master Manager", "Sales User", "Supplier", "Support Manager", "Sales Master Manager", "Sales User", "Supplier", "Support Manager",
"Support Team", "Website Manager"] "Support Team", "Website Manager"]

View File

@ -17,64 +17,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import webnotes import webnotes
import copy
from webnotes.model.bean import Bean
from webnotes.model.doc import Document
from webnotes.utils import flt
sql = webnotes.conn.sql
class TestItem(unittest.TestCase):
def setUp(self):
webnotes.conn.begin()
def tearDown(self):
webnotes.conn.rollback()
def testInsert(self):
d = Bean()
count_before = flt(sql("select count(*) from tab"+_doctype)[0][0])
if docok:
for i in docok:
d.doc = i
d.children = None
d.doc.fields['__islocal']=1
d.save(1)
count_after = flt(sql("select count(*) from tab"+_doctype)[0][0])
self.assertTrue(count_before+len(docok)==count_after)
def testFailAssert(self):
if docnotok:
with self.assertRaises(Exception) as context:
d = Bean()
d.doc = docnotok[0]
d.children = None
d.doc.fields['__islocal']=1
d.save(1)
# Test Data
tabOK = [
{'is_purchase_item': None, 'is_pro_applicable': 'No', 'is_manufactured_item': None, 'description': 'Gel Ink', 'default_warehouse': None, 'item_name': 'Gel Ink', 'item_group': 'Ink', 'item_code': 'GELINK', 'is_sub_contracted_item': None, 'is_stock_item': 'Yes', 'stock_uom': 'Nos', 'docstatus': '0'},
{'is_purchase_item': None, 'is_pro_applicable': 'No', 'is_manufactured_item': None, 'description': 'Gel Refill', 'default_warehouse': None, 'item_name': 'Gel Refill', 'item_group': 'Refill', 'item_code': 'GELREF', 'is_sub_contracted_item': None, 'is_stock_item': 'Yes', 'stock_uom': 'Nos', 'docstatus': '0'},
{'is_purchase_item': None, 'is_pro_applicable': 'No', 'is_manufactured_item': None, 'description': 'Gel Pen', 'default_warehouse': None, 'item_name': 'Gel Pen', 'item_group': 'Pen', 'item_code': 'GELPEN', 'is_sub_contracted_item': None, 'is_stock_item': 'Yes', 'stock_uom': 'Nos', 'docstatus': '0'}
]
tabNotOK = [
{'is_purchase_item': None, 'is_pro_applicable': None, 'is_manufactured_item': None, 'description': 'F Ink', 'default_warehouse': None, 'item_name': 'F Ink', 'item_group': 'F Ink', 'item_code': None, 'is_sub_contracted_item': None, 'is_stock_item': 'No', 'stock_uom': 'Nos', 'docstatus': '0'}
]
_doctype = 'Item'
for i in tabOK: i['doctype']=_doctype
for i in tabNotOK: i['doctype']=_doctype
docok = [Document(fielddata=r) for r in tabOK]
docnotok = [Document(fielddata=r) for r in tabNotOK]
test_ignore = ["BOM"]
test_records = [ test_records = [
[{ [{
@ -164,4 +108,23 @@ test_records = [
"is_sub_contracted_item": "No", "is_sub_contracted_item": "No",
"stock_uom": "_Test UOM" "stock_uom": "_Test UOM"
}], }],
[{
"doctype": "Item",
"item_code": "_Test FG Item",
"item_name": "_Test FG Item",
"description": "_Test FG Item",
"item_group": "_Test Item Group Desktops",
"is_stock_item": "Yes",
"is_asset_item": "No",
"has_batch_no": "No",
"has_serial_no": "No",
"is_purchase_item": "Yes",
"is_sales_item": "Yes",
"is_service_item": "No",
"is_sample_item": "No",
"inspection_required": "No",
"is_pro_applicable": "Yes",
"is_sub_contracted_item": "Yes",
"stock_uom": "_Test UOM"
}],
] ]

View File

@ -18,10 +18,10 @@ from __future__ import unicode_literals
import webnotes import webnotes
from webnotes.utils import cstr, flt, cint from webnotes.utils import cstr, flt, cint
from webnotes.model.doc import addchild
from webnotes.model.bean import getlist from webnotes.model.bean import getlist
from webnotes.model.code import get_obj from webnotes.model.code import get_obj
from webnotes import msgprint from webnotes.model.doc import Document
from webnotes import msgprint, _
sql = webnotes.conn.sql sql = webnotes.conn.sql
@ -89,18 +89,6 @@ class DocType(BuyingController):
webnotes.msgprint("Another Purchase Receipt using the same Challan No. already exists.\ webnotes.msgprint("Another Purchase Receipt using the same Challan No. already exists.\
Please enter a valid Challan No.", raise_exception=1) Please enter a valid Challan No.", raise_exception=1)
# update valuation rate
def update_valuation_rate(self):
for d in self.doclist.get({"parentfield": "purchase_receipt_details"}):
if d.qty:
d.valuation_rate = (flt(d.purchase_rate) + flt(d.item_tax_amount)/flt(d.qty)
+ flt(d.rm_supp_cost) / flt(d.qty)) / flt(d.conversion_factor)
else:
d.valuation_rate = 0.0
#check in manage account if purchase order required or not.
# ====================================================================================
def po_required(self): def po_required(self):
res = sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = 'po_required'") res = sql("select value from `tabSingles` where doctype = 'Global Defaults' and field = 'po_required'")
if res and res[0][0]== 'Yes': if res and res[0][0]== 'Yes':
@ -109,8 +97,6 @@ class DocType(BuyingController):
msgprint("Purchse Order No. required against item %s"%d.item_code) msgprint("Purchse Order No. required against item %s"%d.item_code)
raise Exception raise Exception
# validate
def validate(self): def validate(self):
super(DocType, self).validate() super(DocType, self).validate()
@ -134,18 +120,17 @@ class DocType(BuyingController):
pc_obj.validate_reference_value(self) pc_obj.validate_reference_value(self)
self.check_for_stopped_status(pc_obj) self.check_for_stopped_status(pc_obj)
# update valuation rate # sub-contracting
self.update_valuation_rate() self.validate_for_subcontracting()
self.update_raw_materials_supplied()
self.update_valuation_rate("purchase_receipt_details")
# On Update
# ----------------------------------------------------------------------------------------------------
def on_update(self): def on_update(self):
if self.doc.rejected_warehouse: if self.doc.rejected_warehouse:
for d in getlist(self.doclist,'purchase_receipt_details'): for d in getlist(self.doclist,'purchase_receipt_details'):
d.rejected_warehouse = self.doc.rejected_warehouse d.rejected_warehouse = self.doc.rejected_warehouse
self.update_rw_material_detail()
get_obj('Stock Ledger').scrub_serial_nos(self) get_obj('Stock Ledger').scrub_serial_nos(self)
self.scrub_rejected_serial_nos() self.scrub_rejected_serial_nos()
@ -303,118 +288,66 @@ class DocType(BuyingController):
self.make_gl_entries() self.make_gl_entries()
def validate_for_subcontracting(self):
if self.sub_contracted_items and self.purchase_items and not self.doc.is_subcontracted:
webnotes.msgprint(_("""Please enter whether Purchase Recipt is made for subcontracting
or purchasing, in 'Is Subcontracted' field"""), raise_exception=1)
def update_rw_material_detail(self): if self.doc.is_subcontracted and not self.doc.supplier_warehouse:
webnotes.msgprint(_("Please Enter Supplier Warehouse for subcontracted Items"),
raise_exception=1)
for d in getlist(self.doclist,'purchase_receipt_details'): def update_raw_materials_supplied(self):
item_det = sql("select is_sub_contracted_item, is_purchase_item from `tabItem` where name = '%s'"%(d.item_code)) self.doclist = self.doc.clear_table(self.doclist, 'pr_raw_material_details')
if self.sub_contracted_items:
for item in self.doclist.get({"parentfield": "purchase_receipt_details"}):
if item.item_code in self.sub_contracted_items:
self.add_bom_items(item)
if item_det[0][0] == 'Yes': def add_bom_items(self, d):
if item_det[0][1] == 'Yes': bom_items = self.get_items_from_default_bom(d.item_code)
if not self.doc.is_subcontracted: raw_materials_cost = 0
msgprint("Please enter whether purchase receipt to be made for subcontracting or for purchase in 'Is Subcontracted' field .") for item in bom_items:
raise Exception required_qty = flt(item.qty_consumed_per_unit) * flt(d.qty) * flt(d.conversion_factor)
if self.doc.is_subcontracted == 'Yes': self.doclist.append({
if not self.doc.supplier_warehouse: "parentfield": "pr_raw_material_details",
msgprint("Please Enter Supplier Warehouse for subcontracted Items") "doctype": "Purchase Receipt Item Supplied",
raise Exception "reference_name": d.name,
self.add_bom(d) "bom_detail_no": item.name,
else: "main_item_code": d.item_code,
self.doclist = self.doc.clear_table(self.doclist,'pr_raw_material_details',1) "rm_item_code": item.item_code,
self.doc.save() "description": item.description,
elif item_det[0][1] == 'No': "stock_uom": item.stock_uom,
if not self.doc.supplier_warehouse: "required_qty": required_qty,
msgprint("Please Enter Supplier Warehouse for subcontracted Items") "consumed_qty": required_qty,
raise Exception "conversion_factor": d.conversion_factor,
self.add_bom(d) "rate": item.rate,
"amount": required_qty * flt(item.rate)
})
self.delete_irrelevant_raw_material() raw_materials_cost += required_qty * flt(item.rate)
#---------------calculate amt in Purchase Receipt Item Supplied-------------
self.calculate_amount(d)
d.rm_supp_cost = raw_materials_cost
def add_bom(self, d): def get_items_from_default_bom(self, item_code):
#----- fetching default bom from Bill of Materials instead of Item Master -- # print webnotes.conn.sql("""select name from `tabBOM` where item = '_Test FG Item'""")
bom_det = sql("""select t1.item, t2.item_code, t2.qty_consumed_per_unit, bom_items = sql("""select t2.item_code, t2.qty_consumed_per_unit,
t2.moving_avg_rate, t2.value_as_per_mar, t2.stock_uom, t2.name, t2.description t2.rate, t2.stock_uom, t2.name, t2.description
from `tabBOM` t1, `tabBOM Item` t2 from `tabBOM` t1, `tabBOM Item` t2
where t2.parent = t1.name and t1.item = %s and t1.is_default = 1 where t2.parent = t1.name and t1.item = %s and t1.is_default = 1
and t1.docstatus = 1 and t2.docstatus =1 and t1.is_active = 1""", d.item_code) and t1.docstatus = 1 and t1.is_active = 1""", item_code, as_dict=1)
if not bom_det: if not bom_items:
msgprint("No default BOM exists for item: %s" % d.item_code) msgprint(_("No default BOM exists for item: ") + item_code, raise_exception=1)
raise Exception
else:
#-------------- add child function--------------------
chgd_rqd_qty = []
for i in bom_det:
if i and not sql("select name from `tabPurchase Receipt Item Supplied` where reference_name = '%s' and bom_detail_no = '%s' and parent = '%s' " %(d.name, i[6], self.doc.name)): return bom_items
rm_child = addchild(self.doc, 'pr_raw_material_details', 'Purchase Receipt Item Supplied', self.doclist)
rm_child.reference_name = d.name
rm_child.bom_detail_no = i and i[6] or ''
rm_child.main_item_code = i and i[0] or ''
rm_child.rm_item_code = i and i[1] or ''
rm_child.description = i and i[7] or ''
rm_child.stock_uom = i and i[5] or ''
rm_child.rate = i and flt(i[3]) or flt(i[4])
rm_child.conversion_factor = d.conversion_factor
rm_child.required_qty = flt(i and flt(i[2]) or 0) * flt(d.qty) * flt(d.conversion_factor)
rm_child.consumed_qty = flt(i and flt(i[2]) or 0) * flt(d.qty) * flt(d.conversion_factor)
rm_child.amount = flt(flt(rm_child.consumed_qty)*flt(rm_child.rate))
rm_child.save()
chgd_rqd_qty.append(cstr(i[1]))
else:
act_qty = flt(i and flt(i[2]) or 0) * flt(d.qty) * flt(d.conversion_factor)
for pr_rmd in getlist(self.doclist, 'pr_raw_material_details'):
if i and i[6] == pr_rmd.bom_detail_no and (flt(act_qty) != flt(pr_rmd.required_qty) or i[1] != pr_rmd.rm_item_code or i[7] != pr_rmd.description):
chgd_rqd_qty.append(cstr(i[1]))
pr_rmd.main_item_code = i[0]
pr_rmd.rm_item_code = i[1]
pr_rmd.description = i[7]
pr_rmd.stock_uom = i[5]
pr_rmd.required_qty = flt(act_qty)
pr_rmd.consumed_qty = flt(act_qty)
pr_rmd.rate = i and flt(i[3]) or flt(i[4])
pr_rmd.amount = flt(flt(pr_rmd.consumed_qty)*flt(pr_rmd.rate))
pr_rmd.save()
if chgd_rqd_qty:
msgprint("Please check consumed quantity for Raw Material Item Code: '%s'in Raw materials Detail Table" % ((len(chgd_rqd_qty) > 1 and ','.join(chgd_rqd_qty[:-1]) +' and ' + cstr(chgd_rqd_qty[-1:][0]) ) or cstr(chgd_rqd_qty[0])))
# Delete irrelevant raw material from PR Raw material details
#--------------------------------------------------------------
def delete_irrelevant_raw_material(self):
for d in getlist(self.doclist,'pr_raw_material_details'):
if not sql("select name from `tabPurchase Receipt Item` where name = '%s' and parent = '%s' and item_code = '%s'" % (d.reference_name, self.doc.name, d.main_item_code)):
d.parent = 'old_par:'+self.doc.name
d.save()
def calculate_amount(self, d):
amt = 0
for i in getlist(self.doclist,'pr_raw_material_details'):
if(i.reference_name == d.name):
#if i.consumed_qty == 0:
# msgprint("consumed qty cannot be 0. Please Enter consumed qty ")
#raise Exception
i.amount = flt(i.consumed_qty)* flt(i.rate)
amt += i.amount
d.rm_supp_cost = amt
d.save()
# --------------- Back Flush function called on submit and on cancel from update stock
def bk_flush_supp_wh(self, is_submit): def bk_flush_supp_wh(self, is_submit):
for d in getlist(self.doclist, 'pr_raw_material_details'): for d in getlist(self.doclist, 'pr_raw_material_details'):
#--------- -ve quantity is passed as raw material qty has to be decreased when PR is submitted and it has to be increased when PR is cancelled # negative quantity is passed as raw material qty has to be decreased
# when PR is submitted and it has to be increased when PR is cancelled
consumed_qty = - flt(d.consumed_qty) consumed_qty = - flt(d.consumed_qty)
self.make_sl_entry(d, self.doc.supplier_warehouse, flt(consumed_qty), 0, is_submit) self.make_sl_entry(d, self.doc.supplier_warehouse, flt(consumed_qty), 0, is_submit)
# get current_stock
# ----------------
def get_current_stock(self): def get_current_stock(self):
for d in getlist(self.doclist, 'pr_raw_material_details'): for d in getlist(self.doclist, 'pr_raw_material_details'):
if self.doc.supplier_warehouse: if self.doc.supplier_warehouse:

View File

@ -36,7 +36,6 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_purchase_receipt_gl_entry(self): def test_purchase_receipt_gl_entry(self):
webnotes.defaults.set_global_default("auto_inventory_accounting", 1) webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1) self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1)
pr = webnotes.bean(copy=test_records[0]) pr = webnotes.bean(copy=test_records[0])
@ -47,7 +46,6 @@ class TestPurchaseReceipt(unittest.TestCase):
gl_entries = webnotes.conn.sql("""select account, debit, credit gl_entries = webnotes.conn.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s
order by account desc""", pr.doc.name, as_dict=1) order by account desc""", pr.doc.name, as_dict=1)
self.assertTrue(gl_entries) self.assertTrue(gl_entries)
stock_in_hand_account = webnotes.conn.get_value("Company", pr.doc.company, stock_in_hand_account = webnotes.conn.get_value("Company", pr.doc.company,
@ -65,6 +63,17 @@ class TestPurchaseReceipt(unittest.TestCase):
webnotes.defaults.set_global_default("auto_inventory_accounting", 0) webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
def test_subcontracting(self):
pr = webnotes.bean(copy=test_records[1])
pr.run_method("calculate_taxes_and_totals")
pr.insert()
self.assertEquals(pr.doclist[1].rm_supp_cost, 70000.0)
self.assertEquals(len(pr.doclist.get({"parentfield": "pr_raw_material_details"})), 2)
test_dependencies = ["BOM"]
test_records = [ test_records = [
[ [
{ {
@ -129,4 +138,36 @@ test_records = [
"tax_amount": 150.0, "tax_amount": 150.0,
}, },
], ],
[
{
"company": "_Test Company",
"conversion_rate": 1.0,
"currency": "INR",
"doctype": "Purchase Receipt",
"fiscal_year": "_Test Fiscal Year 2013",
"posting_date": "2013-02-12",
"posting_time": "15:33:30",
"is_subcontracted": "Yes",
"supplier_warehouse": "_Test Warehouse",
"supplier": "_Test Supplier",
"net_total": 5000.0,
"grand_total": 5000.0,
},
{
"conversion_factor": 1.0,
"description": "_Test FG Item",
"doctype": "Purchase Receipt Item",
"item_code": "_Test FG Item",
"item_name": "_Test FG Item",
"parentfield": "purchase_receipt_details",
"received_qty": 10.0,
"qty": 10.0,
"rejected_qty": 0.0,
"import_rate": 500.0,
"amount": 5000.0,
"warehouse": "_Test Warehouse",
"stock_uom": "Nos",
"uom": "_Test UOM",
}
],
] ]

View File

@ -253,6 +253,9 @@ def get_outer_env(page_name, args):
if k in settings.fields: if k in settings.fields:
args[k] = settings.fields.get(k) args[k] = settings.fields.get(k)
for k in ["facebook_share", "google_plus_one", "twitter_share", "linked_in_share"]:
args[k] = int(args.get(k) or 0)
if not args.brand_html: if not args.brand_html:
args.brand_html = "ERPNext" args.brand_html = "ERPNext"
if not args.top_bar_background: if not args.top_bar_background: