Merge branch 'develop' into payment_entry_taxes_unallocated_amount
This commit is contained in:
commit
d86cdb4368
@ -975,8 +975,17 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
acc_settings.save()
|
acc_settings.save()
|
||||||
|
|
||||||
def test_gain_loss_with_advance_entry(self):
|
def test_gain_loss_with_advance_entry(self):
|
||||||
unlink_enabled = frappe.db.get_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice")
|
unlink_enabled = frappe.db.get_value(
|
||||||
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", 1)
|
"Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_payment_on_cancel_of_invoice")
|
||||||
|
|
||||||
|
frappe.db.set_value(
|
||||||
|
"Accounts Settings", "Accounts Settings",
|
||||||
|
"unlink_payment_on_cancel_of_invoice", 1)
|
||||||
|
|
||||||
|
original_account = frappe.db.get_value("Company", "_Test Company", "exchange_gain_loss_account")
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "Exchange Gain/Loss - _TC")
|
||||||
|
|
||||||
pay = frappe.get_doc({
|
pay = frappe.get_doc({
|
||||||
'doctype': 'Payment Entry',
|
'doctype': 'Payment Entry',
|
||||||
'company': '_Test Company',
|
'company': '_Test Company',
|
||||||
@ -1016,7 +1025,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
gl_entries = frappe.db.sql("""
|
gl_entries = frappe.db.sql("""
|
||||||
select account, sum(debit - credit) as balance from `tabGL Entry`
|
select account, sum(debit - credit) as balance from `tabGL Entry`
|
||||||
where voucher_no=%s
|
where voucher_no=%s
|
||||||
group by account order by account asc""", (pi.name), as_dict=1)
|
group by account
|
||||||
|
order by account asc""", (pi.name), as_dict=1)
|
||||||
|
|
||||||
for i, gle in enumerate(gl_entries):
|
for i, gle in enumerate(gl_entries):
|
||||||
self.assertEqual(expected_gle[i][0], gle.account)
|
self.assertEqual(expected_gle[i][0], gle.account)
|
||||||
@ -1076,6 +1086,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
|||||||
pay.cancel()
|
pay.cancel()
|
||||||
|
|
||||||
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
|
frappe.db.set_value("Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled)
|
||||||
|
frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account)
|
||||||
|
|
||||||
def test_purchase_invoice_advance_taxes(self):
|
def test_purchase_invoice_advance_taxes(self):
|
||||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from erpnext.accounts.utils import get_account_currency
|
|||||||
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
|
||||||
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||||
from erpnext.assets.doctype.asset.depreciation \
|
from erpnext.assets.doctype.asset.depreciation \
|
||||||
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
|
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal, get_gl_entries_on_asset_regain
|
||||||
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
from erpnext.stock.doctype.batch.batch import set_batch_nos
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
|
||||||
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
from erpnext.setup.doctype.company.company import update_company_current_month_sales
|
||||||
@ -149,7 +149,7 @@ class SalesInvoice(SellingController):
|
|||||||
if self.update_stock:
|
if self.update_stock:
|
||||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
||||||
|
|
||||||
elif asset.status in ("Scrapped", "Cancelled", "Sold"):
|
elif asset.status in ("Scrapped", "Cancelled") or (asset.status == "Sold" and not self.is_return):
|
||||||
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
|
frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}").format(d.idx, d.asset, asset.status))
|
||||||
|
|
||||||
def validate_item_cost_centers(self):
|
def validate_item_cost_centers(self):
|
||||||
@ -918,22 +918,33 @@ class SalesInvoice(SellingController):
|
|||||||
for item in self.get("items"):
|
for item in self.get("items"):
|
||||||
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
||||||
if item.is_fixed_asset:
|
if item.is_fixed_asset:
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
if item.get('asset'):
|
||||||
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
|
else:
|
||||||
|
frappe.throw(_(
|
||||||
|
"Row #{0}: You must select an Asset for Item {1}.").format(item.idx, item.item_name),
|
||||||
|
title=_("Missing Asset")
|
||||||
|
)
|
||||||
if (len(asset.finance_books) > 1 and not item.finance_book
|
if (len(asset.finance_books) > 1 and not item.finance_book
|
||||||
and asset.finance_books[0].finance_book):
|
and asset.finance_books[0].finance_book):
|
||||||
frappe.throw(_("Select finance book for the item {0} at row {1}")
|
frappe.throw(_("Select finance book for the item {0} at row {1}")
|
||||||
.format(item.item_code, item.idx))
|
.format(item.item_code, item.idx))
|
||||||
|
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
|
if self.is_return:
|
||||||
item.base_net_amount, item.finance_book)
|
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(asset,
|
||||||
|
item.base_net_amount, item.finance_book)
|
||||||
|
asset.db_set("disposal_date", None)
|
||||||
|
else:
|
||||||
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(asset,
|
||||||
|
item.base_net_amount, item.finance_book)
|
||||||
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
|
|
||||||
for gle in fixed_asset_gl_entries:
|
for gle in fixed_asset_gl_entries:
|
||||||
gle["against"] = self.customer
|
gle["against"] = self.customer
|
||||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||||
|
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
self.set_asset_status(asset)
|
||||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
|
||||||
else:
|
else:
|
||||||
# Do not book income for transfer within same company
|
# Do not book income for transfer within same company
|
||||||
if not self.is_internal_transfer():
|
if not self.is_internal_transfer():
|
||||||
@ -959,6 +970,12 @@ class SalesInvoice(SellingController):
|
|||||||
erpnext.is_perpetual_inventory_enabled(self.company):
|
erpnext.is_perpetual_inventory_enabled(self.company):
|
||||||
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
gl_entries += super(SalesInvoice, self).get_gl_entries()
|
||||||
|
|
||||||
|
def set_asset_status(self, asset):
|
||||||
|
if self.is_return:
|
||||||
|
asset.set_status()
|
||||||
|
else:
|
||||||
|
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||||
|
|
||||||
def make_loyalty_point_redemption_gle(self, gl_entries):
|
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||||
if cint(self.redeem_loyalty_points):
|
if cint(self.redeem_loyalty_points):
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
|
|||||||
@ -10,6 +10,7 @@ from frappe.model.dynamic_links import get_dynamic_link_map
|
|||||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
|
from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
|
||||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
@ -1069,6 +1070,36 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertFalse(si1.outstanding_amount)
|
self.assertFalse(si1.outstanding_amount)
|
||||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
|
||||||
|
|
||||||
|
def test_gle_made_when_asset_is_returned(self):
|
||||||
|
create_asset_data()
|
||||||
|
asset = create_asset(item_code="Macbook Pro")
|
||||||
|
|
||||||
|
si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
|
||||||
|
return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
|
||||||
|
|
||||||
|
disposal_account = frappe.get_cached_value("Company", "_Test Company", "disposal_account")
|
||||||
|
|
||||||
|
# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
|
||||||
|
loss_for_si = frappe.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters = {
|
||||||
|
"voucher_no": si.name,
|
||||||
|
"account": disposal_account
|
||||||
|
},
|
||||||
|
fields = ["credit", "debit"]
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
loss_for_return_si = frappe.get_all(
|
||||||
|
"GL Entry",
|
||||||
|
filters = {
|
||||||
|
"voucher_no": return_si.name,
|
||||||
|
"account": disposal_account
|
||||||
|
},
|
||||||
|
fields = ["credit", "debit"]
|
||||||
|
)[0]
|
||||||
|
|
||||||
|
self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
|
||||||
|
self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
|
||||||
|
|
||||||
def test_discount_on_net_total(self):
|
def test_discount_on_net_total(self):
|
||||||
si = frappe.copy_doc(test_records[2])
|
si = frappe.copy_doc(test_records[2])
|
||||||
@ -2164,6 +2195,7 @@ def create_sales_invoice(**args):
|
|||||||
"rate": args.rate if args.get("rate") is not None else 100,
|
"rate": args.rate if args.get("rate") is not None else 100,
|
||||||
"income_account": args.income_account or "Sales - _TC",
|
"income_account": args.income_account or "Sales - _TC",
|
||||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||||
|
"asset": args.asset or None,
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
"conversion_factor": 1
|
"conversion_factor": 1
|
||||||
|
|||||||
@ -743,7 +743,6 @@
|
|||||||
"fieldname": "asset",
|
"fieldname": "asset",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Asset",
|
"label": "Asset",
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Asset"
|
"options": "Asset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -826,7 +825,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-23 01:05:22.123527",
|
"modified": "2021-06-21 23:03:11.599901",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@ -176,22 +176,34 @@ def restore_asset(asset_name):
|
|||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
@frappe.whitelist()
|
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
||||||
|
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
|
||||||
|
get_asset_details(asset, finance_book)
|
||||||
|
|
||||||
|
gl_entries = [
|
||||||
|
{
|
||||||
|
"account": fixed_asset_account,
|
||||||
|
"debit_in_account_currency": asset.gross_purchase_amount,
|
||||||
|
"debit": asset.gross_purchase_amount,
|
||||||
|
"cost_center": depreciation_cost_center
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"account": accumulated_depr_account,
|
||||||
|
"credit_in_account_currency": accumulated_depr_amount,
|
||||||
|
"credit": accumulated_depr_amount,
|
||||||
|
"cost_center": depreciation_cost_center
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
|
||||||
|
if profit_amount:
|
||||||
|
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||||
|
|
||||||
|
return gl_entries
|
||||||
|
|
||||||
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
||||||
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
|
fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation = \
|
||||||
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
get_asset_details(asset, finance_book)
|
||||||
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
|
||||||
|
|
||||||
idx = 1
|
|
||||||
if finance_book:
|
|
||||||
for d in asset.finance_books:
|
|
||||||
if d.finance_book == finance_book:
|
|
||||||
idx = d.idx
|
|
||||||
break
|
|
||||||
|
|
||||||
value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
|
|
||||||
if asset.calculate_depreciation else asset.value_after_depreciation)
|
|
||||||
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
|
|
||||||
|
|
||||||
gl_entries = [
|
gl_entries = [
|
||||||
{
|
{
|
||||||
@ -210,16 +222,37 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
|||||||
|
|
||||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||||
if profit_amount:
|
if profit_amount:
|
||||||
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||||
gl_entries.append({
|
|
||||||
"account": disposal_account,
|
|
||||||
"cost_center": depreciation_cost_center,
|
|
||||||
debit_or_credit: abs(profit_amount),
|
|
||||||
debit_or_credit + "_in_account_currency": abs(profit_amount)
|
|
||||||
})
|
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
|
def get_asset_details(asset, finance_book=None):
|
||||||
|
fixed_asset_account, accumulated_depr_account, depr_expense_account = get_depreciation_accounts(asset)
|
||||||
|
disposal_account, depreciation_cost_center = get_disposal_account_and_cost_center(asset.company)
|
||||||
|
depreciation_cost_center = asset.cost_center or depreciation_cost_center
|
||||||
|
|
||||||
|
idx = 1
|
||||||
|
if finance_book:
|
||||||
|
for d in asset.finance_books:
|
||||||
|
if d.finance_book == finance_book:
|
||||||
|
idx = d.idx
|
||||||
|
break
|
||||||
|
|
||||||
|
value_after_depreciation = (asset.finance_books[idx - 1].value_after_depreciation
|
||||||
|
if asset.calculate_depreciation else asset.value_after_depreciation)
|
||||||
|
accumulated_depr_amount = flt(asset.gross_purchase_amount) - flt(value_after_depreciation)
|
||||||
|
|
||||||
|
return fixed_asset_account, asset, depreciation_cost_center, accumulated_depr_account, accumulated_depr_amount, disposal_account, value_after_depreciation
|
||||||
|
|
||||||
|
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
|
||||||
|
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
||||||
|
gl_entries.append({
|
||||||
|
"account": disposal_account,
|
||||||
|
"cost_center": depreciation_cost_center,
|
||||||
|
debit_or_credit: abs(profit_amount),
|
||||||
|
debit_or_credit + "_in_account_currency": abs(profit_amount)
|
||||||
|
})
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_disposal_account_and_cost_center(company):
|
def get_disposal_account_and_cost_center(company):
|
||||||
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
|
disposal_account, depreciation_cost_center = frappe.get_cached_value('Company', company,
|
||||||
|
|||||||
@ -245,7 +245,10 @@ doc_events = {
|
|||||||
"erpnext.portal.utils.set_default_role"]
|
"erpnext.portal.utils.set_default_role"]
|
||||||
},
|
},
|
||||||
"Communication": {
|
"Communication": {
|
||||||
"on_update": "erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time"
|
"on_update": [
|
||||||
|
"erpnext.support.doctype.service_level_agreement.service_level_agreement.update_hold_time",
|
||||||
|
"erpnext.support.doctype.issue.issue.set_first_response_time"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
("Sales Taxes and Charges Template", 'Price List'): {
|
("Sales Taxes and Charges Template", 'Price List'): {
|
||||||
"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
|
"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
|
||||||
|
|||||||
@ -12,10 +12,14 @@ from frappe.desk.notifications import clear_notifications
|
|||||||
class TransactionDeletionRecord(Document):
|
class TransactionDeletionRecord(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
frappe.only_for('System Manager')
|
frappe.only_for('System Manager')
|
||||||
|
self.validate_doctypes_to_be_ignored()
|
||||||
|
|
||||||
|
def validate_doctypes_to_be_ignored(self):
|
||||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||||
for doctype in self.doctypes_to_be_ignored:
|
for doctype in self.doctypes_to_be_ignored:
|
||||||
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
if doctype.doctype_name not in doctypes_to_be_ignored_list:
|
||||||
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), title=_("Not Allowed"))
|
frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "),
|
||||||
|
title=_("Not Allowed"))
|
||||||
|
|
||||||
def before_submit(self):
|
def before_submit(self):
|
||||||
if not self.doctypes_to_be_ignored:
|
if not self.doctypes_to_be_ignored:
|
||||||
@ -23,54 +27,9 @@ class TransactionDeletionRecord(Document):
|
|||||||
|
|
||||||
self.delete_bins()
|
self.delete_bins()
|
||||||
self.delete_lead_addresses()
|
self.delete_lead_addresses()
|
||||||
|
self.reset_company_values()
|
||||||
company_obj = frappe.get_doc('Company', self.company)
|
|
||||||
# reset company values
|
|
||||||
company_obj.total_monthly_sales = 0
|
|
||||||
company_obj.sales_monthly_history = None
|
|
||||||
company_obj.save()
|
|
||||||
# Clear notification counts
|
|
||||||
clear_notifications()
|
clear_notifications()
|
||||||
|
self.delete_company_transactions()
|
||||||
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
|
||||||
tables = frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
|
|
||||||
doctypes_to_be_ignored_list = singles
|
|
||||||
for doctype in self.doctypes_to_be_ignored:
|
|
||||||
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
|
||||||
|
|
||||||
docfields = frappe.get_all('DocField',
|
|
||||||
filters = {
|
|
||||||
'fieldtype': 'Link',
|
|
||||||
'options': 'Company',
|
|
||||||
'parent': ['not in', doctypes_to_be_ignored_list]},
|
|
||||||
fields=['parent', 'fieldname'])
|
|
||||||
|
|
||||||
for docfield in docfields:
|
|
||||||
if docfield['parent'] != self.doctype:
|
|
||||||
no_of_docs = frappe.db.count(docfield['parent'], {
|
|
||||||
docfield['fieldname'] : self.company
|
|
||||||
})
|
|
||||||
|
|
||||||
if no_of_docs > 0:
|
|
||||||
self.delete_version_log(docfield['parent'], docfield['fieldname'])
|
|
||||||
self.delete_communications(docfield['parent'], docfield['fieldname'])
|
|
||||||
|
|
||||||
# populate DocTypes table
|
|
||||||
if docfield['parent'] not in tables:
|
|
||||||
self.append('doctypes', {
|
|
||||||
'doctype_name' : docfield['parent'],
|
|
||||||
'no_of_docs' : no_of_docs
|
|
||||||
})
|
|
||||||
|
|
||||||
# delete the docs linked with the specified company
|
|
||||||
frappe.db.delete(docfield['parent'], {
|
|
||||||
docfield['fieldname'] : self.company
|
|
||||||
})
|
|
||||||
|
|
||||||
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
|
||||||
if naming_series:
|
|
||||||
if '#' in naming_series:
|
|
||||||
self.update_naming_series(naming_series, docfield['parent'])
|
|
||||||
|
|
||||||
def populate_doctypes_to_be_ignored_table(self):
|
def populate_doctypes_to_be_ignored_table(self):
|
||||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||||
@ -79,6 +38,111 @@ class TransactionDeletionRecord(Document):
|
|||||||
'doctype_name' : doctype
|
'doctype_name' : doctype
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def delete_bins(self):
|
||||||
|
frappe.db.sql("""delete from tabBin where warehouse in
|
||||||
|
(select name from tabWarehouse where company=%s)""", self.company)
|
||||||
|
|
||||||
|
def delete_lead_addresses(self):
|
||||||
|
"""Delete addresses to which leads are linked"""
|
||||||
|
leads = frappe.get_all('Lead', filters={'company': self.company})
|
||||||
|
leads = ["'%s'" % row.get("name") for row in leads]
|
||||||
|
addresses = []
|
||||||
|
if leads:
|
||||||
|
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
||||||
|
in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
if addresses:
|
||||||
|
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
||||||
|
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||||
|
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||||
|
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
||||||
|
|
||||||
|
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||||
|
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
||||||
|
|
||||||
|
def reset_company_values(self):
|
||||||
|
company_obj = frappe.get_doc('Company', self.company)
|
||||||
|
company_obj.total_monthly_sales = 0
|
||||||
|
company_obj.sales_monthly_history = None
|
||||||
|
company_obj.save()
|
||||||
|
|
||||||
|
def delete_company_transactions(self):
|
||||||
|
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||||
|
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||||
|
|
||||||
|
tables = self.get_all_child_doctypes()
|
||||||
|
for docfield in docfields:
|
||||||
|
if docfield['parent'] != self.doctype:
|
||||||
|
no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
||||||
|
|
||||||
|
if no_of_docs > 0:
|
||||||
|
self.delete_version_log(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.delete_communications(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.populate_doctypes_table(tables, docfield['parent'], no_of_docs)
|
||||||
|
|
||||||
|
self.delete_child_tables(docfield['parent'], docfield['fieldname'])
|
||||||
|
self.delete_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
|
||||||
|
|
||||||
|
naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
|
||||||
|
if naming_series:
|
||||||
|
if '#' in naming_series:
|
||||||
|
self.update_naming_series(naming_series, docfield['parent'])
|
||||||
|
|
||||||
|
def get_doctypes_to_be_ignored_list(self):
|
||||||
|
singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
|
||||||
|
doctypes_to_be_ignored_list = singles
|
||||||
|
for doctype in self.doctypes_to_be_ignored:
|
||||||
|
doctypes_to_be_ignored_list.append(doctype.doctype_name)
|
||||||
|
|
||||||
|
return doctypes_to_be_ignored_list
|
||||||
|
|
||||||
|
def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
|
||||||
|
docfields = frappe.get_all('DocField',
|
||||||
|
filters = {
|
||||||
|
'fieldtype': 'Link',
|
||||||
|
'options': 'Company',
|
||||||
|
'parent': ['not in', doctypes_to_be_ignored_list]},
|
||||||
|
fields=['parent', 'fieldname'])
|
||||||
|
|
||||||
|
return docfields
|
||||||
|
|
||||||
|
def get_all_child_doctypes(self):
|
||||||
|
return frappe.get_all('DocType', filters = {'istable': 1}, pluck = 'name')
|
||||||
|
|
||||||
|
def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname):
|
||||||
|
return frappe.db.count(doctype, {company_fieldname : self.company})
|
||||||
|
|
||||||
|
def populate_doctypes_table(self, tables, doctype, no_of_docs):
|
||||||
|
if doctype not in tables:
|
||||||
|
self.append('doctypes', {
|
||||||
|
'doctype_name' : doctype,
|
||||||
|
'no_of_docs' : no_of_docs
|
||||||
|
})
|
||||||
|
|
||||||
|
def delete_child_tables(self, doctype, company_fieldname):
|
||||||
|
parent_docs_to_be_deleted = frappe.get_all(doctype, {
|
||||||
|
company_fieldname : self.company
|
||||||
|
}, pluck = 'name')
|
||||||
|
|
||||||
|
child_tables = frappe.get_all('DocField', filters = {
|
||||||
|
'fieldtype': 'Table',
|
||||||
|
'parent': doctype
|
||||||
|
}, pluck = 'options')
|
||||||
|
|
||||||
|
for table in child_tables:
|
||||||
|
frappe.db.delete(table, {
|
||||||
|
'parent': ['in', parent_docs_to_be_deleted]
|
||||||
|
})
|
||||||
|
|
||||||
|
def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
|
||||||
|
frappe.db.delete(doctype, {
|
||||||
|
company_fieldname : self.company
|
||||||
|
})
|
||||||
|
|
||||||
def update_naming_series(self, naming_series, doctype_name):
|
def update_naming_series(self, naming_series, doctype_name):
|
||||||
if '.' in naming_series:
|
if '.' in naming_series:
|
||||||
prefix, hashes = naming_series.rsplit('.', 1)
|
prefix, hashes = naming_series.rsplit('.', 1)
|
||||||
@ -107,32 +171,6 @@ class TransactionDeletionRecord(Document):
|
|||||||
|
|
||||||
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
|
frappe.delete_doc('Communication', communication_names, ignore_permissions=True)
|
||||||
|
|
||||||
def delete_bins(self):
|
|
||||||
frappe.db.sql("""delete from tabBin where warehouse in
|
|
||||||
(select name from tabWarehouse where company=%s)""", self.company)
|
|
||||||
|
|
||||||
def delete_lead_addresses(self):
|
|
||||||
"""Delete addresses to which leads are linked"""
|
|
||||||
leads = frappe.get_all('Lead', filters={'company': self.company})
|
|
||||||
leads = ["'%s'" % row.get("name") for row in leads]
|
|
||||||
addresses = []
|
|
||||||
if leads:
|
|
||||||
addresses = frappe.db.sql_list("""select parent from `tabDynamic Link` where link_name
|
|
||||||
in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
if addresses:
|
|
||||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
|
||||||
|
|
||||||
frappe.db.sql("""delete from tabAddress where name in ({addresses}) and
|
|
||||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
|
||||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
|
||||||
and dl1.link_doctype<>dl2.link_doctype)""".format(addresses=",".join(addresses)))
|
|
||||||
|
|
||||||
frappe.db.sql("""delete from `tabDynamic Link` where link_doctype='Lead'
|
|
||||||
and parenttype='Address' and link_name in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
frappe.db.sql("""update tabCustomer set lead_name=NULL where lead_name in ({leads})""".format(leads=",".join(leads)))
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctypes_to_be_ignored():
|
def get_doctypes_to_be_ignored():
|
||||||
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
|
doctypes_to_be_ignored_list = ['Account', 'Cost Center', 'Warehouse', 'Budget',
|
||||||
|
|||||||
@ -587,8 +587,8 @@ def make_item_variant():
|
|||||||
test_records = frappe.get_test_records('Item')
|
test_records = frappe.get_test_records('Item')
|
||||||
|
|
||||||
def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC",
|
def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC",
|
||||||
is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0,
|
is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, is_fixed_asset=0,
|
||||||
company="_Test Company"):
|
asset_category=None, company="_Test Company"):
|
||||||
if not frappe.db.exists("Item", item_code):
|
if not frappe.db.exists("Item", item_code):
|
||||||
item = frappe.new_doc("Item")
|
item = frappe.new_doc("Item")
|
||||||
item.item_code = item_code
|
item.item_code = item_code
|
||||||
@ -596,6 +596,8 @@ def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test W
|
|||||||
item.description = item_code
|
item.description = item_code
|
||||||
item.item_group = "All Item Groups"
|
item.item_group = "All Item Groups"
|
||||||
item.is_stock_item = is_stock_item
|
item.is_stock_item = is_stock_item
|
||||||
|
item.is_fixed_asset = is_fixed_asset
|
||||||
|
item.asset_category = asset_category
|
||||||
item.opening_stock = opening_stock
|
item.opening_stock = opening_stock
|
||||||
item.valuation_rate = valuation_rate
|
item.valuation_rate = valuation_rate
|
||||||
item.is_purchase_item = is_purchase_item
|
item.is_purchase_item = is_purchase_item
|
||||||
|
|||||||
@ -5,10 +5,10 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import json
|
import json
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe import utils
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import now_datetime
|
from frappe.utils import now_datetime, time_diff_in_seconds, get_datetime, date_diff
|
||||||
from datetime import datetime, timedelta
|
from frappe.core.utils import get_parent_doc
|
||||||
|
from datetime import timedelta
|
||||||
from frappe.model.mapper import get_mapped_doc
|
from frappe.model.mapper import get_mapped_doc
|
||||||
from frappe.utils.user import is_website_user
|
from frappe.utils.user import is_website_user
|
||||||
from frappe.email.inbox import link_communication_to_document
|
from frappe.email.inbox import link_communication_to_document
|
||||||
@ -212,6 +212,128 @@ def make_issue_from_communication(communication, ignore_communication_links=Fals
|
|||||||
|
|
||||||
return issue.name
|
return issue.name
|
||||||
|
|
||||||
|
def get_time_in_timedelta(time):
|
||||||
|
"""
|
||||||
|
Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)
|
||||||
|
"""
|
||||||
|
return timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
|
||||||
|
|
||||||
|
def set_first_response_time(communication, method):
|
||||||
|
if communication.get('reference_doctype') == "Issue":
|
||||||
|
issue = get_parent_doc(communication)
|
||||||
|
if is_first_response(issue):
|
||||||
|
first_response_time = calculate_first_response_time(issue, get_datetime(issue.first_responded_on))
|
||||||
|
issue.db_set("first_response_time", first_response_time)
|
||||||
|
|
||||||
|
def is_first_response(issue):
|
||||||
|
responses = frappe.get_all('Communication', filters = {'reference_name': issue.name, 'sent_or_received': 'Sent'})
|
||||||
|
if len(responses) == 1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def calculate_first_response_time(issue, first_responded_on):
|
||||||
|
issue_creation_date = issue.creation
|
||||||
|
issue_creation_time = get_time_in_seconds(issue_creation_date)
|
||||||
|
first_responded_on_in_seconds = get_time_in_seconds(first_responded_on)
|
||||||
|
support_hours = frappe.get_cached_doc("Service Level Agreement", issue.service_level_agreement).support_and_resolution
|
||||||
|
|
||||||
|
if issue_creation_date.day == first_responded_on.day:
|
||||||
|
if is_work_day(issue_creation_date, support_hours):
|
||||||
|
start_time, end_time = get_working_hours(issue_creation_date, support_hours)
|
||||||
|
|
||||||
|
# issue creation and response on the same day during working hours
|
||||||
|
if is_during_working_hours(issue_creation_date, support_hours) and is_during_working_hours(first_responded_on, support_hours):
|
||||||
|
return get_elapsed_time(issue_creation_date, first_responded_on)
|
||||||
|
|
||||||
|
# issue creation is during working hours, but first response was after working hours
|
||||||
|
elif is_during_working_hours(issue_creation_date, support_hours):
|
||||||
|
return get_elapsed_time(issue_creation_time, end_time)
|
||||||
|
|
||||||
|
# issue creation was before working hours but first response is during working hours
|
||||||
|
elif is_during_working_hours(first_responded_on, support_hours):
|
||||||
|
return get_elapsed_time(start_time, first_responded_on_in_seconds)
|
||||||
|
|
||||||
|
# both issue creation and first response were after working hours
|
||||||
|
else:
|
||||||
|
return 1.0 # this should ideally be zero, but it gets reset when the next response is sent if the value is zero
|
||||||
|
|
||||||
|
else:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
else:
|
||||||
|
# response on the next day
|
||||||
|
if date_diff(first_responded_on, issue_creation_date) == 1:
|
||||||
|
first_response_time = 0
|
||||||
|
else:
|
||||||
|
first_response_time = calculate_initial_frt(issue_creation_date, date_diff(first_responded_on, issue_creation_date)- 1, support_hours)
|
||||||
|
|
||||||
|
# time taken on day of issue creation
|
||||||
|
if is_work_day(issue_creation_date, support_hours):
|
||||||
|
start_time, end_time = get_working_hours(issue_creation_date, support_hours)
|
||||||
|
|
||||||
|
if is_during_working_hours(issue_creation_date, support_hours):
|
||||||
|
first_response_time += get_elapsed_time(issue_creation_time, end_time)
|
||||||
|
elif is_before_working_hours(issue_creation_date, support_hours):
|
||||||
|
first_response_time += get_elapsed_time(start_time, end_time)
|
||||||
|
|
||||||
|
# time taken on day of first response
|
||||||
|
if is_work_day(first_responded_on, support_hours):
|
||||||
|
start_time, end_time = get_working_hours(first_responded_on, support_hours)
|
||||||
|
|
||||||
|
if is_during_working_hours(first_responded_on, support_hours):
|
||||||
|
first_response_time += get_elapsed_time(start_time, first_responded_on_in_seconds)
|
||||||
|
elif not is_before_working_hours(first_responded_on, support_hours):
|
||||||
|
first_response_time += get_elapsed_time(start_time, end_time)
|
||||||
|
|
||||||
|
if first_response_time:
|
||||||
|
return first_response_time
|
||||||
|
else:
|
||||||
|
return 1.0
|
||||||
|
|
||||||
|
def get_time_in_seconds(date):
|
||||||
|
return timedelta(hours=date.hour, minutes=date.minute, seconds=date.second)
|
||||||
|
|
||||||
|
def get_working_hours(date, support_hours):
|
||||||
|
if is_work_day(date, support_hours):
|
||||||
|
weekday = frappe.utils.get_weekday(date)
|
||||||
|
for day in support_hours:
|
||||||
|
if day.workday == weekday:
|
||||||
|
return day.start_time, day.end_time
|
||||||
|
|
||||||
|
def is_work_day(date, support_hours):
|
||||||
|
weekday = frappe.utils.get_weekday(date)
|
||||||
|
for day in support_hours:
|
||||||
|
if day.workday == weekday:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_during_working_hours(date, support_hours):
|
||||||
|
start_time, end_time = get_working_hours(date, support_hours)
|
||||||
|
time = get_time_in_seconds(date)
|
||||||
|
if time >= start_time and time <= end_time:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_elapsed_time(start_time, end_time):
|
||||||
|
return round(time_diff_in_seconds(end_time, start_time), 2)
|
||||||
|
|
||||||
|
def calculate_initial_frt(issue_creation_date, days_in_between, support_hours):
|
||||||
|
initial_frt = 0
|
||||||
|
for i in range(days_in_between):
|
||||||
|
date = issue_creation_date + timedelta(days = (i+1))
|
||||||
|
if is_work_day(date, support_hours):
|
||||||
|
start_time, end_time = get_working_hours(date, support_hours)
|
||||||
|
initial_frt += get_elapsed_time(start_time, end_time)
|
||||||
|
|
||||||
|
return initial_frt
|
||||||
|
|
||||||
|
def is_before_working_hours(date, support_hours):
|
||||||
|
start_time, end_time = get_working_hours(date, support_hours)
|
||||||
|
time = get_time_in_seconds(date)
|
||||||
|
if time < start_time:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def get_holidays(holiday_list_name):
|
def get_holidays(holiday_list_name):
|
||||||
holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
|
holiday_list = frappe.get_cached_doc("Holiday List", holiday_list_name)
|
||||||
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
|
holidays = [holiday.holiday_date for holiday in holiday_list.holidays]
|
||||||
|
|||||||
@ -5,16 +5,18 @@ from __future__ import unicode_literals
|
|||||||
import frappe
|
import frappe
|
||||||
import unittest
|
import unittest
|
||||||
from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
|
from erpnext.support.doctype.service_level_agreement.test_service_level_agreement import create_service_level_agreements_for_issues
|
||||||
from frappe.utils import now_datetime, get_datetime, flt
|
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||||
|
from frappe.utils import get_datetime, flt
|
||||||
import datetime
|
import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
class TestIssue(unittest.TestCase):
|
class TestSetUp(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
frappe.db.sql("delete from `tabService Level Agreement`")
|
frappe.db.sql("delete from `tabService Level Agreement`")
|
||||||
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
|
frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
|
||||||
create_service_level_agreements_for_issues()
|
create_service_level_agreements_for_issues()
|
||||||
|
|
||||||
|
class TestIssue(TestSetUp):
|
||||||
def test_response_time_and_resolution_time_based_on_different_sla(self):
|
def test_response_time_and_resolution_time_based_on_different_sla(self):
|
||||||
creation = datetime.datetime(2019, 3, 4, 12, 0)
|
creation = datetime.datetime(2019, 3, 4, 12, 0)
|
||||||
|
|
||||||
@ -133,6 +135,223 @@ class TestIssue(unittest.TestCase):
|
|||||||
issue.reload()
|
issue.reload()
|
||||||
self.assertEqual(flt(issue.total_hold_time, 2), 2700)
|
self.assertEqual(flt(issue.total_hold_time, 2), 2700)
|
||||||
|
|
||||||
|
class TestFirstResponseTime(TestSetUp):
|
||||||
|
# working hours used in all cases: Mon-Fri, 10am to 6pm
|
||||||
|
# all dates are in the mm-dd-yyyy format
|
||||||
|
|
||||||
|
# issue creation and first response are on the same day
|
||||||
|
def test_first_response_time_case1(self):
|
||||||
|
"""
|
||||||
|
Test frt when issue creation and first response are during working hours on the same day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 11:00"), get_datetime("06-28-2021 12:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 3600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case2(self):
|
||||||
|
"""
|
||||||
|
Test frt when issue creation was during working hours, but first response is sent after working hours on the same day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-28-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 21600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case3(self):
|
||||||
|
"""
|
||||||
|
Test frt when issue creation was before working hours but first response is sent during working hours on the same day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-28-2021 12:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 7200.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case4(self):
|
||||||
|
"""
|
||||||
|
Test frt when both issue creation and first response were after working hours on the same day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 19:00"), get_datetime("06-28-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 1.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case5(self):
|
||||||
|
"""
|
||||||
|
Test frt when both issue creation and first response are on the same day, but it's not a work day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-27-2021 10:00"), get_datetime("06-27-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 1.0)
|
||||||
|
|
||||||
|
# issue creation and first response are on consecutive days
|
||||||
|
def test_first_response_time_case6(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is also sent before working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 6:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 28800.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case7(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is sent during working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 32400.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case8(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is sent after working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 57600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case9(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is sent on the next day, which is not a work day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-25-2021 6:00"), get_datetime("06-26-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 28800.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case10(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is sent before working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 6:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 21600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case11(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is also sent during working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 25200.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case12(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is sent after working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 50400.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case13(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is sent on the next day, which is not a work day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-25-2021 12:00"), get_datetime("06-26-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 21600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case14(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is sent before working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 6:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 1.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case15(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is sent during working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 3600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case16(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is also sent after working hours, but on the next day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 28800.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case17(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is sent on the next day, which is not a work day.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-25-2021 20:00"), get_datetime("06-26-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 1.0)
|
||||||
|
|
||||||
|
# issue creation and first response are a few days apart
|
||||||
|
def test_first_response_time_case18(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is also sent before working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 6:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 86400.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case19(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is sent during working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 90000.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case20(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is sent after working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 115200.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case21(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created before working hours and the first response is sent after a few days, on a holiday.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-25-2021 6:00"), get_datetime("06-27-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 28800.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case22(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is sent before working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 6:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 79200.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case23(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is also sent during working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 82800.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case24(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is sent after working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 108000.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case25(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created during working hours and the first response is sent after a few days, on a holiday.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-25-2021 12:00"), get_datetime("06-27-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 21600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case26(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is sent before working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 6:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 57600.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case27(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is sent during working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 61200.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case28(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is also sent after working hours, but after a few days.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 20:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 86400.0)
|
||||||
|
|
||||||
|
def test_first_response_time_case29(self):
|
||||||
|
"""
|
||||||
|
Test frt when the issue was created after working hours and the first response is sent after a few days, on a holiday.
|
||||||
|
"""
|
||||||
|
issue = create_issue_and_communication(get_datetime("06-25-2021 20:00"), get_datetime("06-27-2021 11:00"))
|
||||||
|
self.assertEqual(issue.first_response_time, 1.0)
|
||||||
|
|
||||||
|
def create_issue_and_communication(issue_creation, first_responded_on):
|
||||||
|
issue = make_issue(issue_creation, index=1)
|
||||||
|
sender = create_user("test@admin.com")
|
||||||
|
create_communication(issue.name, sender.email, "Sent", first_responded_on)
|
||||||
|
issue.reload()
|
||||||
|
|
||||||
|
return issue
|
||||||
|
|
||||||
def make_issue(creation=None, customer=None, index=0, priority=None, issue_type=None):
|
def make_issue(creation=None, customer=None, index=0, priority=None, issue_type=None):
|
||||||
issue = frappe.get_doc({
|
issue = frappe.get_doc({
|
||||||
@ -185,7 +404,7 @@ def create_territory(territory):
|
|||||||
|
|
||||||
|
|
||||||
def create_communication(reference_name, sender, sent_or_received, creation):
|
def create_communication(reference_name, sender, sent_or_received, creation):
|
||||||
issue = frappe.get_doc({
|
communication = frappe.get_doc({
|
||||||
"doctype": "Communication",
|
"doctype": "Communication",
|
||||||
"communication_type": "Communication",
|
"communication_type": "Communication",
|
||||||
"communication_medium": "Email",
|
"communication_medium": "Email",
|
||||||
@ -199,4 +418,4 @@ def create_communication(reference_name, sender, sent_or_received, creation):
|
|||||||
"creation": creation,
|
"creation": creation,
|
||||||
"reference_name": reference_name
|
"reference_name": reference_name
|
||||||
})
|
})
|
||||||
issue.save()
|
communication.save()
|
||||||
@ -339,16 +339,6 @@ def create_service_level_agreement(default_service_level_agreement, holiday_list
|
|||||||
"workday": "Friday",
|
"workday": "Friday",
|
||||||
"start_time": "10:00:00",
|
"start_time": "10:00:00",
|
||||||
"end_time": "18:00:00",
|
"end_time": "18:00:00",
|
||||||
},
|
|
||||||
{
|
|
||||||
"workday": "Saturday",
|
|
||||||
"start_time": "10:00:00",
|
|
||||||
"end_time": "18:00:00",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"workday": "Sunday",
|
|
||||||
"start_time": "10:00:00",
|
|
||||||
"end_time": "18:00:00",
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user