mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 16:34:46 +00:00
Merge branch 'develop' into item_dashboard_create_new
This commit is contained in:
@@ -1553,7 +1553,7 @@
|
|||||||
"icon": "fa fa-file-text",
|
"icon": "fa fa-file-text",
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-03-22 13:00:24.166684",
|
"modified": "2022-09-27 13:00:24.166684",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "POS Invoice",
|
"name": "POS Invoice",
|
||||||
|
|||||||
@@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice):
|
|||||||
frappe.bold(d.warehouse),
|
frappe.bold(d.warehouse),
|
||||||
frappe.bold(d.qty),
|
frappe.bold(d.qty),
|
||||||
)
|
)
|
||||||
if flt(available_stock) <= 0:
|
if is_stock_item and flt(available_stock) <= 0:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
|
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
|
||||||
d.idx, item_code, warehouse
|
d.idx, item_code, warehouse
|
||||||
),
|
),
|
||||||
title=_("Item Unavailable"),
|
title=_("Item Unavailable"),
|
||||||
)
|
)
|
||||||
elif flt(available_stock) < flt(d.qty):
|
elif is_stock_item and flt(available_stock) < flt(d.qty):
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_(
|
_(
|
||||||
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
||||||
@@ -632,11 +632,12 @@ def get_stock_availability(item_code, warehouse):
|
|||||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||||
return bin_qty - pos_sales_qty, is_stock_item
|
return bin_qty - pos_sales_qty, is_stock_item
|
||||||
else:
|
else:
|
||||||
is_stock_item = False
|
is_stock_item = True
|
||||||
if frappe.db.exists("Product Bundle", item_code):
|
if frappe.db.exists("Product Bundle", item_code):
|
||||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||||
else:
|
else:
|
||||||
# Is a service item
|
is_stock_item = False
|
||||||
|
# Is a service item or non_stock item
|
||||||
return 0, is_stock_item
|
return 0, is_stock_item
|
||||||
|
|
||||||
|
|
||||||
@@ -650,7 +651,9 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
|||||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||||
|
|
||||||
max_available_bundles = available_qty / item.qty
|
max_available_bundles = available_qty / item.qty
|
||||||
if bundle_bin_qty > max_available_bundles:
|
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||||
|
"Item", item.item_code, "is_stock_item"
|
||||||
|
):
|
||||||
bundle_bin_qty = max_available_bundles
|
bundle_bin_qty = max_available_bundles
|
||||||
|
|
||||||
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
||||||
@@ -740,3 +743,7 @@ def add_return_modes(doc, pos_profile):
|
|||||||
]:
|
]:
|
||||||
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||||
append_payment(payment_mode[0])
|
append_payment(payment_mode[0])
|
||||||
|
|
||||||
|
|
||||||
|
def on_doctype_update():
|
||||||
|
frappe.db.add_index("POS Invoice", ["return_against"])
|
||||||
|
|||||||
@@ -1543,6 +1543,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
|||||||
pi.save()
|
pi.save()
|
||||||
self.assertEqual(pi.items[0].conversion_factor, 1000)
|
self.assertEqual(pi.items[0].conversion_factor, 1000)
|
||||||
|
|
||||||
|
def test_batch_expiry_for_purchase_invoice(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
item = self.make_item(
|
||||||
|
"_Test Batch Item For Return Check",
|
||||||
|
{
|
||||||
|
"is_purchase_item": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TBIRC.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_invoice(
|
||||||
|
qty=1,
|
||||||
|
item_code=item.name,
|
||||||
|
update_stock=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.load_from_db()
|
||||||
|
batch_no = pi.items[0].batch_no
|
||||||
|
self.assertTrue(batch_no)
|
||||||
|
|
||||||
|
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||||
|
return_pi.save().submit()
|
||||||
|
|
||||||
|
self.assertTrue(return_pi.docstatus == 1)
|
||||||
|
|
||||||
|
|
||||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||||
gl_entries = frappe.db.sql(
|
gl_entries = frappe.db.sql(
|
||||||
|
|||||||
@@ -22,9 +22,12 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
|
|||||||
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
|
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
|
||||||
from erpnext.accounts.utils import get_account_currency
|
from erpnext.accounts.utils import get_account_currency
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
|
depreciate_asset,
|
||||||
get_disposal_account_and_cost_center,
|
get_disposal_account_and_cost_center,
|
||||||
get_gl_entries_on_asset_disposal,
|
get_gl_entries_on_asset_disposal,
|
||||||
get_gl_entries_on_asset_regain,
|
get_gl_entries_on_asset_regain,
|
||||||
|
reset_depreciation_schedule,
|
||||||
|
reverse_depreciation_entry_made_after_disposal,
|
||||||
)
|
)
|
||||||
from erpnext.controllers.accounts_controller import validate_account_head
|
from erpnext.controllers.accounts_controller import validate_account_head
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
@@ -1081,23 +1084,25 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if self.is_return:
|
if self.is_return:
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
||||||
asset, item.base_net_amount, item.finance_book
|
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
|
||||||
)
|
)
|
||||||
asset.db_set("disposal_date", None)
|
asset.db_set("disposal_date", None)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
self.reverse_depreciation_entry_made_after_disposal(asset)
|
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||||
self.reset_depreciation_schedule(asset)
|
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||||
|
reset_depreciation_schedule(asset, self.posting_date)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if asset.calculate_depreciation:
|
||||||
|
depreciate_asset(asset, self.posting_date)
|
||||||
|
asset.reload()
|
||||||
|
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||||
asset, item.base_net_amount, item.finance_book
|
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
|
||||||
)
|
)
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
|
||||||
self.depreciate_asset(asset)
|
|
||||||
|
|
||||||
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))
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
datasets: [{
|
datasets: [{
|
||||||
color: 'green',
|
color: 'green',
|
||||||
values: asset_values,
|
values: asset_values,
|
||||||
formatted: asset_values.map(d => d.toFixed(2))
|
formatted: asset_values.map(d => d?.toFixed(2))
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
type: 'line'
|
type: 'line'
|
||||||
|
|||||||
@@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, flt, getdate, today
|
from frappe.utils import add_months, cint, flt, getdate, nowdate, today
|
||||||
|
|
||||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||||
get_checks_for_pl_and_bs_accounts,
|
get_checks_for_pl_and_bs_accounts,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||||
|
|
||||||
|
|
||||||
def post_depreciation_entries(date=None, commit=True):
|
def post_depreciation_entries(date=None, commit=True):
|
||||||
@@ -196,6 +197,11 @@ def scrap_asset(asset_name):
|
|||||||
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
|
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
date = today()
|
||||||
|
|
||||||
|
depreciate_asset(asset, date)
|
||||||
|
asset.reload()
|
||||||
|
|
||||||
depreciation_series = frappe.get_cached_value(
|
depreciation_series = frappe.get_cached_value(
|
||||||
"Company", asset.company, "series_for_depreciation_entry"
|
"Company", asset.company, "series_for_depreciation_entry"
|
||||||
)
|
)
|
||||||
@@ -203,7 +209,7 @@ def scrap_asset(asset_name):
|
|||||||
je = frappe.new_doc("Journal Entry")
|
je = frappe.new_doc("Journal Entry")
|
||||||
je.voucher_type = "Journal Entry"
|
je.voucher_type = "Journal Entry"
|
||||||
je.naming_series = depreciation_series
|
je.naming_series = depreciation_series
|
||||||
je.posting_date = today()
|
je.posting_date = date
|
||||||
je.company = asset.company
|
je.company = asset.company
|
||||||
je.remark = "Scrap Entry for asset {0}".format(asset_name)
|
je.remark = "Scrap Entry for asset {0}".format(asset_name)
|
||||||
|
|
||||||
@@ -214,7 +220,7 @@ def scrap_asset(asset_name):
|
|||||||
je.flags.ignore_permissions = True
|
je.flags.ignore_permissions = True
|
||||||
je.submit()
|
je.submit()
|
||||||
|
|
||||||
frappe.db.set_value("Asset", asset_name, "disposal_date", today())
|
frappe.db.set_value("Asset", asset_name, "disposal_date", date)
|
||||||
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
||||||
asset.set_status("Scrapped")
|
asset.set_status("Scrapped")
|
||||||
|
|
||||||
@@ -225,6 +231,9 @@ def scrap_asset(asset_name):
|
|||||||
def restore_asset(asset_name):
|
def restore_asset(asset_name):
|
||||||
asset = frappe.get_doc("Asset", asset_name)
|
asset = frappe.get_doc("Asset", asset_name)
|
||||||
|
|
||||||
|
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
|
||||||
|
reset_depreciation_schedule(asset, asset.disposal_date)
|
||||||
|
|
||||||
je = asset.journal_entry_for_scrap
|
je = asset.journal_entry_for_scrap
|
||||||
|
|
||||||
asset.db_set("disposal_date", None)
|
asset.db_set("disposal_date", None)
|
||||||
@@ -235,7 +244,94 @@ def restore_asset(asset_name):
|
|||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
|
|
||||||
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
def depreciate_asset(asset, date):
|
||||||
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
|
asset.prepare_depreciation_data(date_of_disposal=date)
|
||||||
|
asset.save()
|
||||||
|
|
||||||
|
make_depreciation_entry(asset.name, date)
|
||||||
|
|
||||||
|
|
||||||
|
def reset_depreciation_schedule(asset, date):
|
||||||
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
|
|
||||||
|
# recreate original depreciation schedule of the asset
|
||||||
|
asset.prepare_depreciation_data(date_of_return=date)
|
||||||
|
|
||||||
|
modify_depreciation_schedule_for_asset_repairs(asset)
|
||||||
|
asset.save()
|
||||||
|
|
||||||
|
|
||||||
|
def modify_depreciation_schedule_for_asset_repairs(asset):
|
||||||
|
asset_repairs = frappe.get_all(
|
||||||
|
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
||||||
|
)
|
||||||
|
|
||||||
|
for repair in asset_repairs:
|
||||||
|
if repair.increase_in_asset_life:
|
||||||
|
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
||||||
|
asset_repair.modify_depreciation_schedule()
|
||||||
|
asset.prepare_depreciation_data()
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_depreciation_entry_made_after_disposal(asset, date):
|
||||||
|
row = -1
|
||||||
|
finance_book = asset.get("schedules")[0].get("finance_book")
|
||||||
|
for schedule in asset.get("schedules"):
|
||||||
|
if schedule.finance_book != finance_book:
|
||||||
|
row = 0
|
||||||
|
finance_book = schedule.finance_book
|
||||||
|
else:
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
if schedule.schedule_date == date:
|
||||||
|
if not disposal_was_made_on_original_schedule_date(
|
||||||
|
asset, schedule, row, date
|
||||||
|
) or disposal_happens_in_the_future(date):
|
||||||
|
|
||||||
|
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
|
||||||
|
reverse_journal_entry.posting_date = nowdate()
|
||||||
|
frappe.flags.is_reverse_depr_entry = True
|
||||||
|
reverse_journal_entry.submit()
|
||||||
|
|
||||||
|
frappe.flags.is_reverse_depr_entry = False
|
||||||
|
asset.flags.ignore_validate_update_after_submit = True
|
||||||
|
schedule.journal_entry = None
|
||||||
|
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
|
||||||
|
asset.finance_books[0].value_after_depreciation += depreciation_amount
|
||||||
|
asset.save()
|
||||||
|
|
||||||
|
|
||||||
|
def get_depreciation_amount_in_je(journal_entry):
|
||||||
|
if journal_entry.accounts[0].debit_in_account_currency:
|
||||||
|
return journal_entry.accounts[0].debit_in_account_currency
|
||||||
|
else:
|
||||||
|
return journal_entry.accounts[0].credit_in_account_currency
|
||||||
|
|
||||||
|
|
||||||
|
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
|
||||||
|
def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
|
||||||
|
for finance_book in asset.get("finance_books"):
|
||||||
|
if schedule.finance_book == finance_book.finance_book:
|
||||||
|
orginal_schedule_date = add_months(
|
||||||
|
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if orginal_schedule_date == posting_date_of_disposal:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def disposal_happens_in_the_future(posting_date_of_disposal):
|
||||||
|
if posting_date_of_disposal > getdate():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_gl_entries_on_asset_regain(
|
||||||
|
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
|
||||||
|
):
|
||||||
(
|
(
|
||||||
fixed_asset_account,
|
fixed_asset_account,
|
||||||
asset,
|
asset,
|
||||||
@@ -247,28 +343,45 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
|||||||
) = get_asset_details(asset, finance_book)
|
) = get_asset_details(asset, finance_book)
|
||||||
|
|
||||||
gl_entries = [
|
gl_entries = [
|
||||||
{
|
asset.get_gl_dict(
|
||||||
"account": fixed_asset_account,
|
{
|
||||||
"debit_in_account_currency": asset.gross_purchase_amount,
|
"account": fixed_asset_account,
|
||||||
"debit": asset.gross_purchase_amount,
|
"debit_in_account_currency": asset.gross_purchase_amount,
|
||||||
"cost_center": depreciation_cost_center,
|
"debit": asset.gross_purchase_amount,
|
||||||
},
|
"cost_center": depreciation_cost_center,
|
||||||
{
|
"posting_date": getdate(),
|
||||||
"account": accumulated_depr_account,
|
},
|
||||||
"credit_in_account_currency": accumulated_depr_amount,
|
item=asset,
|
||||||
"credit": accumulated_depr_amount,
|
),
|
||||||
"cost_center": depreciation_cost_center,
|
asset.get_gl_dict(
|
||||||
},
|
{
|
||||||
|
"account": accumulated_depr_account,
|
||||||
|
"credit_in_account_currency": accumulated_depr_amount,
|
||||||
|
"credit": accumulated_depr_amount,
|
||||||
|
"cost_center": depreciation_cost_center,
|
||||||
|
"posting_date": getdate(),
|
||||||
|
},
|
||||||
|
item=asset,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
|
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
|
||||||
if profit_amount:
|
if profit_amount:
|
||||||
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
get_profit_gl_entries(
|
||||||
|
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
|
||||||
|
)
|
||||||
|
|
||||||
|
if voucher_type and voucher_no:
|
||||||
|
for entry in gl_entries:
|
||||||
|
entry["voucher_type"] = voucher_type
|
||||||
|
entry["voucher_no"] = voucher_no
|
||||||
|
|
||||||
return gl_entries
|
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, voucher_type=None, voucher_no=None
|
||||||
|
):
|
||||||
(
|
(
|
||||||
fixed_asset_account,
|
fixed_asset_account,
|
||||||
asset,
|
asset,
|
||||||
@@ -280,23 +393,38 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
|||||||
) = get_asset_details(asset, finance_book)
|
) = get_asset_details(asset, finance_book)
|
||||||
|
|
||||||
gl_entries = [
|
gl_entries = [
|
||||||
{
|
asset.get_gl_dict(
|
||||||
"account": fixed_asset_account,
|
{
|
||||||
"credit_in_account_currency": asset.gross_purchase_amount,
|
"account": fixed_asset_account,
|
||||||
"credit": asset.gross_purchase_amount,
|
"credit_in_account_currency": asset.gross_purchase_amount,
|
||||||
"cost_center": depreciation_cost_center,
|
"credit": asset.gross_purchase_amount,
|
||||||
},
|
"cost_center": depreciation_cost_center,
|
||||||
{
|
"posting_date": getdate(),
|
||||||
"account": accumulated_depr_account,
|
},
|
||||||
"debit_in_account_currency": accumulated_depr_amount,
|
item=asset,
|
||||||
"debit": accumulated_depr_amount,
|
),
|
||||||
"cost_center": depreciation_cost_center,
|
asset.get_gl_dict(
|
||||||
},
|
{
|
||||||
|
"account": accumulated_depr_account,
|
||||||
|
"debit_in_account_currency": accumulated_depr_amount,
|
||||||
|
"debit": accumulated_depr_amount,
|
||||||
|
"cost_center": depreciation_cost_center,
|
||||||
|
"posting_date": getdate(),
|
||||||
|
},
|
||||||
|
item=asset,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||||
if profit_amount:
|
if profit_amount:
|
||||||
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
get_profit_gl_entries(
|
||||||
|
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
|
||||||
|
)
|
||||||
|
|
||||||
|
if voucher_type and voucher_no:
|
||||||
|
for entry in gl_entries:
|
||||||
|
entry["voucher_type"] = voucher_type
|
||||||
|
entry["voucher_no"] = voucher_no
|
||||||
|
|
||||||
return gl_entries
|
return gl_entries
|
||||||
|
|
||||||
@@ -333,15 +461,21 @@ def get_asset_details(asset, finance_book=None):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
|
def get_profit_gl_entries(
|
||||||
|
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
|
||||||
|
):
|
||||||
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
{
|
asset.get_gl_dict(
|
||||||
"account": disposal_account,
|
{
|
||||||
"cost_center": depreciation_cost_center,
|
"account": disposal_account,
|
||||||
debit_or_credit: abs(profit_amount),
|
"cost_center": depreciation_cost_center,
|
||||||
debit_or_credit + "_in_account_currency": abs(profit_amount),
|
debit_or_credit: abs(profit_amount),
|
||||||
}
|
debit_or_credit + "_in_account_currency": abs(profit_amount),
|
||||||
|
"posting_date": getdate(),
|
||||||
|
},
|
||||||
|
item=asset,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,16 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
add_months,
|
||||||
|
cstr,
|
||||||
|
flt,
|
||||||
|
get_first_day,
|
||||||
|
get_last_day,
|
||||||
|
getdate,
|
||||||
|
nowdate,
|
||||||
|
)
|
||||||
|
|
||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
|
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
|
||||||
@@ -178,28 +187,48 @@ class TestAsset(AssetSetup):
|
|||||||
self.assertEqual(doc.items[0].is_fixed_asset, 1)
|
self.assertEqual(doc.items[0].is_fixed_asset, 1)
|
||||||
|
|
||||||
def test_scrap_asset(self):
|
def test_scrap_asset(self):
|
||||||
|
date = nowdate()
|
||||||
|
purchase_date = add_months(get_first_day(date), -2)
|
||||||
|
|
||||||
asset = create_asset(
|
asset = create_asset(
|
||||||
calculate_depreciation=1,
|
calculate_depreciation=1,
|
||||||
available_for_use_date="2020-01-01",
|
available_for_use_date=purchase_date,
|
||||||
purchase_date="2020-01-01",
|
purchase_date=purchase_date,
|
||||||
expected_value_after_useful_life=10000,
|
expected_value_after_useful_life=10000,
|
||||||
total_number_of_depreciations=10,
|
total_number_of_depreciations=10,
|
||||||
frequency_of_depreciation=1,
|
frequency_of_depreciation=1,
|
||||||
submit=1,
|
submit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
post_depreciation_entries(date=add_months("2020-01-01", 4))
|
post_depreciation_entries(date=add_months(purchase_date, 2))
|
||||||
|
asset.load_from_db()
|
||||||
|
|
||||||
|
accumulated_depr_amount = flt(
|
||||||
|
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||||
|
asset.precision("gross_purchase_amount"),
|
||||||
|
)
|
||||||
|
self.assertEquals(accumulated_depr_amount, 18000.0)
|
||||||
|
|
||||||
scrap_asset(asset.name)
|
scrap_asset(asset.name)
|
||||||
|
|
||||||
asset.load_from_db()
|
asset.load_from_db()
|
||||||
|
|
||||||
|
accumulated_depr_amount = flt(
|
||||||
|
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||||
|
asset.precision("gross_purchase_amount"),
|
||||||
|
)
|
||||||
|
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||||
|
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||||
|
)
|
||||||
|
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||||
|
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
|
||||||
|
|
||||||
self.assertEqual(asset.status, "Scrapped")
|
self.assertEqual(asset.status, "Scrapped")
|
||||||
self.assertTrue(asset.journal_entry_for_scrap)
|
self.assertTrue(asset.journal_entry_for_scrap)
|
||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
|
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
|
||||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||||
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
|
("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
gle = frappe.db.sql(
|
gle = frappe.db.sql(
|
||||||
@@ -216,19 +245,27 @@ class TestAsset(AssetSetup):
|
|||||||
self.assertFalse(asset.journal_entry_for_scrap)
|
self.assertFalse(asset.journal_entry_for_scrap)
|
||||||
self.assertEqual(asset.status, "Partially Depreciated")
|
self.assertEqual(asset.status, "Partially Depreciated")
|
||||||
|
|
||||||
|
accumulated_depr_amount = flt(
|
||||||
|
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||||
|
asset.precision("gross_purchase_amount"),
|
||||||
|
)
|
||||||
|
self.assertEquals(accumulated_depr_amount, 18000.0)
|
||||||
|
|
||||||
def test_gle_made_by_asset_sale(self):
|
def test_gle_made_by_asset_sale(self):
|
||||||
|
date = nowdate()
|
||||||
|
purchase_date = add_months(get_first_day(date), -2)
|
||||||
|
|
||||||
asset = create_asset(
|
asset = create_asset(
|
||||||
calculate_depreciation=1,
|
calculate_depreciation=1,
|
||||||
available_for_use_date="2020-06-06",
|
available_for_use_date=purchase_date,
|
||||||
purchase_date="2020-01-01",
|
purchase_date=purchase_date,
|
||||||
expected_value_after_useful_life=10000,
|
expected_value_after_useful_life=10000,
|
||||||
total_number_of_depreciations=3,
|
total_number_of_depreciations=10,
|
||||||
frequency_of_depreciation=10,
|
frequency_of_depreciation=1,
|
||||||
depreciation_start_date="2020-12-31",
|
|
||||||
submit=1,
|
submit=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
post_depreciation_entries(date="2021-01-01")
|
post_depreciation_entries(date=add_months(purchase_date, 2))
|
||||||
|
|
||||||
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
|
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
|
||||||
si.customer = "_Test Customer"
|
si.customer = "_Test Customer"
|
||||||
@@ -239,10 +276,15 @@ class TestAsset(AssetSetup):
|
|||||||
|
|
||||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||||
|
|
||||||
|
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||||
|
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||||
|
)
|
||||||
|
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||||
|
|
||||||
expected_gle = (
|
expected_gle = (
|
||||||
("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
|
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
|
||||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||||
("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
|
("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0),
|
||||||
("Debtors - _TC", 25000.0, 0.0),
|
("Debtors - _TC", 25000.0, 0.0),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ from six import string_types
|
|||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
|
depreciate_asset,
|
||||||
get_gl_entries_on_asset_disposal,
|
get_gl_entries_on_asset_disposal,
|
||||||
get_value_after_depreciation_on_disposal_date,
|
get_value_after_depreciation_on_disposal_date,
|
||||||
|
reset_depreciation_schedule,
|
||||||
|
reverse_depreciation_entry_made_after_disposal,
|
||||||
)
|
)
|
||||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
|
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
|
||||||
@@ -424,11 +427,15 @@ class AssetCapitalization(StockController):
|
|||||||
asset = self.get_asset(item)
|
asset = self.get_asset(item)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
self.depreciate_asset(asset)
|
depreciate_asset(asset, self.posting_date)
|
||||||
asset.reload()
|
asset.reload()
|
||||||
|
|
||||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||||
asset, item.asset_value, item.get("finance_book") or self.get("finance_book")
|
asset,
|
||||||
|
item.asset_value,
|
||||||
|
item.get("finance_book") or self.get("finance_book"),
|
||||||
|
self.get("doctype"),
|
||||||
|
self.get("name"),
|
||||||
)
|
)
|
||||||
|
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
@@ -516,8 +523,8 @@ class AssetCapitalization(StockController):
|
|||||||
self.set_consumed_asset_status(asset)
|
self.set_consumed_asset_status(asset)
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
self.reverse_depreciation_entry_made_after_disposal(asset)
|
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||||
self.reset_depreciation_schedule(asset)
|
reset_depreciation_schedule(asset, self.posting_date)
|
||||||
|
|
||||||
def get_asset(self, item):
|
def get_asset(self, item):
|
||||||
asset = frappe.get_doc("Asset", item.asset)
|
asset = frappe.get_doc("Asset", item.asset)
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ from erpnext.accounts.party import (
|
|||||||
validate_party_frozen_disabled,
|
validate_party_frozen_disabled,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
|
from erpnext.accounts.utils import get_account_currency, get_fiscal_years, validate_fiscal_year
|
||||||
from erpnext.assets.doctype.asset.depreciation import make_depreciation_entry
|
|
||||||
from erpnext.buying.utils import update_last_purchase_rate
|
from erpnext.buying.utils import update_last_purchase_rate
|
||||||
from erpnext.controllers.print_settings import (
|
from erpnext.controllers.print_settings import (
|
||||||
set_print_templates_for_item_table,
|
set_print_templates_for_item_table,
|
||||||
@@ -1891,88 +1890,6 @@ class AccountsController(TransactionBase):
|
|||||||
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
|
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
|
||||||
)
|
)
|
||||||
|
|
||||||
def depreciate_asset(self, asset):
|
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
|
||||||
asset.prepare_depreciation_data(date_of_disposal=self.posting_date)
|
|
||||||
asset.save()
|
|
||||||
|
|
||||||
make_depreciation_entry(asset.name, self.posting_date)
|
|
||||||
|
|
||||||
def reset_depreciation_schedule(self, asset):
|
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
|
||||||
|
|
||||||
# recreate original depreciation schedule of the asset
|
|
||||||
asset.prepare_depreciation_data(date_of_return=self.posting_date)
|
|
||||||
|
|
||||||
self.modify_depreciation_schedule_for_asset_repairs(asset)
|
|
||||||
asset.save()
|
|
||||||
|
|
||||||
def modify_depreciation_schedule_for_asset_repairs(self, asset):
|
|
||||||
asset_repairs = frappe.get_all(
|
|
||||||
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
|
||||||
)
|
|
||||||
|
|
||||||
for repair in asset_repairs:
|
|
||||||
if repair.increase_in_asset_life:
|
|
||||||
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
|
||||||
asset_repair.modify_depreciation_schedule()
|
|
||||||
asset.prepare_depreciation_data()
|
|
||||||
|
|
||||||
def reverse_depreciation_entry_made_after_disposal(self, asset):
|
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
|
||||||
|
|
||||||
posting_date_of_original_disposal = self.get_posting_date_of_disposal_entry()
|
|
||||||
|
|
||||||
row = -1
|
|
||||||
finance_book = asset.get("schedules")[0].get("finance_book")
|
|
||||||
for schedule in asset.get("schedules"):
|
|
||||||
if schedule.finance_book != finance_book:
|
|
||||||
row = 0
|
|
||||||
finance_book = schedule.finance_book
|
|
||||||
else:
|
|
||||||
row += 1
|
|
||||||
|
|
||||||
if schedule.schedule_date == posting_date_of_original_disposal:
|
|
||||||
if not self.disposal_was_made_on_original_schedule_date(
|
|
||||||
asset, schedule, row, posting_date_of_original_disposal
|
|
||||||
) or self.disposal_happens_in_the_future(posting_date_of_original_disposal):
|
|
||||||
|
|
||||||
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
|
|
||||||
reverse_journal_entry.posting_date = nowdate()
|
|
||||||
frappe.flags.is_reverse_depr_entry = True
|
|
||||||
reverse_journal_entry.submit()
|
|
||||||
|
|
||||||
frappe.flags.is_reverse_depr_entry = False
|
|
||||||
asset.flags.ignore_validate_update_after_submit = True
|
|
||||||
schedule.journal_entry = None
|
|
||||||
asset.save()
|
|
||||||
|
|
||||||
def get_posting_date_of_disposal_entry(self):
|
|
||||||
if self.doctype == "Sales Invoice" and self.return_against:
|
|
||||||
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
|
||||||
else:
|
|
||||||
return self.posting_date
|
|
||||||
|
|
||||||
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
|
|
||||||
def disposal_was_made_on_original_schedule_date(
|
|
||||||
self, asset, schedule, row, posting_date_of_disposal
|
|
||||||
):
|
|
||||||
for finance_book in asset.get("finance_books"):
|
|
||||||
if schedule.finance_book == finance_book.finance_book:
|
|
||||||
orginal_schedule_date = add_months(
|
|
||||||
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
|
|
||||||
)
|
|
||||||
|
|
||||||
if orginal_schedule_date == posting_date_of_disposal:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def disposal_happens_in_the_future(self, posting_date_of_disposal):
|
|
||||||
if posting_date_of_disposal > getdate():
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_tax_rate(account_head):
|
def get_tax_rate(account_head):
|
||||||
|
|||||||
@@ -212,21 +212,15 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
meta = frappe.get_meta(doctype, cached=True)
|
meta = frappe.get_meta(doctype, cached=True)
|
||||||
searchfields = meta.get_search_fields()
|
searchfields = meta.get_search_fields()
|
||||||
|
|
||||||
# these are handled separately
|
|
||||||
ignored_search_fields = ("item_name", "description")
|
|
||||||
for ignored_field in ignored_search_fields:
|
|
||||||
if ignored_field in searchfields:
|
|
||||||
searchfields.remove(ignored_field)
|
|
||||||
|
|
||||||
columns = ""
|
columns = ""
|
||||||
extra_searchfields = [
|
extra_searchfields = [field for field in searchfields if not field in ["name", "description"]]
|
||||||
field
|
|
||||||
for field in searchfields
|
|
||||||
if not field in ["name", "item_group", "description", "item_name"]
|
|
||||||
]
|
|
||||||
|
|
||||||
if extra_searchfields:
|
if extra_searchfields:
|
||||||
columns = ", " + ", ".join(extra_searchfields)
|
columns += ", " + ", ".join(extra_searchfields)
|
||||||
|
|
||||||
|
if "description" in searchfields:
|
||||||
|
columns += """, if(length(tabItem.description) > 40, \
|
||||||
|
concat(substr(tabItem.description, 1, 40), "..."), description) as description"""
|
||||||
|
|
||||||
searchfields = searchfields + [
|
searchfields = searchfields + [
|
||||||
field
|
field
|
||||||
@@ -266,12 +260,10 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
|||||||
if frappe.db.count(doctype, cache=True) < 50000:
|
if frappe.db.count(doctype, cache=True) < 50000:
|
||||||
# scan description only if items are less than 50000
|
# scan description only if items are less than 50000
|
||||||
description_cond = "or tabItem.description LIKE %(txt)s"
|
description_cond = "or tabItem.description LIKE %(txt)s"
|
||||||
|
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""select
|
"""select
|
||||||
tabItem.name, tabItem.item_name, tabItem.item_group,
|
tabItem.name {columns}
|
||||||
if(length(tabItem.description) > 40, \
|
|
||||||
concat(substr(tabItem.description, 1, 40), "..."), description) as description
|
|
||||||
{columns}
|
|
||||||
from tabItem
|
from tabItem
|
||||||
where tabItem.docstatus < 2
|
where tabItem.docstatus < 2
|
||||||
and tabItem.disabled=0
|
and tabItem.disabled=0
|
||||||
|
|||||||
@@ -841,7 +841,7 @@ def make_rm_stock_entry(
|
|||||||
for fg_item_code in fg_item_code_list:
|
for fg_item_code in fg_item_code_list:
|
||||||
for rm_item in rm_items:
|
for rm_item in rm_items:
|
||||||
|
|
||||||
if rm_item.get("main_item_code") or rm_item.get("item_code") == fg_item_code:
|
if rm_item.get("main_item_code") == fg_item_code or rm_item.get("item_code") == fg_item_code:
|
||||||
rm_item_code = rm_item.get("rm_item_code")
|
rm_item_code = rm_item.get("rm_item_code")
|
||||||
|
|
||||||
items_dict = {
|
items_dict = {
|
||||||
|
|||||||
@@ -391,12 +391,12 @@ scheduler_events = {
|
|||||||
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
||||||
],
|
],
|
||||||
"hourly": [
|
"hourly": [
|
||||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
|
||||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
||||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||||
],
|
],
|
||||||
"hourly_long": [
|
"hourly_long": [
|
||||||
|
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||||
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -557,37 +557,52 @@ erpnext.work_order = {
|
|||||||
|
|
||||||
if(!frm.doc.skip_transfer){
|
if(!frm.doc.skip_transfer){
|
||||||
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
// If "Material Consumption is check in Manufacturing Settings, allow Material Consumption
|
||||||
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))
|
if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') {
|
||||||
&& frm.doc.status != 'Stopped') {
|
if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) {
|
||||||
frm.has_finish_btn = true;
|
frm.has_finish_btn = true;
|
||||||
|
|
||||||
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) {
|
||||||
// Only show "Material Consumption" when required_qty > consumed_qty
|
// Only show "Material Consumption" when required_qty > consumed_qty
|
||||||
var counter = 0;
|
var counter = 0;
|
||||||
var tbl = frm.doc.required_items || [];
|
var tbl = frm.doc.required_items || [];
|
||||||
var tbl_lenght = tbl.length;
|
var tbl_lenght = tbl.length;
|
||||||
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
for (var i = 0, len = tbl_lenght; i < len; i++) {
|
||||||
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
|
||||||
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
|
||||||
counter += 1;
|
counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (counter > 0) {
|
||||||
|
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
||||||
|
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
|
||||||
|
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
|
||||||
|
});
|
||||||
|
consumption_btn.addClass('btn-primary');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (counter > 0) {
|
|
||||||
var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() {
|
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
||||||
const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on;
|
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||||
erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on);
|
});
|
||||||
});
|
|
||||||
consumption_btn.addClass('btn-primary');
|
if(doc.material_transferred_for_manufacturing>=doc.qty) {
|
||||||
|
// all materials transferred for manufacturing, make this primary
|
||||||
|
finish_btn.addClass('btn-primary');
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
|
||||||
|
let allowance_percentage = doc.overproduction_percentage_for_work_order;
|
||||||
|
|
||||||
var finish_btn = frm.add_custom_button(__('Finish'), function() {
|
if (allowance_percentage > 0) {
|
||||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
|
||||||
});
|
|
||||||
|
|
||||||
if(doc.material_transferred_for_manufacturing>=doc.qty) {
|
if ((flt(doc.produced_qty) < allowed_qty)) {
|
||||||
// all materials transferred for manufacturing, make this primary
|
frm.add_custom_button(__('Finish'), function() {
|
||||||
finish_btn.addClass('btn-primary');
|
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ def execute():
|
|||||||
"mode_of_payment": loan.mode_of_payment,
|
"mode_of_payment": loan.mode_of_payment,
|
||||||
"loan_account": loan.loan_account,
|
"loan_account": loan.loan_account,
|
||||||
"payment_account": loan.payment_account,
|
"payment_account": loan.payment_account,
|
||||||
|
"disbursement_account": loan.payment_account,
|
||||||
"interest_income_account": loan.interest_income_account,
|
"interest_income_account": loan.interest_income_account,
|
||||||
"penalty_income_account": loan.penalty_income_account,
|
"penalty_income_account": loan.penalty_income_account,
|
||||||
},
|
},
|
||||||
@@ -190,6 +191,7 @@ def create_loan_type(loan, loan_type_name, penalty_account):
|
|||||||
loan_type_doc.company = loan.company
|
loan_type_doc.company = loan.company
|
||||||
loan_type_doc.mode_of_payment = loan.mode_of_payment
|
loan_type_doc.mode_of_payment = loan.mode_of_payment
|
||||||
loan_type_doc.payment_account = loan.payment_account
|
loan_type_doc.payment_account = loan.payment_account
|
||||||
|
loan_type_doc.disbursement_account = loan.payment_account
|
||||||
loan_type_doc.loan_account = loan.loan_account
|
loan_type_doc.loan_account = loan.loan_account
|
||||||
loan_type_doc.interest_income_account = loan.interest_income_account
|
loan_type_doc.interest_income_account = loan.interest_income_account
|
||||||
loan_type_doc.penalty_income_account = penalty_account
|
loan_type_doc.penalty_income_account = penalty_account
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (available_qty < qty_needed) {
|
} else if (is_stock_item && available_qty < qty_needed) {
|
||||||
frappe.throw({
|
frappe.throw({
|
||||||
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
|
message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
|
||||||
indicator: 'orange'
|
indicator: 'orange'
|
||||||
@@ -694,7 +694,7 @@ erpnext.PointOfSale.Controller = class {
|
|||||||
callback(res) {
|
callback(res) {
|
||||||
if (!me.item_stock_map[item_code])
|
if (!me.item_stock_map[item_code])
|
||||||
me.item_stock_map[item_code] = {};
|
me.item_stock_map[item_code] = {};
|
||||||
me.item_stock_map[item_code][warehouse] = res.message[0];
|
me.item_stock_map[item_code][warehouse] = res.message;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,13 +242,14 @@ erpnext.PointOfSale.ItemDetails = class {
|
|||||||
if (this.value) {
|
if (this.value) {
|
||||||
me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
|
me.events.form_updated(me.current_item, 'warehouse', this.value).then(() => {
|
||||||
me.item_stock_map = me.events.get_item_stock_map();
|
me.item_stock_map = me.events.get_item_stock_map();
|
||||||
const available_qty = me.item_stock_map[me.item_row.item_code] && me.item_stock_map[me.item_row.item_code][this.value];
|
const available_qty = me.item_stock_map[me.item_row.item_code][this.value][0];
|
||||||
|
const is_stock_item = Boolean(me.item_stock_map[me.item_row.item_code][this.value][1]);
|
||||||
if (available_qty === undefined) {
|
if (available_qty === undefined) {
|
||||||
me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
|
me.events.get_available_stock(me.item_row.item_code, this.value).then(() => {
|
||||||
// item stock map is updated now reset warehouse
|
// item stock map is updated now reset warehouse
|
||||||
me.warehouse_control.set_value(this.value);
|
me.warehouse_control.set_value(this.value);
|
||||||
})
|
})
|
||||||
} else if (available_qty === 0) {
|
} else if (available_qty === 0 && is_stock_item) {
|
||||||
me.warehouse_control.set_value('');
|
me.warehouse_control.set_value('');
|
||||||
const bold_item_code = me.item_row.item_code.bold();
|
const bold_item_code = me.item_row.item_code.bold();
|
||||||
const bold_warehouse = this.value.bold();
|
const bold_warehouse = this.value.bold();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.test_runner import make_test_objects
|
||||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import add_days, today
|
from frappe.utils import add_days, today
|
||||||
@@ -816,6 +817,30 @@ class TestItem(FrappeTestCase):
|
|||||||
item.reload()
|
item.reload()
|
||||||
self.assertEqual(item.is_stock_item, 1)
|
self.assertEqual(item.is_stock_item, 1)
|
||||||
|
|
||||||
|
def test_serach_fields_for_item(self):
|
||||||
|
from erpnext.controllers.queries import item_query
|
||||||
|
|
||||||
|
make_property_setter("Item", None, "search_fields", "item_name", "Data", for_doctype="Doctype")
|
||||||
|
|
||||||
|
item = make_item(properties={"item_name": "Test Item", "description": "Test Description"})
|
||||||
|
data = item_query(
|
||||||
|
"Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
|
||||||
|
)
|
||||||
|
self.assertEqual(data[0].name, item.name)
|
||||||
|
self.assertEqual(data[0].item_name, item.item_name)
|
||||||
|
self.assertTrue("description" not in data[0])
|
||||||
|
|
||||||
|
make_property_setter(
|
||||||
|
"Item", None, "search_fields", "item_name, description", "Data", for_doctype="Doctype"
|
||||||
|
)
|
||||||
|
data = item_query(
|
||||||
|
"Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
|
||||||
|
)
|
||||||
|
self.assertEqual(data[0].name, item.name)
|
||||||
|
self.assertEqual(data[0].item_name, item.item_name)
|
||||||
|
self.assertEqual(data[0].description, item.description)
|
||||||
|
self.assertTrue("description" in data[0])
|
||||||
|
|
||||||
|
|
||||||
def set_item_variant_settings(fields):
|
def set_item_variant_settings(fields):
|
||||||
doc = frappe.get_doc("Item Variant Settings")
|
doc = frappe.get_doc("Item Variant Settings")
|
||||||
|
|||||||
@@ -1241,6 +1241,37 @@ class TestPurchaseReceipt(FrappeTestCase):
|
|||||||
|
|
||||||
self.assertEqual(query[0].value, 0)
|
self.assertEqual(query[0].value, 0)
|
||||||
|
|
||||||
|
def test_batch_expiry_for_purchase_receipt(self):
|
||||||
|
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||||
|
|
||||||
|
item = make_item(
|
||||||
|
"_Test Batch Item For Return Check",
|
||||||
|
{
|
||||||
|
"is_purchase_item": 1,
|
||||||
|
"is_stock_item": 1,
|
||||||
|
"has_batch_no": 1,
|
||||||
|
"create_new_batch": 1,
|
||||||
|
"batch_number_series": "TBIRC.#####",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
pi = make_purchase_receipt(
|
||||||
|
qty=1,
|
||||||
|
item_code=item.name,
|
||||||
|
update_stock=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
pi.load_from_db()
|
||||||
|
batch_no = pi.items[0].batch_no
|
||||||
|
self.assertTrue(batch_no)
|
||||||
|
|
||||||
|
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
|
||||||
|
|
||||||
|
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||||
|
return_pi.save().submit()
|
||||||
|
|
||||||
|
self.assertTrue(return_pi.docstatus == 1)
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -1073,8 +1073,8 @@ class StockEntry(StockController):
|
|||||||
# No work order could mean independent Manufacture entry, if so skip validation
|
# No work order could mean independent Manufacture entry, if so skip validation
|
||||||
if self.work_order and self.fg_completed_qty > allowed_qty:
|
if self.work_order and self.fg_completed_qty > allowed_qty:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("For quantity {0} should not be greater than work order quantity {1}").format(
|
_("For quantity {0} should not be greater than allowed quantity {1}").format(
|
||||||
flt(self.fg_completed_qty), wo_qty
|
flt(self.fg_completed_qty), allowed_qty
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,9 @@ class StockLedgerEntry(Document):
|
|||||||
|
|
||||||
def validate_batch(self):
|
def validate_batch(self):
|
||||||
if self.batch_no and self.voucher_type != "Stock Entry":
|
if self.batch_no and self.voucher_type != "Stock Entry":
|
||||||
|
if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0:
|
||||||
|
return
|
||||||
|
|
||||||
expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
|
expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
|
||||||
if expiry_date:
|
if expiry_date:
|
||||||
if getdate(self.posting_date) > getdate(expiry_date):
|
if getdate(self.posting_date) > getdate(expiry_date):
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<b class="caret"></b>
|
<b class="caret"></b>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||||
{% if doc.doctype == 'Purchase Order' %}
|
{% if doc.doctype == 'Purchase Order' and show_make_pi_button %}
|
||||||
<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
|
<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
|
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ def get_context(context):
|
|||||||
)
|
)
|
||||||
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
|
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
|
||||||
|
|
||||||
|
# show Make Purchase Invoice button based on permission
|
||||||
|
context.show_make_pi_button = frappe.has_permission("Purchase Invoice", "create")
|
||||||
|
|
||||||
|
|
||||||
def get_attachments(dt, dn):
|
def get_attachments(dt, dn):
|
||||||
return frappe.get_all(
|
return frappe.get_all(
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
<!-- title: Book an appointment -->
|
<!-- title: Book an appointment -->
|
||||||
<div id="select-date-time">
|
<div id="select-date-time">
|
||||||
<div class="text-center mt-5">
|
<div class="text-center mt-5">
|
||||||
<h3>Book an appointment</h3>
|
<h3>{{ _("Book an appointment") }}</h3>
|
||||||
<p class="lead text-muted" id="lead-text">Select the date and your timezone</p>
|
<p class="lead text-muted" id="lead-text">{{ _("Select the date and your timezone") }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center mt-3">
|
<div class="row justify-content-center mt-3">
|
||||||
<div class="col-md-6 align-self-center ">
|
<div class="col-md-6 align-self-center ">
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center mt-3">
|
<div class="row justify-content-center mt-3">
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<button class="btn btn-primary form-control" id="next-button">Next</button>
|
<button class="btn btn-primary form-control" id="next-button">{{ _("Next") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,24 +39,24 @@
|
|||||||
<!--Enter Details-->
|
<!--Enter Details-->
|
||||||
<div id="enter-details" class="mb-5">
|
<div id="enter-details" class="mb-5">
|
||||||
<div class="text-center mt-5">
|
<div class="text-center mt-5">
|
||||||
<h3>Add details</h3>
|
<h3>{{ _("Add details") }}</h3>
|
||||||
<p class="lead text-muted">Selected date is <span class="date-span"></span> at <span class="time-span">
|
<p class="lead text-muted">{{ _("Selected date is") }} <span class="date-span"></span> {{ _("at") }} <span class="time-span">
|
||||||
</span></p>
|
</span></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center mt-3">
|
<div class="row justify-content-center mt-3">
|
||||||
<div class="col-md-4 align-items-center">
|
<div class="col-md-4 align-items-center">
|
||||||
<form id="customer-form" action='#'>
|
<form id="customer-form" action='#'>
|
||||||
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="Your Name (required)" required>
|
<input class="form-control mt-3" type="text" name="customer_name" id="customer_name" placeholder="{{ _('Your Name (required)') }}" required>
|
||||||
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number" placeholder="+910000000000">
|
<input class="form-control mt-3" type="tel" name="customer_number" id="customer_number" placeholder="+910000000000">
|
||||||
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype">
|
<input class="form-control mt-3" type="text" name="customer_skype" id="customer_skype" placeholder="Skype">
|
||||||
<input class="form-control mt-3"type="email" name="customer_email" id="customer_email" placeholder="Email Address (required)" required>
|
<input class="form-control mt-3"type="email" name="customer_email" id="customer_email" placeholder="{{ _('Email Address (required)') }}" required>
|
||||||
|
|
||||||
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
|
<textarea class="form-control mt-3" name="customer_notes" id="customer_notes" cols="30" rows="10"
|
||||||
placeholder="Notes"></textarea>
|
placeholder="{{ _('Notes') }}"></textarea>
|
||||||
</form>
|
</form>
|
||||||
<div class="row mt-3 " id="submit-button-area">
|
<div class="row mt-3 " id="submit-button-area">
|
||||||
<div class="col-md mt-3" style="grid-area: back;"><button class="btn btn-dark form-control" onclick="initialise_select_date()">Go back</button></div>
|
<div class="col-md mt-3" style="grid-area: back;"><button class="btn btn-dark form-control" onclick="initialise_select_date()">{{ _("Go back") }}</button></div>
|
||||||
<div class="col-md mt-3" style="grid-area: submit;"><button class="btn btn-primary form-control " onclick="submit()" id="submit-button">Submit</button></div>
|
<div class="col-md mt-3" style="grid-area: submit;"><button class="btn btn-primary form-control " onclick="submit()" id="submit-button">{{ _("Submit") }}</button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ function on_date_or_timezone_select() {
|
|||||||
window.selected_timezone = timezone.value;
|
window.selected_timezone = timezone.value;
|
||||||
update_time_slots(date_picker.value, timezone.value);
|
update_time_slots(date_picker.value, timezone.value);
|
||||||
let lead_text = document.getElementById('lead-text');
|
let lead_text = document.getElementById('lead-text');
|
||||||
lead_text.innerHTML = "Select Time"
|
lead_text.innerHTML = __("Select Time")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get_time_slots(date, timezone) {
|
async function get_time_slots(date, timezone) {
|
||||||
@@ -89,7 +89,7 @@ async function update_time_slots(selected_date, selected_timezone) {
|
|||||||
clear_time_slots();
|
clear_time_slots();
|
||||||
if (window.slots.length <= 0) {
|
if (window.slots.length <= 0) {
|
||||||
let message_div = document.createElement('p');
|
let message_div = document.createElement('p');
|
||||||
message_div.innerHTML = "There are no slots available on this date";
|
message_div.innerHTML = __("There are no slots available on this date");
|
||||||
timeslot_container.appendChild(message_div);
|
timeslot_container.appendChild(message_div);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ function get_slot_layout(time) {
|
|||||||
let start_time_string = moment(time).tz(timezone).format("LT");
|
let start_time_string = moment(time).tz(timezone).format("LT");
|
||||||
let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes');
|
let end_time = moment(time).tz(timezone).add(window.appointment_settings.appointment_duration, 'minutes');
|
||||||
let end_time_string = end_time.format("LT");
|
let end_time_string = end_time.format("LT");
|
||||||
return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">to ${end_time_string}</span>`;
|
return `<span style="font-size: 1.2em;">${start_time_string}</span><br><span class="text-muted small">${__("to") } ${end_time_string}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function select_time() {
|
function select_time() {
|
||||||
@@ -227,9 +227,9 @@ async function submit() {
|
|||||||
},
|
},
|
||||||
callback: (response)=>{
|
callback: (response)=>{
|
||||||
if (response.message.status == "Unverified") {
|
if (response.message.status == "Unverified") {
|
||||||
frappe.show_alert("Please check your email to confirm the appointment")
|
frappe.show_alert(__("Please check your email to confirm the appointment"))
|
||||||
} else {
|
} else {
|
||||||
frappe.show_alert("Appointment Created Successfully");
|
frappe.show_alert(__("Appointment Created Successfully"));
|
||||||
}
|
}
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
let redirect_url = "/";
|
let redirect_url = "/";
|
||||||
@@ -239,7 +239,7 @@ async function submit() {
|
|||||||
window.location.href = redirect_url;},5000)
|
window.location.href = redirect_url;},5000)
|
||||||
},
|
},
|
||||||
error: (err)=>{
|
error: (err)=>{
|
||||||
frappe.show_alert("Something went wrong please try again");
|
frappe.show_alert(__("Something went wrong please try again"));
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
|
|
||||||
{% if success==True %}
|
{% if success==True %}
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
Your email has been verified and your appointment has been scheduled
|
{{ _("Your email has been verified and your appointment has been scheduled") }}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
Verification failed please check the link
|
{{ _("Verification failed please check the link") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock%}
|
{% endblock%}
|
||||||
|
|||||||
Reference in New Issue
Block a user