Merge branch 'develop' into salary-slip-fix--develop

This commit is contained in:
Jannat Patel 2021-07-08 13:44:12 +05:30 committed by GitHub
commit 42214999a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 894 additions and 201 deletions

View File

@ -301,17 +301,21 @@ def process_deferred_accounting(posting_date=None):
start_date = add_months(today(), -1)
end_date = add_days(today(), -1)
for record_type in ('Income', 'Expense'):
doc = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type
))
companies = frappe.get_all('Company')
doc.insert()
doc.submit()
for company in companies:
for record_type in ('Income', 'Expense'):
doc = frappe.get_doc(dict(
doctype='Process Deferred Accounting',
company=company.name,
posting_date=posting_date,
start_date=start_date,
end_date=end_date,
type=record_type
))
doc.insert()
doc.submit()
def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):

View File

@ -51,7 +51,7 @@ class BankStatementImport(DataImport):
self.import_file, self.google_sheets_url
)
if 'Bank Account' not in json.dumps(preview):
if 'Bank Account' not in json.dumps(preview['columns']):
frappe.throw(_("Please add the Bank Account column"))
from frappe.core.page.background_jobs.background_jobs import get_info

View File

@ -207,10 +207,9 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
@frappe.whitelist()
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
billing_email = frappe.db.sql("""
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
c.is_billing_contact=1 \
order by c.creation desc""")
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent
WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1
order by c.creation desc""", customer_name)
if len(billing_email) == 0 or (billing_email[0][0] is None):
if billing_and_primary:

View File

@ -231,25 +231,25 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_purchase_invoice_with_exchange_rate_difference(self):
pr = make_purchase_receipt(currency = "USD", conversion_rate = 70)
pi = make_purchase_invoice(currency = "USD", conversion_rate = 80, do_not_save = "True")
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice as create_purchase_invoice
pi.items[0].purchase_receipt = pr.name
pi.items[0].pr_detail = pr.items[0].name
pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse='Stores - TCP1',
currency = "USD", conversion_rate = 70)
pi = create_purchase_invoice(pr.name)
pi.conversion_rate = 80
pi.insert()
pi.submit()
# fetching the latest GL Entry with 'Exchange Gain/Loss - _TC' account
gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - _TC'})
voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no')
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
self.assertEqual(pi.name, voucher_no)
exchange_gain_loss_amount = frappe.get_value('GL Entry', gl_entries[0]['name'], 'debit')
# fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pi.name}, 'debit')
discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
self.assertEqual(exchange_gain_loss_amount, discrepancy_caused_by_exchange_rate_diff)
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1])
@ -1031,21 +1031,21 @@ class TestPurchaseInvoice(unittest.TestCase):
# Check GLE for Purchase Invoice
# Zero net effect on final TDS Payable on invoice
expected_gle = [
['_Test Account Cost for Goods Sold - _TC', 30000, 0],
['_Test Account Excise Duty - _TC', 0, 3000],
['Creditors - _TC', 0, 27000],
['TDS Payable - _TC', 3000, 3000]
['_Test Account Cost for Goods Sold - _TC', 30000],
['_Test Account Excise Duty - _TC', -3000],
['Creditors - _TC', -27000],
['TDS Payable - _TC', 0]
]
gl_entries = frappe.db.sql("""select account, debit, credit
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no=%s
group by account
order by account asc""", (purchase_invoice.name), as_dict=1)
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.debit)
self.assertEqual(expected_gle[i][2], gle.credit)
self.assertEqual(expected_gle[i][1], gle.amount)
def update_tax_witholding_category(company, account, date):
from erpnext.accounts.utils import get_fiscal_year

View File

@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
from `tabGL Entry`
where party_type = %s and party=%s
and is_cancelled = 0
group by company""", (party_type, party)))
for d in companies:

View File

@ -397,6 +397,7 @@ def get_chart_data(filters, columns, data):
{'name': 'Budget', 'chartType': 'bar', 'values': budget_values},
{'name': 'Actual Expense', 'chartType': 'bar', 'values': actual_values}
]
}
},
'type' : 'bar'
}

View File

@ -380,7 +380,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g
gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
acc.account_name, acc.account_number
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions),
{

View File

@ -82,24 +82,46 @@ frappe.ui.form.on('Asset', {
if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
frm.add_custom_button("Transfer Asset", function() {
erpnext.asset.transfer_asset(frm);
});
}, __("Manage"));
frm.add_custom_button("Scrap Asset", function() {
erpnext.asset.scrap_asset(frm);
});
}, __("Manage"));
frm.add_custom_button("Sell Asset", function() {
frm.trigger("make_sales_invoice");
});
}, __("Manage"));
} else if (frm.doc.status=='Scrapped') {
frm.add_custom_button("Restore Asset", function() {
erpnext.asset.restore_asset(frm);
});
}, __("Manage"));
}
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
frm.add_custom_button(__("Maintain Asset"), function() {
frm.trigger("create_asset_maintenance");
}, __("Manage"));
}
frm.add_custom_button(__("Repair Asset"), function() {
frm.trigger("create_asset_repair");
}, __("Manage"));
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Adjust Asset Value"), function() {
frm.trigger("create_asset_adjustment");
}, __("Manage"));
}
if (!frm.doc.calculate_depreciation) {
frm.add_custom_button(__("Create Depreciation Entry"), function() {
frm.trigger("make_journal_entry");
}, __("Manage"));
}
if (frm.doc.purchase_receipt || !frm.doc.is_existing_asset) {
frm.add_custom_button("General Ledger", function() {
frm.add_custom_button("View General Ledger", function() {
frappe.route_options = {
"voucher_no": frm.doc.name,
"from_date": frm.doc.available_for_use_date,
@ -107,27 +129,9 @@ frappe.ui.form.on('Asset', {
"company": frm.doc.company
};
frappe.set_route("query-report", "General Ledger");
});
}, __("Manage"));
}
if (frm.doc.maintenance_required && !frm.doc.maintenance_schedule) {
frm.add_custom_button(__("Asset Maintenance"), function() {
frm.trigger("create_asset_maintenance");
}, __('Create'));
}
if (frm.doc.status != 'Fully Depreciated') {
frm.add_custom_button(__("Asset Value Adjustment"), function() {
frm.trigger("create_asset_adjustment");
}, __('Create'));
}
if (!frm.doc.calculate_depreciation) {
frm.add_custom_button(__("Depreciation Entry"), function() {
frm.trigger("make_journal_entry");
}, __('Create'));
}
frm.page.set_inner_btn_group_as_primary(__('Create'));
frm.trigger("setup_chart");
}
@ -304,6 +308,20 @@ frappe.ui.form.on('Asset', {
})
},
create_asset_repair: function(frm) {
frappe.call({
args: {
"asset": frm.doc.name,
"asset_name": frm.doc.asset_name
},
method: "erpnext.assets.doctype.asset.asset.create_asset_repair",
callback: function(r) {
var doclist = frappe.model.sync(r.message);
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
}
});
},
create_asset_adjustment: function(frm) {
frappe.call({
args: {

View File

@ -502,7 +502,7 @@
"link_fieldname": "asset"
}
],
"modified": "2021-01-22 12:38:59.091510",
"modified": "2021-06-24 14:58:51.097908",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset",

View File

@ -168,17 +168,24 @@ class Asset(AccountsController):
d.precision("rate_of_depreciation"))
def make_depreciation_schedule(self):
if 'Manual' not in [d.depreciation_method for d in self.finance_books]:
if 'Manual' not in [d.depreciation_method for d in self.finance_books] and not self.schedules:
self.schedules = []
if self.get("schedules") or not self.available_for_use_date:
if not self.available_for_use_date:
return
for d in self.get('finance_books'):
self.validate_asset_finance_books(d)
start = self.clear_depreciation_schedule()
value_after_depreciation = (flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation))
# value_after_depreciation - current Asset value
if d.value_after_depreciation:
value_after_depreciation = (flt(d.value_after_depreciation) -
flt(self.opening_accumulated_depreciation))
else:
value_after_depreciation = (flt(self.gross_purchase_amount) -
flt(self.opening_accumulated_depreciation))
d.value_after_depreciation = value_after_depreciation
@ -191,7 +198,7 @@ class Asset(AccountsController):
number_of_pending_depreciations += 1
skip_row = False
for n in range(number_of_pending_depreciations):
for n in range(start, number_of_pending_depreciations):
# If depreciation is already completed (for double declining balance)
if skip_row: continue
@ -216,11 +223,13 @@ class Asset(AccountsController):
# For last row
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
if not self.flags.increase_in_asset_life:
# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
self.to_date = add_months(self.available_for_use_date,
n * cint(d.frequency_of_depreciation))
depreciation_amount, days, months = self.get_pro_rata_amt(d,
depreciation_amount, schedule_date, to_date)
depreciation_amount, schedule_date, self.to_date)
monthly_schedule_date = add_months(schedule_date, 1)
@ -284,10 +293,23 @@ class Asset(AccountsController):
"finance_book_id": d.idx
})
# used when depreciation schedule needs to be modified due to increase in asset life
def clear_depreciation_schedule(self):
start = 0
for n in range(len(self.schedules)):
if not self.schedules[n].journal_entry:
del self.schedules[n:]
start = n
break
return start
# if it returns True, depreciation_amount will not be equal for the first and last rows
def check_is_pro_rata(self, row):
has_pro_rata = False
days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1
# if frequency_of_depreciation is 12 months, total_days = 365
total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
if days < total_days:
@ -346,11 +368,12 @@ class Asset(AccountsController):
if d.finance_book_id not in finance_books:
accumulated_depreciation = flt(self.opening_accumulated_depreciation)
value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id))
finance_books.append(d.finance_book_id)
finance_books.append(int(d.finance_book_id))
depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
value_after_depreciation -= flt(depreciation_amount)
# for the last row, if depreciation method = Straight Line
if straight_line_idx and i == max(straight_line_idx) - 1:
book = self.get('finance_books')[cint(d.finance_book_id) - 1]
depreciation_amount += flt(value_after_depreciation -
@ -625,9 +648,18 @@ def create_asset_maintenance(asset, item_code, item_name, asset_category, compan
})
return asset_maintenance
@frappe.whitelist()
def create_asset_repair(asset, asset_name):
asset_repair = frappe.new_doc("Asset Repair")
asset_repair.update({
"asset": asset,
"asset_name": asset_name
})
return asset_repair
@frappe.whitelist()
def create_asset_adjustment(asset, asset_category, company):
asset_maintenance = frappe.new_doc("Asset Value Adjustment")
asset_maintenance = frappe.get_doc("Asset Value Adjustment")
asset_maintenance.update({
"asset": asset,
"company": company,
@ -757,8 +789,15 @@ def get_depreciation_amount(asset, depreciable_value, row):
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
if row.depreciation_method in ("Straight Line", "Manual"):
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
else:
depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))

View File

@ -125,7 +125,6 @@ class TestAsset(unittest.TestCase):
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.insert()
self.assertEqual(asset.status, "Draft")
asset.save()
expected_schedules = [
@ -154,9 +153,8 @@ class TestAsset(unittest.TestCase):
"frequency_of_depreciation": 12,
"depreciation_start_date": '2030-12-31'
})
asset.insert()
self.assertEqual(asset.status, "Draft")
asset.save()
self.assertEqual(asset.status, "Draft")
expected_schedules = [
['2030-12-31', 66667.00, 66667.00],
@ -185,7 +183,7 @@ class TestAsset(unittest.TestCase):
"frequency_of_depreciation": 12,
"depreciation_start_date": "2030-12-31"
})
asset.insert()
asset.save()
self.assertEqual(asset.status, "Draft")
expected_schedules = [
@ -216,7 +214,6 @@ class TestAsset(unittest.TestCase):
"depreciation_start_date": "2030-12-31"
})
asset.insert()
asset.save()
expected_schedules = [
@ -247,7 +244,6 @@ class TestAsset(unittest.TestCase):
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
})
asset.insert()
asset.submit()
asset.load_from_db()
self.assertEqual(asset.status, "Submitted")
@ -350,7 +346,6 @@ class TestAsset(unittest.TestCase):
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
})
asset.insert()
asset.submit()
post_depreciation_entries(date="2021-01-01")
@ -380,7 +375,6 @@ class TestAsset(unittest.TestCase):
"total_number_of_depreciations": 10,
"frequency_of_depreciation": 1
})
asset.insert()
asset.submit()
post_depreciation_entries(date=add_months('2020-01-01', 4))
@ -424,7 +418,6 @@ class TestAsset(unittest.TestCase):
"frequency_of_depreciation": 10,
"depreciation_start_date": "2020-12-31"
})
asset.insert()
asset.submit()
post_depreciation_entries(date="2021-01-01")
@ -468,7 +461,7 @@ class TestAsset(unittest.TestCase):
"total_number_of_depreciations": 3,
"frequency_of_depreciation": 10
})
asset.insert()
asset.save()
accumulated_depreciation_after_full_schedule = \
max(d.accumulated_depreciation_amount for d in asset.get("schedules"))
@ -699,7 +692,7 @@ def create_asset(**args):
"item_code": args.item_code or "Macbook Pro",
"company": args.company or"_Test Company",
"purchase_date": "2015-01-01",
"calculate_depreciation": 0,
"calculate_depreciation": args.calculate_depreciation or 0,
"gross_purchase_amount": 100000,
"purchase_receipt_amount": 100000,
"expected_value_after_useful_life": 10000,
@ -707,9 +700,16 @@ def create_asset(**args):
"available_for_use_date": "2020-06-06",
"location": "Test Location",
"asset_owner": "Company",
"is_existing_asset": args.is_existing_asset or 0
"is_existing_asset": 1
})
if asset.calculate_depreciation:
asset.append("finance_books", {
"depreciation_method": "Straight Line",
"frequency_of_depreciation": 12,
"total_number_of_depreciations": 5
})
try:
asset.save()
except frappe.DuplicateEntryError:

View File

@ -67,7 +67,6 @@
{
"fieldname": "value_after_depreciation",
"fieldtype": "Currency",
"hidden": 1,
"label": "Value After Depreciation",
"no_copy": 1,
"options": "Company:company:default_currency",
@ -85,7 +84,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2020-11-05 16:30:09.213479",
"modified": "2021-06-17 12:59:05.743683",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Finance Book",

View File

@ -2,6 +2,45 @@
// For license information, please see license.txt
frappe.ui.form.on('Asset Repair', {
setup: function(frm) {
frm.fields_dict.cost_center.get_query = function(doc) {
return {
filters: {
'is_group': 0,
'company': doc.company
}
};
};
frm.fields_dict.project.get_query = function(doc) {
return {
filters: {
'company': doc.company
}
};
};
frm.fields_dict.warehouse.get_query = function(doc) {
return {
filters: {
'is_group': 0,
'company': doc.company
}
};
};
},
refresh: function(frm) {
if (frm.doc.docstatus) {
frm.add_custom_button("View General Ledger", function() {
frappe.route_options = {
"voucher_no": frm.doc.name
};
frappe.set_route("query-report", "General Ledger");
});
}
},
repair_status: (frm) => {
if (frm.doc.completion_date && frm.doc.repair_status == "Completed") {
frappe.call ({
@ -17,5 +56,16 @@ frappe.ui.form.on('Asset Repair', {
}
});
}
if (frm.doc.repair_status == "Completed") {
frm.set_value('completion_date', frappe.datetime.now_datetime());
}
}
});
frappe.ui.form.on('Asset Repair Consumed Item', {
consumed_quantity: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
},
});

View File

@ -7,38 +7,43 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"asset_name",
"asset",
"company",
"column_break_2",
"item_code",
"item_name",
"asset_name",
"naming_series",
"section_break_5",
"failure_date",
"assign_to",
"assign_to_name",
"repair_status",
"column_break_6",
"completion_date",
"repair_status",
"accounting_dimensions_section",
"cost_center",
"column_break_14",
"project",
"accounting_details",
"repair_cost",
"capitalize_repair_cost",
"stock_consumption",
"column_break_8",
"purchase_invoice",
"stock_consumption_details_section",
"warehouse",
"stock_items",
"total_repair_cost",
"stock_entry",
"asset_depreciation_details_section",
"increase_in_asset_life",
"section_break_9",
"description",
"column_break_9",
"actions_performed",
"section_break_17",
"section_break_23",
"downtime",
"column_break_19",
"amended_from"
],
"fields": [
{
"columns": 1,
"fieldname": "asset_name",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Asset",
"options": "Asset",
"reqd": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Select",
@ -50,18 +55,6 @@
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
{
"fetch_from": "asset_name.item_code",
"fieldname": "item_code",
"fieldtype": "Read Only",
"label": "Item Code"
},
{
"fetch_from": "asset_name.item_name",
"fieldname": "item_name",
"fieldtype": "Read Only",
"label": "Item Name"
},
{
"fieldname": "section_break_5",
"fieldtype": "Section Break",
@ -74,33 +67,20 @@
"label": "Failure Date",
"reqd": 1
},
{
"allow_on_submit": 1,
"fieldname": "assign_to",
"fieldtype": "Link",
"label": "Assign To",
"options": "User"
},
{
"allow_on_submit": 1,
"fetch_from": "assign_to.full_name",
"fieldname": "assign_to_name",
"fieldtype": "Read Only",
"label": "Assign To Name"
},
{
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"depends_on": "eval:!doc.__islocal",
"fieldname": "completion_date",
"fieldtype": "Datetime",
"label": "Completion Date"
"label": "Completion Date",
"no_copy": 1
},
{
"allow_on_submit": 1,
"default": "Pending",
"depends_on": "eval:!doc.__islocal",
"fieldname": "repair_status",
"fieldtype": "Select",
"label": "Repair Status",
@ -116,25 +96,18 @@
{
"fieldname": "description",
"fieldtype": "Long Text",
"label": "Error Description",
"reqd": 1
"label": "Error Description"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "actions_performed",
"fieldtype": "Long Text",
"label": "Actions performed"
},
{
"fieldname": "section_break_17",
"fieldtype": "Section Break"
},
{
"allow_on_submit": 1,
"fieldname": "downtime",
"fieldtype": "Data",
"in_list_view": 1,
@ -146,7 +119,7 @@
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"default": "0",
"fieldname": "repair_cost",
"fieldtype": "Currency",
"label": "Repair Cost"
@ -159,12 +132,139 @@
"options": "Asset Repair",
"print_hide": 1,
"read_only": 1
},
{
"columns": 1,
"fieldname": "asset",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Asset",
"options": "Asset",
"reqd": 1
},
{
"fetch_from": "asset.asset_name",
"fieldname": "asset_name",
"fieldtype": "Read Only",
"label": "Asset Name"
},
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval:!doc.__islocal",
"fieldname": "capitalize_repair_cost",
"fieldtype": "Check",
"label": "Capitalize Repair Cost"
},
{
"fieldname": "accounting_details",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fieldname": "stock_items",
"fieldtype": "Table",
"label": "Stock Items",
"mandatory_depends_on": "stock_consumption",
"options": "Asset Repair Consumed Item"
},
{
"fieldname": "section_break_23",
"fieldtype": "Section Break"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "column_break_14",
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval:!doc.__islocal",
"fieldname": "stock_consumption",
"fieldtype": "Check",
"label": "Stock Consumed During Repair"
},
{
"depends_on": "stock_consumption",
"fieldname": "stock_consumption_details_section",
"fieldtype": "Section Break",
"label": "Stock Consumption Details"
},
{
"depends_on": "eval: doc.stock_consumption && doc.total_repair_cost > 0",
"description": "Sum of Repair Cost and Value of Consumed Stock Items.",
"fieldname": "total_repair_cost",
"fieldtype": "Currency",
"label": "Total Repair Cost",
"read_only": 1
},
{
"depends_on": "stock_consumption",
"fieldname": "warehouse",
"fieldtype": "Link",
"label": "Warehouse",
"options": "Warehouse"
},
{
"depends_on": "capitalize_repair_cost",
"fieldname": "asset_depreciation_details_section",
"fieldtype": "Section Break",
"label": "Asset Depreciation Details"
},
{
"fieldname": "increase_in_asset_life",
"fieldtype": "Int",
"label": "Increase In Asset Life(Months)",
"no_copy": 1
},
{
"depends_on": "eval:!doc.__islocal",
"fieldname": "purchase_invoice",
"fieldtype": "Link",
"label": "Purchase Invoice",
"mandatory_depends_on": "eval: doc.repair_status == 'Completed' && doc.repair_cost > 0",
"no_copy": 1,
"options": "Purchase Invoice"
},
{
"fetch_from": "asset.company",
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company"
},
{
"fieldname": "stock_entry",
"fieldtype": "Link",
"label": "Stock Entry",
"options": "Stock Entry",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-01-22 15:08:12.495850",
"modified": "2021-06-25 13:14:38.307723",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair",
@ -203,6 +303,7 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "asset_name",
"track_changes": 1,
"track_seen": 1
}

View File

@ -5,16 +5,252 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import time_diff_in_hours
from frappe.model.document import Document
from frappe.utils import time_diff_in_hours, getdate, add_months, flt, cint
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.assets.doctype.asset.asset import get_asset_account
from erpnext.controllers.accounts_controller import AccountsController
class AssetRepair(Document):
class AssetRepair(AccountsController):
def validate(self):
if self.repair_status == "Completed" and not self.completion_date:
frappe.throw(_("Please select Completion Date for Completed Repair"))
self.asset_doc = frappe.get_doc('Asset', self.asset)
self.update_status()
if self.get('stock_items'):
self.set_total_value()
self.calculate_total_repair_cost()
def update_status(self):
if self.repair_status == 'Pending':
frappe.db.set_value('Asset', self.asset, 'status', 'Out of Order')
else:
self.asset_doc.set_status()
def set_total_value(self):
for item in self.get('stock_items'):
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
def calculate_total_repair_cost(self):
self.total_repair_cost = flt(self.repair_cost)
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
self.total_repair_cost += total_value_of_stock_consumed
def before_submit(self):
self.check_repair_status()
if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
self.increase_asset_value()
if self.get('stock_consumption'):
self.check_for_stock_items_and_warehouse()
self.decrease_stock_quantity()
if self.get('capitalize_repair_cost'):
self.make_gl_entries()
if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
self.modify_depreciation_schedule()
self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
def before_cancel(self):
self.asset_doc = frappe.get_doc('Asset', self.asset)
if self.get('stock_consumption') or self.get('capitalize_repair_cost'):
self.decrease_asset_value()
if self.get('stock_consumption'):
self.increase_stock_quantity()
if self.get('capitalize_repair_cost'):
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
self.make_gl_entries(cancel=True)
if frappe.db.get_value('Asset', self.asset, 'calculate_depreciation') and self.increase_in_asset_life:
self.revert_depreciation_schedule_on_cancellation()
self.asset_doc.flags.ignore_validate_update_after_submit = True
self.asset_doc.prepare_depreciation_data()
self.asset_doc.save()
def check_repair_status(self):
if self.repair_status == "Pending":
frappe.throw(_("Please update Repair Status."))
def check_for_stock_items_and_warehouse(self):
if not self.get('stock_items'):
frappe.throw(_("Please enter Stock Items consumed during the Repair."), title=_("Missing Items"))
if not self.warehouse:
frappe.throw(_("Please enter Warehouse from which Stock Items consumed during the Repair were taken."), title=_("Missing Warehouse"))
def increase_asset_value(self):
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
if self.asset_doc.calculate_depreciation:
for row in self.asset_doc.finance_books:
row.value_after_depreciation += total_value_of_stock_consumed
if self.capitalize_repair_cost:
row.value_after_depreciation += self.repair_cost
def decrease_asset_value(self):
total_value_of_stock_consumed = self.get_total_value_of_stock_consumed()
if self.asset_doc.calculate_depreciation:
for row in self.asset_doc.finance_books:
row.value_after_depreciation -= total_value_of_stock_consumed
if self.capitalize_repair_cost:
row.value_after_depreciation -= self.repair_cost
def get_total_value_of_stock_consumed(self):
total_value_of_stock_consumed = 0
if self.get('stock_consumption'):
for item in self.get('stock_items'):
total_value_of_stock_consumed += item.total_value
return total_value_of_stock_consumed
def decrease_stock_quantity(self):
stock_entry = frappe.get_doc({
"doctype": "Stock Entry",
"stock_entry_type": "Material Issue",
"company": self.company
})
for stock_item in self.get('stock_items'):
stock_entry.append('items', {
"s_warehouse": self.warehouse,
"item_code": stock_item.item,
"qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate
})
stock_entry.insert()
stock_entry.submit()
self.db_set('stock_entry', stock_entry.name)
def increase_stock_quantity(self):
stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
stock_entry.flags.ignore_links = True
stock_entry.cancel()
def make_gl_entries(self, cancel=False):
if flt(self.repair_cost) > 0:
gl_entries = self.get_gl_entries()
make_gl_entries(gl_entries, cancel)
def get_gl_entries(self):
gl_entries = []
repair_and_maintenance_account = frappe.db.get_value('Company', self.company, 'repair_and_maintenance_account')
fixed_asset_account = get_asset_account("fixed_asset_account", asset=self.asset, company=self.company)
expense_account = frappe.get_doc('Purchase Invoice', self.purchase_invoice).items[0].expense_account
gl_entries.append(
self.get_gl_dict({
"account": expense_account,
"credit": self.repair_cost,
"credit_in_account_currency": self.repair_cost,
"against": repair_and_maintenance_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"company": self.company
}, item=self)
)
if self.get('stock_consumption'):
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
stock_entry = frappe.get_doc('Stock Entry', self.stock_entry)
for item in stock_entry.items:
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"credit": item.amount,
"credit_in_account_currency": item.amount,
"against": repair_and_maintenance_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"company": self.company
}, item=self)
)
gl_entries.append(
self.get_gl_dict({
"account": fixed_asset_account,
"debit": self.total_repair_cost,
"debit_in_account_currency": self.total_repair_cost,
"against": expense_account,
"voucher_type": self.doctype,
"voucher_no": self.name,
"cost_center": self.cost_center,
"posting_date": getdate(),
"against_voucher_type": "Purchase Invoice",
"against_voucher": self.purchase_invoice,
"company": self.company
}, item=self)
)
return gl_entries
def modify_depreciation_schedule(self):
for row in self.asset_doc.finance_books:
row.total_number_of_depreciations += self.increase_in_asset_life/row.frequency_of_depreciation
self.asset_doc.flags.increase_in_asset_life = False
extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
if extra_months != 0:
self.calculate_last_schedule_date(self.asset_doc, row, extra_months)
# to help modify depreciation schedule when increase_in_asset_life is not a multiple of frequency_of_depreciation
def calculate_last_schedule_date(self, asset, row, extra_months):
asset.flags.increase_in_asset_life = True
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
cint(asset.number_of_depreciations_booked)
# the Schedule Date in the final row of the old Depreciation Schedule
last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
# the Schedule Date in the final row of the new Depreciation Schedule
asset.to_date = add_months(last_schedule_date, extra_months)
# the latest possible date at which the depreciation can occur, without increasing the Total Number of Depreciations
# if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
schedule_date = add_months(row.depreciation_start_date,
number_of_pending_depreciations * cint(row.frequency_of_depreciation))
if asset.to_date > schedule_date:
row.total_number_of_depreciations += 1
def revert_depreciation_schedule_on_cancellation(self):
for row in self.asset_doc.finance_books:
row.total_number_of_depreciations -= self.increase_in_asset_life/row.frequency_of_depreciation
self.asset_doc.flags.increase_in_asset_life = False
extra_months = self.increase_in_asset_life % row.frequency_of_depreciation
if extra_months != 0:
self.calculate_last_schedule_date_before_modification(self.asset_doc, row, extra_months)
def calculate_last_schedule_date_before_modification(self, asset, row, extra_months):
asset.flags.increase_in_asset_life = True
number_of_pending_depreciations = cint(row.total_number_of_depreciations) - \
cint(asset.number_of_depreciations_booked)
# the Schedule Date in the final row of the modified Depreciation Schedule
last_schedule_date = asset.schedules[len(asset.schedules)-1].schedule_date
# the Schedule Date in the final row of the original Depreciation Schedule
asset.to_date = add_months(last_schedule_date, -extra_months)
# the latest possible date at which the depreciation can occur, without decreasing the Total Number of Depreciations
# if depreciations happen yearly and the Depreciation Posting Date is 01-01-2020, this could be 01-01-2021, 01-01-2022...
schedule_date = add_months(row.depreciation_start_date,
(number_of_pending_depreciations - 1) * cint(row.frequency_of_depreciation))
if asset.to_date < schedule_date:
row.total_number_of_depreciations -= 1
@frappe.whitelist()
def get_downtime(failure_date, completion_date):
downtime = time_diff_in_hours(completion_date, failure_date)
return round(downtime, 2)
return round(downtime, 2)

View File

@ -2,8 +2,167 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import nowdate, flt
import unittest
from erpnext.assets.doctype.asset.test_asset import create_asset_data, create_asset, set_depreciation_settings_in_company
class TestAssetRepair(unittest.TestCase):
pass
def setUp(self):
set_depreciation_settings_in_company()
create_asset_data()
frappe.db.sql("delete from `tabTax Rule`")
def test_update_status(self):
asset = create_asset()
initial_status = asset.status
asset_repair = create_asset_repair(asset = asset)
if asset_repair.repair_status == "Pending":
asset.reload()
self.assertEqual(asset.status, "Out of Order")
asset_repair.repair_status = "Completed"
asset_repair.save()
asset_status = frappe.db.get_value("Asset", asset_repair.asset, "status")
self.assertEqual(asset_status, initial_status)
def test_stock_item_total_value(self):
asset_repair = create_asset_repair(stock_consumption = 1)
for item in asset_repair.stock_items:
total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
self.assertEqual(item.total_value, total_value)
def test_total_repair_cost(self):
asset_repair = create_asset_repair(stock_consumption = 1)
total_repair_cost = asset_repair.repair_cost
self.assertEqual(total_repair_cost, asset_repair.repair_cost)
for item in asset_repair.stock_items:
total_repair_cost += item.total_value
self.assertEqual(total_repair_cost, asset_repair.total_repair_cost)
def test_repair_status_after_submit(self):
asset_repair = create_asset_repair(submit = 1)
self.assertNotEqual(asset_repair.repair_status, "Pending")
def test_stock_items(self):
asset_repair = create_asset_repair(stock_consumption = 1)
self.assertTrue(asset_repair.stock_consumption)
self.assertTrue(asset_repair.stock_items)
def test_warehouse(self):
asset_repair = create_asset_repair(stock_consumption = 1)
self.assertTrue(asset_repair.stock_consumption)
self.assertTrue(asset_repair.warehouse)
def test_decrease_stock_quantity(self):
asset_repair = create_asset_repair(stock_consumption = 1, submit = 1)
stock_entry = frappe.get_last_doc('Stock Entry')
self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item)
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation = 1)
initial_asset_value = get_asset_value(asset)
asset_repair = create_asset_repair(asset= asset, stock_consumption = 1, submit = 1)
asset.reload()
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
self.assertEqual(asset_repair.stock_items[0].total_value, increase_in_asset_value)
def test_increase_in_asset_value_due_to_repair_cost_capitalisation(self):
asset = create_asset(calculate_depreciation = 1)
initial_asset_value = get_asset_value(asset)
asset_repair = create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
asset.reload()
increase_in_asset_value = get_asset_value(asset) - initial_asset_value
self.assertEqual(asset_repair.repair_cost, increase_in_asset_value)
def test_purchase_invoice(self):
asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
self.assertTrue(asset_repair.purchase_invoice)
def test_gl_entries(self):
asset_repair = create_asset_repair(capitalize_repair_cost = 1, submit = 1)
gl_entry = frappe.get_last_doc('GL Entry')
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
def test_increase_in_asset_life(self):
asset = create_asset(calculate_depreciation = 1)
initial_num_of_depreciations = num_of_depreciations(asset)
create_asset_repair(asset= asset, capitalize_repair_cost = 1, submit = 1)
asset.reload()
self.assertEqual((initial_num_of_depreciations + 1), num_of_depreciations(asset))
self.assertEqual(asset.schedules[-1].accumulated_depreciation_amount, asset.finance_books[0].value_after_depreciation)
def get_asset_value(asset):
return asset.finance_books[0].value_after_depreciation
def num_of_depreciations(asset):
return asset.finance_books[0].total_number_of_depreciations
def create_asset_repair(**args):
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
args = frappe._dict(args)
if args.asset:
asset = args.asset
else:
asset = create_asset(is_existing_asset = 1)
asset_repair = frappe.new_doc("Asset Repair")
asset_repair.update({
"asset": asset.name,
"asset_name": asset.asset_name,
"failure_date": nowdate(),
"description": "Test Description",
"repair_cost": 0,
"company": asset.company
})
if args.stock_consumption:
asset_repair.stock_consumption = 1
asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company)
asset_repair.append("stock_items", {
"item": args.item or args.item_code or "_Test Item",
"valuation_rate": args.rate if args.get("rate") is not None else 100,
"consumed_quantity": args.qty or 1
})
asset_repair.insert(ignore_if_duplicate=True)
if args.submit:
asset_repair.repair_status = "Completed"
asset_repair.cost_center = "_Test Cost Center - _TC"
if args.stock_consumption:
stock_entry = frappe.get_doc({
"doctype": "Stock Entry",
"stock_entry_type": "Material Receipt",
"company": asset.company
})
stock_entry.append('items', {
"t_warehouse": asset_repair.warehouse,
"item_code": asset_repair.stock_items[0].item,
"qty": asset_repair.stock_items[0].consumed_quantity
})
stock_entry.submit()
if args.capitalize_repair_cost:
asset_repair.capitalize_repair_cost = 1
asset_repair.repair_cost = 1000
if asset.calculate_depreciation:
asset_repair.increase_in_asset_life = 12
asset_repair.purchase_invoice = make_purchase_invoice().name
asset_repair.submit()
return asset_repair

View File

@ -0,0 +1,55 @@
{
"actions": [],
"creation": "2021-05-12 02:41:54.161024",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item",
"valuation_rate",
"consumed_quantity",
"total_value"
],
"fields": [
{
"fieldname": "item",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
},
{
"fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Valuation Rate",
"read_only": 1
},
{
"fieldname": "consumed_quantity",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Consumed Quantity"
},
{
"fieldname": "total_value",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Total Value",
"read_only": 1
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-12 03:19:55.006300",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair Consumed Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AssetRepairConsumedItem(Document):
pass

View File

@ -35,6 +35,7 @@ frappe.ui.form.on('GST Settings', {
return {
filters: {
company: row.company,
account_type: "Tax",
is_group: 0
}
};

View File

@ -834,8 +834,16 @@ def get_depreciation_amount(asset, depreciable_value, row):
depreciation_left = flt(row.total_number_of_depreciations) - flt(asset.number_of_depreciations_booked)
if row.depreciation_method in ("Straight Line", "Manual"):
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being prepared for the first time
if not asset.flags.increase_in_asset_life:
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / depreciation_left
# if the Depreciation Schedule is being modified after Asset Repair
else:
depreciation_amount = (flt(row.value_after_depreciation) -
flt(row.expected_value_after_useful_life)) / (date_diff(asset.to_date, asset.available_for_use_date) / 365)
else:
rate_of_depreciation = row.rate_of_depreciation
# if its the first depreciation

View File

@ -74,7 +74,7 @@
"stock_received_but_not_billed",
"service_received_but_not_billed",
"expenses_included_in_valuation",
"fixed_asset_depreciation_settings",
"fixed_asset_defaults",
"accumulated_depreciation_account",
"depreciation_expense_account",
"series_for_depreciation_entry",
@ -83,6 +83,7 @@
"disposal_account",
"depreciation_cost_center",
"capital_work_in_progress_account",
"repair_and_maintenance_account",
"asset_received_but_not_billed",
"budget_detail",
"exception_budget_approver_role",
@ -519,12 +520,6 @@
"no_copy": 1,
"options": "Account"
},
{
"collapsible": 1,
"fieldname": "fixed_asset_depreciation_settings",
"fieldtype": "Section Break",
"label": "Fixed Asset Depreciation Settings"
},
{
"fieldname": "accumulated_depreciation_account",
"fieldtype": "Link",
@ -734,6 +729,18 @@
"fieldtype": "Link",
"label": "Default Payment Discount Account",
"options": "Account"
},
{
"collapsible": 1,
"fieldname": "fixed_asset_defaults",
"fieldtype": "Section Break",
"label": "Fixed Asset Defaults"
},
{
"fieldname": "repair_and_maintenance_account",
"fieldtype": "Link",
"label": "Repair and Maintenance Account",
"options": "Account"
}
],
"icon": "fa fa-building",
@ -741,7 +748,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2021-05-07 03:11:28.189740",
"modified": "2021-05-12 16:51:08.187233",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",

View File

@ -11,10 +11,11 @@
"hide_custom": 0,
"icon": "settings",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"label": "ERPNext Settings",
"links": [],
"modified": "2020-12-01 13:38:37.759596",
"modified": "2021-06-12 01:58:11.399566",
"modified_by": "Administrator",
"module": "Setup",
"name": "ERPNext Settings",
@ -109,6 +110,13 @@
"label": "Domain Settings",
"link_to": "Domain Settings",
"type": "DocType"
},
{
"doc_view": "",
"icon": "retail",
"label": "Products Settings",
"link_to": "Products Settings",
"type": "DocType"
}
]
}
}

View File

@ -291,7 +291,7 @@ class PurchaseReceipt(BuyingController):
continue
self.add_gl_entry(gl_entries, warehouse_account_name, d.cost_center, stock_value_diff, 0.0, remarks,
stock_rbnb, account_currency=warehouse_account_currency, item=d)
stock_rbnb, account_currency=warehouse_account_currency, item=d)
# GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
@ -318,11 +318,11 @@ class PurchaseReceipt(BuyingController):
(exchange_rate_map[d.purchase_invoice] - self.conversion_rate)
self.add_gl_entry(gl_entries, account, d.cost_center, 0.0, discrepancy_caused_by_exchange_rate_difference,
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency, item=d)
self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0,
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
self.add_gl_entry(gl_entries, self.get_company_default("exchange_gain_loss_account"), d.cost_center, discrepancy_caused_by_exchange_rate_difference, 0.0,
remarks, self.supplier, debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency, item=d)
# Amount added through landed-cos-voucher
@ -407,6 +407,7 @@ class PurchaseReceipt(BuyingController):
against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
total_valuation_amount = sum(valuation_tax.values())
amount_including_divisional_loss = negative_expense_to_be_booked
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
i = 1
for tax in self.get("taxes"):
if valuation_tax.get(tax.name):

View File

@ -1054,30 +1054,30 @@ class TestPurchaseReceipt(unittest.TestCase):
def test_purchase_receipt_with_exchange_rate_difference(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
pi = create_purchase_invoice(currency = "USD", conversion_rate = 70)
create_warehouse("_Test Warehouse for Valuation", company="_Test Company with perpetual inventory",
properties={"account": '_Test Account Stock In Hand - TCP1'})
from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
pr = make_purchase_receipt(warehouse = '_Test Warehouse for Valuation - TCP1',
company="_Test Company with perpetual inventory", currency = "USD", conversion_rate = 80,
do_not_save = "True")
pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
cost_center = "Main - TCP1",
warehouse = "Stores - TCP1",
expense_account ="_Test Account Cost for Goods Sold - TCP1",
currency = "USD", conversion_rate = 70)
pr = create_purchase_receipt(pi.name)
pr.conversion_rate = 80
pr.items[0].purchase_invoice = pi.name
pr.items[0].purchase_invoice_item = pi.items[0].name
pr.insert()
pr.save()
pr.submit()
# fetching the latest GL Entry with 'Exchange Gain/Loss - TCP1' account
gl_entries = frappe.get_all('GL Entry', filters = {'account': 'Exchange Gain/Loss - TCP1'})
voucher_no = frappe.get_value('GL Entry', gl_entries[0]['name'], 'voucher_no')
self.assertEqual(pr.name, voucher_no)
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value('Company', pr.company, 'exchange_gain_loss_account')
exchange_gain_loss_amount = frappe.get_value('GL Entry', gl_entries[0]['name'], 'debit')
# fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value('GL Entry', {'account': exchange_gain_loss_account, 'voucher_no': pr.name}, 'credit')
discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
self.assertEqual(exchange_gain_loss_amount, discrepancy_caused_by_exchange_rate_diff)
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference

View File

@ -54,7 +54,7 @@ class TestStockLedgerEntry(unittest.TestCase):
)
# _Test Item for Reposting transferred from Stores to FG warehouse on 30-04-2020
make_stock_entry(
se = make_stock_entry(
item_code="_Test Item for Reposting",
source="Stores - _TC",
target="Finished Goods - _TC",
@ -64,29 +64,29 @@ class TestStockLedgerEntry(unittest.TestCase):
posting_date='2020-04-30',
posting_time='14:00'
)
target_wh_sle = get_previous_sle({
target_wh_sle = frappe.db.get_value('Stock Ledger Entry', {
"item_code": "_Test Item for Reposting",
"warehouse": "Finished Goods - _TC",
"posting_date": '2020-04-30',
"posting_time": '14:00'
})
"voucher_type": "Stock Entry",
"voucher_no": se.name
}, ["valuation_rate"], as_dict=1)
self.assertEqual(target_wh_sle.get("valuation_rate"), 150)
# Repack entry on 5-5-2020
repack = create_repack_entry(company=company, posting_date='2020-05-05', posting_time='14:00')
finished_item_sle = get_previous_sle({
finished_item_sle = frappe.db.get_value('Stock Ledger Entry', {
"item_code": "_Test Finished Item for Reposting",
"warehouse": "Finished Goods - _TC",
"posting_date": '2020-05-05',
"posting_time": '14:00'
})
"voucher_type": "Stock Entry",
"voucher_no": repack.name
}, ["incoming_rate", "valuation_rate"], as_dict=1)
self.assertEqual(finished_item_sle.get("incoming_rate"), 540)
self.assertEqual(finished_item_sle.get("valuation_rate"), 540)
# Reconciliation for _Test Item for Reposting at Stores on 12-04-2020: Qty = 50, Rate = 150
create_stock_reconciliation(
sr = create_stock_reconciliation(
item_code="_Test Item for Reposting",
warehouse="Stores - _TC",
qty=50,
@ -109,12 +109,12 @@ class TestStockLedgerEntry(unittest.TestCase):
self.assertEqual(target_wh_sle.get("valuation_rate"), 175)
# Check valuation rate of repacked item after back-dated entry at Stores
finished_item_sle = get_previous_sle({
finished_item_sle = frappe.db.get_value('Stock Ledger Entry', {
"item_code": "_Test Finished Item for Reposting",
"warehouse": "Finished Goods - _TC",
"posting_date": '2020-05-05',
"posting_time": '14:00'
})
"voucher_type": "Stock Entry",
"voucher_no": repack.name
}, ["incoming_rate", "valuation_rate"], as_dict=1)
self.assertEqual(finished_item_sle.get("incoming_rate"), 790)
self.assertEqual(finished_item_sle.get("valuation_rate"), 790)

View File

@ -81,10 +81,9 @@ class TestServiceLevelAgreement(unittest.TestCase):
# check SLA custom fields created for leads
sla_fields = get_service_level_agreement_fields()
meta = frappe.get_meta(doctype, cached=False)
for field in sla_fields:
self.assertTrue(meta.has_field(field.get("fieldname")))
self.assertTrue(frappe.db.exists("Custom Field", {"dt": doctype, "fieldname": field.get("fieldname")}))
def test_docfield_creation_for_sla_on_custom_dt(self):
doctype = create_custom_doctype()
@ -102,10 +101,9 @@ class TestServiceLevelAgreement(unittest.TestCase):
# check SLA docfields created
sla_fields = get_service_level_agreement_fields()
meta = frappe.get_meta(doctype.name, cached=False)
for field in sla_fields:
self.assertTrue(meta.has_field(field.get("fieldname")))
self.assertTrue(frappe.db.exists("DocField", {"fieldname": field.get("fieldname"), "parent": doctype.name}))
def test_sla_application(self):
# Default Service Level Agreement