Merge branch 'staging-fixes' into company_address

This commit is contained in:
Deepesh Garg 2019-01-16 11:17:55 +05:30 committed by GitHub
commit 7bb7f95aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 167 additions and 69 deletions

View File

@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '10.1.76'
__version__ = '10.1.77'
def get_default_company(user=None):
'''Get default company for user'''

View File

@ -39,6 +39,8 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
});
frm.events.get_total_gain_loss(frm);
refresh_field("accounts");
} else {
frappe.msgprint(__("No records found"));
}
}
});

View File

@ -67,17 +67,19 @@ class ExchangeRateRevaluation(Document):
and account_currency != %s
order by name""",(self.company, company_currency))
account_details = frappe.db.sql("""
select
account, party_type, party, account_currency,
sum(debit_in_account_currency) - sum(credit_in_account_currency) as balance_in_account_currency,
sum(debit) - sum(credit) as balance
from `tabGL Entry`
where account in (%s)
group by account, party_type, party
having sum(debit) != sum(credit)
order by account
""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
account_details = []
if accounts:
account_details = frappe.db.sql("""
select
account, party_type, party, account_currency,
sum(debit_in_account_currency) - sum(credit_in_account_currency) as balance_in_account_currency,
sum(debit) - sum(credit) as balance
from `tabGL Entry`
where account in (%s)
group by account, party_type, party
having sum(debit) != sum(credit)
order by account
""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
return account_details

View File

@ -18,15 +18,14 @@ class GLEntry(Document):
self.flags.ignore_submit_comment = True
self.check_mandatory()
self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center()
self.validate_cost_center()
if not self.flags.from_repost:
self.pl_must_have_cost_center()
self.check_pl_account()
self.validate_cost_center()
self.validate_party()
self.validate_currency()
def on_update_with_args(self, adv_adj, update_outstanding = 'Yes', from_repost=False):
if not from_repost:
self.validate_account_details(adv_adj)

View File

@ -200,7 +200,7 @@ class GrossProfitGenerator(object):
def skip_row(self, row, product_bundles):
if self.filters.get("group_by") != "Invoice":
if not row.get(scrub(self.filters.get("group_by"))):
if not row.get(scrub(self.filters.get("group_by", ""))):
return True
elif row.get("is_return") == 1:
return True
@ -316,7 +316,7 @@ class GrossProfitGenerator(object):
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
{sales_team_table}
where
`tabSales Invoice`.docstatus=1 {conditions} {match_cond}
`tabSales Invoice`.docstatus=1 and `tabSales Invoice`.is_opening!='Yes' {conditions} {match_cond}
order by
`tabSales Invoice`.posting_date desc, `tabSales Invoice`.posting_time desc"""
.format(conditions=conditions, sales_person_cols=sales_person_cols,

View File

@ -1,5 +1,6 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 1,
@ -154,7 +155,7 @@
"columns": 0,
"fetch_from": "item_code.asset_category",
"fieldname": "asset_category",
"fieldtype": "Read Only",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
@ -165,12 +166,12 @@
"label": "Asset Category",
"length": 0,
"no_copy": 0,
"options": "",
"options": "Asset Category",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
@ -1881,7 +1882,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 14:44:24.507215",
"modified": "2019-01-15 16:12:48.314196",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@ -499,7 +499,8 @@ def get_bin_details(row):
conditions = ""
warehouse = row.source_warehouse or row.default_warehouse or row.warehouse
if warehouse:
conditions = " and warehouse='{0}'".format(frappe.db.escape(warehouse))
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
conditions = " and exists(select name from `tabWarehouse` where lft >= {0} and rgt <= {1} and name=`tabBin`.warehouse)".format(lft, rgt)
item_projected_qty = frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
ifnull(sum(actual_qty),0) as actual_qty from `tabBin`

View File

@ -1,9 +1,9 @@
execute:import unidecode # new requirement
erpnext.patches.v8_0.move_perpetual_inventory_setting
erpnext.patches.v11_0.rename_production_order_to_work_order
erpnext.patches.v11_0.refactor_naming_series
erpnext.patches.v11_0.refactor_autoname_naming
erpnext.patches.v10_0.rename_schools_to_education
erpnext.patches.v11_0.rename_production_order_to_work_order
erpnext.patches.v4_0.validate_v3_patch
erpnext.patches.v4_0.fix_employee_user_id
erpnext.patches.v4_0.remove_employee_role_if_no_employee

View File

@ -1,3 +1,4 @@
from __future__ import unicode_literals
import frappe
from frappe.model.rename_doc import rename_doc
from frappe.model.utils.rename_field import rename_field

View File

@ -12,6 +12,7 @@ from frappe.utils.nestedset import NestedSet
class CircularReferenceError(frappe.ValidationError): pass
class EndDateCannotBeGreaterThanProjectEndDateError(frappe.ValidationError): pass
class Task(NestedSet):
nsm_parent_field = 'parent_task'
@ -43,6 +44,12 @@ class Task(NestedSet):
if self.act_start_date and self.act_end_date and getdate(self.act_start_date) > getdate(self.act_end_date):
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
if(self.project):
if frappe.db.exists("Project", self.project):
expected_end_date = frappe.db.get_value("Project", self.project, "expected_end_date")
if self.exp_end_date and expected_end_date and getdate(self.exp_end_date) > getdate(expected_end_date) :
frappe.throw(_("Expected end date cannot be after Project: <b>'{0}'</b> Expected end date").format(self.project), EndDateCannotBeGreaterThanProjectEndDateError)
def validate_status(self):
if self.status!=self.get_db_value("status") and self.status == "Closed":
for d in self.depends_on:

View File

@ -5,11 +5,11 @@ import frappe
import unittest
from frappe.utils import getdate, nowdate, add_days
from erpnext.projects.doctype.task.task import CircularReferenceError
from erpnext.projects.doctype.task.task import CircularReferenceError, EndDateCannotBeGreaterThanProjectEndDateError
class TestTask(unittest.TestCase):
def test_circular_reference(self):
task1 = create_task("_Test Task 1", nowdate(), add_days(nowdate(), 10))
task1 = create_task("_Test Task 1", add_days(nowdate(), -15), add_days(nowdate(), -10))
task2 = create_task("_Test Task 2", add_days(nowdate(), 11), add_days(nowdate(), 15), task1.name)
task3 = create_task("_Test Task 3", add_days(nowdate(), 11), add_days(nowdate(), 15), task2.name)
@ -97,7 +97,16 @@ class TestTask(unittest.TestCase):
self.assertEqual(frappe.db.get_value("Task", task.name, "status"), "Overdue")
def create_task(subject, start=None, end=None, depends_on=None, project=None):
def test_end_date_validation(self):
task_end = create_task("Testing_Enddate_validation", add_days(nowdate(), 35), add_days(nowdate(), 45), save=False)
pro = frappe.get_doc("Project", task_end.project)
pro.expected_end_date = add_days(nowdate(), 40)
pro.save()
self.assertRaises(EndDateCannotBeGreaterThanProjectEndDateError, task_end.save)
def create_task(subject, start=None, end=None, depends_on=None, project=None, save=True):
if not frappe.db.exists("Task", subject):
task = frappe.new_doc('Task')
task.status = "Open"
@ -105,7 +114,8 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None):
task.exp_start_date = start or nowdate()
task.exp_end_date = end or nowdate()
task.project = project or "_Test Project"
task.save()
if save:
task.save()
else:
task = frappe.get_doc("Task", subject)
@ -113,6 +123,7 @@ def create_task(subject, start=None, end=None, depends_on=None, project=None):
task.append("depends_on", {
"task": depends_on
})
task.save()
if save:
task.save()
return task

View File

@ -416,6 +416,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
item_code: item.item_code,
barcode: item.barcode,
serial_no: item.serial_no,
set_warehouse: me.frm.doc.set_warehouse,
warehouse: item.warehouse,
customer: me.frm.doc.customer,
supplier: me.frm.doc.supplier,

View File

@ -11,22 +11,49 @@ def validate_gstin_for_india(doc, method):
if not hasattr(doc, 'gstin'):
return
if doc.gstin:
doc.gstin = doc.gstin.upper()
if doc.gstin not in ["NA", "na"]:
p = re.compile("[0-9]{2}[a-zA-Z]{5}[0-9]{4}[a-zA-Z]{1}[1-9A-Za-z]{1}[Z]{1}[0-9a-zA-Z]{1}")
if not p.match(doc.gstin):
frappe.throw(_("Invalid GSTIN or Enter NA for Unregistered"))
doc.gstin = doc.gstin.upper().strip()
if not doc.gstin or doc.gstin == 'NA':
return
if len(doc.gstin) != 15:
frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters."))
p = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$")
if not p.match(doc.gstin):
frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN."))
validate_gstin_check_digit(doc.gstin)
if not doc.gst_state:
if doc.state in states:
doc.gst_state = doc.state
if not doc.state:
return
state = doc.state.lower()
states_lowercase = {s.lower():s for s in states}
if state in states_lowercase:
doc.gst_state = states_lowercase[state]
else:
return
if doc.gst_state:
doc.gst_state_number = state_numbers[doc.gst_state]
if doc.gstin and doc.gstin != "NA" and doc.gst_state_number != doc.gstin[:2]:
frappe.throw(_("First 2 digits of GSTIN should match with State number {0}")
.format(doc.gst_state_number))
doc.gst_state_number = state_numbers[doc.gst_state]
if doc.gst_state_number != doc.gstin[:2]:
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
def validate_gstin_check_digit(gstin):
''' Function to validate the check digit of the GSTIN.'''
factor = 1
total = 0
code_point_chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
mod = len(code_point_chars)
input_chars = gstin[:-1]
for char in input_chars:
digit = factor * code_point_chars.find(char)
digit = (digit // mod) + (digit % mod)
total += digit
factor = 2 if factor == 1 else 1
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
frappe.throw(_("Invalid GSTIN! The check digit validation has failed. " +
"Please ensure you've typed the GSTIN correctly."))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):

View File

@ -177,6 +177,11 @@ class Customer(TransactionBase):
frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt))
def on_trash(self):
if self.customer_primary_contact:
frappe.db.sql("""update `tabCustomer`
set customer_primary_contact=null, mobile_no=null, email_id=null
where name=%s""", self.name)
delete_contact_and_address('Customer', self.name)
if self.lead_name:
frappe.db.sql("update `tabLead` set status='Interested' where name=%s", self.lead_name)

View File

@ -98,6 +98,15 @@ class TestCustomer(unittest.TestCase):
so.save()
def test_delete_customer_contact(self):
customer = frappe.get_doc(
get_customer_dict('_Test Customer for delete')).insert(ignore_permissions=True)
customer.mobile_no = "8989889890"
customer.save()
self.assertTrue(customer.customer_primary_contact)
frappe.delete_doc('Customer', customer.name)
def test_disabled_customer(self):
make_test_records("Item")

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe import _, scrub
from frappe.utils import getdate, flt
from frappe.utils import getdate, flt, add_to_date, add_days
from six import iteritems
from erpnext.accounts.utils import get_fiscal_year
@ -40,7 +40,7 @@ class Analytics(object):
"fieldtype": "Data",
"width": 140
})
for dummy, end_date in self.periodic_daterange:
for end_date in self.periodic_daterange:
period = self.get_period(end_date)
self.columns.append({
"label": _(period),
@ -169,7 +169,7 @@ class Analytics(object):
"entity_name": self.entity_names.get(entity)
}
total = 0
for dummy, end_date in self.periodic_daterange:
for end_date in self.periodic_daterange:
period = self.get_period(end_date)
amount = flt(period_data.get(period, 0.0))
row[scrub(period)] = amount
@ -188,7 +188,7 @@ class Analytics(object):
"indent": self.depth_map.get(d.name)
}
total = 0
for dummy, end_date in self.periodic_daterange:
for end_date in self.periodic_daterange:
period = self.get_period(end_date)
amount = flt(self.entity_periodic_data.get(d.name, {}).get(period, 0.0))
row[scrub(period)] = amount
@ -219,12 +219,11 @@ class Analytics(object):
period = "Quarter " + str(((posting_date.month-1)//3)+1) +" " + str(posting_date.year)
else:
year = get_fiscal_year(posting_date, company=self.filters.company)
period = str(year[2])
period = str(year[0])
return period
def get_period_date_ranges(self):
from dateutil.relativedelta import relativedelta
from dateutil.relativedelta import relativedelta, MO
from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date)
increment = {
@ -234,18 +233,26 @@ class Analytics(object):
"Yearly": 12
}.get(self.filters.range, 1)
if self.filters.range in ['Monthly', 'Quarterly']:
from_date = from_date.replace(day = 1)
elif self.filters.range == "Yearly":
from_date = get_fiscal_year(from_date)[1]
else:
from_date = from_date + relativedelta(from_date, weekday=MO(-1))
self.periodic_daterange = []
for dummy in range(1, 53, increment):
for dummy in range(1, 53):
if self.filters.range == "Weekly":
period_end_date = from_date + relativedelta(days=6)
period_end_date = add_days(from_date, 6)
else:
period_end_date = from_date + relativedelta(months=increment, days=-1)
period_end_date = add_to_date(from_date, months=increment, days=-1)
if period_end_date > to_date:
period_end_date = to_date
self.periodic_daterange.append([from_date, period_end_date])
from_date = period_end_date + relativedelta(days=1)
self.periodic_daterange.append(period_end_date)
from_date = add_days(period_end_date, 1)
if period_end_date == to_date:
break

View File

@ -1,3 +1,4 @@
from __future__ import unicode_literals
from frappe import _
doctype_list = [

View File

@ -13,7 +13,7 @@ def get_data():
'goal_doctype_link': 'company',
'goal_field': 'base_grand_total',
'date_field': 'posting_date',
'filter_str': 'docstatus = 1',
'filter_str': "docstatus = 1 and is_opening != 'Yes'",
'aggregation': 'sum'
},

View File

@ -136,7 +136,10 @@ def get_child_groups_for_list_in_html(item_group, start, limit, search):
rgt = ('<', item_group.rgt),
),
or_filters = search_filters,
order_by = 'weightage desc, name asc')
order_by = 'weightage desc, name asc',
start = start,
limit = limit
)
return [get_item_for_list_in_html(r) for r in data]

View File

@ -10,7 +10,9 @@ from frappe.utils import flt, nowdate, nowtime
from erpnext.accounts.utils import get_stock_and_account_difference
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError
from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import EmptyStockReconciliationItemsError, get_items
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.doctype.item.test_item import make_item
class TestStockReconciliation(unittest.TestCase):
def setUp(self):
@ -79,6 +81,17 @@ class TestStockReconciliation(unittest.TestCase):
set_perpetual_inventory(0)
def test_get_items(self):
create_warehouse("_Test Warehouse Group 1", {"is_group": 1})
create_warehouse("_Test Warehouse Ledger 1", {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC"})
make_item("_Test Stock Reco Item", {"default_warehouse": "_Test Warehouse Ledger 1 - _TC",
"is_stock_item": 1, "opening_stock": 100, "valuation_rate": 100})
items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime())
self.assertEqual(["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100],
[items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]])
def insert_existing_sle(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry

View File

@ -90,7 +90,7 @@ class TestWarehouse(unittest.TestCase):
self.assertTrue(frappe.db.get_value("Warehouse",
filters={"account": "Test Warehouse for Merging 2 - _TC"}))
def create_warehouse(warehouse_name):
def create_warehouse(warehouse_name, properties=None):
if not frappe.db.exists("Warehouse", warehouse_name + " - _TC"):
w = frappe.new_doc("Warehouse")
w.warehouse_name = warehouse_name
@ -98,11 +98,13 @@ def create_warehouse(warehouse_name):
w.company = "_Test Company"
make_account_for_warehouse(warehouse_name, w)
w.account = warehouse_name + " - _TC"
if properties:
w.update(properties)
w.save()
def make_account_for_warehouse(warehouse_name, warehouse_obj):
if not frappe.db.exists("Account", warehouse_name + " - _TC"):
parent_account = frappe.db.get_value('Account',
parent_account = frappe.db.get_value('Account',
{'company': warehouse_obj.company, 'is_group':1, 'account_type': 'Stock'},'name')
account = create_account(account_name=warehouse_name, \
account_type="Stock", parent_account= parent_account, company=warehouse_obj.company)

View File

@ -36,6 +36,7 @@ def get_item_details(args):
"is_subcontracted": "Yes" / "No",
"ignore_pricing_rule": 0/1
"project": ""
"set_warehouse": ""
}
"""
args = process_args(args)
@ -189,7 +190,6 @@ def get_basic_details(args, item):
"project": "",
barcode: "",
serial_no: "",
warehouse: "",
currency: "",
update_stock: "",
price_list: "",
@ -219,7 +219,7 @@ def get_basic_details(args, item):
item_defaults = get_item_defaults(item.name, args.company)
item_group_defaults = get_item_group_defaults(item.name, args.company)
warehouse = user_default_warehouse or item_defaults.get("default_warehouse") or\
warehouse = args.get("set_warehouse") or user_default_warehouse or item_defaults.get("default_warehouse") or\
item_group_defaults.get("default_warehouse") or args.warehouse
if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
@ -273,7 +273,7 @@ def get_basic_details(args, item):
"transaction_date": args.get("transaction_date")
})
if item.enable_deferred_revenue:
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
out.update(calculate_service_end_date(args, item))
# calculate conversion factor
@ -310,9 +310,15 @@ def calculate_service_end_date(args, item=None):
if not item:
item = frappe.get_cached_doc("Item", args.item_code)
enable_deferred = "enable_deferred_revenue" if args.doctype=="Sales Invoice" else "enable_deferred_expense"
no_of_months = "no_of_months" if args.doctype=="Sales Invoice" else "no_of_months_exp"
account = "deferred_revenue_account" if args.doctype=="Sales Invoice" else "deferred_expense_account"
doctype = args.get("parenttype") or args.get("doctype")
if doctype == "Sales Invoice":
enable_deferred = "enable_deferred_revenue"
no_of_months = "no_of_months"
account = "deferred_revenue_account"
else:
enable_deferred = "enable_deferred_expense"
no_of_months = "no_of_months_exp"
account = "deferred_expense_account"
service_start_date = args.service_start_date if args.service_start_date else args.transaction_date
service_end_date = add_months(service_start_date, item.get(no_of_months))
@ -336,7 +342,7 @@ def get_default_expense_account(args, item, item_group):
or args.expense_account)
def get_default_deferred_account(args, item, fieldname=None):
if item.enable_deferred_revenue:
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
return (item.get(fieldname)
or args.get(fieldname)
or frappe.get_cached_value('Company', args.company, "default_"+fieldname))
@ -370,16 +376,16 @@ def get_price_list_rate(args, item_doc, out):
price_list_rate = get_price_list_rate_for(args, item_doc.name) or 0
# variant
if not price_list_rate and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
# insert in database
if not price_list_rate:
if args.price_list and args.rate:
insert_item_price(args)
return {}
# variant
if not price_list_rate and item_doc.variant_of:
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
/ flt(args.conversion_rate)