Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
217e407371 | ||
|
|
90607becb8 | ||
|
|
d6888bc8c8 | ||
|
|
063f206b9b | ||
|
|
7a8038500d | ||
|
|
8851826a57 | ||
|
|
2a82f16680 | ||
|
|
32107c4a79 | ||
|
|
bbc7559380 | ||
|
|
9c56a5ff7c | ||
|
|
c8c0f345d7 | ||
|
|
d3c151f32b | ||
|
|
f09222a9e5 | ||
|
|
ba80ba07f1 | ||
|
|
34845b7630 | ||
|
|
ecfa64f22e | ||
|
|
3e7582f55b | ||
|
|
6f571218bf | ||
|
|
8ab2f2f925 | ||
|
|
2d34761057 | ||
|
|
c0a141bfcd | ||
|
|
fd232d1db7 | ||
|
|
df9147f2e5 | ||
|
|
8e68219c5b | ||
|
|
060ce45572 | ||
|
|
432da7d1d1 | ||
|
|
9318d9a7fc | ||
|
|
faca06d6c3 | ||
|
|
1918b5c4b0 | ||
|
|
444f3b5397 | ||
|
|
3736f4ecb8 | ||
|
|
5fe2079dcc | ||
|
|
11f92797b9 | ||
|
|
17996efbc7 | ||
|
|
c2bed24c31 | ||
|
|
e5d1f59d96 | ||
|
|
793fa18225 | ||
|
|
f7ec00ef49 | ||
|
|
4d51d73797 | ||
|
|
7a096231fb | ||
|
|
db60299cf3 | ||
|
|
9ec0d104a5 | ||
|
|
7243089027 | ||
|
|
5aa9c4fd07 | ||
|
|
fd86876a0e | ||
|
|
3f0a3b702d | ||
|
|
f167abbbfd | ||
|
|
5015f38a8d | ||
|
|
381231257b | ||
|
|
f4648ed610 | ||
|
|
51fcb3c666 | ||
|
|
7b11da2373 | ||
|
|
5c0446d9fc | ||
|
|
097398914e | ||
|
|
d91ac5e549 | ||
|
|
b3b7650ac8 | ||
|
|
86052450a5 | ||
|
|
bad3b330f4 | ||
|
|
671033755f | ||
|
|
f5f13c4611 |
@@ -2,7 +2,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.2.1"
|
||||
__version__ = "14.2.2"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -99,7 +99,7 @@ class BankClearance(Document):
|
||||
.where(loan_disbursement.clearance_date.isnull())
|
||||
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
||||
.orderby(loan_disbursement.disbursement_date)
|
||||
.orderby(loan_disbursement.name, frappe.qb.desc)
|
||||
.orderby(loan_disbursement.name, order=frappe.qb.desc)
|
||||
).run(as_dict=1)
|
||||
|
||||
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||
@@ -126,7 +126,9 @@ class BankClearance(Document):
|
||||
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||
query = query.where((loan_repayment.repay_from_salary == 0))
|
||||
|
||||
query = query.orderby(loan_repayment.posting_date).orderby(loan_repayment.name, frappe.qb.desc)
|
||||
query = query.orderby(loan_repayment.posting_date).orderby(
|
||||
loan_repayment.name, order=frappe.qb.desc
|
||||
)
|
||||
|
||||
loan_repayments = query.run(as_dict=True)
|
||||
|
||||
|
||||
@@ -186,8 +186,10 @@
|
||||
{
|
||||
"fetch_from": "bank_account.bank",
|
||||
"fieldname": "bank",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Bank"
|
||||
"fieldtype": "Link",
|
||||
"label": "Bank",
|
||||
"options": "Bank",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "bank_account.bank_account_no",
|
||||
@@ -366,10 +368,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 12:24:14.178853",
|
||||
"modified": "2022-09-30 16:19:43.680025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -401,5 +404,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -98,7 +98,6 @@
|
||||
"section_break_44",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"additional_discount_account",
|
||||
"column_break_46",
|
||||
"additional_discount_percentage",
|
||||
"discount_amount",
|
||||
@@ -1387,12 +1386,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Additional Discount Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_default_payment_terms_template",
|
||||
@@ -1445,7 +1438,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-13 23:39:54.525037",
|
||||
"modified": "2022-09-27 11:07:55.766844",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -669,9 +669,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
||||
@@ -1159,9 +1156,6 @@ class PurchaseInvoice(BuyingController):
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
# tax table gl entries
|
||||
valuation_tax = {}
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, None)
|
||||
@@ -1249,15 +1243,6 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def enable_discount_accounting(self):
|
||||
if not hasattr(self, "_enable_discount_accounting"):
|
||||
self._enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
return self._enable_discount_accounting
|
||||
|
||||
def make_internal_transfer_gl_entries(self, gl_entries):
|
||||
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
|
||||
account_currency = get_account_currency(self.unrealized_profit_loss_account)
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
"manufacturer_part_no",
|
||||
"accounting",
|
||||
"expense_account",
|
||||
"discount_account",
|
||||
"col_break5",
|
||||
"is_fixed_asset",
|
||||
"asset_location",
|
||||
@@ -860,12 +859,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Discount Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "product_bundle",
|
||||
"fieldtype": "Link",
|
||||
@@ -877,7 +870,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-17 05:31:10.520171",
|
||||
"modified": "2022-09-27 10:54:23.980713",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -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.utils import get_account_currency
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
depreciate_asset,
|
||||
get_disposal_account_and_cost_center,
|
||||
get_gl_entries_on_asset_disposal,
|
||||
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.selling_controller import SellingController
|
||||
@@ -1086,18 +1089,20 @@ class SalesInvoice(SellingController):
|
||||
asset.db_set("disposal_date", None)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.reverse_depreciation_entry_made_after_disposal(asset)
|
||||
self.reset_depreciation_schedule(asset)
|
||||
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||
reset_depreciation_schedule(asset, self.posting_date)
|
||||
|
||||
else:
|
||||
if asset.calculate_depreciation:
|
||||
depreciate_asset(asset, self.posting_date)
|
||||
asset.reload()
|
||||
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
|
||||
)
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.depreciate_asset(asset)
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = self.customer
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
|
||||
@@ -8,7 +8,7 @@ import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
||||
@@ -3196,6 +3196,37 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
)
|
||||
|
||||
def test_batch_expiry_for_sales_invoice_return(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
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.#####",
|
||||
},
|
||||
)
|
||||
|
||||
pr = make_purchase_receipt(qty=1, item_code=item.name)
|
||||
|
||||
batch_no = pr.items[0].batch_no
|
||||
si = create_sales_invoice(qty=1, item_code=item.name, update_stock=1, batch_no=batch_no)
|
||||
|
||||
si.load_from_db()
|
||||
batch_no = si.items[0].batch_no
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
|
||||
|
||||
return_si = make_return_doc(si.doctype, si.name)
|
||||
return_si.save().submit()
|
||||
|
||||
self.assertTrue(return_si.docstatus == 1)
|
||||
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
@@ -3289,6 +3320,7 @@ def create_sales_invoice(**args):
|
||||
"serial_no": args.serial_no,
|
||||
"conversion_factor": 1,
|
||||
"incoming_rate": args.incoming_rate or 0,
|
||||
"batch_no": args.batch_no or None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -335,6 +335,9 @@ def get_advance_vouchers(
|
||||
"party": ["in", parties],
|
||||
}
|
||||
|
||||
if party_type == "Customer":
|
||||
filters.update({"against_voucher": ["is", "not set"]})
|
||||
|
||||
if company:
|
||||
filters["company"] = company
|
||||
if from_date and to_date:
|
||||
|
||||
@@ -370,7 +370,7 @@ def get_conditions(filters):
|
||||
where parent=`tabSales Invoice`.name
|
||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
||||
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("cost_center")
|
||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||
conditions += get_sales_invoice_item_field_condition("brand")
|
||||
|
||||
@@ -86,7 +86,7 @@ def get_fiscal_years(
|
||||
)
|
||||
)
|
||||
|
||||
query = query.orderby(FY.year_start_date, Order.desc)
|
||||
query = query.orderby(FY.year_start_date, order=Order.desc)
|
||||
fiscal_years = query.run(as_dict=True)
|
||||
|
||||
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
||||
|
||||
@@ -388,7 +388,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt",
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nDecapitalized",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -828,7 +828,9 @@ class Asset(AccountsController):
|
||||
|
||||
|
||||
def update_maintenance_status():
|
||||
assets = frappe.get_all("Asset", filters={"docstatus": 1, "maintenance_required": 1})
|
||||
assets = frappe.get_all(
|
||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")}
|
||||
)
|
||||
|
||||
for asset in assets:
|
||||
asset = frappe.get_doc("Asset", asset.name)
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
import frappe
|
||||
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 (
|
||||
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):
|
||||
@@ -196,6 +197,11 @@ def scrap_asset(asset_name):
|
||||
_("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(
|
||||
"Company", asset.company, "series_for_depreciation_entry"
|
||||
)
|
||||
@@ -203,7 +209,7 @@ def scrap_asset(asset_name):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.naming_series = depreciation_series
|
||||
je.posting_date = today()
|
||||
je.posting_date = date
|
||||
je.company = asset.company
|
||||
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.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)
|
||||
asset.set_status("Scrapped")
|
||||
|
||||
@@ -225,6 +231,9 @@ def scrap_asset(asset_name):
|
||||
def restore_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
|
||||
|
||||
asset.db_set("disposal_date", None)
|
||||
@@ -235,6 +244,91 @@ def restore_asset(asset_name):
|
||||
asset.set_status()
|
||||
|
||||
|
||||
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
|
||||
):
|
||||
|
||||
@@ -4,10 +4,23 @@
|
||||
import unittest
|
||||
|
||||
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.assets.doctype.asset.asset import make_sales_invoice, split_asset
|
||||
from erpnext.assets.doctype.asset.asset import (
|
||||
make_sales_invoice,
|
||||
split_asset,
|
||||
update_maintenance_status,
|
||||
)
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
post_depreciation_entries,
|
||||
restore_asset,
|
||||
@@ -178,28 +191,48 @@ class TestAsset(AssetSetup):
|
||||
self.assertEqual(doc.items[0].is_fixed_asset, 1)
|
||||
|
||||
def test_scrap_asset(self):
|
||||
date = nowdate()
|
||||
purchase_date = add_months(get_first_day(date), -2)
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2020-01-01",
|
||||
purchase_date="2020-01-01",
|
||||
available_for_use_date=purchase_date,
|
||||
purchase_date=purchase_date,
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=10,
|
||||
frequency_of_depreciation=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)
|
||||
|
||||
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.assertTrue(asset.journal_entry_for_scrap)
|
||||
|
||||
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 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(
|
||||
@@ -216,7 +249,64 @@ class TestAsset(AssetSetup):
|
||||
self.assertFalse(asset.journal_entry_for_scrap)
|
||||
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"),
|
||||
)
|
||||
this_month_depr_amount = 9000.0 if get_last_day(date) == date else 0
|
||||
|
||||
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
|
||||
|
||||
def test_gle_made_by_asset_sale(self):
|
||||
date = nowdate()
|
||||
purchase_date = add_months(get_first_day(date), -2)
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date=purchase_date,
|
||||
purchase_date=purchase_date,
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=10,
|
||||
frequency_of_depreciation=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
post_depreciation_entries(date=add_months(purchase_date, 2))
|
||||
|
||||
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
|
||||
si.customer = "_Test Customer"
|
||||
si.due_date = nowdate()
|
||||
si.get("items")[0].rate = 25000
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
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 = (
|
||||
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0),
|
||||
("Debtors - _TC", 25000.0, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
si.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
|
||||
def test_asset_with_maintenance_required_status_after_sale(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2020-06-06",
|
||||
@@ -224,6 +314,7 @@ class TestAsset(AssetSetup):
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=3,
|
||||
frequency_of_depreciation=10,
|
||||
maintenance_required=1,
|
||||
depreciation_start_date="2020-12-31",
|
||||
submit=1,
|
||||
)
|
||||
@@ -239,24 +330,9 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
|
||||
("Debtors - _TC", 25000.0, 0.0),
|
||||
)
|
||||
update_maintenance_status()
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
si.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
def test_asset_splitting(self):
|
||||
asset = create_asset(
|
||||
@@ -1376,6 +1452,7 @@ def create_asset(**args):
|
||||
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
|
||||
"gross_purchase_amount": args.gross_purchase_amount or 100000,
|
||||
"purchase_receipt_amount": args.purchase_receipt_amount or 100000,
|
||||
"maintenance_required": args.maintenance_required or 0,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
||||
"location": args.location or "Test Location",
|
||||
|
||||
@@ -12,8 +12,11 @@ from six import string_types
|
||||
|
||||
import erpnext
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
depreciate_asset,
|
||||
get_gl_entries_on_asset_disposal,
|
||||
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_value_adjustment.asset_value_adjustment import (
|
||||
@@ -424,7 +427,7 @@ class AssetCapitalization(StockController):
|
||||
asset = self.get_asset(item)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.depreciate_asset(asset)
|
||||
depreciate_asset(asset, self.posting_date)
|
||||
asset.reload()
|
||||
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
@@ -520,8 +523,8 @@ class AssetCapitalization(StockController):
|
||||
self.set_consumed_asset_status(asset)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.reverse_depreciation_entry_made_after_disposal(asset)
|
||||
self.reset_depreciation_schedule(asset)
|
||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||
reset_depreciation_schedule(asset, self.posting_date)
|
||||
|
||||
def get_asset(self, item):
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"maintain_same_rate",
|
||||
"allow_multiple_items",
|
||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"enable_discount_accounting",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
"column_break_11",
|
||||
@@ -134,13 +133,6 @@
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||
"fieldname": "enable_discount_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discount Accounting for Buying"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -148,7 +140,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-01 18:01:34.994657",
|
||||
"modified": "2022-09-27 10:50:27.050252",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -5,15 +5,10 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
|
||||
class BuyingSettings(Document):
|
||||
def on_update(self):
|
||||
self.toggle_discount_accounting_fields()
|
||||
|
||||
def validate(self):
|
||||
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
||||
frappe.db.set_default(key, self.get(key, ""))
|
||||
@@ -26,60 +21,3 @@ class BuyingSettings(Document):
|
||||
self.get("supp_master_name") == "Naming Series",
|
||||
hide_name_field=False,
|
||||
)
|
||||
|
||||
def toggle_discount_accounting_fields(self):
|
||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
frm.set_query("fg_item", "items", function() {
|
||||
return {
|
||||
filters: {
|
||||
'is_stock_item': 1,
|
||||
'is_sub_contracted_item': 1,
|
||||
'default_bom': ['!=', '']
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ from erpnext.accounts.party import (
|
||||
validate_party_frozen_disabled,
|
||||
)
|
||||
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.controllers.print_settings import (
|
||||
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)
|
||||
)
|
||||
|
||||
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()
|
||||
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)
|
||||
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 = ""
|
||||
extra_searchfields = [
|
||||
field
|
||||
for field in searchfields
|
||||
if not field in ["name", "item_group", "description", "item_name"]
|
||||
]
|
||||
extra_searchfields = [field for field in searchfields if not field in ["name", "description"]]
|
||||
|
||||
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 + [
|
||||
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:
|
||||
# scan description only if items are less than 50000
|
||||
description_cond = "or tabItem.description LIKE %(txt)s"
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select
|
||||
tabItem.name, tabItem.item_name, tabItem.item_group,
|
||||
if(length(tabItem.description) > 40, \
|
||||
concat(substr(tabItem.description, 1, 40), "..."), description) as description
|
||||
{columns}
|
||||
tabItem.name {columns}
|
||||
from tabItem
|
||||
where tabItem.docstatus < 2
|
||||
and tabItem.disabled=0
|
||||
|
||||
@@ -69,9 +69,18 @@ class SubcontractingController(StockController):
|
||||
|
||||
def validate_items(self):
|
||||
for item in self.items:
|
||||
if not frappe.get_value("Item", item.item_code, "is_sub_contracted_item"):
|
||||
is_stock_item, is_sub_contracted_item = frappe.get_value(
|
||||
"Item", item.item_code, ["is_stock_item", "is_sub_contracted_item"]
|
||||
)
|
||||
|
||||
if not is_stock_item:
|
||||
msg = f"Item {item.item_name} must be a stock item."
|
||||
frappe.throw(_(msg))
|
||||
|
||||
if not is_sub_contracted_item:
|
||||
msg = f"Item {item.item_name} must be a subcontracted item."
|
||||
frappe.throw(_(msg))
|
||||
|
||||
if item.bom:
|
||||
bom = frappe.get_doc("BOM", item.bom)
|
||||
if not bom.is_active:
|
||||
@@ -841,7 +850,7 @@ def make_rm_stock_entry(
|
||||
for fg_item_code in fg_item_code_list:
|
||||
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")
|
||||
|
||||
items_dict = {
|
||||
|
||||
@@ -508,6 +508,7 @@ accounting_dimension_doctypes = [
|
||||
"Landed Cost Item",
|
||||
"Asset Value Adjustment",
|
||||
"Asset Repair",
|
||||
"Asset Capitalization",
|
||||
"Loyalty Program",
|
||||
"Stock Reconciliation",
|
||||
"POS Profile",
|
||||
|
||||
@@ -315,3 +315,4 @@ erpnext.patches.v14_0.fix_crm_no_of_employees
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes
|
||||
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization
|
||||
|
||||
@@ -100,6 +100,7 @@ def execute():
|
||||
"mode_of_payment": loan.mode_of_payment,
|
||||
"loan_account": loan.loan_account,
|
||||
"payment_account": loan.payment_account,
|
||||
"disbursement_account": loan.payment_account,
|
||||
"interest_income_account": loan.interest_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.mode_of_payment = loan.mode_of_payment
|
||||
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.interest_income_account = loan.interest_income_account
|
||||
loan_type_doc.penalty_income_account = penalty_account
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
|
||||
|
||||
def execute():
|
||||
accounting_dimensions = frappe.db.get_all(
|
||||
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
|
||||
)
|
||||
|
||||
if not accounting_dimensions:
|
||||
return
|
||||
|
||||
doctype = "Asset Capitalization"
|
||||
|
||||
for d in accounting_dimensions:
|
||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
|
||||
|
||||
if field:
|
||||
continue
|
||||
|
||||
df = {
|
||||
"fieldname": d.fieldname,
|
||||
"label": d.label,
|
||||
"fieldtype": "Link",
|
||||
"options": d.document_type,
|
||||
"insert_after": "accounting_dimensions_section",
|
||||
}
|
||||
|
||||
create_custom_field(doctype, df, ignore_validate=True)
|
||||
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
115
erpnext/public/scss/order-page.scss
Normal file
115
erpnext/public/scss/order-page.scss
Normal file
@@ -0,0 +1,115 @@
|
||||
#page-order {
|
||||
.main-column {
|
||||
.page-content-wrapper {
|
||||
|
||||
.breadcrumb-container {
|
||||
@media screen and (min-width: 567px) {
|
||||
padding-left: var(--padding-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.container.my-4 {
|
||||
background-color: var(--fg-color);
|
||||
|
||||
@media screen and (min-width: 567px) {
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-radius: var(--border-radius-md);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicator-container {
|
||||
@media screen and (max-width: 567px) {
|
||||
padding-bottom: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.order-items {
|
||||
padding: 1.5rem 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--gray-700);
|
||||
|
||||
@media screen and (max-width: 567px) {
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
.col-2 {
|
||||
@media screen and (max-width: 567px) {
|
||||
flex: auto;
|
||||
max-width: 28%;
|
||||
}
|
||||
}
|
||||
|
||||
.order-item-name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn:focus,
|
||||
.btn:hover {
|
||||
background-color: var(--control-bg);
|
||||
}
|
||||
|
||||
|
||||
.col-6 {
|
||||
@media screen and (max-width: 567px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&.order-item-name {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-grand-total {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.list-item-name,
|
||||
.item-total,
|
||||
.order-container,
|
||||
.order-qty {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.d-s-n {
|
||||
@media screen and (max-width: 567px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.d-l-n {
|
||||
@media screen and (min-width: 567px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.border-btm {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.order-taxes {
|
||||
display: flex;
|
||||
|
||||
@media screen and (min-width: 567px) {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.col-4 {
|
||||
padding-right: 0;
|
||||
|
||||
.col-8 {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 567px) {
|
||||
padding-left: 0;
|
||||
flex: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
@import './order-page';
|
||||
|
||||
.filter-options {
|
||||
max-height: 300px;
|
||||
@@ -32,19 +33,29 @@
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.website-list .result {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.website-list {
|
||||
background-color: var(--fg-color);
|
||||
padding: 0 var(--padding-lg);
|
||||
border-radius: var(--border-radius-md);
|
||||
|
||||
.result {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
@media screen and (max-width: 567px) {
|
||||
margin-left: -2rem;
|
||||
}
|
||||
|
||||
&.result {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.transaction-list-item {
|
||||
padding: 1rem 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
position: relative;
|
||||
|
||||
&:only-child, &:last-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
a.transaction-item-link {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -68,3 +79,13 @@
|
||||
line-height: 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item-name, .item-total {
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.items-preview {
|
||||
@media screen and (max-width: 567px) {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import cstr, flt, nowdate, nowtime
|
||||
from frappe.utils import add_days, cstr, flt, nowdate, nowtime, today
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
@@ -1091,6 +1091,36 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
|
||||
)
|
||||
|
||||
def test_batch_expiry_for_delivery_note(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
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)
|
||||
|
||||
dn = create_delivery_note(qty=1, item_code=item.name, batch_no=pi.items[0].batch_no)
|
||||
|
||||
dn.load_from_db()
|
||||
batch_no = dn.items[0].batch_no
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
|
||||
|
||||
return_dn = make_return_doc(dn.doctype, dn.name)
|
||||
return_dn.save().submit()
|
||||
|
||||
self.assertTrue(return_dn.docstatus == 1)
|
||||
|
||||
|
||||
def create_delivery_note(**args):
|
||||
dn = frappe.new_doc("Delivery Note")
|
||||
@@ -1117,6 +1147,7 @@ def create_delivery_note(**args):
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no or None,
|
||||
"target_warehouse": args.target_warehouse,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -10,6 +10,31 @@ frappe.ui.form.on("Item", {
|
||||
frm.add_fetch('attribute', 'to_range', 'to_range');
|
||||
frm.add_fetch('attribute', 'increment', 'increment');
|
||||
frm.add_fetch('tax_type', 'tax_rate', 'tax_rate');
|
||||
|
||||
frm.make_methods = {
|
||||
'Sales Order': () => {
|
||||
open_form(frm, "Sales Order", "Sales Order Item", "items");
|
||||
},
|
||||
'Delivery Note': () => {
|
||||
open_form(frm, "Delivery Note", "Delivery Note Item", "items");
|
||||
},
|
||||
'Sales Invoice': () => {
|
||||
open_form(frm, "Sales Invoice", "Sales Invoice Item", "items");
|
||||
},
|
||||
'Purchase Order': () => {
|
||||
open_form(frm, "Purchase Order", "Purchase Order Item", "items");
|
||||
},
|
||||
'Purchase Receipt': () => {
|
||||
open_form(frm, "Purchase Receipt", "Purchase Receipt Item", "items");
|
||||
},
|
||||
'Purchase Invoice': () => {
|
||||
open_form(frm, "Purchase Invoice", "Purchase Invoice Item", "items");
|
||||
},
|
||||
'Material Request': () => {
|
||||
open_form(frm, "Material Request", "Material Request Item", "items");
|
||||
},
|
||||
};
|
||||
|
||||
},
|
||||
onload: function(frm) {
|
||||
erpnext.item.setup_queries(frm);
|
||||
@@ -858,3 +883,17 @@ frappe.tour['Item'] = [
|
||||
|
||||
|
||||
];
|
||||
|
||||
function open_form(frm, doctype, child_doctype, parentfield) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
let new_doc = frappe.model.get_new_doc(doctype);
|
||||
|
||||
let new_child_doc = frappe.model.add_child(new_doc, child_doctype, parentfield);
|
||||
new_child_doc.item_code = frm.doc.name;
|
||||
new_child_doc.item_name = frm.doc.item_name;
|
||||
new_child_doc.uom = frm.doc.stock_uom;
|
||||
new_child_doc.description = frm.doc.description;
|
||||
|
||||
frappe.ui.form.make_quick_entry(doctype, null, null, new_doc);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -937,17 +937,21 @@ class Item(Document):
|
||||
"Purchase Order Item",
|
||||
"Material Request Item",
|
||||
"Product Bundle",
|
||||
"BOM",
|
||||
]
|
||||
|
||||
for doctype in linked_doctypes:
|
||||
filters = {"item_code": self.name, "docstatus": 1}
|
||||
|
||||
if doctype == "Product Bundle":
|
||||
filters = {"new_item_code": self.name}
|
||||
if doctype in ("Product Bundle", "BOM"):
|
||||
if doctype == "Product Bundle":
|
||||
filters = {"new_item_code": self.name}
|
||||
fieldname = "new_item_code as docname"
|
||||
else:
|
||||
filters = {"item": self.name, "docstatus": 1}
|
||||
fieldname = "name as docname"
|
||||
|
||||
if linked_doc := frappe.db.get_value(
|
||||
doctype, filters, ["new_item_code as docname"], as_dict=True
|
||||
):
|
||||
if linked_doc := frappe.db.get_value(doctype, filters, fieldname, as_dict=True):
|
||||
return linked_doc.update({"doctype": doctype})
|
||||
|
||||
elif doctype in (
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.test_runner import make_test_objects
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, today
|
||||
@@ -816,6 +817,30 @@ class TestItem(FrappeTestCase):
|
||||
item.reload()
|
||||
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):
|
||||
doc = frappe.get_doc("Item Variant Settings")
|
||||
|
||||
@@ -183,7 +183,7 @@ class PickList(Document):
|
||||
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
||||
item_code = item.item_code
|
||||
reference = item.sales_order_item or item.material_request_item
|
||||
key = (item_code, item.uom, item.warehouse, reference)
|
||||
key = (item_code, item.uom, item.warehouse, item.batch_no, reference)
|
||||
|
||||
item.idx = None
|
||||
item.name = None
|
||||
|
||||
@@ -153,7 +153,9 @@ class StockLedgerEntry(Document):
|
||||
|
||||
def validate_batch(self):
|
||||
if self.batch_no and self.voucher_type != "Stock Entry":
|
||||
if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0:
|
||||
if (self.voucher_type in ["Purchase Receipt", "Purchase Invoice"] and self.actual_qty < 0) or (
|
||||
self.voucher_type in ["Delivery Note", "Sales Invoice"] and self.actual_qty > 0
|
||||
):
|
||||
return
|
||||
|
||||
expiry_date = frappe.db.get_value("Batch", self.batch_no, "expiry_date")
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
aria-label="{{ _('Your email address...') }}"
|
||||
aria-describedby="footer-subscribe-button">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-sm btn-default"
|
||||
<button class="btn btn-sm btn-secondary pl-3 pr-3 ml-2"
|
||||
type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% macro product_image_square(website_image, css_class="") %}
|
||||
<div class="product-image product-image-square
|
||||
<div class="product-image product-image-square h-100 rounded
|
||||
{% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
|
||||
{% if website_image -%}
|
||||
style="background-image: url('{{ frappe.utils.quoted(website_image) | abs_url }}');"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% macro item_name_and_description(d) %}
|
||||
<div class="row item_name_and_description">
|
||||
<div class="col-xs-4 col-sm-2 order-image-col">
|
||||
<div class="order-image">
|
||||
<div class="order-image h-100">
|
||||
{% if d.thumbnail or d.image %}
|
||||
{{ product_image(d.thumbnail or d.image, no_border=True) }}
|
||||
{% else %}
|
||||
@@ -18,6 +18,9 @@
|
||||
<div class="text-muted small item-description">
|
||||
{{ html2text(d.description) | truncate(140) }}
|
||||
</div>
|
||||
<span class="text-muted mt-2 d-l-n order-qty">
|
||||
{{ _("Qty ") }}({{ d.get_formatted("qty") }})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -1,84 +1,111 @@
|
||||
{% if doc.taxes %}
|
||||
<tr>
|
||||
<td class="text-left" colspan="1">
|
||||
{{ _("Net Total") }}
|
||||
</td>
|
||||
<td class="text-right totals" colspan="3">
|
||||
{{ doc.get_formatted("net_total") }}
|
||||
</td>
|
||||
</tr>
|
||||
<div class="w-100 order-taxes mt-5">
|
||||
<div class="col-4 d-flex border-btm pb-5">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ _("Net Total") }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
{{ doc.get_formatted("net_total") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% for d in doc.taxes %}
|
||||
{% if d.base_tax_amount %}
|
||||
<tr>
|
||||
<td class="text-left" colspan="1">
|
||||
{{ d.description }}
|
||||
</td>
|
||||
<td class="text-right totals" colspan="3">
|
||||
{{ d.get_formatted("base_tax_amount") }}
|
||||
</td>
|
||||
</tr>
|
||||
<div class="order-taxes w-100 mt-5">
|
||||
<div class="col-4 d-flex border-btm pb-5">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ d.description }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
{{ doc.get_formatted("net_total") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if doc.doctype == 'Quotation' %}
|
||||
{% if doc.coupon_code %}
|
||||
<tr>
|
||||
<td class="text-left total-discount" colspan="1">
|
||||
{{ _("Savings") }}
|
||||
</td>
|
||||
<td class="text-right tot_quotation_discount total-discount totals" colspan="3">
|
||||
{% set tot_quotation_discount = [] %}
|
||||
{%- for item in doc.items -%}
|
||||
{% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
|
||||
* item.discount_percentage) / 100)) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
|
||||
</td>
|
||||
</tr>
|
||||
<div class="w-100 mt-5 order-taxes font-weight-bold">
|
||||
<div class="col-4 d-flex border-btm pb-5">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ _("Savings") }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
{% set tot_quotation_discount = [] %}
|
||||
{%- for item in doc.items -%}
|
||||
{% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
|
||||
* item.discount_percentage) / 100)) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }} </div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if doc.doctype == 'Sales Order' %}
|
||||
{% if doc.coupon_code %}
|
||||
<tr>
|
||||
<td class="text-left total-discount" colspan="2" style="padding-right: 2rem;">
|
||||
{{ _("Applied Coupon Code") }}
|
||||
</td>
|
||||
<td class="text-right total-discount">
|
||||
<span>
|
||||
{%- for row in frappe.get_all(doctype="Coupon Code",
|
||||
fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
|
||||
<span>{{ row.coupon_code }}</span>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-left total-discount" colspan="2">
|
||||
{{ _("Savings") }}
|
||||
</td>
|
||||
<td class="text-right total-discount">
|
||||
<span>
|
||||
{% set tot_SO_discount = [] %}
|
||||
{%- for item in doc.items -%}
|
||||
{% if tot_SO_discount.append((((item.price_list_rate * item.qty)
|
||||
* item.discount_percentage) / 100)) %}{% endif %}
|
||||
{% endfor %}
|
||||
{{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<div class="w-100 order-taxes mt-5">
|
||||
<div class="col-4 d-flex border-btm pb-5">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ _("Total Amount") }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
<span>
|
||||
{% set total_amount = [] %}
|
||||
{%- for item in doc.items -%}
|
||||
{% if total_amount.append((item.price_list_rate * item.qty)) %}{% endif %}
|
||||
{% endfor %}
|
||||
{{ frappe.utils.fmt_money((total_amount | sum),currency=doc.currency) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-taxes w-100 mt-5">
|
||||
<div class="col-4 d-flex">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ _("Applied Coupon Code") }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
<span>
|
||||
{%- for row in frappe.get_all(doctype="Coupon Code",
|
||||
fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
|
||||
<span>{{ row.coupon_code }}</span>
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-taxes mt-5">
|
||||
<div class="col-4 d-flex border-btm pb-5">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ _("Savings") }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
<span>
|
||||
{% set tot_SO_discount = [] %}
|
||||
{%- for item in doc.items -%}
|
||||
{% if tot_SO_discount.append((((item.price_list_rate * item.qty)
|
||||
* item.discount_percentage) / 100)) %}{% endif %}
|
||||
{% endfor %}
|
||||
{{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<tr>
|
||||
<th class="text-left item-grand-total" colspan="1">
|
||||
{{ _("Grand Total") }}
|
||||
</th>
|
||||
<th class="text-right item-grand-total totals" colspan="3">
|
||||
{{ doc.get_formatted("grand_total") }}
|
||||
</th>
|
||||
</tr>
|
||||
<div class="w-100 mt-5 order-taxes font-weight-bold">
|
||||
<div class="col-4 d-flex">
|
||||
<div class="item-grand-total col-8">
|
||||
{{ _("Grand Total") }}
|
||||
</div>
|
||||
<div class="item-grand-total col-4 text-right pr-0">
|
||||
{{ doc.get_formatted("grand_total") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<div class="web-list-item transaction-list-item">
|
||||
<div class="row">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-sm-4">
|
||||
<span class="indicator small {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "gray") }}">
|
||||
{{ doc.name }}</span>
|
||||
<span class="list-item-name font-weight-bold">{{ doc.name }}</span>
|
||||
<div class="small text-muted transaction-time"
|
||||
title="{{ frappe.utils.format_datetime(doc.modified, "medium") }}">
|
||||
{{ frappe.utils.global_date_format(doc.modified) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="col-sm-3">
|
||||
<span class="indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "gray") }} list-item-status">{{doc.status}}</span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div class="small text-muted items-preview ellipsis ellipsis-width">
|
||||
{{ doc.items_preview }}
|
||||
</div>
|
||||
</div>
|
||||
{% if doc.get('grand_total') %}
|
||||
<div class="col-sm-3 text-right bold">
|
||||
<div class="col-sm-3 text-right font-weight-bold item-total">
|
||||
{{ doc.get_formatted("grand_total") }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -5,149 +5,159 @@
|
||||
{% include "templates/includes/breadcrumbs.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{{ doc.name }}{% endblock %}
|
||||
{% block title %}
|
||||
{{ doc.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<h2 class="m-0">{{ doc.name }}</h2>
|
||||
<h3 class="m-0">{{ doc.name }}</h3>
|
||||
{% endblock %}
|
||||
|
||||
{% block header_actions %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="font-md">{{ _('Actions') }}</span>
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
{% if doc.doctype == 'Purchase Order' %}
|
||||
<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 %}
|
||||
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
{{ _("Print") }}
|
||||
</a>
|
||||
</ul>
|
||||
<div class="row">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="font-md">{{ _('Actions') }}</span>
|
||||
<b class="caret"></b>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
{% 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>
|
||||
{% endif %}
|
||||
<a class="dropdown-item"
|
||||
href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{{ _("Print") }}
|
||||
</a>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="form-column col-sm-6">
|
||||
<div class="page-header-actions-block" data-html-block="header-actions">
|
||||
<p>
|
||||
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
|
||||
class="btn btn-primary btn-sm" id="pay-for-order">
|
||||
{{ _("Pay") }} {{doc.get_formatted("grand_total") }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
<div class="row transaction-subheading">
|
||||
<div class="col-6">
|
||||
<span class="font-md indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
|
||||
{% if doc.doctype == "Quotation" and not doc.docstatus %}
|
||||
{{ _("Pending") }}
|
||||
{% else %}
|
||||
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
||||
<div>
|
||||
<div class="row transaction-subheading mt-1">
|
||||
<div class="col-6 text-muted small mt-1">
|
||||
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
|
||||
{% if doc.valid_till %}
|
||||
<p>
|
||||
{{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 text-muted text-right small pt-3">
|
||||
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
|
||||
{% if doc.valid_till %}
|
||||
<p>
|
||||
{{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="small my-3">
|
||||
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %}
|
||||
<b>{{ party_name }}</b>
|
||||
|
||||
{% if doc.contact_display and doc.contact_display != party_name %}
|
||||
<br>
|
||||
{{ doc.contact_display }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if doc._header %}
|
||||
{{ doc._header }}
|
||||
{% endif %}
|
||||
|
||||
<div class="order-container">
|
||||
<!-- items -->
|
||||
<table class="order-item-table w-100 table">
|
||||
<thead class="order-items order-item-header">
|
||||
<th width="60%">
|
||||
{{ _("Item") }}
|
||||
</th>
|
||||
<th width="20%" class="text-right">
|
||||
{{ _("Quantity") }}
|
||||
</th>
|
||||
<th width="20%" class="text-right">
|
||||
{{ _("Amount") }}
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for d in doc.items %}
|
||||
<tr class="order-items">
|
||||
<td>
|
||||
{{ item_name_and_description(d) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ d.qty }}
|
||||
{% if d.delivered_qty is defined and d.delivered_qty != None %}
|
||||
<p class="text-muted small">{{ _("Delivered") }} {{ d.delivered_qty }}</p>
|
||||
<div class="row indicator-container mt-2">
|
||||
<div class="col-10">
|
||||
<span class="indicator-pill {{ doc.indicator_color or (" blue" if doc.docstatus==1 else "darkgrey" ) }}">
|
||||
{% if doc.doctype == "Quotation" and not doc.docstatus %}
|
||||
{{ _("Pending") }}
|
||||
{% else %}
|
||||
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ d.get_formatted("amount") }}
|
||||
<p class="text-muted small">{{ _("Rate:") }} {{ d.get_formatted("rate") }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- taxes -->
|
||||
<div class="order-taxes d-flex justify-content-end">
|
||||
<table>
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-right col-2">
|
||||
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase
|
||||
Order'] else doc.customer_name %}
|
||||
<b>{{ party_name }}</b>
|
||||
|
||||
{% if doc.contact_display and doc.contact_display != party_name %}
|
||||
<br>
|
||||
{{ doc.contact_display }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if doc._header %}
|
||||
{{ doc._header }}
|
||||
{% endif %}
|
||||
|
||||
<div class="order-container mt-4">
|
||||
<!-- items -->
|
||||
<div class="w-100">
|
||||
<div class="order-items order-item-header mb-1 row text-muted">
|
||||
<span class="col-5">
|
||||
{{ _("Item") }}
|
||||
</span>
|
||||
<span class="d-s-n col-3">
|
||||
{{ _("Quantity") }}
|
||||
</span>
|
||||
<span class="col-2 pl-10">
|
||||
{{ _("Rate") }}
|
||||
</span>
|
||||
<span class="col-2 text-right">
|
||||
{{ _("Amount") }}
|
||||
</span>
|
||||
</div>
|
||||
{% for d in doc.items %}
|
||||
<div class="order-items row align-items-center">
|
||||
<span class="order-item-name col-5 pr-0">
|
||||
{{ item_name_and_description(d) }}
|
||||
</span>
|
||||
|
||||
<span class="d-s-n col-3 pl-10">
|
||||
{{ d.get_formatted("qty") }}
|
||||
</span>
|
||||
<span class="order-rate pl-4 col-2">
|
||||
{{ d.get_formatted("rate") }}
|
||||
</span>
|
||||
<span class="col-2 text-right">
|
||||
{{ d.get_formatted("amount") }}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- taxes -->
|
||||
<div class="">
|
||||
{% include "erpnext/templates/includes/order/order_taxes.html" %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0)
|
||||
or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0)) %}
|
||||
or (doc.doctype=="Sales Invoice" and doc.outstanding_amount> 0)) %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="form-column col-sm-6 address-title">
|
||||
<strong>Payment</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-collapse">
|
||||
<div class="panel-body text-muted small">
|
||||
<div class="row">
|
||||
<div class="form-column col-sm-6">
|
||||
{% if available_loyalty_points %}
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="form-column col-sm-6 address-title">
|
||||
<strong>Loyalty Points</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="h6">Enter Loyalty Points</div>
|
||||
<div class="control-input-wrapper">
|
||||
<div class="control-input">
|
||||
<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
|
||||
<input class="form-control" type="number" min="0"
|
||||
max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
|
||||
</div>
|
||||
<p class="help-box small text-muted d-none d-sm-block"> Available Points: {{ available_loyalty_points }} </p>
|
||||
<p class="help-box small text-muted d-none d-sm-block"> Available Points: {{
|
||||
available_loyalty_points }} </p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-column col-sm-6">
|
||||
<div id="loyalty-points-status" style="text-align: right"></div>
|
||||
<div class="page-header-actions-block" data-html-block="header-actions">
|
||||
<p class="mt-2" style="float: right;">
|
||||
<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
|
||||
class="btn btn-primary btn-sm"
|
||||
id="pay-for-order">
|
||||
{{ _("Pay") }} {{ doc.get_formatted("grand_total") }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -172,17 +182,17 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if doc.terms %}
|
||||
<div class="terms-and-condition text-muted small">
|
||||
<hr><p>{{ doc.terms }}</p>
|
||||
<hr>
|
||||
<p>{{ doc.terms }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script> {% include "templates/pages/order.js" %} </script>
|
||||
<script> {% include "templates/pages/order.js" %}</script>
|
||||
<script>
|
||||
window.doc_info = {
|
||||
customer: '{{doc.customer}}',
|
||||
@@ -192,4 +202,4 @@
|
||||
currency: '{{ doc.currency }}'
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -52,6 +52,9 @@ def get_context(context):
|
||||
)
|
||||
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):
|
||||
return frappe.get_all(
|
||||
|
||||
@@ -9,7 +9,6 @@ from frappe import _
|
||||
def transaction_processing(data, from_doctype, to_doctype):
|
||||
if isinstance(data, str):
|
||||
deserialized_data = json.loads(data)
|
||||
|
||||
else:
|
||||
deserialized_data = data
|
||||
|
||||
@@ -30,30 +29,29 @@ def transaction_processing(data, from_doctype, to_doctype):
|
||||
|
||||
|
||||
def job(deserialized_data, from_doctype, to_doctype):
|
||||
failed_history = []
|
||||
i = 0
|
||||
fail_count = 0
|
||||
for d in deserialized_data:
|
||||
failed = []
|
||||
|
||||
try:
|
||||
i += 1
|
||||
doc_name = d.get("name")
|
||||
frappe.db.savepoint("before_creation_state")
|
||||
task(doc_name, from_doctype, to_doctype)
|
||||
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point="before_creation_state")
|
||||
failed_history.append(e)
|
||||
failed.append(e)
|
||||
fail_count += 1
|
||||
update_logger(
|
||||
doc_name, e, from_doctype, to_doctype, status="Failed", log_date=str(date.today())
|
||||
doc_name,
|
||||
str(frappe.get_traceback()),
|
||||
from_doctype,
|
||||
to_doctype,
|
||||
status="Failed",
|
||||
log_date=str(date.today()),
|
||||
)
|
||||
if not failed:
|
||||
else:
|
||||
update_logger(
|
||||
doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today())
|
||||
)
|
||||
|
||||
show_job_status(failed_history, deserialized_data, to_doctype)
|
||||
show_job_status(fail_count, len(deserialized_data), to_doctype)
|
||||
|
||||
|
||||
def task(doc_name, from_doctype, to_doctype):
|
||||
@@ -94,7 +92,7 @@ def task(doc_name, from_doctype, to_doctype):
|
||||
"Purchase Invoice": purchase_order.make_purchase_invoice,
|
||||
"Purchase Receipt": purchase_order.make_purchase_receipt,
|
||||
},
|
||||
"Purhcase Invoice": {
|
||||
"Purchase Invoice": {
|
||||
"Purchase Receipt": purchase_invoice.make_purchase_receipt,
|
||||
"Payment": payment_entry.get_payment_entry,
|
||||
},
|
||||
@@ -150,15 +148,14 @@ def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None,
|
||||
log_doc.save()
|
||||
|
||||
|
||||
def show_job_status(failed_history, deserialized_data, to_doctype):
|
||||
if not failed_history:
|
||||
def show_job_status(fail_count, deserialized_data_count, to_doctype):
|
||||
if not fail_count:
|
||||
frappe.msgprint(
|
||||
_("Creation of {0} successful").format(to_doctype),
|
||||
title="Successful",
|
||||
indicator="green",
|
||||
)
|
||||
|
||||
if len(failed_history) != 0 and len(failed_history) < len(deserialized_data):
|
||||
elif fail_count != 0 and fail_count < deserialized_data_count:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"""Creation of {0} partially successful.
|
||||
@@ -167,8 +164,7 @@ def show_job_status(failed_history, deserialized_data, to_doctype):
|
||||
title="Partially successful",
|
||||
indicator="orange",
|
||||
)
|
||||
|
||||
if len(failed_history) == len(deserialized_data):
|
||||
else:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"""Creation of {0} failed.
|
||||
@@ -180,9 +176,7 @@ def show_job_status(failed_history, deserialized_data, to_doctype):
|
||||
|
||||
|
||||
def record_exists(log_doc, doc_name, status):
|
||||
|
||||
record = mark_retrired_transaction(log_doc, doc_name)
|
||||
|
||||
if record and status == "Failed":
|
||||
return False
|
||||
elif record and status == "Success":
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
.time-slot.selected {
|
||||
color: white;
|
||||
background: #5e64ff;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.time-slot.selected .text-muted {
|
||||
|
||||
Reference in New Issue
Block a user