Merge branch 'hotfix' into fix_by_voucher_order

This commit is contained in:
Don-Leopardo
2019-06-24 11:30:19 -03:00
committed by GitHub
54 changed files with 868 additions and 256 deletions

View File

@@ -3,6 +3,11 @@ dist: trusty
python: python:
- "2.7" - "2.7"
- "3.6"
env:
- TEST_TYPE="Server Side Test"
- TEST_TYPE="Patch Test"
services: services:
- mysql - mysql
@@ -39,18 +44,8 @@ before_script:
- bench start & - bench start &
- sleep 10 - sleep 10
jobs: script:
include: - bash $TRAVIS_BUILD_DIR/travis/run-tests.sh
- stage: test
script: after_script:
- set -e - coveralls -b apps/erpnext -d ../../sites/.coverage
- bench run-tests --app erpnext --coverage
after_script:
- coveralls -b apps/erpnext -d ../../sites/.coverage
env: Server Side Test
- # stage
script:
- wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
- bench --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz --mariadb-root-password travis
- bench migrate
env: Patch Testing

View File

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

View File

@@ -43,8 +43,13 @@ frappe.ui.form.on('Bank Guarantee', {
reference_docname: function(frm) { reference_docname: function(frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) { if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let fields_to_fetch = ["project", "grand_total"]; let fields_to_fetch = ["grand_total"];
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier"; let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
if (frm.doc.reference_doctype == "Sales Order") {
fields_to_fetch.push("project");
}
fields_to_fetch.push(party_field); fields_to_fetch.push(party_field);
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials", method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials",

View File

@@ -350,7 +350,7 @@ def filter_pricing_rules(args, pricing_rules):
if len(pricing_rules) > 1: if len(pricing_rules) > 1:
rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules])) rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage": if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
pricing_rules = filter(lambda x: x.for_price_list==args.price_list, pricing_rules) \ pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \
or pricing_rules or pricing_rules
if len(pricing_rules) > 1 and not args.for_shopping_cart: if len(pricing_rules) > 1 and not args.for_shopping_cart:
@@ -373,7 +373,7 @@ def apply_internal_priority(pricing_rules, field_set, args):
filtered_rules = [] filtered_rules = []
for field in field_set: for field in field_set:
if args.get(field): if args.get(field):
filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules) filtered_rules = list(filter(lambda x: x[field]==args[field], pricing_rules))
if filtered_rules: break if filtered_rules: break
return filtered_rules or pricing_rules return filtered_rules or pricing_rules

View File

@@ -371,6 +371,10 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.pos_print_format = r.message.print_format; me.frm.pos_print_format = r.message.print_format;
} }
me.frm.script_manager.trigger("update_stock"); me.frm.script_manager.trigger("update_stock");
if(me.frm.doc.taxes_and_charges) {
me.frm.script_manager.trigger("taxes_and_charges");
}
frappe.model.set_default_values(me.frm.doc); frappe.model.set_default_values(me.frm.doc);
me.set_dynamic_labels(); me.set_dynamic_labels();
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();

View File

@@ -22,7 +22,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
invoices = [] invoices = []
# create invoices for lower than single threshold tax rate # create invoices for lower than single threshold tax rate
for _ in xrange(2): for _ in range(2):
pi = create_purchase_invoice(supplier = "Test TDS Supplier") pi = create_purchase_invoice(supplier = "Test TDS Supplier")
pi.submit() pi.submit()
invoices.append(pi) invoices.append(pi)

View File

@@ -10,4 +10,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
}); });
frappe.query_reports["Balance Sheet"]["filters"].push({
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check"
});
}); });

View File

@@ -60,7 +60,7 @@ def validate_filters(filters):
frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center")) frappe.throw(_("Filter based on Cost Center is only applicable if Budget Against is selected as Cost Center"))
def get_columns(filters): def get_columns(filters):
columns = [_(filters.get("budget_against")) + ":Link/%s:80"%(filters.get("budget_against")), _("Account") + ":Link/Account:80"] columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
group_months = False if filters["period"] == "Monthly" else True group_months = False if filters["period"] == "Monthly" else True
@@ -71,7 +71,7 @@ def get_columns(filters):
if filters["period"] == "Yearly": if filters["period"] == "Yearly":
labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])] labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Varaiance ") + " " + str(year[0])]
for label in labels: for label in labels:
columns.append(label+":Float:80") columns.append(label+":Float:150")
else: else:
for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]: for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]:
if group_months: if group_months:
@@ -79,11 +79,11 @@ def get_columns(filters):
else: else:
label = label % formatdate(from_date, format_string="MMM") label = label % formatdate(from_date, format_string="MMM")
columns.append(label+":Float:80") columns.append(label+":Float:150")
if filters["period"] != "Yearly" : if filters["period"] != "Yearly" :
return columns + [_("Total Budget") + ":Float:80", _("Total Actual") + ":Float:80", return columns + [_("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150",
_("Total Variance") + ":Float:80"] _("Total Variance") + ":Float:150"]
else: else:
return columns return columns

View File

@@ -15,4 +15,10 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Accumulated Values"), "label": __("Accumulated Values"),
"fieldtype": "Check" "fieldtype": "Check"
}); });
frappe.query_reports["Cash Flow"]["filters"].push({
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check"
});
}); });

View File

@@ -52,7 +52,7 @@ def execute(filters=None):
for account in cash_flow_account['account_types']: for account in cash_flow_account['account_types']:
account_data = get_account_type_based_data(filters.company, account_data = get_account_type_based_data(filters.company,
account['account_type'], period_list, filters.accumulated_values) account['account_type'], period_list, filters.accumulated_values, filters)
account_data.update({ account_data.update({
"account_name": account['label'], "account_name": account['label'],
"account": account['label'], "account": account['label'],
@@ -105,13 +105,15 @@ def get_cash_flow_accounts():
# combine all cash flow accounts for iteration # combine all cash flow accounts for iteration
return [operation_accounts, investing_accounts, financing_accounts] return [operation_accounts, investing_accounts, financing_accounts]
def get_account_type_based_data(company, account_type, period_list, accumulated_values): def get_account_type_based_data(company, account_type, period_list, accumulated_values, filters):
data = {} data = {}
total = 0 total = 0
for period in period_list: for period in period_list:
start_date = get_start_date(period, accumulated_values, company) start_date = get_start_date(period, accumulated_values, company)
amount = get_account_type_based_gl_data(company, start_date, period['to_date'], account_type) amount = get_account_type_based_gl_data(company, start_date,
period['to_date'], account_type, filters)
if amount and account_type == "Depreciation": if amount and account_type == "Depreciation":
amount *= -1 amount *= -1
@@ -121,14 +123,24 @@ def get_account_type_based_data(company, account_type, period_list, accumulated_
data["total"] = total data["total"] = total
return data return data
def get_account_type_based_gl_data(company, start_date, end_date, account_type): def get_account_type_based_gl_data(company, start_date, end_date, account_type, filters):
cond = ""
if filters.finance_book:
cond = " and finance_book = '%s'" %(frappe.db.escape(filters.finance_book))
if filters.include_default_book_entries:
company_fb = frappe.db.get_value("Company", company, 'default_finance_book')
cond = """ and finance_book in ('%s', '%s')
""" %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb))
gl_sum = frappe.db.sql_list(""" gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit) select sum(credit) - sum(debit)
from `tabGL Entry` from `tabGL Entry`
where company=%s and posting_date >= %s and posting_date <= %s where company=%s and posting_date >= %s and posting_date <= %s
and voucher_type != 'Period Closing Voucher' and voucher_type != 'Period Closing Voucher'
and account in ( SELECT name FROM tabAccount WHERE account_type = %s) and account in ( SELECT name FROM tabAccount WHERE account_type = %s) {cond}
""", (company, start_date, end_date, account_type)) """.format(cond=cond), (company, start_date, end_date, account_type))
return gl_sum[0] if gl_sum and gl_sum[0] else 0 return gl_sum[0] if gl_sum and gl_sum[0] else 0

View File

@@ -55,5 +55,10 @@ frappe.query_reports["Consolidated Financial Statement"] = {
"fieldtype": "Check", "fieldtype": "Check",
"default": 0 "default": 0
}, },
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check"
}
] ]
} }

View File

@@ -355,7 +355,8 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
"lft": root_lft, "lft": root_lft,
"rgt": root_rgt, "rgt": root_rgt,
"company": d.name, "company": d.name,
"finance_book": filters.get("finance_book") "finance_book": filters.get("finance_book"),
"company_fb": frappe.db.get_value("Company", d.name, 'default_finance_book')
}, },
as_dict=True) as_dict=True)
@@ -386,7 +387,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions.append("gl.posting_date >= %(from_date)s") additional_conditions.append("gl.posting_date >= %(from_date)s")
if filters.get("finance_book"): if filters.get("finance_book"):
additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") if filters.get("include_default_book_entries"):
additional_conditions.append("finance_book in (%(finance_book)s, %(company_fb)s)")
else:
additional_conditions.append("finance_book in (%(finance_book)s)")
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""

View File

@@ -359,7 +359,8 @@ def set_gl_entries_by_account(
"to_date": to_date, "to_date": to_date,
"cost_center": filters.cost_center, "cost_center": filters.cost_center,
"project": filters.project, "project": filters.project,
"finance_book": filters.get("finance_book") "finance_book": filters.get("finance_book"),
"company_fb": frappe.db.get_value("Company", company, 'default_finance_book')
}, },
as_dict=True) as_dict=True)
@@ -393,7 +394,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
additional_conditions.append("cost_center in %(cost_center)s") additional_conditions.append("cost_center in %(cost_center)s")
if filters.get("finance_book"): if filters.get("finance_book"):
additional_conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") if filters.get("include_default_book_entries"):
additional_conditions.append("finance_book in (%(finance_book)s, %(company_fb)s)")
else:
additional_conditions.append("finance_book in (%(finance_book)s)")
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""

View File

@@ -216,6 +216,11 @@ frappe.query_reports["General Ledger"] = {
"fieldname": "show_opening_entries", "fieldname": "show_opening_entries",
"label": __("Show Opening Entries"), "label": __("Show Opening Entries"),
"fieldtype": "Check" "fieldtype": "Check"
},
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check"
} }
] ]
} }

View File

@@ -128,13 +128,16 @@ def get_gl_entries(filters):
order_by_statement = "order by posting_date, voucher_type, voucher_no" order_by_statement = "order by posting_date, voucher_type, voucher_no"
if filters.get("group_by") == _("Group by Voucher (Consolidated)"): if filters.get("group_by") == _("Group by Voucher (Consolidated)"):
group_by_statement = """group by voucher_type, voucher_no, account, group_by_statement = "group by voucher_type, voucher_no, account, cost_center"
cost_center, against_voucher_type, against_voucher, posting_date"""
select_fields = """, sum(debit) as debit, sum(credit) as credit, select_fields = """, sum(debit) as debit, sum(credit) as credit,
sum(debit_in_account_currency) as debit_in_account_currency, sum(debit_in_account_currency) as debit_in_account_currency,
sum(credit_in_account_currency) as credit_in_account_currency""" sum(credit_in_account_currency) as credit_in_account_currency"""
if filters.get("include_default_book_entries"):
filters['company_fb'] = frappe.db.get_value("Company",
filters.get("company"), 'default_finance_book')
gl_entries = frappe.db.sql( gl_entries = frappe.db.sql(
""" """
select select
@@ -190,7 +193,10 @@ def get_conditions(filters):
conditions.append("project in %(project)s") conditions.append("project in %(project)s")
if filters.get("finance_book"): if filters.get("finance_book"):
conditions.append("ifnull(finance_book, '') in (%(finance_book)s, '')") if filters.get("include_default_book_entries"):
conditions.append("finance_book in (%(finance_book)s, %(company_fb)s)")
else:
conditions.append("finance_book in (%(finance_book)s)")
from frappe.desk.reportview import build_match_conditions from frappe.desk.reportview import build_match_conditions
match_conditions = build_match_conditions("GL Entry") match_conditions = build_match_conditions("GL Entry")

View File

@@ -41,6 +41,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldname": "accumulated_values", "fieldname": "accumulated_values",
"label": __("Accumulated Values"), "label": __("Accumulated Values"),
"fieldtype": "Check" "fieldtype": "Check"
},
{
"fieldname": "include_default_book_entries",
"label": __("Include Default Book Entries"),
"fieldtype": "Check"
} }
); );
}); });

View File

@@ -47,8 +47,8 @@ class TestSalesPaymentSummary(unittest.TestCase):
pe.submit() pe.submit()
mop = get_mode_of_payments(filters) mop = get_mode_of_payments(filters)
self.assertTrue('Credit Card' in mop.values()[0]) self.assertTrue('Credit Card' in list(mop.values())[0])
self.assertTrue('Cash' in mop.values()[0]) self.assertTrue('Cash' in list(mop.values())[0])
# Cancel all Cash payment entry and check if this mode of payment is still fetched. # Cancel all Cash payment entry and check if this mode of payment is still fetched.
payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Cash", "docstatus": 1}, fields=["name", "docstatus"]) payment_entries = frappe.get_all("Payment Entry", filters={"mode_of_payment": "Cash", "docstatus": 1}, fields=["name", "docstatus"])
@@ -57,8 +57,8 @@ class TestSalesPaymentSummary(unittest.TestCase):
pe.cancel() pe.cancel()
mop = get_mode_of_payments(filters) mop = get_mode_of_payments(filters)
self.assertTrue('Credit Card' in mop.values()[0]) self.assertTrue('Credit Card' in list(mop.values())[0])
self.assertTrue('Cash' not in mop.values()[0]) self.assertTrue('Cash' not in list(mop.values())[0])
def test_get_mode_of_payments_details(self): def test_get_mode_of_payments_details(self):
filters = get_filters() filters = get_filters()
@@ -84,7 +84,7 @@ class TestSalesPaymentSummary(unittest.TestCase):
mopd = get_mode_of_payment_details(filters) mopd = get_mode_of_payment_details(filters)
mopd_values = mopd.values()[0] mopd_values = list(mopd.values())[0]
for mopd_value in mopd_values: for mopd_value in mopd_values:
if mopd_value[0] == "Credit Card": if mopd_value[0] == "Credit Card":
cc_init_amount = mopd_value[1] cc_init_amount = mopd_value[1]
@@ -96,7 +96,7 @@ class TestSalesPaymentSummary(unittest.TestCase):
pe.cancel() pe.cancel()
mopd = get_mode_of_payment_details(filters) mopd = get_mode_of_payment_details(filters)
mopd_values = mopd.values()[0] mopd_values = list(mopd.values())[0]
for mopd_value in mopd_values: for mopd_value in mopd_values:
if mopd_value[0] == "Credit Card": if mopd_value[0] == "Credit Card":
cc_final_amount = mopd_value[1] cc_final_amount = mopd_value[1]

View File

@@ -105,7 +105,7 @@ def get_rootwise_opening_balances(filters, report_type):
if filters.finance_book: if filters.finance_book:
fb_conditions = " and finance_book = %(finance_book)s" fb_conditions = " and finance_book = %(finance_book)s"
if filters.include_default_book_entries: if filters.include_default_book_entries:
fb_conditions = " and (finance_book in (%(finance_book)s, %(company_fb)s) or finance_book is null)" fb_conditions = " and (finance_book in (%(finance_book)s, %(company_fb)s))"
additional_conditions += fb_conditions additional_conditions += fb_conditions
@@ -180,20 +180,28 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense":
d["opening_debit"] -= d["opening_credit"] d["opening_debit"] -= d["opening_credit"]
d["opening_credit"] = 0.0 d["closing_debit"] -= d["closing_credit"]
total_row["opening_debit"] += d["opening_debit"]
# For opening
check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit")
# For closing
check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit")
if d["root_type"] == "Liability" or d["root_type"] == "Income": if d["root_type"] == "Liability" or d["root_type"] == "Income":
d["opening_credit"] -= d["opening_debit"] d["opening_credit"] -= d["opening_debit"]
d["opening_debit"] = 0.0
total_row["opening_credit"] += d["opening_credit"]
if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense":
d["closing_debit"] -= d["closing_credit"]
d["closing_credit"] = 0.0
total_row["closing_debit"] += d["closing_debit"]
if d["root_type"] == "Liability" or d["root_type"] == "Income":
d["closing_credit"] -= d["closing_debit"] d["closing_credit"] -= d["closing_debit"]
d["closing_debit"] = 0.0
total_row["closing_credit"] += d["closing_credit"] # For opening
check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit")
# For closing
check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit")
total_row["opening_debit"] += d["opening_debit"]
total_row["closing_debit"] += d["closing_debit"]
total_row["opening_credit"] += d["opening_credit"]
total_row["closing_credit"] += d["closing_credit"]
return total_row return total_row
@@ -219,8 +227,6 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr
if d.account_number else d.account_name) if d.account_number else d.account_name)
} }
prepare_opening_and_closing(d)
for key in value_fields: for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3) row[key] = flt(d.get(key, 0.0), 3)
@@ -295,22 +301,11 @@ def get_columns():
} }
] ]
def prepare_opening_and_closing(d): def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column):
d["closing_debit"] = d["opening_debit"] + d["debit"] # If opening debit has negetive value then move it to opening credit and vice versa.
d["closing_credit"] = d["opening_credit"] + d["credit"]
if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": if d[dr_or_cr] < 0:
d["opening_debit"] -= d["opening_credit"] d[switch_to_column] = abs(d[dr_or_cr])
d["opening_credit"] = 0.0 d[dr_or_cr] = 0.0
else:
if d["root_type"] == "Liability" or d["root_type"] == "Income": d[switch_to_column] = 0.0
d["opening_credit"] -= d["opening_debit"]
d["opening_debit"] = 0.0
if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense":
d["closing_debit"] -= d["closing_credit"]
d["closing_credit"] = 0.0
if d["root_type"] == "Liability" or d["root_type"] == "Income":
d["closing_credit"] -= d["closing_debit"]
d["closing_debit"] = 0.0

View File

@@ -291,16 +291,19 @@ class Asset(AccountsController):
def validate_expected_value_after_useful_life(self): def validate_expected_value_after_useful_life(self):
for row in self.get('finance_books'): for row in self.get('finance_books'):
accumulated_depreciation_after_full_schedule = max([d.accumulated_depreciation_amount accumulated_depreciation_after_full_schedule = [d.accumulated_depreciation_amount
for d in self.get("schedules") if cint(d.finance_book_id) == row.idx]) for d in self.get("schedules") if cint(d.finance_book_id) == row.idx]
asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - if accumulated_depreciation_after_full_schedule:
flt(accumulated_depreciation_after_full_schedule), accumulated_depreciation_after_full_schedule = max(accumulated_depreciation_after_full_schedule)
self.precision('gross_purchase_amount'))
if row.expected_value_after_useful_life < asset_value_after_full_schedule: asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) -
frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") flt(accumulated_depreciation_after_full_schedule),
.format(row.idx, asset_value_after_full_schedule)) self.precision('gross_purchase_amount'))
if row.expected_value_after_useful_life < asset_value_after_full_schedule:
frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}")
.format(row.idx, asset_value_after_full_schedule))
def validate_cancellation(self): def validate_cancellation(self):
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):

View File

@@ -75,8 +75,8 @@ class AssetValueAdjustment(Document):
rate_per_day = flt(d.value_after_depreciation) / flt(total_days) rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
from_date = self.date from_date = self.date
else: else:
no_of_depreciations = len([e.name for e in asset.schedules no_of_depreciations = len([s.name for s in asset.schedules
if (cint(s.finance_book_id) == d.idx and not e.journal_entry)]) if (cint(s.finance_book_id) == d.idx and not s.journal_entry)])
value_after_depreciation = d.value_after_depreciation value_after_depreciation = d.value_after_depreciation
for data in asset.schedules: for data in asset.schedules:

View File

@@ -25,9 +25,12 @@ class TestLocation(unittest.TestCase):
temp['features'][0]['properties']['feature_of'] = location temp['features'][0]['properties']['feature_of'] = location
formatted_locations.extend(temp['features']) formatted_locations.extend(temp['features'])
formatted_location_string = str(formatted_locations)
test_location = frappe.get_doc('Location', 'Test Location Area') test_location = frappe.get_doc('Location', 'Test Location Area')
test_location.save() test_location.save()
self.assertEqual(formatted_location_string, str(json.loads(test_location.get('location'))['features'])) test_location_features = json.loads(test_location.get('location'))['features']
ordered_test_location_features = sorted(test_location_features, key=lambda x: x['properties']['feature_of'])
ordered_formatted_locations = sorted(formatted_locations, key=lambda x: x['properties']['feature_of'])
self.assertEqual(ordered_formatted_locations, ordered_test_location_features)
self.assertEqual(area, test_location.get('area')) self.assertEqual(area, test_location.get('area'))

View File

@@ -116,7 +116,7 @@ def call_mws_method(mws_method, *args, **kwargs):
mws_settings = frappe.get_doc("Amazon MWS Settings") mws_settings = frappe.get_doc("Amazon MWS Settings")
max_retries = mws_settings.max_retry_limit max_retries = mws_settings.max_retry_limit
for x in xrange(0, max_retries): for x in range(0, max_retries):
try: try:
response = mws_method(*args, **kwargs) response = mws_method(*args, **kwargs)
return response return response

View File

@@ -4,7 +4,6 @@ frappe.ui.form.on("Employee Attendance Tool", {
}, },
onload: function(frm) { onload: function(frm) {
frm.doc.department = frm.doc.branch = frm.doc.company = "All";
frm.set_value("date", frappe.datetime.get_today()); frm.set_value("date", frappe.datetime.get_today());
erpnext.employee_attendance_tool.load_employees(frm); erpnext.employee_attendance_tool.load_employees(frm);
}, },

View File

@@ -17,12 +17,11 @@ def get_employees(date, department = None, branch = None, company = None):
attendance_not_marked = [] attendance_not_marked = []
attendance_marked = [] attendance_marked = []
filters = {"status": "Active", "date_of_joining": ["<=", date]} filters = {"status": "Active", "date_of_joining": ["<=", date]}
if department != "All":
filters["department"] = department for field, value in {'department': department,
if branch != "All": 'branch': branch, 'company': company}.items():
filters["branch"] = branch if value:
if company != "All": filters[field] = value
filters["company"] = company
employee_list = frappe.get_list("Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name") employee_list = frappe.get_list("Employee", fields=["employee", "employee_name"], filters=filters, order_by="employee_name")
marked_employee = {} marked_employee = {}

View File

@@ -2,20 +2,8 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Employee Benefit Application', { frappe.ui.form.on('Employee Benefit Application', {
setup: function(frm) {
if(!frm.doc.employee || !frm.doc.date) {
frappe.throw(__("Please select Employee and Date first"));
} else {
frm.set_query("earning_component", "employee_benefits", function() {
return {
query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components",
filters: {date: frm.doc.date, employee: frm.doc.employee}
};
});
}
},
employee: function(frm) { employee: function(frm) {
frm.trigger('set_earning_component');
var method, args; var method, args;
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){
method = "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining"; method = "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_max_benefits_remaining";
@@ -35,6 +23,21 @@ frappe.ui.form.on('Employee Benefit Application', {
get_max_benefits(frm, method, args); get_max_benefits(frm, method, args);
} }
}, },
date: function(frm) {
frm.trigger('set_earning_component');
},
set_earning_component: function(frm) {
if(!frm.doc.employee && !frm.doc.date) return;
frm.set_query("earning_component", "employee_benefits", function() {
return {
query : "erpnext.hr.doctype.employee_benefit_application.employee_benefit_application.get_earning_components",
filters: {date: frm.doc.date, employee: frm.doc.employee}
};
});
},
payroll_period: function(frm) { payroll_period: function(frm) {
var method, args; var method, args;
if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){ if(frm.doc.employee && frm.doc.date && frm.doc.payroll_period){

View File

@@ -445,6 +445,8 @@ class SalarySlip(TransactionBase):
if not overwrite and component_row.default_amount: if not overwrite and component_row.default_amount:
amount += component_row.default_amount amount += component_row.default_amount
else:
component_row.default_amount = amount
component_row.amount = amount component_row.amount = amount
component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date component_row.deduct_full_tax_on_selected_payroll_date = struct_row.deduct_full_tax_on_selected_payroll_date

View File

@@ -19,12 +19,12 @@ def execute(filters=None):
data = [] data = []
for ss in salary_slips: for ss in salary_slips:
row = [ss.name, ss.employee, ss.employee_name, ss.branch, ss.department, ss.designation, row = [ss.name, ss.employee, ss.employee_name, ss.branch, ss.department, ss.designation,
ss.company, ss.start_date, ss.end_date, ss.leave_withut_pay, ss.payment_days] ss.company, ss.start_date, ss.end_date, ss.leave_without_pay, ss.payment_days]
if not ss.branch == None:columns[3] = columns[3].replace('-1','120') if not ss.branch == None:columns[3] = columns[3].replace('-1','120')
if not ss.department == None: columns[4] = columns[4].replace('-1','120') if not ss.department == None: columns[4] = columns[4].replace('-1','120')
if not ss.designation == None: columns[5] = columns[5].replace('-1','120') if not ss.designation == None: columns[5] = columns[5].replace('-1','120')
if not ss.leave_withut_pay == None: columns[9] = columns[9].replace('-1','130') if not ss.leave_without_pay == None: columns[9] = columns[9].replace('-1','130')
for e in earning_types: for e in earning_types:

View File

@@ -18,6 +18,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty
from frappe.utils.csvutils import getlink from frappe.utils.csvutils import getlink
from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty
from erpnext.utilities.transaction_base import validate_uom_is_integer from erpnext.utilities.transaction_base import validate_uom_is_integer
from six import text_type
class OverProductionError(frappe.ValidationError): pass class OverProductionError(frappe.ValidationError): pass
class StockOverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass
@@ -591,10 +592,10 @@ def make_timesheet(production_order, company):
@frappe.whitelist() @frappe.whitelist()
def add_timesheet_detail(timesheet, args): def add_timesheet_detail(timesheet, args):
if isinstance(timesheet, unicode): if isinstance(timesheet, text_type):
timesheet = frappe.get_doc('Timesheet', timesheet) timesheet = frappe.get_doc('Timesheet', timesheet)
if isinstance(args, unicode): if isinstance(args, text_type):
args = json.loads(args) args = json.loads(args)
timesheet.append('time_logs', args) timesheet.append('time_logs', args)

View File

@@ -533,7 +533,7 @@ erpnext.patches.v11_0.create_department_records_for_each_company
erpnext.patches.v11_0.make_location_from_warehouse erpnext.patches.v11_0.make_location_from_warehouse
erpnext.patches.v11_0.make_asset_finance_book_against_old_entries erpnext.patches.v11_0.make_asset_finance_book_against_old_entries
erpnext.patches.v11_0.check_buying_selling_in_currency_exchange erpnext.patches.v11_0.check_buying_selling_in_currency_exchange
erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019
erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07 erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07
erpnext.patches.v11_0.rename_overproduction_percent_field erpnext.patches.v11_0.rename_overproduction_percent_field
erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom

View File

@@ -17,10 +17,8 @@ def execute():
frappe.reload_doc('stock', 'doctype', 'item_default') frappe.reload_doc('stock', 'doctype', 'item_default')
frappe.reload_doc('stock', 'doctype', 'item') frappe.reload_doc('stock', 'doctype', 'item')
if frappe.db.a_row_exists('Item Default'): return
companies = frappe.get_all("Company") companies = frappe.get_all("Company")
if len(companies) == 1: if len(companies) == 1 and not frappe.get_all("Item Default", limit=1):
try: try:
frappe.db.sql(''' frappe.db.sql('''
INSERT INTO `tabItem Default` INSERT INTO `tabItem Default`
@@ -35,32 +33,64 @@ def execute():
except: except:
pass pass
else: else:
item_details = frappe.get_all("Item", fields=["name", "default_warehouse", "buying_cost_center", item_details = frappe.db.sql(""" SELECT name, default_warehouse,
"expense_account", "selling_cost_center", "income_account"], limit=100) buying_cost_center, expense_account, selling_cost_center, income_account
FROM tabItem
WHERE
name not in (select distinct parent from `tabItem Default`) and ifnull(disabled, 0) = 0"""
, as_dict=1)
for item in item_details: items_default_data = {}
item_defaults = [] for item_data in item_details:
for d in [["default_warehouse", "Warehouse"], ["expense_account", "Account"],
["income_account", "Account"], ["buying_cost_center", "Cost Center"],
["selling_cost_center", "Cost Center"]]:
if item_data.get(d[0]):
company = frappe.get_value(d[1], item_data.get(d[0]), "company", cache=True)
def insert_into_item_defaults(doc_field_name, doc_field_value, company): if item_data.name not in items_default_data:
for d in item_defaults: items_default_data[item_data.name] = {}
if d.get("company") == company:
d[doc_field_name] = doc_field_value
return
item_defaults.append({
"company": company,
doc_field_name: doc_field_value
})
for d in [ company_wise_data = items_default_data[item_data.name]
["default_warehouse", "Warehouse"], ["expense_account", "Account"], ["income_account", "Account"],
["buying_cost_center", "Cost Center"], ["selling_cost_center", "Cost Center"]
]:
if item.get(d[0]):
company = frappe.get_value(d[1], item.get(d[0]), "company", cache=True)
insert_into_item_defaults(d[0], item.get(d[0]), company)
doc = frappe.get_doc("Item", item.name) if company not in company_wise_data:
doc.extend("item_defaults", item_defaults) company_wise_data[company] = {}
for child_doc in doc.item_defaults: default_data = company_wise_data[company]
child_doc.db_insert() default_data[d[0]] = item_data.get(d[0])
to_insert_data = []
# items_default_data data structure will be as follow
# {
# 'item_code 1': {'company 1': {'default_warehouse': 'Test Warehouse 1'}},
# 'item_code 2': {
# 'company 1': {'default_warehouse': 'Test Warehouse 1'},
# 'company 2': {'default_warehouse': 'Test Warehouse 1'}
# }
# }
for item_code, companywise_item_data in items_default_data.items():
for company, item_default_data in companywise_item_data.items():
to_insert_data.append((
frappe.generate_hash("", 10),
item_code,
'Item',
'item_defaults',
company,
item_default_data.get('default_warehouse'),
item_default_data.get('expense_account'),
item_default_data.get('income_account'),
item_default_data.get('buying_cost_center'),
item_default_data.get('selling_cost_center'),
))
if to_insert_data:
frappe.db.sql('''
INSERT INTO `tabItem Default`
(
`name`, `parent`, `parenttype`, `parentfield`, `company`, `default_warehouse`,
`expense_account`, `income_account`, `buying_cost_center`, `selling_cost_center`
)
VALUES {}
'''.format(', '.join(['%s'] * len(to_insert_data))), tuple(to_insert_data))

View File

@@ -103,8 +103,8 @@ class TestTimesheet(unittest.TestCase):
{ {
"billable": 1, "billable": 1,
"activity_type": "_Test Activity Type", "activity_type": "_Test Activity Type",
"from_type": now_datetime(), "from_time": now_datetime(),
"hours": 3, "to_time": now_datetime() + datetime.timedelta(hours=3),
"company": "_Test Company" "company": "_Test Company"
} }
) )
@@ -113,8 +113,8 @@ class TestTimesheet(unittest.TestCase):
{ {
"billable": 1, "billable": 1,
"activity_type": "_Test Activity Type", "activity_type": "_Test Activity Type",
"from_type": now_datetime(), "from_time": now_datetime(),
"hours": 3, "to_time": now_datetime() + datetime.timedelta(hours=3),
"company": "_Test Company" "company": "_Test Company"
} }
) )

View File

@@ -30,23 +30,23 @@ def get_columns():
"options": "Timesheet", "options": "Timesheet",
"width": 150 "width": 150
}, },
{
"label": _("Billable Hours"),
"fieldtype": "Float",
"fieldname": "total_billable_hours",
"width": 50
},
{ {
"label": _("Working Hours"), "label": _("Working Hours"),
"fieldtype": "Float", "fieldtype": "Float",
"fieldname": "total_hours", "fieldname": "total_hours",
"width": 50 "width": 150
},
{
"label": _("Billable Hours"),
"fieldtype": "Float",
"fieldname": "total_billable_hours",
"width": 150
}, },
{ {
"label": _("Billing Amount"), "label": _("Billing Amount"),
"fieldtype": "Currency", "fieldtype": "Currency",
"fieldname": "amount", "fieldname": "amount",
"width": 100 "width": 150
} }
] ]

View File

@@ -1290,7 +1290,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}, },
callback: function(r) { callback: function(r) {
if(!r.exc) { if(!r.exc) {
me.frm.set_value("taxes", r.message); for (let tax of r.message) {
me.frm.add_child("taxes", tax);
}
me.calculate_taxes_and_totals(); me.calculate_taxes_and_totals();
} }
} }

View File

@@ -233,30 +233,32 @@ erpnext.SerialNoBatchSelector = Class.extend({
get_batch_fields: function() { get_batch_fields: function() {
var me = this; var me = this;
return [ return [
{fieldtype:'Section Break', label: __('Batches')}, { fieldtype: 'Section Break', label: __('Batches') },
{fieldname: 'batches', fieldtype: 'Table', {
fieldname: 'batches', fieldtype: 'Table',
fields: [ fields: [
{ {
fieldtype:'Link', 'fieldtype': 'Link',
fieldname:'batch_no', 'read_only': 0,
options: 'Batch', 'fieldname': 'batch_no',
label: __('Select Batch'), 'options': 'Batch',
in_list_view:1, 'label': __('Select Batch'),
get_query: function() { 'in_list_view': 1,
get_query: function () {
return { return {
filters: {item: me.item_code }, filters: { item: me.item_code },
query: 'erpnext.controllers.queries.get_batch_numbers' query: 'erpnext.controllers.queries.get_batch_numbers'
}; };
}, },
onchange: function(e) { change: function () {
let val = this.get_value(); let val = this.get_value();
if(val.length === 0) { if (val.length === 0) {
this.grid_row.on_grid_fields_dict this.grid_row.on_grid_fields_dict
.available_qty.set_value(0); .available_qty.set_value(0);
return; return;
} }
let selected_batches = this.grid.grid_rows.map((row) => { let selected_batches = this.grid.grid_rows.map((row) => {
if(row === this.grid_row) { if (row === this.grid_row) {
return ""; return "";
} }
@@ -264,12 +266,12 @@ erpnext.SerialNoBatchSelector = Class.extend({
return row.on_grid_fields_dict.batch_no.get_value(); return row.on_grid_fields_dict.batch_no.get_value();
} }
}); });
if(selected_batches.includes(val)) { if (selected_batches.includes(val)) {
this.set_value(""); this.set_value("");
frappe.throw(__(`Batch ${val} already selected.`)); frappe.throw(__(`Batch ${val} already selected.`));
return; return;
} }
if(me.warehouse_details.name) { if (me.warehouse_details.name) {
frappe.call({ frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
args: { args: {
@@ -292,31 +294,32 @@ erpnext.SerialNoBatchSelector = Class.extend({
} }
}, },
{ {
fieldtype:'Float', 'fieldtype': 'Float',
read_only:1, 'read_only': 1,
fieldname:'available_qty', 'fieldname': 'available_qty',
label: __('Available'), 'label': __('Available'),
in_list_view:1, 'in_list_view': 1,
default: 0, 'default': 0,
onchange: function() { change: function () {
this.grid_row.on_grid_fields_dict.selected_qty.set_value('0'); this.grid_row.on_grid_fields_dict.selected_qty.set_value('0');
} }
}, },
{ {
fieldtype:'Float', 'fieldtype': 'Float',
fieldname:'selected_qty', 'read_only': 0,
label: __('Qty'), 'fieldname': 'selected_qty',
in_list_view:1, 'label': __('Qty'),
'in_list_view': 1,
'default': 0, 'default': 0,
onchange: function(e) { change: function () {
var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value(); var batch_no = this.grid_row.on_grid_fields_dict.batch_no.get_value();
var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value(); var available_qty = this.grid_row.on_grid_fields_dict.available_qty.get_value();
var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value(); var selected_qty = this.grid_row.on_grid_fields_dict.selected_qty.get_value();
if(batch_no.length === 0 && parseInt(selected_qty)!==0) { if (batch_no.length === 0 && parseInt(selected_qty) !== 0) {
frappe.throw(__("Please select a batch")); frappe.throw(__("Please select a batch"));
} }
if(me.warehouse_details.type === 'Source Warehouse' && if (me.warehouse_details.type === 'Source Warehouse' &&
parseFloat(available_qty) < parseFloat(selected_qty)) { parseFloat(available_qty) < parseFloat(selected_qty)) {
this.set_value('0'); this.set_value('0');
@@ -332,7 +335,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
], ],
in_place_edit: true, in_place_edit: true,
data: this.data, data: this.data,
get_data: function() { get_data: function () {
return this.data; return this.data;
}, },
} }

View File

@@ -0,0 +1,32 @@
frappe.query_reports["DATEV"] = {
"filters": [
{
"fieldname": "company",
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
"default": frappe.defaults.get_user_default("Company") || frappe.defaults.get_global_default("Company"),
"reqd": 1
},
{
"fieldname": "from_date",
"label": __("From Date"),
"default": frappe.datetime.month_start(),
"fieldtype": "Date",
"reqd": 1
},
{
"fieldname": "to_date",
"label": __("To Date"),
"default": frappe.datetime.now_date(),
"fieldtype": "Date",
"reqd": 1
}
],
onload: function(query_report) {
query_report.page.add_inner_button("Download DATEV Export", () => {
const filters = JSON.stringify(query_report.get_values());
window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`);
});
}
};

View File

@@ -0,0 +1,29 @@
{
"add_total_row": 0,
"apply_user_permissions": 0,
"creation": "2019-04-24 08:45:16.650129",
"disabled": 0,
"icon": "octicon octicon-repo-pull",
"color": "#4CB944",
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"module": "Regional",
"name": "DATEV",
"owner": "Administrator",
"ref_doctype": "GL Entry",
"report_name": "DATEV",
"report_type": "Script Report",
"roles": [
{
"role": "Accounts User"
},
{
"role": "Accounts Manager"
},
{
"role": "Auditor"
}
]
}

View File

@@ -0,0 +1,373 @@
# coding: utf-8
"""
Provide a report and downloadable CSV according to the German DATEV format.
- Query report showing only the columns that contain data, formatted nicely for
dispay to the user.
- CSV download functionality `download_datev_csv` that provides a CSV file with
all required columns. Used to import the data into the DATEV Software.
"""
from __future__ import unicode_literals
import json
from six import string_types
import frappe
from frappe import _
import pandas as pd
def execute(filters=None):
"""Entry point for frappe."""
validate_filters(filters)
result = get_gl_entries(filters, as_dict=0)
columns = get_columns()
return columns, result
def validate_filters(filters):
"""Make sure all mandatory filters are present."""
if not filters.get('company'):
frappe.throw(_('{0} is mandatory').format(_('Company')))
if not filters.get('from_date'):
frappe.throw(_('{0} is mandatory').format(_('From Date')))
if not filters.get('to_date'):
frappe.throw(_('{0} is mandatory').format(_('To Date')))
def get_columns():
"""Return the list of columns that will be shown in query report."""
columns = [
{
"label": "Umsatz (ohne Soll/Haben-Kz)",
"fieldname": "Umsatz (ohne Soll/Haben-Kz)",
"fieldtype": "Currency",
},
{
"label": "Soll/Haben-Kennzeichen",
"fieldname": "Soll/Haben-Kennzeichen",
"fieldtype": "Data",
},
{
"label": "Kontonummer",
"fieldname": "Kontonummer",
"fieldtype": "Data",
},
{
"label": "Gegenkonto (ohne BU-Schlüssel)",
"fieldname": "Gegenkonto (ohne BU-Schlüssel)",
"fieldtype": "Data",
},
{
"label": "Belegdatum",
"fieldname": "Belegdatum",
"fieldtype": "Date",
},
{
"label": "Buchungstext",
"fieldname": "Buchungstext",
"fieldtype": "Text",
},
{
"label": "Beleginfo - Art 1",
"fieldname": "Beleginfo - Art 1",
"fieldtype": "Data",
},
{
"label": "Beleginfo - Inhalt 1",
"fieldname": "Beleginfo - Inhalt 1",
"fieldtype": "Data",
},
{
"label": "Beleginfo - Art 2",
"fieldname": "Beleginfo - Art 2",
"fieldtype": "Data",
},
{
"label": "Beleginfo - Inhalt 2",
"fieldname": "Beleginfo - Inhalt 2",
"fieldtype": "Data",
}
]
return columns
def get_gl_entries(filters, as_dict):
"""
Get a list of accounting entries.
Select GL Entries joined with Account and Party Account in order to get the
account numbers. Returns a list of accounting entries.
Arguments:
filters -- dict of filters to be passed to the sql query
as_dict -- return as list of dicts [0,1]
"""
gl_entries = frappe.db.sql("""
select
/* either debit or credit amount; always positive */
case gl.debit when 0 then gl.credit else gl.debit end as 'Umsatz (ohne Soll/Haben-Kz)',
/* 'H' when credit, 'S' when debit */
case gl.debit when 0 then 'H' else 'S' end as 'Soll/Haben-Kennzeichen',
/* account number or, if empty, party account number */
coalesce(acc.account_number, acc_pa.account_number) as 'Kontonummer',
/* against number or, if empty, party against number */
coalesce(acc_against.account_number, acc_against_pa.account_number) as 'Gegenkonto (ohne BU-Schlüssel)',
gl.posting_date as 'Belegdatum',
gl.remarks as 'Buchungstext',
gl.voucher_type as 'Beleginfo - Art 1',
gl.voucher_no as 'Beleginfo - Inhalt 1',
gl.against_voucher_type as 'Beleginfo - Art 2',
gl.against_voucher as 'Beleginfo - Inhalt 2'
from `tabGL Entry` gl
/* Statistisches Konto (Debitoren/Kreditoren) */
left join `tabParty Account` pa
on gl.against = pa.parent
and gl.company = pa.company
/* Kontonummer */
left join `tabAccount` acc
on gl.account = acc.name
/* Gegenkonto-Nummer */
left join `tabAccount` acc_against
on gl.against = acc_against.name
/* Statistische Kontonummer */
left join `tabAccount` acc_pa
on pa.account = acc_pa.name
/* Statistische Gegenkonto-Nummer */
left join `tabAccount` acc_against_pa
on pa.account = acc_against_pa.name
where gl.company = %(company)s
and DATE(gl.posting_date) >= %(from_date)s
and DATE(gl.posting_date) <= %(to_date)s
order by 'Belegdatum', gl.voucher_no""", filters, as_dict=as_dict)
return gl_entries
def get_datev_csv(data):
"""
Fill in missing columns and return a CSV in DATEV Format.
Arguments:
data -- array of dictionaries
"""
columns = [
# All possible columns must tbe listed here, because DATEV requires them to
# be present in the CSV.
# ---
# Umsatz
"Umsatz (ohne Soll/Haben-Kz)",
"Soll/Haben-Kennzeichen",
"WKZ Umsatz",
"Kurs",
"Basis-Umsatz",
"WKZ Basis-Umsatz",
# Konto/Gegenkonto
"Kontonummer",
"Gegenkonto (ohne BU-Schlüssel)",
"BU-Schlüssel",
# Datum
"Belegdatum",
# Belegfelder
"Belegfeld 1",
"Belegfeld 2",
# Weitere Felder
"Skonto",
"Buchungstext",
# OPOS-Informationen
"Postensperre",
"Diverse Adressnummer",
"Geschäftspartnerbank",
"Sachverhalt",
"Zinssperre",
# Digitaler Beleg
"Beleglink",
# Beleginfo
"Beleginfo - Art 1",
"Beleginfo - Inhalt 1",
"Beleginfo - Art 2",
"Beleginfo - Inhalt 2",
"Beleginfo - Art 3",
"Beleginfo - Inhalt 3",
"Beleginfo - Art 4",
"Beleginfo - Inhalt 4",
"Beleginfo - Art 5",
"Beleginfo - Inhalt 5",
"Beleginfo - Art 6",
"Beleginfo - Inhalt 6",
"Beleginfo - Art 7",
"Beleginfo - Inhalt 7",
"Beleginfo - Art 8",
"Beleginfo - Inhalt 8",
# Kostenrechnung
"Kost 1 - Kostenstelle",
"Kost 2 - Kostenstelle",
"Kost-Menge",
# Steuerrechnung
"EU-Land u. UStID",
"EU-Steuersatz",
"Abw. Versteuerungsart",
# L+L Sachverhalt
"Sachverhalt L+L",
"Funktionsergänzung L+L",
# Funktion Steuerschlüssel 49
"BU 49 Hauptfunktionstyp",
"BU 49 Hauptfunktionsnummer",
"BU 49 Funktionsergänzung",
# Zusatzinformationen
"Zusatzinformation - Art 1",
"Zusatzinformation - Inhalt 1",
"Zusatzinformation - Art 2",
"Zusatzinformation - Inhalt 2",
"Zusatzinformation - Art 3",
"Zusatzinformation - Inhalt 3",
"Zusatzinformation - Art 4",
"Zusatzinformation - Inhalt 4",
"Zusatzinformation - Art 5",
"Zusatzinformation - Inhalt 5",
"Zusatzinformation - Art 6",
"Zusatzinformation - Inhalt 6",
"Zusatzinformation - Art 7",
"Zusatzinformation - Inhalt 7",
"Zusatzinformation - Art 8",
"Zusatzinformation - Inhalt 8",
"Zusatzinformation - Art 9",
"Zusatzinformation - Inhalt 9",
"Zusatzinformation - Art 10",
"Zusatzinformation - Inhalt 10",
"Zusatzinformation - Art 11",
"Zusatzinformation - Inhalt 11",
"Zusatzinformation - Art 12",
"Zusatzinformation - Inhalt 12",
"Zusatzinformation - Art 13",
"Zusatzinformation - Inhalt 13",
"Zusatzinformation - Art 14",
"Zusatzinformation - Inhalt 14",
"Zusatzinformation - Art 15",
"Zusatzinformation - Inhalt 15",
"Zusatzinformation - Art 16",
"Zusatzinformation - Inhalt 16",
"Zusatzinformation - Art 17",
"Zusatzinformation - Inhalt 17",
"Zusatzinformation - Art 18",
"Zusatzinformation - Inhalt 18",
"Zusatzinformation - Art 19",
"Zusatzinformation - Inhalt 19",
"Zusatzinformation - Art 20",
"Zusatzinformation - Inhalt 20",
# Mengenfelder LuF
"Stück",
"Gewicht",
# Forderungsart
"Zahlweise",
"Forderungsart",
"Veranlagungsjahr",
"Zugeordnete Fälligkeit",
# Weitere Felder
"Skontotyp",
# Anzahlungen
"Auftragsnummer",
"Buchungstyp",
"USt-Schlüssel (Anzahlungen)",
"EU-Land (Anzahlungen)",
"Sachverhalt L+L (Anzahlungen)",
"EU-Steuersatz (Anzahlungen)",
"Erlöskonto (Anzahlungen)",
# Stapelinformationen
"Herkunft-Kz",
# Technische Identifikation
"Buchungs GUID",
# Kostenrechnung
"Kost-Datum",
# OPOS-Informationen
"SEPA-Mandatsreferenz",
"Skontosperre",
# Gesellschafter und Sonderbilanzsachverhalt
"Gesellschaftername",
"Beteiligtennummer",
"Identifikationsnummer",
"Zeichnernummer",
# OPOS-Informationen
"Postensperre bis",
# Gesellschafter und Sonderbilanzsachverhalt
"Bezeichnung SoBil-Sachverhalt",
"Kennzeichen SoBil-Buchung",
# Stapelinformationen
"Festschreibung",
# Datum
"Leistungsdatum",
"Datum Zuord. Steuerperiode",
# OPOS-Informationen
"Fälligkeit",
# Konto/Gegenkonto
"Generalumkehr (GU)",
# Steuersatz für Steuerschlüssel
"Steuersatz",
"Land"
]
empty_df = pd.DataFrame(columns=columns)
data_df = pd.DataFrame.from_records(data)
result = empty_df.append(data_df)
result["Belegdatum"] = pd.to_datetime(result["Belegdatum"])
return result.to_csv(
sep=b';',
# European decimal seperator
decimal=',',
# Windows "ANSI" encoding
encoding='latin_1',
# format date as DDMM
date_format='%d%m',
# Windows line terminator
line_terminator=b'\r\n',
# Do not number rows
index=False,
# Use all columns defined above
columns=columns
)
@frappe.whitelist()
def download_datev_csv(filters=None):
"""
Provide accounting entries for download in DATEV format.
Validate the filters, get the data, produce the CSV file and provide it for
download. Can be called like this:
GET /api/method/erpnext.regional.report.datev.datev.download_datev_csv
Arguments / Params:
filters -- dict of filters to be passed to the sql query
"""
if isinstance(filters, string_types):
filters = json.loads(filters)
validate_filters(filters)
data = get_gl_entries(filters, as_dict=1)
filename = 'DATEV_Buchungsstapel_{}-{}_bis_{}'.format(
filters.get('company'),
filters.get('from_date'),
filters.get('to_date')
)
frappe.response['result'] = get_datev_csv(data)
frappe.response['doctype'] = filename
frappe.response['type'] = 'csv'

View File

@@ -124,7 +124,7 @@ def get_result_as_list(data, filters):
if account_number[0] is not None: if account_number[0] is not None:
CompteNum = account_number[0] CompteNum = account_number[0]
else: else:
frappe.throw(_("Account number for account {0} is not available.<br> Please setup your Chart of Accounts correctly.").format(account.name)) frappe.throw(_("Account number for account {0} is not available.<br> Please setup your Chart of Accounts correctly.").format(d.get("account")))
if d.get("party_type") == "Customer": if d.get("party_type") == "Customer":
CompAuxNum = d.get("cusName") CompAuxNum = d.get("cusName")

View File

@@ -29,7 +29,20 @@ frappe.query_reports["HSN-wise-summary of outward supplies"] = {
"placeholder":"Company GSTIN", "placeholder":"Company GSTIN",
"options": [""], "options": [""],
"width": "80" "width": "80"
} },
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
"width": "80"
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
"width": "80"
},
], ],
onload: (report) => { onload: (report) => {
fetch_gstins(report); fetch_gstins(report);

View File

@@ -88,7 +88,9 @@ def get_conditions(filters):
for opts in (("company", " and company=%(company)s"), for opts in (("company", " and company=%(company)s"),
("gst_hsn_code", " and gst_hsn_code=%(gst_hsn_code)s"), ("gst_hsn_code", " and gst_hsn_code=%(gst_hsn_code)s"),
("company_gstin", " and company_gstin=%(company_gstin)s")): ("company_gstin", " and company_gstin=%(company_gstin)s"),
("from_date", " and posting_date >= %(from_date)s"),
("to_date", "and posting_date <= %(to_date)s")):
if filters.get(opts[0]): if filters.get(opts[0]):
conditions += opts[1] conditions += opts[1]

View File

@@ -90,22 +90,29 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
if (this.frm.doc.docstatus===0) { if (this.frm.doc.docstatus===0) {
this.frm.add_custom_button(__('Opportunity'), this.frm.add_custom_button(__('Opportunity'),
function() { function() {
var setters = {};
if(me.frm.doc.quotation_to == "Customer" && me.frm.doc.party_name) {
setters.customer = me.frm.doc.party_name || undefined;
} else if (me.frm.doc.quotation_to == "Lead" && me.frm.doc.party_name) {
setters.lead = me.frm.doc.party_name || undefined;
}
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation", method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation",
source_doctype: "Opportunity", source_doctype: "Opportunity",
target: me.frm, target: me.frm,
setters: setters, setters: [
{
label: "Party",
fieldname: "party_name",
fieldtype: "Link",
options: me.frm.doc.quotation_to,
default: me.frm.doc.party_name || undefined
},
{
label: "Opportunity Type",
fieldname: "opportunity_type",
fieldtype: "Link",
options: "Opportunity Type",
default: me.frm.doc.order_type || undefined
}
],
get_query_filters: { get_query_filters: {
status: ["not in", ["Lost", "Closed"]], status: ["not in", ["Lost", "Closed"]],
company: me.frm.doc.company, company: me.frm.doc.company
// cannot set opportunity_type as setter, as the fieldname is order_type
opportunity_type: me.frm.doc.order_type,
} }
}) })
}, __("Get items from"), "btn-default"); }, __("Get items from"), "btn-default");

View File

@@ -161,6 +161,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
"Sales Team": { "Sales Team": {
"doctype": "Sales Team", "doctype": "Sales Team",
"add_if_empty": True "add_if_empty": True
},
"Payment Schedule": {
"doctype": "Payment Schedule",
"add_if_empty": True
} }
}, target_doc, set_missing_values, ignore_permissions=ignore_permissions) }, target_doc, set_missing_values, ignore_permissions=ignore_permissions)

View File

@@ -219,13 +219,19 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
method: "erpnext.selling.doctype.quotation.quotation.make_sales_order", method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
source_doctype: "Quotation", source_doctype: "Quotation",
target: me.frm, target: me.frm,
setters: { setters: [
customer: me.frm.doc.customer || undefined {
}, label: "Customer",
fieldname: "party_name",
fieldtype: "Link",
options: "Customer",
default: me.frm.doc.customer || undefined
}
],
get_query_filters: { get_query_filters: {
company: me.frm.doc.company, company: me.frm.doc.company,
docstatus: 1, docstatus: 1,
status: ["!=", "Lost"], status: ["!=", "Lost"]
} }
}) })
}, __("Get items from")); }, __("Get items from"));

View File

@@ -574,8 +574,8 @@ def make_delivery_note(source_name, target_doc=None):
if item: if item:
target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \ target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") \
or item.get("selling_cost_center") \ or item.get("buying_cost_center") \
or item_group.get("selling_cost_center") or item_group.get("buying_cost_center")
target_doc = get_mapped_doc("Sales Order", source_name, { target_doc = get_mapped_doc("Sales Order", source_name, {
"Sales Order": { "Sales Order": {

View File

@@ -737,6 +737,17 @@ class POSCart {
const customer = this.frm.doc.customer; const customer = this.frm.doc.customer;
this.customer_field.set_value(customer); this.customer_field.set_value(customer);
if (this.numpad) {
const disable_btns = this.disable_numpad_control()
const enable_btns = [__('Rate'), __('Disc')]
if (disable_btns) {
enable_btns.filter(btn => !disable_btns.includes(btn))
}
this.numpad.enable_buttons(enable_btns);
}
} }
get_grand_total() { get_grand_total() {
@@ -1507,6 +1518,16 @@ class NumberPad {
} }
} }
enable_buttons(btns) {
btns.forEach((btn) => {
const $btn = this.get_btn(btn);
$btn.prop("disabled", false)
$btn.hover(() => {
$btn.css('cursor','pointer');
})
})
}
set_class() { set_class() {
for (const btn in this.add_class) { for (const btn in this.add_class) {
const class_name = this.add_class[btn]; const class_name = this.add_class[btn];

View File

@@ -20,8 +20,7 @@ class DeliveryTrip(Document):
# Google Maps returns distances in meters by default # Google Maps returns distances in meters by default
self.default_distance_uom = frappe.db.get_single_value("Global Defaults", "default_distance_unit") or "Meter" self.default_distance_uom = frappe.db.get_single_value("Global Defaults", "default_distance_unit") or "Meter"
self.uom_conversion_factor = frappe.db.get_value("UOM Conversion Factor", self.uom_conversion_factor = frappe.db.get_value("UOM Conversion Factor",
{"from_uom": "Meter", "to_uom": self.default_distance_uom}, {"from_uom": "Meter", "to_uom": self.default_distance_uom}, "value")
"value")
def validate(self): def validate(self):
self.validate_stop_addresses() self.validate_stop_addresses()
@@ -139,7 +138,7 @@ class DeliveryTrip(Document):
# Include last leg in the final distance calculation # Include last leg in the final distance calculation
self.uom = self.default_distance_uom self.uom = self.default_distance_uom
total_distance = sum([leg.get("distance", {}).get("value", 0.0) total_distance = sum([leg.get("distance", {}).get("value", 0.0)
for leg in directions.get("legs")]) # in meters for leg in directions.get("legs")]) # in meters
self.total_distance = total_distance * self.uom_conversion_factor self.total_distance = total_distance * self.uom_conversion_factor
else: else:
idx += len(route) - 1 idx += len(route) - 1
@@ -358,8 +357,12 @@ def notify_customers(delivery_trip):
email_recipients = [] email_recipients = []
for stop in delivery_trip.delivery_stops: for stop in delivery_trip.delivery_stops:
contact_info = frappe.db.get_value("Contact", stop.contact, contact_info = frappe.db.get_value("Contact", stop.contact, ["first_name", "last_name", "email_id"], as_dict=1)
["first_name", "last_name", "email_id", "gender"], as_dict=1)
context.update({"items": []})
if stop.delivery_note:
items = frappe.get_all("Delivery Note Item", filters={"parent": stop.delivery_note, "docstatus": 1}, fields=["*"])
context.update({"items": items})
if contact_info and contact_info.email_id: if contact_info and contact_info.email_id:
context.update(stop.as_dict()) context.update(stop.as_dict())
@@ -369,9 +372,9 @@ def notify_customers(delivery_trip):
dispatch_template = frappe.get_doc("Email Template", dispatch_template_name) dispatch_template = frappe.get_doc("Email Template", dispatch_template_name)
frappe.sendmail(recipients=contact_info.email_id, frappe.sendmail(recipients=contact_info.email_id,
subject=dispatch_template.subject, subject=dispatch_template.subject,
message=frappe.render_template(dispatch_template.response, context), message=frappe.render_template(dispatch_template.response, context),
attachments=get_attachments(stop)) attachments=get_attachments(stop))
stop.db_set("email_sent_to", contact_info.email_id) stop.db_set("email_sent_to", contact_info.email_id)
email_recipients.append(contact_info.email_id) email_recipients.append(contact_info.email_id)
@@ -388,9 +391,7 @@ def get_attachments(delivery_stop):
return [] return []
dispatch_attachment = frappe.db.get_single_value("Delivery Settings", "dispatch_attachment") dispatch_attachment = frappe.db.get_single_value("Delivery Settings", "dispatch_attachment")
attachments = frappe.attach_print("Delivery Note", attachments = frappe.attach_print("Delivery Note", delivery_stop.delivery_note,
delivery_stop.delivery_note, file_name="Delivery Note", print_format=dispatch_attachment)
file_name="Delivery Note",
print_format=dispatch_attachment)
return [attachments] return [attachments]

View File

@@ -67,8 +67,6 @@ class Item(WebsiteGenerator):
from frappe.model.naming import set_name_by_naming_series from frappe.model.naming import set_name_by_naming_series
set_name_by_naming_series(self) set_name_by_naming_series(self)
self.item_code = self.name self.item_code = self.name
elif not self.item_code:
msgprint(_("Item Code is mandatory because Item is not automatically numbered"), raise_exception=1)
self.item_code = strip(self.item_code) self.item_code = strip(self.item_code)
self.name = self.item_code self.name = self.item_code

View File

@@ -186,6 +186,7 @@ frappe.ui.form.on('Material Request', {
var values = d.get_values(); var values = d.get_values();
if(!values) return; if(!values) return;
values["company"] = frm.doc.company; values["company"] = frm.doc.company;
if(!frm.doc.company) frappe.throw(__("Company field is required"));
frappe.call({ frappe.call({
method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items", method: "erpnext.manufacturing.doctype.bom.bom.get_bom_items",
args: values, args: values,

View File

@@ -8,7 +8,7 @@ from frappe.utils import flt, cint, nowdate
from frappe import throw, _ from frappe import throw, _
import frappe.defaults import frappe.defaults
from frappe.utils import getdate from frappe.utils import getdate, cint
from erpnext.controllers.buying_controller import BuyingController from erpnext.controllers.buying_controller import BuyingController
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from frappe.desk.notifications import clear_doctype_notifications from frappe.desk.notifications import clear_doctype_notifications
@@ -128,7 +128,7 @@ class PurchaseReceipt(BuyingController):
self.company, self.base_grand_total) self.company, self.base_grand_total)
self.update_prevdoc_status() self.update_prevdoc_status()
if self.per_billed < 100: if cint(self.per_billed) < 100:
self.update_billing_status() self.update_billing_status()
else: else:
self.status = "Completed" self.status = "Completed"

View File

@@ -562,7 +562,7 @@ class TestStockEntry(unittest.TestCase):
for d in stock_entry.get("items"): for d in stock_entry.get("items"):
if d.item_code != "_Test FG Item 2": if d.item_code != "_Test FG Item 2":
rm_cost += flt(d.amount) rm_cost += flt(d.amount)
fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items")))[0].amount
self.assertEqual(fg_cost, self.assertEqual(fg_cost,
flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2)) flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2))

View File

@@ -10,8 +10,23 @@ frappe.query_reports["Total Stock Summary"] = {
"fieldtype": "Select", "fieldtype": "Select",
"width": "80", "width": "80",
"reqd": 1, "reqd": 1,
"options": ["","Warehouse", "Company"], "options": ["", "Warehouse", "Company"],
"default": "Warehouse" "change": function() {
let group_by = frappe.query_report.get_filter_value("group_by")
let company_filter = frappe.query_report.get_filter("company")
if (group_by == "Company") {
company_filter.df.reqd = 0;
company_filter.df.hidden = 1;
frappe.query_report.set_filter_value("company", "");
company_filter.refresh();
}
else {
company_filter.df.reqd = 1;
company_filter.df.hidden = 0;
company_filter.refresh();
frappe.query_report.refresh();
}
}
}, },
{ {
"fieldname": "company", "fieldname": "company",

View File

@@ -5,6 +5,7 @@ from jinja2 import utils
from html2text import html2text from html2text import html2text
from frappe.utils import sanitize_html from frappe.utils import sanitize_html
from frappe.utils.global_search import search from frappe.utils.global_search import search
from six import text_type
def get_context(context): def get_context(context):
context.no_cache = 1 context.no_cache = 1
@@ -73,7 +74,7 @@ def prepare_api_results(api, topics_data):
for topic in topics_data: for topic in topics_data:
route = api.base_url + '/' + (api.post_route + '/' if api.post_route else "") route = api.base_url + '/' + (api.post_route + '/' if api.post_route else "")
for key in api.post_route_key_list.split(','): for key in api.post_route_key_list.split(','):
route += unicode(topic[key]) route += text_type(topic[key])
results.append(frappe._dict({ results.append(frappe._dict({
'title': topic[api.post_title_key], 'title': topic[api.post_title_key],

12
travis/run-tests.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
set -e
if [[ $TEST_TYPE == 'Server Side Test' ]]; then
bench run-tests --app erpnext --coverage
elif [[ $TEST_TYPE == 'Patch Test' ]]; then
wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz
bench --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz --mariadb-root-password travis
bench migrate
fi