Merge branch 'develop' into asset-delete-fix

This commit is contained in:
Saqib
2020-06-29 10:54:01 +05:30
committed by GitHub
57 changed files with 766 additions and 245 deletions

View File

@@ -188,14 +188,15 @@ frappe.ui.form.on('Invoice Discounting', {
}, },
show_general_ledger: (frm) => { show_general_ledger: (frm) => {
if(frm.doc.docstatus===1) { if(frm.doc.docstatus > 0) {
cur_frm.add_custom_button(__('Accounting Ledger'), function() { cur_frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
from_date: frm.doc.posting_date, from_date: frm.doc.posting_date,
to_date: frm.doc.posting_date, to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company, company: frm.doc.company,
group_by: "Group by Voucher (Consolidated)" group_by: "Group by Voucher (Consolidated)",
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));

View File

@@ -13,15 +13,16 @@ frappe.ui.form.on("Journal Entry", {
refresh: function(frm) { refresh: function(frm) {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
if(frm.doc.docstatus==1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() { frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,
"from_date": frm.doc.posting_date, "from_date": frm.doc.posting_date,
"to_date": frm.doc.posting_date, "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
"company": frm.doc.company, "company": frm.doc.company,
"finance_book": frm.doc.finance_book, "finance_book": frm.doc.finance_book,
"group_by_voucher": 0 "group_by": '',
"show_cancelled_entries": frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __('View')); }, __('View'));

View File

@@ -68,6 +68,9 @@ class OpeningInvoiceCreationTool(Document):
if not self.company: if not self.company:
frappe.throw(_("Please select the Company")) frappe.throw(_("Please select the Company"))
company_details = frappe.get_cached_value('Company', self.company,
["default_currency", "default_letter_head"], as_dict=1) or {}
for row in self.invoices: for row in self.invoices:
if not row.qty: if not row.qty:
row.qty = 1.0 row.qty = 1.0
@@ -99,6 +102,12 @@ class OpeningInvoiceCreationTool(Document):
if not args: if not args:
continue continue
if company_details:
args.update({
"currency": company_details.get("default_currency"),
"letter_head": company_details.get("default_letter_head")
})
doc = frappe.get_doc(args).insert() doc = frappe.get_doc(args).insert()
doc.submit() doc.submit()
names.append(doc.name) names.append(doc.name)
@@ -172,8 +181,7 @@ class OpeningInvoiceCreationTool(Document):
"due_date": row.due_date, "due_date": row.due_date,
"posting_date": row.posting_date, "posting_date": row.posting_date,
frappe.scrub(party_type): row.party, frappe.scrub(party_type): row.party,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
"currency": frappe.get_cached_value('Company', self.company, "default_currency")
}) })
accounting_dimension = get_accounting_dimensions() accounting_dimension = get_accounting_dimensions()

View File

@@ -172,8 +172,8 @@ frappe.ui.form.on('Payment Entry', {
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
frm.toggle_display("base_received_amount", ( frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency frm.doc.paid_to_account_currency != company_currency
&& frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency && frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency
&& frm.doc.base_paid_amount != frm.doc.base_received_amount && frm.doc.base_paid_amount != frm.doc.base_received_amount
)); ));
@@ -234,14 +234,15 @@ frappe.ui.form.on('Payment Entry', {
}, },
show_general_ledger: function(frm) { show_general_ledger: function(frm) {
if(frm.doc.docstatus==1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Ledger'), function() { frm.add_custom_button(__('Ledger'), function() {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,
"from_date": frm.doc.posting_date, "from_date": frm.doc.posting_date,
"to_date": frm.doc.posting_date, "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
"company": frm.doc.company, "company": frm.doc.company,
group_by: "" "group_by": "",
"show_cancelled_entries": frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, "fa fa-table"); }, "fa fa-table");

View File

@@ -25,9 +25,10 @@ frappe.ui.form.on('Period Closing Voucher', {
frappe.route_options = { frappe.route_options = {
"voucher_no": frm.doc.name, "voucher_no": frm.doc.name,
"from_date": frm.doc.posting_date, "from_date": frm.doc.posting_date,
"to_date": frm.doc.posting_date, "to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
"company": frm.doc.company, "company": frm.doc.company,
group_by_voucher: 0 "group_by": "",
"show_cancelled_entries": frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, "fa fa-table"); }, "fa fa-table");

View File

@@ -393,6 +393,8 @@ class Asset(AccountsController):
row.expected_value_after_useful_life = asset_value_after_full_schedule row.expected_value_after_useful_life = asset_value_after_full_schedule
def validate_cancellation(self): def validate_cancellation(self):
if self.status in ("In Maintenance", "Out of Order"):
frappe.throw(_("There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset."))
if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"):
frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status))

View File

@@ -33,7 +33,7 @@ def validate_filters(filters):
frappe.throw(_("{0} is mandatory").format(f)) frappe.throw(_("{0} is mandatory").format(f))
if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")): if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")):
frappe.throw(_("Fiscal Year: {0} does not exists").format(filters.get("fiscal_year"))) frappe.throw(_("Fiscal Year {0} Does Not Exist").format(filters.get("fiscal_year")))
if filters.get("based_on") == filters.get("group_by"): if filters.get("based_on") == filters.get("group_by"):
frappe.throw(_("'Based On' and 'Group By' can not be same")) frappe.throw(_("'Based On' and 'Group By' can not be same"))

View File

@@ -55,14 +55,15 @@ frappe.ui.form.on("Fees", {
frm.set_df_property('posting_date', 'read_only', 1); frm.set_df_property('posting_date', 'read_only', 1);
frm.set_df_property('posting_time', 'read_only', 1); frm.set_df_property('posting_time', 'read_only', 1);
} }
if(frm.doc.docstatus===1) { if(frm.doc.docstatus > 0) {
frm.add_custom_button(__('Accounting Ledger'), function() { frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
from_date: frm.doc.posting_date, from_date: frm.doc.posting_date,
to_date: frm.doc.posting_date, to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
company: frm.doc.company, company: frm.doc.company,
group_by_voucher: false group_by: '',
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));

View File

@@ -0,0 +1,9 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('TaxJar Settings', {
is_sandbox: (frm) => {
frm.toggle_reqd("api_key", !frm.doc.is_sandbox);
frm.toggle_reqd("sandbox_api_key", frm.doc.is_sandbox);
}
});

View File

@@ -0,0 +1,110 @@
{
"actions": [],
"creation": "2017-06-15 08:21:24.624315",
"doctype": "DocType",
"document_type": "Setup",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"is_sandbox",
"taxjar_calculate_tax",
"taxjar_create_transactions",
"credentials",
"api_key",
"cb_keys",
"sandbox_api_key",
"configuration",
"tax_account_head",
"configuration_cb",
"shipping_account_head"
],
"fields": [
{
"fieldname": "credentials",
"fieldtype": "Section Break",
"label": "Credentials"
},
{
"fieldname": "api_key",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Live API Key",
"reqd": 1
},
{
"fieldname": "configuration",
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"fieldname": "tax_account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Tax Account Head",
"options": "Account",
"reqd": 1
},
{
"fieldname": "shipping_account_head",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Shipping Account Head",
"options": "Account",
"reqd": 1
},
{
"default": "0",
"fieldname": "is_sandbox",
"fieldtype": "Check",
"label": "Sandbox Mode"
},
{
"fieldname": "sandbox_api_key",
"fieldtype": "Password",
"label": "Sandbox API Key"
},
{
"fieldname": "configuration_cb",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "taxjar_create_transactions",
"fieldtype": "Check",
"label": "Create TaxJar Transaction"
},
{
"default": "0",
"fieldname": "taxjar_calculate_tax",
"fieldtype": "Check",
"label": "Enable Tax Calculation"
},
{
"fieldname": "cb_keys",
"fieldtype": "Column Break"
}
],
"issingle": 1,
"links": [],
"modified": "2020-04-30 04:38:03.311089",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "TaxJar Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
# import frappe
from frappe.model.document import Document
class TaxJarSettings(Document):
pass

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestTaxJarSettings(unittest.TestCase):
pass

View File

@@ -0,0 +1,251 @@
import traceback
import pycountry
import taxjar
import frappe
from erpnext import get_default_company
from frappe import _
from frappe.contacts.doctype.address.address import get_company_address
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
"FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
"SE", "SI", "SK", "US"]
def get_client():
taxjar_settings = frappe.get_single("TaxJar Settings")
if not taxjar_settings.is_sandbox:
api_key = taxjar_settings.api_key and taxjar_settings.get_password("api_key")
api_url = taxjar.DEFAULT_API_URL
else:
api_key = taxjar_settings.sandbox_api_key and taxjar_settings.get_password("sandbox_api_key")
api_url = taxjar.SANDBOX_API_URL
if api_key and api_url:
return taxjar.Client(api_key=api_key, api_url=api_url)
def create_transaction(doc, method):
"""Create an order transaction in TaxJar"""
if not TAXJAR_CREATE_TRANSACTIONS:
return
client = get_client()
if not client:
return
sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD])
if not sales_tax:
return
tax_dict = get_tax_data(doc)
if not tax_dict:
return
tax_dict['transaction_id'] = doc.name
tax_dict['transaction_date'] = frappe.utils.today()
tax_dict['sales_tax'] = sales_tax
tax_dict['amount'] = doc.total + tax_dict['shipping']
try:
client.create_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
except Exception as ex:
print(traceback.format_exc(ex))
def delete_transaction(doc, method):
"""Delete an existing TaxJar order transaction"""
if not TAXJAR_CREATE_TRANSACTIONS:
return
client = get_client()
if not client:
return
client.delete_order(doc.name)
def get_tax_data(doc):
from_address = get_company_address_details(doc)
from_shipping_state = from_address.get("state")
from_country_code = frappe.db.get_value("Country", from_address.country, "code")
from_country_code = from_country_code.upper()
to_address = get_shipping_address_details(doc)
to_shipping_state = to_address.get("state")
to_country_code = frappe.db.get_value("Country", to_address.country, "code")
to_country_code = to_country_code.upper()
if to_country_code not in SUPPORTED_COUNTRY_CODES:
return
shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD])
if to_shipping_state is not None:
to_shipping_state = get_iso_3166_2_state_code(to_address)
tax_dict = {
'from_country': from_country_code,
'from_zip': from_address.pincode,
'from_state': from_shipping_state,
'from_city': from_address.city,
'from_street': from_address.address_line1,
'to_country': to_country_code,
'to_zip': to_address.pincode,
'to_city': to_address.city,
'to_street': to_address.address_line1,
'to_state': to_shipping_state,
'shipping': shipping,
'amount': doc.net_total
}
return tax_dict
def set_sales_tax(doc, method):
if not TAXJAR_CALCULATE_TAX:
return
if not doc.items:
return
# if the party is exempt from sales tax, then set all tax account heads to zero
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
if sales_tax_exempted:
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
tax.tax_amount = 0
break
doc.run_method("calculate_taxes_and_totals")
return
tax_dict = get_tax_data(doc)
if not tax_dict:
# Remove existing tax rows if address is changed from a taxable state/country
setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
return
tax_data = validate_tax_request(tax_dict)
if tax_data is not None:
if not tax_data.amount_to_collect:
setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
elif tax_data.amount_to_collect > 0:
# Loop through tax rows for existing Sales Tax entry
# If none are found, add a row with the tax amount
for tax in doc.taxes:
if tax.account_head == TAX_ACCOUNT_HEAD:
tax.tax_amount = tax_data.amount_to_collect
doc.run_method("calculate_taxes_and_totals")
break
else:
doc.append("taxes", {
"charge_type": "Actual",
"description": "Sales Tax",
"account_head": TAX_ACCOUNT_HEAD,
"tax_amount": tax_data.amount_to_collect
})
doc.run_method("calculate_taxes_and_totals")
def validate_tax_request(tax_dict):
"""Return the sales tax that should be collected for a given order."""
client = get_client()
if not client:
return
try:
tax_data = client.tax_for_order(tax_dict)
except taxjar.exceptions.TaxJarResponseError as err:
frappe.throw(_(sanitize_error_response(err)))
else:
return tax_data
def get_company_address_details(doc):
"""Return default company address details"""
company_address = get_company_address(get_default_company()).company_address
if not company_address:
frappe.throw(_("Please set a default company address"))
company_address = frappe.get_doc("Address", company_address)
return company_address
def get_shipping_address_details(doc):
"""Return customer shipping address details"""
if doc.shipping_address_name:
shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
else:
shipping_address = get_company_address_details(doc)
return shipping_address
def get_iso_3166_2_state_code(address):
country_code = frappe.db.get_value("Country", address.get("country"), "code")
error_message = _("""{0} is not a valid state! Check for typos or enter the ISO code for your state.""").format(address.get("state"))
state = address.get("state").upper().strip()
# The max length for ISO state codes is 3, excluding the country code
if len(state) <= 3:
# PyCountry returns state code as {country_code}-{state-code} (e.g. US-FL)
address_state = (country_code + "-" + state).upper()
states = pycountry.subdivisions.get(country_code=country_code.upper())
states = [pystate.code for pystate in states]
if address_state in states:
return state
frappe.throw(_(error_message))
else:
try:
lookup_state = pycountry.subdivisions.lookup(state)
except LookupError:
frappe.throw(_(error_message))
else:
return lookup_state.code.split('-')[1]
def sanitize_error_response(response):
response = response.full_response.get("detail")
response = response.replace("_", " ")
sanitized_responses = {
"to zip": "Zipcode",
"to city": "City",
"to state": "State",
"to country": "Country"
}
for k, v in sanitized_responses.items():
response = response.replace(k, v)
return response

View File

@@ -234,8 +234,15 @@ doc_events = {
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products" "validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
}, },
"Sales Invoice": { "Sales Invoice": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "on_submit": [
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit",
"erpnext.erpnext_integrations.taxjar_integration.create_transaction"
],
"on_cancel": [
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
],
"on_trash": "erpnext.regional.check_deletion_permission" "on_trash": "erpnext.regional.check_deletion_permission"
}, },
"Purchase Invoice": { "Purchase Invoice": {
@@ -261,6 +268,9 @@ doc_events = {
}, },
"Email Unsubscribe": { "Email Unsubscribe": {
"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient" "after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
},
('Quotation', 'Sales Order', 'Sales Invoice'): {
'validate': ["erpnext.erpnext_integrations.taxjar_integration.set_sales_tax"]
} }
} }

View File

@@ -213,12 +213,15 @@ frappe.ui.form.on("Expense Claim", {
refresh: function(frm) { refresh: function(frm) {
frm.trigger("toggle_fields"); frm.trigger("toggle_fields");
if(frm.doc.docstatus === 1 && frm.doc.approval_status !== "Rejected") { if(frm.doc.docstatus > 0 && frm.doc.approval_status !== "Rejected") {
frm.add_custom_button(__('Accounting Ledger'), function() { frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
company: frm.doc.company, company: frm.doc.company,
group_by_voucher: false from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
group_by: '',
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));

View File

@@ -0,0 +1,15 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.listview_settings['Job Applicant'] = {
add_fields: ["company", "designation", "job_applicant", "status"],
get_indicator: function (doc) {
if (doc.status == "Accepted") {
return [__(doc.status), "green", "status,=," + doc.status];
} else if (["Open", "Replied"].includes(doc.status)) {
return [__(doc.status), "orange", "status,=," + doc.status];
} else if (["Hold", "Rejected"].includes(doc.status)) {
return [__(doc.status), "red", "status,=," + doc.status];
}
}
};

View File

@@ -30,7 +30,6 @@
{ {
"fieldname": "job_applicant", "fieldname": "job_applicant",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Job Applicant", "label": "Job Applicant",
"options": "Job Applicant", "options": "Job Applicant",
"print_hide": 1, "print_hide": 1,
@@ -161,7 +160,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2019-12-31 02:40:33.650728", "modified": "2020-06-25 00:56:24.756395",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Job Offer", "name": "Job Offer",

View File

@@ -0,0 +1,15 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.listview_settings['Job Offer'] = {
add_fields: ["company", "designation", "job_applicant", "status"],
get_indicator: function (doc) {
if (doc.status == "Accepted") {
return [__(doc.status), "green", "status,=," + doc.status];
} else if (doc.status == "Awaiting Response") {
return [__(doc.status), "orange", "status,=," + doc.status];
} else if (doc.status == "Rejected") {
return [__(doc.status), "red", "status,=," + doc.status];
}
}
};

View File

@@ -235,8 +235,10 @@ def make_repayment_entry(loan, applicant_type, applicant, loan_type, company, as
@frappe.whitelist() @frappe.whitelist()
def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1): def create_loan_security_unpledge(loan, applicant_type, applicant, company, as_dict=1):
loan_security_pledge_details = frappe.db.sql(""" loan_security_pledge_details = frappe.db.sql("""
SELECT p.parent, p.loan_security, p.qty as qty FROM `tabLoan Security Pledge` lsp , `tabPledge` p SELECT p.loan_security, sum(p.qty) as qty
FROM `tabLoan Security Pledge` lsp , `tabPledge` p
WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1 WHERE p.parent = lsp.name AND lsp.loan = %s AND lsp.docstatus = 1
GROUP BY p.loan_security
""",(loan), as_dict=1) """,(loan), as_dict=1)
unpledge_request = frappe.new_doc("Loan Security Unpledge") unpledge_request = frappe.new_doc("Loan Security Unpledge")

View File

@@ -116,7 +116,7 @@ class LoanRepayment(AccountsController):
def allocate_amounts(self, paid_entries): def allocate_amounts(self, paid_entries):
self.set('repayment_details', []) self.set('repayment_details', [])
self.principal_amount_paid = 0 self.principal_amount_paid = 0
interest_paid = 0 interest_paid = self.amount_paid - self.penalty_amount
if self.amount_paid - self.penalty_amount > 0 and paid_entries: if self.amount_paid - self.penalty_amount > 0 and paid_entries:
interest_paid = self.amount_paid - self.penalty_amount interest_paid = self.amount_paid - self.penalty_amount

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "LM-LSP-.####", "autoname": "LM-LSP-.####",
"creation": "2019-09-03 18:20:31.382887", "creation": "2019-09-03 18:20:31.382887",
"doctype": "DocType", "doctype": "DocType",
@@ -46,6 +47,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Loan Security Price", "label": "Loan Security Price",
"options": "Company:company:default_currency",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -79,7 +81,8 @@
"read_only": 1 "read_only": 1
} }
], ],
"modified": "2019-10-26 09:46:46.069667", "links": [],
"modified": "2020-06-11 03:41:33.900340",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Security Price", "name": "Loan Security Price",

View File

@@ -19,7 +19,9 @@ def update_shortfall_status(loan, security_value):
return return
if security_value >= loan_security_shortfall.shortfall_amount: if security_value >= loan_security_shortfall.shortfall_amount:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, "status", "Completed") frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
"status": "Completed",
"shortfall_value": loan_security_shortfall.shortfall_amount})
else: else:
frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value) "shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)

View File

@@ -9,12 +9,15 @@ frappe.ui.form.on(cur_frm.doctype, {
} }
if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype) if (['Loan Disbursement', 'Loan Repayment', 'Loan Interest Accrual'].includes(frm.doc.doctype)
&& frm.doc.docstatus == 1) { && frm.doc.docstatus > 0) {
frm.add_custom_button(__("Accounting Ledger"), function() { frm.add_custom_button(__("Accounting Ledger"), function() {
frappe.route_options = { frappe.route_options = {
voucher_no: frm.doc.name, voucher_no: frm.doc.name,
company: frm.doc.company company: frm.doc.company,
from_date: frm.doc.posting_date,
to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
show_cancelled_entries: frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");

View File

@@ -98,11 +98,17 @@ class ProductionPlan(Document):
elif self.get_items_from == "Material Request": elif self.get_items_from == "Material Request":
self.get_mr_items() self.get_mr_items()
def get_so_mr_list(self, field, table):
"""Returns a list of Sales Orders or Material Requests from the respective tables"""
so_mr_list = [d.get(field) for d in self.get(table) if d.get(field)]
return so_mr_list
def get_so_items(self): def get_so_items(self):
so_list = [d.sales_order for d in self.sales_orders if d.sales_order] # Check for empty table or empty rows
if not so_list: if not self.get("sales_orders") or not self.get_so_mr_list("sales_order", "sales_orders"):
msgprint(_("Please enter Sales Orders in the above table")) frappe.throw(_("Please fill the Sales Orders table"), title=_("Sales Orders Required"))
return []
so_list = self.get_so_mr_list("sales_order", "sales_orders")
item_condition = "" item_condition = ""
if self.item_code: if self.item_code:
@@ -134,10 +140,11 @@ class ProductionPlan(Document):
self.calculate_total_planned_qty() self.calculate_total_planned_qty()
def get_mr_items(self): def get_mr_items(self):
mr_list = [d.material_request for d in self.material_requests if d.material_request] # Check for empty table or empty rows
if not mr_list: if not self.get("material_requests") or not self.get_so_mr_list("material_request", "material_requests"):
msgprint(_("Please enter Material Requests in the above table")) frappe.throw(_("Please fill the Material Requests table"), title=_("Material Requests Required"))
return []
mr_list = self.get_so_mr_list("material_request", "material_requests")
item_condition = "" item_condition = ""
if self.item_code: if self.item_code:
@@ -628,16 +635,19 @@ def get_items_for_material_requests(doc, warehouses=None):
if warehouse_list: if warehouse_list:
warehouses = list(set(warehouse_list)) warehouses = list(set(warehouse_list))
if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses: if doc.get("for_warehouse") and doc.get("for_warehouse") in warehouses:
warehouses.remove(doc.get("for_warehouse")) warehouses.remove(doc.get("for_warehouse"))
warehouse_list = None warehouse_list = None
doc['mr_items'] = [] doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items') po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')
if not po_items: # Check for empty table or empty rows
frappe.throw(_("Items are required to pull the raw materials which is associated with it.")) if not po_items or not [row.get('item_code') for row in po_items if row.get('item_code')]:
frappe.throw(_("Items to Manufacture are required to pull the Raw Materials associated with it."),
title=_("Items Required"))
company = doc.get('company') company = doc.get('company')
ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty') ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')

View File

@@ -706,3 +706,4 @@ execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
erpnext.patches.v12_0.add_taxjar_integration_field

View File

@@ -0,0 +1,12 @@
from __future__ import unicode_literals
import frappe
from erpnext.regional.united_states.setup import make_custom_fields
def execute():
company = frappe.get_all('Company', filters={'country': 'United States'})
if not company:
return
make_custom_fields()

View File

@@ -5,6 +5,8 @@ from erpnext.regional.united_states.setup import make_custom_fields
def execute(): def execute():
frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True) frappe.reload_doc('accounts', 'doctype', 'allowed_to_transact_with', force=True)
frappe.reload_doc('accounts', 'doctype', 'pricing_rule_detail', force=True)
frappe.reload_doc('crm', 'doctype', 'lost_reason_detail', force=True)
company = frappe.get_all('Company', filters = {'country': 'United States'}) company = frappe.get_all('Company', filters = {'country': 'United States'})
if not company: if not company:

View File

@@ -381,7 +381,6 @@
{ {
"fieldname": "earning", "fieldname": "earning",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"label": "Earning",
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"show_days": 1, "show_days": 1,
"show_seconds": 1, "show_seconds": 1,
@@ -400,7 +399,6 @@
{ {
"fieldname": "deduction", "fieldname": "deduction",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"label": "Deduction",
"oldfieldtype": "Column Break", "oldfieldtype": "Column Break",
"show_days": 1, "show_days": 1,
"show_seconds": 1, "show_seconds": 1,
@@ -616,7 +614,7 @@
"idx": 9, "idx": 9,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-06-22 14:42:43.921828", "modified": "2020-06-25 14:42:43.921828",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Payroll", "module": "Payroll",
"name": "Salary Slip", "name": "Salary Slip",

View File

@@ -271,6 +271,7 @@ class TestSalarySlip(unittest.TestCase):
# as per assigned salary structure 40500 in monthly salary so 236000*5/100/12 # as per assigned salary structure 40500 in monthly salary so 236000*5/100/12
frappe.db.sql("""delete from `tabPayroll Period`""") frappe.db.sql("""delete from `tabPayroll Period`""")
frappe.db.sql("""delete from `tabSalary Component`""") frappe.db.sql("""delete from `tabSalary Component`""")
frappe.db.sql("""delete from `tabAdditional Salary`""")
payroll_period = create_payroll_period() payroll_period = create_payroll_period()

View File

@@ -35,7 +35,9 @@ frappe.ui.form.on('Salary Structure', {
d.show() d.show()
}); });
frm.get_field("conditions_and_formula_variable_and_example").$wrapper.append(frm.doc.filters_html).append(help_button) let help_button_wrapper = frm.get_field("conditions_and_formula_variable_and_example").$wrapper;
help_button_wrapper.empty();
help_button_wrapper.append(frm.doc.filters_html).append(help_button)
frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet) frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet)

View File

@@ -6,8 +6,8 @@ import frappe, erpnext
from frappe import _ from frappe import _
def execute(filters=None): def execute(filters=None):
columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
columns = get_columns(filters) if len(data) else []
return columns, data return columns, data
@@ -78,8 +78,11 @@ def get_conditions(filters):
if filters.get("company"): if filters.get("company"):
conditions.append("sal.company = '%s' " % (filters["company"]) ) conditions.append("sal.company = '%s' " % (filters["company"]) )
if filters.get("period"): if filters.get("month"):
conditions.append("month(sal.start_date) = '%s' " % (filters["period"])) conditions.append("month(sal.start_date) = '%s' " % (filters["month"]))
if filters.get("year"):
conditions.append("year(start_date) = '%s' " % (filters["year"]))
return " and ".join(conditions) return " and ".join(conditions)
@@ -96,6 +99,9 @@ def get_data(filters):
component_types = [comp_type[0] for comp_type in component_types] component_types = [comp_type[0] for comp_type in component_types]
if not len(component_types):
return []
conditions = get_conditions(filters) conditions = get_conditions(filters)
entry = frappe.db.sql(""" select sal.employee, sal.employee_name, sal.posting_date, ded.salary_component, ded.amount,sal.gross_pay entry = frappe.db.sql(""" select sal.employee, sal.employee_name, sal.posting_date, ded.salary_component, ded.amount,sal.gross_pay

View File

@@ -84,9 +84,11 @@ def get_conditions(filters):
if filters.get("company"): if filters.get("company"):
conditions.append("company = '%s' " % (filters["company"]) ) conditions.append("company = '%s' " % (filters["company"]) )
if filters.get("period"): if filters.get("month"):
conditions.append("month(start_date) = '%s' " % (filters["period"])) conditions.append("month(start_date) = '%s' " % (filters["month"]))
conditions.append("year(start_date) = '%s' " % (frappe.utils.getdate().year))
if filters.get("year"):
conditions.append("year(start_date) = '%s' " % (filters["year"]))
return " and ".join(conditions) return " and ".join(conditions)

View File

@@ -239,13 +239,12 @@ def get_next_attribute_and_values(item_code, selected_attributes):
if exact_match: if exact_match:
data = get_product_info_for_website(exact_match[0]) data = get_product_info_for_website(exact_match[0])
product_info = data.product_info product_info = data.product_info
product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
if not data.cart_settings.show_price: if not data.cart_settings.show_price:
product_info = None product_info = None
else: else:
product_info = None product_info = None
product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
return { return {
'next_attribute': next_attribute, 'next_attribute': next_attribute,
'valid_options_for_attributes': valid_options_for_attributes, 'valid_options_for_attributes': valid_options_for_attributes,

View File

@@ -140,52 +140,6 @@ class TestTimesheet(unittest.TestCase):
settings.ignore_employee_time_overlap = initial_setting settings.ignore_employee_time_overlap = initial_setting
settings.save() settings.save()
def test_timesheet_std_working_hours(self):
emp = make_employee("test_employee_6@salary.com")
company = frappe.get_doc('Company', "_Test Company")
company.standard_working_hours = 8
company.save()
timesheet = frappe.new_doc("Timesheet")
timesheet.employee = emp
timesheet.company = '_Test Company'
timesheet.append(
'time_logs',
{
"activity_type": "_Test Activity Type",
"from_time": now_datetime(),
"to_time": now_datetime() + datetime.timedelta(days= 4)
}
)
timesheet.save()
ts = frappe.get_doc('Timesheet', timesheet.name)
self.assertEqual(ts.total_hours, 32)
ts.submit()
ts.cancel()
company = frappe.get_doc('Company', "_Test Company")
company.standard_working_hours = 0
company.save()
timesheet = frappe.new_doc("Timesheet")
timesheet.employee = emp
timesheet.company = '_Test Company'
timesheet.append(
'time_logs',
{
"activity_type": "_Test Activity Type",
"from_time": now_datetime(),
"to_time": now_datetime() + datetime.timedelta(days= 4)
}
)
timesheet.save()
ts = frappe.get_doc('Timesheet', timesheet.name)
self.assertEqual(ts.total_hours, 96)
ts.submit()
ts.cancel()
def make_salary_structure_for_timesheet(employee): def make_salary_structure_for_timesheet(employee):
salary_structure_name = "Timesheet Salary Structure Test" salary_structure_name = "Timesheet Salary Structure Test"

View File

@@ -162,19 +162,11 @@ frappe.ui.form.on("Timesheet Detail", {
to_time: function(frm, cdt, cdn) { to_time: function(frm, cdt, cdn) {
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
var time_diff = (moment(child.to_time).diff(moment(child.from_time),"seconds")) / ( 60 * 60 * 24);
var std_working_hours = 0;
if(frm._setting_hours) return; if(frm._setting_hours) return;
var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600; var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600;
std_working_hours = time_diff * frappe.working_hours; frappe.model.set_value(cdt, cdn, "hours", hours);
if (std_working_hours < hours && std_working_hours > 0) {
frappe.model.set_value(cdt, cdn, "hours", std_working_hours);
} else {
frappe.model.set_value(cdt, cdn, "hours", hours);
}
}, },
time_logs_add: function(frm) { time_logs_add: function(frm) {
@@ -236,23 +228,12 @@ var calculate_end_time = function(frm, cdt, cdn) {
let d = moment(child.from_time); let d = moment(child.from_time);
if(child.hours) { if(child.hours) {
var time_diff = (moment(child.to_time).diff(moment(child.from_time),"seconds")) / (60 * 60 * 24); d.add(child.hours, "hours");
var std_working_hours = 0; frm._setting_hours = true;
var hours = moment(child.to_time).diff(moment(child.from_time), "seconds") / 3600; frappe.model.set_value(cdt, cdn, "to_time",
d.format(frappe.defaultDatetimeFormat)).then(() => {
std_working_hours = time_diff * frappe.working_hours; frm._setting_hours = false;
});
if (std_working_hours < hours && std_working_hours > 0) {
frappe.model.set_value(cdt, cdn, "hours", std_working_hours);
frappe.model.set_value(cdt, cdn, "to_time", d.add(hours, "hours").format(frappe.defaultDatetimeFormat));
} else {
d.add(child.hours, "hours");
frm._setting_hours = true;
frappe.model.set_value(cdt, cdn, "to_time",
d.format(frappe.defaultDatetimeFormat)).then(() => {
frm._setting_hours = false;
});
}
} }
}; };

View File

@@ -24,7 +24,6 @@ class Timesheet(Document):
self.set_status() self.set_status()
self.validate_dates() self.validate_dates()
self.validate_time_logs() self.validate_time_logs()
self.calculate_std_hours()
self.update_cost() self.update_cost()
self.calculate_total_amounts() self.calculate_total_amounts()
self.calculate_percentage_billed() self.calculate_percentage_billed()
@@ -91,17 +90,6 @@ class Timesheet(Document):
self.start_date = getdate(start_date) self.start_date = getdate(start_date)
self.end_date = getdate(end_date) self.end_date = getdate(end_date)
def calculate_std_hours(self):
std_working_hours = frappe.get_value("Company", self.company, 'standard_working_hours')
for time in self.time_logs:
if time.from_time and time.to_time:
if flt(std_working_hours) and date_diff(time.to_time, time.from_time):
time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time)
else:
if not time.hours:
time.hours = time_diff_in_hours(time.to_time, time.from_time)
def before_cancel(self): def before_cancel(self):
self.set_status() self.set_status()

View File

@@ -55,8 +55,9 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
frappe.route_options = { frappe.route_options = {
voucher_no: me.frm.doc.name, voucher_no: me.frm.doc.name,
from_date: me.frm.doc.posting_date, from_date: me.frm.doc.posting_date,
to_date: me.frm.doc.posting_date, to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
company: me.frm.doc.company company: me.frm.doc.company,
show_cancelled_entries: me.frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "Stock Ledger"); frappe.set_route("query-report", "Stock Ledger");
}, __("View")); }, __("View"));
@@ -71,9 +72,10 @@ erpnext.stock.StockController = frappe.ui.form.Controller.extend({
frappe.route_options = { frappe.route_options = {
voucher_no: me.frm.doc.name, voucher_no: me.frm.doc.name,
from_date: me.frm.doc.posting_date, from_date: me.frm.doc.posting_date,
to_date: me.frm.doc.posting_date, to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
company: me.frm.doc.company, company: me.frm.doc.company,
group_by: "Group by Voucher (Consolidated)" group_by: "Group by Voucher (Consolidated)",
show_cancelled_entries: me.frm.doc.docstatus === 2
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, __("View")); }, __("View"));

View File

@@ -159,6 +159,26 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}; };
}); });
} }
if (this.frm.fields_dict["items"].grid.get_field("cost_center")) {
this.frm.set_query("cost_center", "items", function(doc) {
return {
filters: {
"company": doc.company,
"is_group": 0
}
};
});
}
if (this.frm.fields_dict["items"].grid.get_field("expense_account")) {
this.frm.set_query("expense_account", "items", function(doc) {
return {
filters: {
"company": doc.company
}
};
});
}
if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) { if(frappe.meta.get_docfield(this.frm.doc.doctype, "pricing_rules")) {
this.frm.set_indicator_formatter('pricing_rule', function(doc) { this.frm.set_indicator_formatter('pricing_rule', function(doc) {

View File

@@ -11,8 +11,8 @@ erpnext.salary_slip_deductions_report_filters = {
default: frappe.defaults.get_user_default("Company"), default: frappe.defaults.get_user_default("Company"),
}, },
{ {
fieldname: "period", fieldname: "month",
label: __("Period"), label: __("Month"),
fieldtype: "Select", fieldtype: "Select",
reqd: 1 , reqd: 1 ,
options: [ options: [
@@ -31,6 +31,12 @@ erpnext.salary_slip_deductions_report_filters = {
], ],
default: frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1 default: frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth() + 1
}, },
{
fieldname:"year",
label: __("Year"),
fieldtype: "Select",
reqd: 1
},
{ {
fieldname: "department", fieldname: "department",
label: __("Department"), label: __("Department"),
@@ -43,5 +49,18 @@ erpnext.salary_slip_deductions_report_filters = {
fieldtype: "Link", fieldtype: "Link",
options: "Branch", options: "Branch",
} }
] ],
"onload": function() {
return frappe.call({
method: "erpnext.regional.report.provident_fund_deductions.provident_fund_deductions.get_years",
callback: function(r) {
var year_filter = frappe.query_report.get_filter('year');
year_filter.df.options = r.message;
year_filter.df.default = r.message.split("\n")[0];
year_filter.refresh();
year_filter.set_input(year_filter.df.default);
}
});
}
} }

View File

@@ -8,17 +8,18 @@ Provide a report and downloadable CSV according to the German DATEV format.
all required columns. Used to import the data into the DATEV Software. all required columns. Used to import the data into the DATEV Software.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import json import json
import zlib
import zipfile import zipfile
import six import six
import frappe
import pandas as pd
from frappe import _
from csv import QUOTE_NONNUMERIC from csv import QUOTE_NONNUMERIC
from six import BytesIO from six import BytesIO
from six import string_types from six import string_types
import frappe
from frappe import _
import pandas as pd
from .datev_constants import DataCategory from .datev_constants import DataCategory
from .datev_constants import Transactions from .datev_constants import Transactions
from .datev_constants import DebtorsCreditors from .datev_constants import DebtorsCreditors
@@ -130,8 +131,10 @@ def get_customers(filters):
SELECT SELECT
acc.account_number as 'Konto', acc.account_number as 'Konto',
cus.customer_name as 'Name (Adressatentyp Unternehmen)', CASE cus.customer_type WHEN 'Company' THEN cus.customer_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
case cus.customer_type when 'Individual' then 1 when 'Company' then 2 else 0 end as 'Adressatentyp', CASE cus.customer_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
CASE cus.customer_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
CASE cus.customer_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
adr.address_line1 as 'Straße', adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl', adr.pincode as 'Postleitzahl',
adr.city as 'Ort', adr.city as 'Ort',
@@ -140,8 +143,7 @@ def get_customers(filters):
con.email_id as 'E-Mail', con.email_id as 'E-Mail',
coalesce(con.mobile_no, con.phone) as 'Telefon', coalesce(con.mobile_no, con.phone) as 'Telefon',
cus.website as 'Internet', cus.website as 'Internet',
cus.tax_id as 'Steuernummer', cus.tax_id as 'Steuernummer'
ccl.credit_limit as 'Kreditlimit (Debitor)'
FROM `tabParty Account` par FROM `tabParty Account` par
@@ -160,10 +162,6 @@ def get_customers(filters):
left join `tabContact` con left join `tabContact` con
on con.name = cus.customer_primary_contact on con.name = cus.customer_primary_contact
left join `tabCustomer Credit Limit` ccl
on ccl.parent = cus.name
and ccl.company = par.company
WHERE par.company = %(company)s WHERE par.company = %(company)s
AND par.parenttype = 'Customer'""", filters, as_dict=1) AND par.parenttype = 'Customer'""", filters, as_dict=1)
@@ -179,8 +177,10 @@ def get_suppliers(filters):
SELECT SELECT
acc.account_number as 'Konto', acc.account_number as 'Konto',
sup.supplier_name as 'Name (Adressatentyp Unternehmen)', CASE sup.supplier_type WHEN 'Company' THEN sup.supplier_name ELSE null END as 'Name (Adressatentyp Unternehmen)',
case sup.supplier_type when 'Individual' then '1' when 'Company' then '2' else '0' end as 'Adressatentyp', CASE sup.supplier_type WHEN 'Individual' THEN con.last_name ELSE null END as 'Name (Adressatentyp natürl. Person)',
CASE sup.supplier_type WHEN 'Individual' THEN con.first_name ELSE null END as 'Vorname (Adressatentyp natürl. Person)',
CASE sup.supplier_type WHEN 'Individual' THEN '1' WHEN 'Company' THEN '2' ELSE '0' end as 'Adressatentyp',
adr.address_line1 as 'Straße', adr.address_line1 as 'Straße',
adr.pincode as 'Postleitzahl', adr.pincode as 'Postleitzahl',
adr.city as 'Ort', adr.city as 'Ort',
@@ -226,9 +226,18 @@ def get_suppliers(filters):
def get_account_names(filters): def get_account_names(filters):
return frappe.get_list("Account", return frappe.db.sql("""
fields=["account_number as Konto", "name as Kontenbeschriftung"], SELECT
filters={"company": filters.get("company"), "is_group": "0"})
account_number as 'Konto',
LEFT(account_name, 40) as 'Kontenbeschriftung',
'de-DE' as 'Sprach-ID'
FROM `tabAccount`
WHERE company = %(company)s
AND is_group = 0
AND account_number != ''
""", filters, as_dict=1)
def get_datev_csv(data, filters, csv_class): def get_datev_csv(data, filters, csv_class):
@@ -287,9 +296,7 @@ def get_datev_csv(data, filters, csv_class):
def get_header(filters, csv_class): def get_header(filters, csv_class):
coa = frappe.get_value("Company", filters.get("company"), "chart_of_accounts") description = filters.get('voucher_type', csv_class.FORMAT_NAME)
description = filters.get("voucher_type", csv_class.FORMAT_NAME)
coa_used = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "")
header = [ header = [
# DATEV format # DATEV format
@@ -316,19 +323,19 @@ def get_header(filters, csv_class):
# J = Imported by -- stays empty # J = Imported by -- stays empty
'', '',
# K = Tax consultant number (Beraternummer) # K = Tax consultant number (Beraternummer)
frappe.get_value("DATEV Settings", filters.get("company"), "consultant_number"), filters.get('consultant_number', '0000000'),
# L = Tax client number (Mandantennummer) # L = Tax client number (Mandantennummer)
frappe.get_value("DATEV Settings", filters.get("company"), "client_number"), filters.get('client_number', '00000'),
# M = Start of the fiscal year (Wirtschaftsjahresbeginn) # M = Start of the fiscal year (Wirtschaftsjahresbeginn)
frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"), frappe.utils.formatdate(frappe.defaults.get_user_default("year_start_date"), "yyyyMMdd"),
# N = Length of account numbers (Sachkontenlänge) # N = Length of account numbers (Sachkontenlänge)
'4', '%d' % filters.get('acc_len', 4),
# O = Transaction batch start date (YYYYMMDD) # O = Transaction batch start date (YYYYMMDD)
frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd"), frappe.utils.formatdate(filters.get('from_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# P = Transaction batch end date (YYYYMMDD) # P = Transaction batch end date (YYYYMMDD)
frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd"), frappe.utils.formatdate(filters.get('to_date'), "yyyyMMdd") if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# Q = Description (for example, "Sales Invoice") Max. 30 chars # Q = Description (for example, "Sales Invoice") Max. 30 chars
'"{}"'.format(_(description)), '"{}"'.format(_(description)) if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# R = Diktatkürzel # R = Diktatkürzel
'', '',
# S = Buchungstyp # S = Buchungstyp
@@ -343,12 +350,12 @@ def get_header(filters, csv_class):
# 40 = Kalkulatorik # 40 = Kalkulatorik
# 11 = Reserviert # 11 = Reserviert
# 12 = Reserviert # 12 = Reserviert
'0', '0' if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# U = Festschreibung # U = Festschreibung
# TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1" # TODO: Filter by Accounting Period. In export for closed Accounting Period, this will be "1"
'0', '0',
# V = Default currency, for example, "EUR" # V = Default currency, for example, "EUR"
'"%s"' % frappe.get_value("Company", filters.get("company"), "default_currency"), '"%s"' % filters.get('default_currency', 'EUR') if csv_class.DATA_CATEGORY == DataCategory.TRANSACTIONS else '',
# reserviert # reserviert
'', '',
# Derivatskennzeichen # Derivatskennzeichen
@@ -358,7 +365,7 @@ def get_header(filters, csv_class):
# reserviert # reserviert
'', '',
# SKR # SKR
'"%s"' % coa_used, '"%s"' % filters.get('skr', '04'),
# Branchen-Lösungs-ID # Branchen-Lösungs-ID
'', '',
# reserviert # reserviert
@@ -389,6 +396,18 @@ def download_datev_csv(filters=None):
validate(filters) validate(filters)
# set chart of accounts used
coa = frappe.get_value('Company', filters.get('company'), 'chart_of_accounts')
filters['skr'] = '04' if 'SKR04' in coa else ('03' if 'SKR03' in coa else '')
# set account number length
account_numbers = frappe.get_list('Account', fields=['account_number'], filters={'is_group': 0, 'account_number': ('!=', '')})
filters['acc_len'] = max([len(a.account_number) for a in account_numbers])
filters['consultant_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'consultant_number')
filters['client_number'] = frappe.get_value('DATEV Settings', filters.get('company'), 'client_number')
filters['default_currency'] = frappe.get_value('Company', filters.get('company'), 'default_currency')
# This is where my zip will be written # This is where my zip will be written
zip_buffer = BytesIO() zip_buffer = BytesIO()
# This is my zip file # This is my zip file

View File

@@ -465,60 +465,71 @@ QUERY_REPORT_COLUMNS = [
"label": "Umsatz (ohne Soll/Haben-Kz)", "label": "Umsatz (ohne Soll/Haben-Kz)",
"fieldname": "Umsatz (ohne Soll/Haben-Kz)", "fieldname": "Umsatz (ohne Soll/Haben-Kz)",
"fieldtype": "Currency", "fieldtype": "Currency",
"width": 100
}, },
{ {
"label": "Soll/Haben-Kennzeichen", "label": "Soll/Haben-Kennzeichen",
"fieldname": "Soll/Haben-Kennzeichen", "fieldname": "Soll/Haben-Kennzeichen",
"fieldtype": "Data", "fieldtype": "Data",
"width": 100
}, },
{ {
"label": "Konto", "label": "Konto",
"fieldname": "Konto", "fieldname": "Konto",
"fieldtype": "Data", "fieldtype": "Data",
"width": 100
}, },
{ {
"label": "Gegenkonto (ohne BU-Schlüssel)", "label": "Gegenkonto (ohne BU-Schlüssel)",
"fieldname": "Gegenkonto (ohne BU-Schlüssel)", "fieldname": "Gegenkonto (ohne BU-Schlüssel)",
"fieldtype": "Data", "fieldtype": "Data",
"width": 100
}, },
{ {
"label": "Belegdatum", "label": "Belegdatum",
"fieldname": "Belegdatum", "fieldname": "Belegdatum",
"fieldtype": "Date", "fieldtype": "Date",
"width": 100
}, },
{ {
"label": "Belegfeld 1", "label": "Belegfeld 1",
"fieldname": "Belegfeld 1", "fieldname": "Belegfeld 1",
"fieldtype": "Data", "fieldtype": "Data",
"width": 150
}, },
{ {
"label": "Buchungstext", "label": "Buchungstext",
"fieldname": "Buchungstext", "fieldname": "Buchungstext",
"fieldtype": "Text", "fieldtype": "Text",
"width": 300
}, },
{ {
"label": "Beleginfo - Art 1", "label": "Beleginfo - Art 1",
"fieldname": "Beleginfo - Art 1", "fieldname": "Beleginfo - Art 1",
"fieldtype": "Link", "fieldtype": "Link",
"options": "DocType" "options": "DocType",
"width": 100
}, },
{ {
"label": "Beleginfo - Inhalt 1", "label": "Beleginfo - Inhalt 1",
"fieldname": "Beleginfo - Inhalt 1", "fieldname": "Beleginfo - Inhalt 1",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 1" "options": "Beleginfo - Art 1",
"width": 150
}, },
{ {
"label": "Beleginfo - Art 2", "label": "Beleginfo - Art 2",
"fieldname": "Beleginfo - Art 2", "fieldname": "Beleginfo - Art 2",
"fieldtype": "Link", "fieldtype": "Link",
"options": "DocType" "options": "DocType",
"width": 100
}, },
{ {
"label": "Beleginfo - Inhalt 2", "label": "Beleginfo - Inhalt 2",
"fieldname": "Beleginfo - Inhalt 2", "fieldname": "Beleginfo - Inhalt 2",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"options": "Beleginfo - Art 2" "options": "Beleginfo - Art 2",
"width": 150
} }
] ]

View File

@@ -7,8 +7,8 @@ from frappe import _
from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import get_conditions from erpnext.regional.report.provident_fund_deductions.provident_fund_deductions import get_conditions
def execute(filters=None): def execute(filters=None):
columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
columns = get_columns(filters) if len(data) else []
return columns, data return columns, data
@@ -45,6 +45,9 @@ def get_data(filters):
component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component` component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component`
where component_type = 'Professional Tax' """)) where component_type = 'Professional Tax' """))
if not len(component_type_dict):
return []
conditions = get_conditions(filters) conditions = get_conditions(filters)
entry = frappe.db.sql(""" select sal.employee, sal.employee_name, ded.salary_component, ded.amount entry = frappe.db.sql(""" select sal.employee, sal.employee_name, ded.salary_component, ded.amount

View File

@@ -3,11 +3,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import getdate
from frappe import _ from frappe import _
def execute(filters=None): def execute(filters=None):
columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
columns = get_columns(filters) if len(data) else []
return columns, data return columns, data
@@ -71,10 +72,13 @@ def get_conditions(filters):
conditions.append("sal.branch = '%s' " % (filters["branch"]) ) conditions.append("sal.branch = '%s' " % (filters["branch"]) )
if filters.get("company"): if filters.get("company"):
conditions.append("sal.company = '%s' " % (filters["company"]) ) conditions.append("sal.company = '%s' " % (filters["company"]))
if filters.get("period"): if filters.get("month"):
conditions.append("month(sal.start_date) = '%s' " % (filters["period"])) conditions.append("month(sal.start_date) = '%s' " % (filters["month"]))
if filters.get("year"):
conditions.append("year(start_date) = '%s' " % (filters["year"]))
if filters.get("mode_of_payment"): if filters.get("mode_of_payment"):
conditions.append("sal.mode_of_payment = '%s' " % (filters["mode_of_payment"])) conditions.append("sal.mode_of_payment = '%s' " % (filters["mode_of_payment"]))
@@ -114,6 +118,9 @@ def get_data(filters):
component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component` component_type_dict = frappe._dict(frappe.db.sql(""" select name, component_type from `tabSalary Component`
where component_type in ('Provident Fund', 'Additional Provident Fund', 'Provident Fund Loan')""")) where component_type in ('Provident Fund', 'Additional Provident Fund', 'Provident Fund Loan')"""))
if not len(component_type_dict):
return []
entry = frappe.db.sql(""" select sal.name, sal.employee, sal.employee_name, ded.salary_component, ded.amount entry = frappe.db.sql(""" select sal.name, sal.employee, sal.employee_name, ded.salary_component, ded.amount
from `tabSalary Slip` sal, `tabSalary Detail` ded from `tabSalary Slip` sal, `tabSalary Detail` ded
where sal.name = ded.parent where sal.name = ded.parent
@@ -150,4 +157,12 @@ def get_data(filters):
data.append(employee) data.append(employee)
return data return data
@frappe.whitelist()
def get_years():
year_list = frappe.db.sql_list("""select distinct YEAR(end_date) from `tabSalary Slip` ORDER BY YEAR(end_date) DESC""")
if not year_list:
year_list = [getdate().year]
return "\n".join(str(year) for year in year_list)

View File

@@ -14,6 +14,22 @@ def make_custom_fields(update=True):
'Supplier': [ 'Supplier': [
dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id', dict(fieldname='irs_1099', fieldtype='Check', insert_after='tax_id',
label='Is IRS 1099 reporting required for supplier?') label='Is IRS 1099 reporting required for supplier?')
],
'Sales Order': [
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
label='Is customer exempted from sales tax?')
],
'Sales Invoice': [
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_section',
label='Is customer exempted from sales tax?')
],
'Customer': [
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='represents_company',
label='Is customer exempted from sales tax?')
],
'Quotation': [
dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
label='Is customer exempted from sales tax?')
] ]
} }
create_custom_fields(custom_fields, update=update) create_custom_fields(custom_fields, update=update)

View File

@@ -388,8 +388,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager") credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager")
# form a list of emails and names to show to the user # form a list of emails and names to show to the user
credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})] credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users]
credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list]
if not credit_controller_users: if not credit_controller_users:
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer))) frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
@@ -409,7 +408,7 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
'customer': customer, 'customer': customer,
'customer_outstanding': customer_outstanding, 'customer_outstanding': customer_outstanding,
'credit_limit': credit_limit, 'credit_limit': credit_limit,
'credit_controller_users_list': credit_controller_users_list 'credit_controller_users_list': credit_controller_users
} }
} }
) )

View File

@@ -280,5 +280,3 @@ def make_quotation(**args):
qo.submit() qo.submit()
return qo return qo

View File

@@ -158,7 +158,7 @@ def get_data():
} }
pending_so.append(so_record) pending_so.append(so_record)
else: else:
for item in bundled_item_map.get((so.name, so.item_code)): for item in bundled_item_map.get((so.name, so.item_code), []):
material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {} material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {}
if flt(item.qty) > flt(material_requests_against_so.get('qty')): if flt(item.qty) > flt(material_requests_against_so.get('qty')):
so_record = { so_record = {

View File

@@ -22,7 +22,6 @@
"default_letter_head", "default_letter_head",
"default_holiday_list", "default_holiday_list",
"default_finance_book", "default_finance_book",
"standard_working_hours",
"default_selling_terms", "default_selling_terms",
"default_buying_terms", "default_buying_terms",
"default_warehouse_for_sales_return", "default_warehouse_for_sales_return",
@@ -240,11 +239,6 @@
"label": "Default Holiday List", "label": "Default Holiday List",
"options": "Holiday List" "options": "Holiday List"
}, },
{
"fieldname": "standard_working_hours",
"fieldtype": "Float",
"label": "Standard Working Hours"
},
{ {
"fieldname": "default_warehouse_for_sales_return", "fieldname": "default_warehouse_for_sales_return",
"fieldtype": "Link", "fieldtype": "Link",
@@ -746,7 +740,7 @@
"image_field": "company_logo", "image_field": "company_logo",
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2020-06-20 11:38:43.178970", "modified": "2020-06-24 12:45:31.462195",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Setup", "module": "Setup",
"name": "Company", "name": "Company",

View File

@@ -405,8 +405,8 @@ class EmailDigest(Document):
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0), value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0),
count(*) from `tabSales Order` count(*) from `tabSales Order`
where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed" where (transaction_date <= %(to_date)s) and billing_status != "Fully Billed" and company = %(company)s
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_bill"), label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_bill"),
report_type="Report Builder", report_type="Report Builder",
@@ -430,8 +430,8 @@ class EmailDigest(Document):
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_delivered/100)),0), value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_delivered/100)),0),
count(*) from `tabSales Order` count(*) from `tabSales Order`
where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered" where (transaction_date <= %(to_date)s) and delivery_status != "Fully Delivered" and company = %(company)s
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_deliver"), label = get_link_to_report('Sales Order', label=self.meta.get_label("sales_orders_to_deliver"),
report_type="Report Builder", report_type="Report Builder",
@@ -455,8 +455,8 @@ class EmailDigest(Document):
value, count = frappe.db.sql("""select ifnull((sum(grand_total))-(sum(grand_total*per_received/100)),0), value, count = frappe.db.sql("""select ifnull((sum(grand_total))-(sum(grand_total*per_received/100)),0),
count(*) from `tabPurchase Order` count(*) from `tabPurchase Order`
where (transaction_date <= %(to_date)s) and per_received < 100 where (transaction_date <= %(to_date)s) and per_received < 100 and company = %(company)s
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_receive"), label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_receive"),
report_type="Report Builder", report_type="Report Builder",
@@ -480,8 +480,8 @@ class EmailDigest(Document):
value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0), value, count = frappe.db.sql("""select ifnull((sum(grand_total)) - (sum(grand_total*per_billed/100)),0),
count(*) from `tabPurchase Order` count(*) from `tabPurchase Order`
where (transaction_date <= %(to_date)s) and per_billed < 100 where (transaction_date <= %(to_date)s) and per_billed < 100 and company = %(company)s
and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date})[0] and status not in ('Closed','Cancelled', 'Completed') """, {"to_date": self.future_to_date, "company": self.company})[0]
label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_bill"), label = get_link_to_report('Purchase Order', label=self.meta.get_label("purchase_orders_to_bill"),
report_type="Report Builder", report_type="Report Builder",

View File

@@ -13,6 +13,12 @@ $.extend(cur_frm.cscript, {
}, },
enable_checkout: function(){ enable_checkout: function(){
toggle_mandatory(cur_frm) toggle_mandatory(cur_frm)
},
enabled: function() {
if (cur_frm.doc.enabled === 1) {
cur_frm.doc.show_configure_button = 1;
cur_frm.refresh_field('show_configure_button');
}
} }
}); });

View File

@@ -55,7 +55,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
def set_product_info_for_website(item): def set_product_info_for_website(item):
"""set product price uom for website""" """set product price uom for website"""
product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True) product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
if product_info: if product_info:
item.update(product_info) item.update(product_info)

View File

@@ -71,7 +71,7 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
@@ -88,7 +88,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-09-12 09:30:03.951743", "modified": "2020-06-26 09:30:03.951743",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Customs Tariff Number", "name": "Customs Tariff Number",
@@ -143,4 +143,4 @@
"track_changes": 1, "track_changes": 1,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@@ -1,7 +1,6 @@
{ {
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1,
"autoname": "field:serial_no", "autoname": "field:serial_no",
"creation": "2013-05-16 10:59:15", "creation": "2013-05-16 10:59:15",
"description": "Distinct unit of an Item", "description": "Distinct unit of an Item",
@@ -427,7 +426,7 @@
"icon": "fa fa-barcode", "icon": "fa fa-barcode",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2020-05-21 19:29:58.517772", "modified": "2020-06-25 15:53:50.900855",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Serial No", "name": "Serial No",

View File

@@ -79,7 +79,7 @@ frappe.ui.form.on("Issue", {
method: "erpnext.support.doctype.issue.issue.make_task", method: "erpnext.support.doctype.issue.issue.make_task",
frm: frm frm: frm
}); });
}, __("Make")); }, __("Create"));
} else { } else {
if (frm.doc.service_level_agreement) { if (frm.doc.service_level_agreement) {
@@ -232,4 +232,4 @@ function get_status(variance) {
} else { } else {
return {"diff_display": "Failed", "indicator": "red"}; return {"diff_display": "Failed", "indicator": "red"};
} }
} }

View File

@@ -79,47 +79,52 @@ class Issue(Document):
def handle_hold_time(self, status): def handle_hold_time(self, status):
if self.service_level_agreement: if self.service_level_agreement:
# set response and resolution variance as None as the issue is on Hold for status as Replied # set response and resolution variance as None as the issue is on Hold
pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"], pause_sla_on = frappe.db.get_all("Pause SLA On Status", fields=["status"],
filters={"parent": self.service_level_agreement}) filters={"parent": self.service_level_agreement})
hold_statuses = [entry.status for entry in pause_sla_on] hold_statuses = [entry.status for entry in pause_sla_on]
update_values = {} update_values = {}
if self.status in hold_statuses and status not in hold_statuses: if hold_statuses:
update_values['on_hold_since'] = frappe.flags.current_time or now_datetime() if self.status in hold_statuses and status not in hold_statuses:
if not self.first_responded_on: update_values['on_hold_since'] = frappe.flags.current_time or now_datetime()
update_values['response_by'] = None if not self.first_responded_on:
update_values['response_by_variance'] = 0 update_values['response_by'] = None
update_values['resolution_by'] = None update_values['response_by_variance'] = 0
update_values['resolution_by_variance'] = 0 update_values['resolution_by'] = None
update_values['resolution_by_variance'] = 0
# calculate hold time when status is changed from Replied to any other status # calculate hold time when status is changed from any hold status to any non-hold status
if self.status not in hold_statuses and status in hold_statuses: if self.status not in hold_statuses and status in hold_statuses:
hold_time = self.total_hold_time if self.total_hold_time else 0 hold_time = self.total_hold_time if self.total_hold_time else 0
now_time = frappe.flags.current_time or now_datetime() now_time = frappe.flags.current_time or now_datetime()
update_values['total_hold_time'] = hold_time + time_diff_in_seconds(now_time, self.on_hold_since) last_hold_time = 0
if self.on_hold_since:
# last_hold_time will be added to the sla variables
last_hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
update_values['total_hold_time'] = hold_time + last_hold_time
# re-calculate SLA variables after issue changes from Replied to Open # re-calculate SLA variables after issue changes from any hold status to any non-hold status
# add hold time to SLA variables # add hold time to SLA variables
if self.status == "Open" and status in hold_statuses: start_date_time = get_datetime(self.service_level_agreement_creation)
start_date_time = get_datetime(self.service_level_agreement_creation) priority = get_priority(self)
priority = get_priority(self) now_time = frappe.flags.current_time or now_datetime()
now_time = frappe.flags.current_time or now_datetime()
hold_time = time_diff_in_seconds(now_time, self.on_hold_since)
if not self.first_responded_on: if not self.first_responded_on:
response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time) response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
update_values['response_by'] = add_to_date(response_by, seconds=round(hold_time)) response_by = add_to_date(response_by, seconds=round(last_hold_time))
response_by_variance = round(time_diff_in_hours(self.response_by, now_time)) response_by_variance = round(time_diff_in_hours(response_by, now_time))
update_values['response_by_variance'] = response_by_variance + (hold_time // 3600) update_values['response_by'] = response_by
update_values['response_by_variance'] = response_by_variance + (last_hold_time // 3600)
resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time) resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
update_values['resolution_by'] = add_to_date(resolution_by, seconds=round(hold_time)) resolution_by = add_to_date(resolution_by, seconds=round(last_hold_time))
resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_time)) resolution_by_variance = round(time_diff_in_hours(resolution_by, now_time))
update_values['resolution_by_variance'] = resolution_by_variance + (hold_time // 3600) update_values['resolution_by'] = resolution_by
update_values['on_hold_since'] = None update_values['resolution_by_variance'] = resolution_by_variance + (last_hold_time // 3600)
update_values['on_hold_since'] = None
self.db_set(update_values) self.db_set(update_values)
def update_agreement_status(self): def update_agreement_status(self):
if self.service_level_agreement and self.agreement_fulfilled == "Ongoing": if self.service_level_agreement and self.agreement_fulfilled == "Ongoing":

View File

@@ -4,8 +4,10 @@ gocardless-pro==1.11.0
googlemaps==3.1.1 googlemaps==3.1.1
pandas==0.24.2 pandas==0.24.2
plaid-python==3.4.0 plaid-python==3.4.0
pycountry==19.8.18
PyGithub==1.44.1 PyGithub==1.44.1
python-stdnum==1.12 python-stdnum==1.12
taxjar==1.9.0
tweepy==3.8.0
Unidecode==1.1.1 Unidecode==1.1.1
WooCommerce==2.1.1 WooCommerce==2.1.1
tweepy==3.8.0