mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-31 18:59:08 +00:00
Merge pull request #32493 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -99,7 +99,7 @@ class BankClearance(Document):
|
|||||||
.where(loan_disbursement.clearance_date.isnull())
|
.where(loan_disbursement.clearance_date.isnull())
|
||||||
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
||||||
.orderby(loan_disbursement.disbursement_date)
|
.orderby(loan_disbursement.disbursement_date)
|
||||||
.orderby(loan_disbursement.name, frappe.qb.desc)
|
.orderby(loan_disbursement.name, order=frappe.qb.desc)
|
||||||
).run(as_dict=1)
|
).run(as_dict=1)
|
||||||
|
|
||||||
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||||
@@ -126,7 +126,9 @@ class BankClearance(Document):
|
|||||||
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||||
query = query.where((loan_repayment.repay_from_salary == 0))
|
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)
|
loan_repayments = query.run(as_dict=True)
|
||||||
|
|
||||||
|
|||||||
@@ -186,8 +186,10 @@
|
|||||||
{
|
{
|
||||||
"fetch_from": "bank_account.bank",
|
"fetch_from": "bank_account.bank",
|
||||||
"fieldname": "bank",
|
"fieldname": "bank",
|
||||||
"fieldtype": "Read Only",
|
"fieldtype": "Link",
|
||||||
"label": "Bank"
|
"label": "Bank",
|
||||||
|
"options": "Bank",
|
||||||
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "bank_account.bank_account_no",
|
"fetch_from": "bank_account.bank_account_no",
|
||||||
@@ -366,10 +368,11 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-18 12:24:14.178853",
|
"modified": "2022-09-30 16:19:43.680025",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Request",
|
"name": "Payment Request",
|
||||||
|
"naming_rule": "By \"Naming Series\" field",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
@@ -401,5 +404,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC"
|
"sort_order": "DESC",
|
||||||
|
"states": []
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,6 @@
|
|||||||
"section_break_44",
|
"section_break_44",
|
||||||
"apply_discount_on",
|
"apply_discount_on",
|
||||||
"base_discount_amount",
|
"base_discount_amount",
|
||||||
"additional_discount_account",
|
|
||||||
"column_break_46",
|
"column_break_46",
|
||||||
"additional_discount_percentage",
|
"additional_discount_percentage",
|
||||||
"discount_amount",
|
"discount_amount",
|
||||||
@@ -1387,12 +1386,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "additional_discount_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Additional Discount Account",
|
|
||||||
"options": "Account"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"fieldname": "ignore_default_payment_terms_template",
|
"fieldname": "ignore_default_payment_terms_template",
|
||||||
@@ -1445,7 +1438,7 @@
|
|||||||
"idx": 204,
|
"idx": 204,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-13 23:39:54.525037",
|
"modified": "2022-09-27 11:07:55.766844",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice",
|
"name": "Purchase Invoice",
|
||||||
|
|||||||
@@ -669,9 +669,6 @@ class PurchaseInvoice(BuyingController):
|
|||||||
|
|
||||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
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(
|
provisional_accounting_for_non_stock_items = cint(
|
||||||
frappe.db.get_value(
|
frappe.db.get_value(
|
||||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
"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):
|
def make_tax_gl_entries(self, gl_entries):
|
||||||
# tax table gl entries
|
# tax table gl entries
|
||||||
valuation_tax = {}
|
valuation_tax = {}
|
||||||
enable_discount_accounting = cint(
|
|
||||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
|
||||||
)
|
|
||||||
|
|
||||||
for tax in self.get("taxes"):
|
for tax in self.get("taxes"):
|
||||||
amount, base_amount = self.get_tax_amounts(tax, None)
|
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):
|
def make_internal_transfer_gl_entries(self, gl_entries):
|
||||||
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
|
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
|
||||||
account_currency = get_account_currency(self.unrealized_profit_loss_account)
|
account_currency = get_account_currency(self.unrealized_profit_loss_account)
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
"manufacturer_part_no",
|
"manufacturer_part_no",
|
||||||
"accounting",
|
"accounting",
|
||||||
"expense_account",
|
"expense_account",
|
||||||
"discount_account",
|
|
||||||
"col_break5",
|
"col_break5",
|
||||||
"is_fixed_asset",
|
"is_fixed_asset",
|
||||||
"asset_location",
|
"asset_location",
|
||||||
@@ -860,12 +859,6 @@
|
|||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"fieldname": "discount_account",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Discount Account",
|
|
||||||
"options": "Account"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"fieldname": "product_bundle",
|
"fieldname": "product_bundle",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
@@ -877,7 +870,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-06-17 05:31:10.520171",
|
"modified": "2022-09-27 10:54:23.980713",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"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.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
|
||||||
@@ -1086,18 +1089,20 @@ class SalesInvoice(SellingController):
|
|||||||
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, self.get("doctype"), self.get("name")
|
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))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import frappe
|
|||||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||||
from frappe.model.naming import make_autoname
|
from frappe.model.naming import make_autoname
|
||||||
from frappe.tests.utils import change_settings
|
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
|
import erpnext
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
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
|
"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():
|
def get_sales_invoice_for_e_invoice():
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
@@ -3289,6 +3320,7 @@ def create_sales_invoice(**args):
|
|||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
"conversion_factor": 1,
|
"conversion_factor": 1,
|
||||||
"incoming_rate": args.incoming_rate or 0,
|
"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],
|
"party": ["in", parties],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if party_type == "Customer":
|
||||||
|
filters.update({"against_voucher": ["is", "not set"]})
|
||||||
|
|
||||||
if company:
|
if company:
|
||||||
filters["company"] = company
|
filters["company"] = company
|
||||||
if from_date and to_date:
|
if from_date and to_date:
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ def get_conditions(filters):
|
|||||||
where parent=`tabSales Invoice`.name
|
where parent=`tabSales Invoice`.name
|
||||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
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("cost_center")
|
||||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||||
conditions += get_sales_invoice_item_field_condition("brand")
|
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)
|
fiscal_years = query.run(as_dict=True)
|
||||||
|
|
||||||
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
||||||
|
|||||||
@@ -388,7 +388,7 @@
|
|||||||
"in_standard_filter": 1,
|
"in_standard_filter": 1,
|
||||||
"label": "Status",
|
"label": "Status",
|
||||||
"no_copy": 1,
|
"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
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -828,7 +828,9 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
|
|
||||||
def update_maintenance_status():
|
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:
|
for asset in assets:
|
||||||
asset = frappe.get_doc("Asset", asset.name)
|
asset = frappe.get_doc("Asset", asset.name)
|
||||||
|
|||||||
@@ -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,6 +244,91 @@ def restore_asset(asset_name):
|
|||||||
asset.set_status()
|
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(
|
def get_gl_entries_on_asset_regain(
|
||||||
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
|
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -4,10 +4,23 @@
|
|||||||
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,
|
||||||
|
update_maintenance_status,
|
||||||
|
)
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
post_depreciation_entries,
|
post_depreciation_entries,
|
||||||
restore_asset,
|
restore_asset,
|
||||||
@@ -178,28 +191,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,7 +249,64 @@ 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"),
|
||||||
|
)
|
||||||
|
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):
|
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(
|
asset = create_asset(
|
||||||
calculate_depreciation=1,
|
calculate_depreciation=1,
|
||||||
available_for_use_date="2020-06-06",
|
available_for_use_date="2020-06-06",
|
||||||
@@ -224,6 +314,7 @@ class TestAsset(AssetSetup):
|
|||||||
expected_value_after_useful_life=10000,
|
expected_value_after_useful_life=10000,
|
||||||
total_number_of_depreciations=3,
|
total_number_of_depreciations=3,
|
||||||
frequency_of_depreciation=10,
|
frequency_of_depreciation=10,
|
||||||
|
maintenance_required=1,
|
||||||
depreciation_start_date="2020-12-31",
|
depreciation_start_date="2020-12-31",
|
||||||
submit=1,
|
submit=1,
|
||||||
)
|
)
|
||||||
@@ -239,24 +330,9 @@ 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")
|
||||||
|
|
||||||
expected_gle = (
|
update_maintenance_status()
|
||||||
("_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),
|
|
||||||
)
|
|
||||||
|
|
||||||
gle = frappe.db.sql(
|
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||||
"""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_splitting(self):
|
def test_asset_splitting(self):
|
||||||
asset = create_asset(
|
asset = create_asset(
|
||||||
@@ -1376,6 +1452,7 @@ def create_asset(**args):
|
|||||||
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
|
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
|
||||||
"gross_purchase_amount": args.gross_purchase_amount or 100000,
|
"gross_purchase_amount": args.gross_purchase_amount or 100000,
|
||||||
"purchase_receipt_amount": args.purchase_receipt_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",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
||||||
"location": args.location or "Test Location",
|
"location": args.location or "Test Location",
|
||||||
|
|||||||
@@ -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,7 +427,7 @@ 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(
|
||||||
@@ -520,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)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
"maintain_same_rate",
|
"maintain_same_rate",
|
||||||
"allow_multiple_items",
|
"allow_multiple_items",
|
||||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||||
"enable_discount_accounting",
|
|
||||||
"subcontract",
|
"subcontract",
|
||||||
"backflush_raw_materials_of_subcontract_based_on",
|
"backflush_raw_materials_of_subcontract_based_on",
|
||||||
"column_break_11",
|
"column_break_11",
|
||||||
@@ -134,13 +133,6 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_12",
|
"fieldname": "column_break_12",
|
||||||
"fieldtype": "Column Break"
|
"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",
|
"icon": "fa fa-cog",
|
||||||
@@ -148,7 +140,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-09-01 18:01:34.994657",
|
"modified": "2022-09-27 10:50:27.050252",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Buying",
|
"module": "Buying",
|
||||||
"name": "Buying Settings",
|
"name": "Buying Settings",
|
||||||
|
|||||||
@@ -5,15 +5,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint
|
|
||||||
|
|
||||||
|
|
||||||
class BuyingSettings(Document):
|
class BuyingSettings(Document):
|
||||||
def on_update(self):
|
|
||||||
self.toggle_discount_accounting_fields()
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
||||||
frappe.db.set_default(key, self.get(key, ""))
|
frappe.db.set_default(key, self.get(key, ""))
|
||||||
@@ -26,60 +21,3 @@ class BuyingSettings(Document):
|
|||||||
self.get("supp_master_name") == "Naming Series",
|
self.get("supp_master_name") == "Naming Series",
|
||||||
hide_name_field=False,
|
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() {
|
frm.set_query("fg_item", "items", function() {
|
||||||
return {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
|
'is_stock_item': 1,
|
||||||
'is_sub_contracted_item': 1,
|
'is_sub_contracted_item': 1,
|
||||||
'default_bom': ['!=', '']
|
'default_bom': ['!=', '']
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -69,9 +69,18 @@ class SubcontractingController(StockController):
|
|||||||
|
|
||||||
def validate_items(self):
|
def validate_items(self):
|
||||||
for item in self.items:
|
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."
|
msg = f"Item {item.item_name} must be a subcontracted item."
|
||||||
frappe.throw(_(msg))
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
if item.bom:
|
if item.bom:
|
||||||
bom = frappe.get_doc("BOM", item.bom)
|
bom = frappe.get_doc("BOM", item.bom)
|
||||||
if not bom.is_active:
|
if not bom.is_active:
|
||||||
@@ -841,7 +850,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 = {
|
||||||
|
|||||||
@@ -508,6 +508,7 @@ accounting_dimension_doctypes = [
|
|||||||
"Landed Cost Item",
|
"Landed Cost Item",
|
||||||
"Asset Value Adjustment",
|
"Asset Value Adjustment",
|
||||||
"Asset Repair",
|
"Asset Repair",
|
||||||
|
"Asset Capitalization",
|
||||||
"Loyalty Program",
|
"Loyalty Program",
|
||||||
"Stock Reconciliation",
|
"Stock Reconciliation",
|
||||||
"POS Profile",
|
"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.create_accounting_dimensions_in_subcontracting_doctypes
|
||||||
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries
|
||||||
erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
|
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,
|
"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
|
||||||
|
|||||||
@@ -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 {
|
.filter-options {
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
@@ -32,19 +33,29 @@
|
|||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.website-list .result {
|
.website-list {
|
||||||
margin-top: 2rem;
|
background-color: var(--fg-color);
|
||||||
}
|
padding: 0 var(--padding-lg);
|
||||||
|
border-radius: var(--border-radius-md);
|
||||||
|
|
||||||
.result {
|
@media screen and (max-width: 567px) {
|
||||||
border-bottom: 1px solid var(--border-color);
|
margin-left: -2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.result {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-list-item {
|
.transaction-list-item {
|
||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
border-top: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&:only-child, &:last-child {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
a.transaction-item-link {
|
a.transaction-item-link {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -68,3 +79,13 @@
|
|||||||
line-height: 1.3;
|
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
|
import frappe
|
||||||
from frappe.tests.utils import FrappeTestCase
|
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.doctype.account.test_account import get_inventory_account
|
||||||
from erpnext.accounts.utils import get_balance_on
|
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})
|
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):
|
def create_delivery_note(**args):
|
||||||
dn = frappe.new_doc("Delivery Note")
|
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",
|
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"serial_no": args.serial_no,
|
"serial_no": args.serial_no,
|
||||||
|
"batch_no": args.batch_no or None,
|
||||||
"target_warehouse": args.target_warehouse,
|
"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', 'to_range', 'to_range');
|
||||||
frm.add_fetch('attribute', 'increment', 'increment');
|
frm.add_fetch('attribute', 'increment', 'increment');
|
||||||
frm.add_fetch('tax_type', 'tax_rate', 'tax_rate');
|
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) {
|
onload: function(frm) {
|
||||||
erpnext.item.setup_queries(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",
|
"Purchase Order Item",
|
||||||
"Material Request Item",
|
"Material Request Item",
|
||||||
"Product Bundle",
|
"Product Bundle",
|
||||||
|
"BOM",
|
||||||
]
|
]
|
||||||
|
|
||||||
for doctype in linked_doctypes:
|
for doctype in linked_doctypes:
|
||||||
filters = {"item_code": self.name, "docstatus": 1}
|
filters = {"item_code": self.name, "docstatus": 1}
|
||||||
|
|
||||||
if doctype == "Product Bundle":
|
if doctype in ("Product Bundle", "BOM"):
|
||||||
filters = {"new_item_code": self.name}
|
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(
|
if linked_doc := frappe.db.get_value(doctype, filters, fieldname, as_dict=True):
|
||||||
doctype, filters, ["new_item_code as docname"], as_dict=True
|
|
||||||
):
|
|
||||||
return linked_doc.update({"doctype": doctype})
|
return linked_doc.update({"doctype": doctype})
|
||||||
|
|
||||||
elif doctype in (
|
elif doctype in (
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ class PickList(Document):
|
|||||||
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
|
||||||
item_code = item.item_code
|
item_code = item.item_code
|
||||||
reference = item.sales_order_item or item.material_request_item
|
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.idx = None
|
||||||
item.name = None
|
item.name = None
|
||||||
|
|||||||
@@ -153,7 +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:
|
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
|
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")
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
aria-label="{{ _('Your email address...') }}"
|
aria-label="{{ _('Your email address...') }}"
|
||||||
aria-describedby="footer-subscribe-button">
|
aria-describedby="footer-subscribe-button">
|
||||||
<div class="input-group-append">
|
<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>
|
type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% macro product_image_square(website_image, css_class="") %}
|
{% 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 not website_image -%} missing-image {%- endif %} {{ css_class }}"
|
||||||
{% if website_image -%}
|
{% if website_image -%}
|
||||||
style="background-image: url('{{ frappe.utils.quoted(website_image) | abs_url }}');"
|
style="background-image: url('{{ frappe.utils.quoted(website_image) | abs_url }}');"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% macro item_name_and_description(d) %}
|
{% macro item_name_and_description(d) %}
|
||||||
<div class="row item_name_and_description">
|
<div class="row item_name_and_description">
|
||||||
<div class="col-xs-4 col-sm-2 order-image-col">
|
<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 %}
|
{% if d.thumbnail or d.image %}
|
||||||
{{ product_image(d.thumbnail or d.image, no_border=True) }}
|
{{ product_image(d.thumbnail or d.image, no_border=True) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
<div class="text-muted small item-description">
|
<div class="text-muted small item-description">
|
||||||
{{ html2text(d.description) | truncate(140) }}
|
{{ html2text(d.description) | truncate(140) }}
|
||||||
</div>
|
</div>
|
||||||
|
<span class="text-muted mt-2 d-l-n order-qty">
|
||||||
|
{{ _("Qty ") }}({{ d.get_formatted("qty") }})
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,84 +1,111 @@
|
|||||||
{% if doc.taxes %}
|
{% if doc.taxes %}
|
||||||
<tr>
|
<div class="w-100 order-taxes mt-5">
|
||||||
<td class="text-left" colspan="1">
|
<div class="col-4 d-flex border-btm pb-5">
|
||||||
{{ _("Net Total") }}
|
<div class="item-grand-total col-8">
|
||||||
</td>
|
{{ _("Net Total") }}
|
||||||
<td class="text-right totals" colspan="3">
|
</div>
|
||||||
{{ doc.get_formatted("net_total") }}
|
<div class="item-grand-total col-4 text-right pr-0">
|
||||||
</td>
|
{{ doc.get_formatted("net_total") }}
|
||||||
</tr>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for d in doc.taxes %}
|
{% for d in doc.taxes %}
|
||||||
{% if d.base_tax_amount %}
|
{% if d.base_tax_amount %}
|
||||||
<tr>
|
<div class="order-taxes w-100 mt-5">
|
||||||
<td class="text-left" colspan="1">
|
<div class="col-4 d-flex border-btm pb-5">
|
||||||
{{ d.description }}
|
<div class="item-grand-total col-8">
|
||||||
</td>
|
{{ d.description }}
|
||||||
<td class="text-right totals" colspan="3">
|
</div>
|
||||||
{{ d.get_formatted("base_tax_amount") }}
|
<div class="item-grand-total col-4 text-right pr-0">
|
||||||
</td>
|
{{ doc.get_formatted("net_total") }}
|
||||||
</tr>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if doc.doctype == 'Quotation' %}
|
{% if doc.doctype == 'Quotation' %}
|
||||||
{% if doc.coupon_code %}
|
{% if doc.coupon_code %}
|
||||||
<tr>
|
<div class="w-100 mt-5 order-taxes font-weight-bold">
|
||||||
<td class="text-left total-discount" colspan="1">
|
<div class="col-4 d-flex border-btm pb-5">
|
||||||
{{ _("Savings") }}
|
<div class="item-grand-total col-8">
|
||||||
</td>
|
{{ _("Savings") }}
|
||||||
<td class="text-right tot_quotation_discount total-discount totals" colspan="3">
|
</div>
|
||||||
{% set tot_quotation_discount = [] %}
|
<div class="item-grand-total col-4 text-right pr-0">
|
||||||
{%- for item in doc.items -%}
|
{% set tot_quotation_discount = [] %}
|
||||||
{% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
|
{%- for item in doc.items -%}
|
||||||
* item.discount_percentage) / 100)) %}
|
{% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
|
||||||
{% endif %}
|
* item.discount_percentage) / 100)) %}
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
{{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
|
{% endfor %}
|
||||||
</td>
|
{{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }} </div>
|
||||||
</tr>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if doc.doctype == 'Sales Order' %}
|
{% if doc.doctype == 'Sales Order' %}
|
||||||
{% if doc.coupon_code %}
|
{% if doc.coupon_code %}
|
||||||
<tr>
|
<div class="w-100 order-taxes mt-5">
|
||||||
<td class="text-left total-discount" colspan="2" style="padding-right: 2rem;">
|
<div class="col-4 d-flex border-btm pb-5">
|
||||||
{{ _("Applied Coupon Code") }}
|
<div class="item-grand-total col-8">
|
||||||
</td>
|
{{ _("Total Amount") }}
|
||||||
<td class="text-right total-discount">
|
</div>
|
||||||
<span>
|
<div class="item-grand-total col-4 text-right pr-0">
|
||||||
{%- for row in frappe.get_all(doctype="Coupon Code",
|
<span>
|
||||||
fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
|
{% set total_amount = [] %}
|
||||||
<span>{{ row.coupon_code }}</span>
|
{%- for item in doc.items -%}
|
||||||
{% endfor %}
|
{% if total_amount.append((item.price_list_rate * item.qty)) %}{% endif %}
|
||||||
</span>
|
{% endfor %}
|
||||||
</td>
|
{{ frappe.utils.fmt_money((total_amount | sum),currency=doc.currency) }}
|
||||||
</tr>
|
</span>
|
||||||
<tr>
|
</div>
|
||||||
<td class="text-left total-discount" colspan="2">
|
</div>
|
||||||
{{ _("Savings") }}
|
</div>
|
||||||
</td>
|
<div class="order-taxes w-100 mt-5">
|
||||||
<td class="text-right total-discount">
|
<div class="col-4 d-flex">
|
||||||
<span>
|
<div class="item-grand-total col-8">
|
||||||
{% set tot_SO_discount = [] %}
|
{{ _("Applied Coupon Code") }}
|
||||||
{%- for item in doc.items -%}
|
</div>
|
||||||
{% if tot_SO_discount.append((((item.price_list_rate * item.qty)
|
<div class="item-grand-total col-4 text-right pr-0">
|
||||||
* item.discount_percentage) / 100)) %}{% endif %}
|
<span>
|
||||||
{% endfor %}
|
{%- for row in frappe.get_all(doctype="Coupon Code",
|
||||||
{{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
|
fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
|
||||||
</span>
|
<span>{{ row.coupon_code }}</span>
|
||||||
</td>
|
{% endfor %}
|
||||||
</tr>
|
</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 %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<tr>
|
<div class="w-100 mt-5 order-taxes font-weight-bold">
|
||||||
<th class="text-left item-grand-total" colspan="1">
|
<div class="col-4 d-flex">
|
||||||
{{ _("Grand Total") }}
|
<div class="item-grand-total col-8">
|
||||||
</th>
|
{{ _("Grand Total") }}
|
||||||
<th class="text-right item-grand-total totals" colspan="3">
|
</div>
|
||||||
{{ doc.get_formatted("grand_total") }}
|
<div class="item-grand-total col-4 text-right pr-0">
|
||||||
</th>
|
{{ doc.get_formatted("grand_total") }}
|
||||||
</tr>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
<div class="web-list-item transaction-list-item">
|
<div class="web-list-item transaction-list-item">
|
||||||
<div class="row">
|
<div class="row align-items-center">
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<span class="indicator small {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "gray") }}">
|
<span class="list-item-name font-weight-bold">{{ doc.name }}</span>
|
||||||
{{ doc.name }}</span>
|
|
||||||
<div class="small text-muted transaction-time"
|
<div class="small text-muted transaction-time"
|
||||||
title="{{ frappe.utils.format_datetime(doc.modified, "medium") }}">
|
title="{{ frappe.utils.format_datetime(doc.modified, "medium") }}">
|
||||||
{{ frappe.utils.global_date_format(doc.modified) }}
|
{{ frappe.utils.global_date_format(doc.modified) }}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="small text-muted items-preview ellipsis ellipsis-width">
|
||||||
{{ doc.items_preview }}
|
{{ doc.items_preview }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if doc.get('grand_total') %}
|
{% 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") }}
|
{{ doc.get_formatted("grand_total") }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -5,149 +5,159 @@
|
|||||||
{% include "templates/includes/breadcrumbs.html" %}
|
{% include "templates/includes/breadcrumbs.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block title %}{{ doc.name }}{% endblock %}
|
{% block title %}
|
||||||
|
{{ doc.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<h2 class="m-0">{{ doc.name }}</h2>
|
<h3 class="m-0">{{ doc.name }}</h3>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block header_actions %}
|
{% block header_actions %}
|
||||||
<div class="dropdown">
|
<div class="row">
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
<div class="dropdown">
|
||||||
<span class="font-md">{{ _('Actions') }}</span>
|
<button class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||||
<b class="caret"></b>
|
<span class="font-md">{{ _('Actions') }}</span>
|
||||||
</button>
|
<b class="caret"></b>
|
||||||
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
</button>
|
||||||
{% if doc.doctype == 'Purchase Order' %}
|
<ul class="dropdown-menu dropdown-menu-right" role="menu">
|
||||||
<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>
|
{% if doc.doctype == 'Purchase Order' and show_make_pi_button %}
|
||||||
{% endif %}
|
<a class="dropdown-item"
|
||||||
<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
|
href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}"
|
||||||
target="_blank" rel="noopener noreferrer">
|
data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}
|
||||||
{{ _("Print") }}
|
</a>
|
||||||
</a>
|
{% endif %}
|
||||||
</ul>
|
<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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block page_content %}
|
{% block page_content %}
|
||||||
<div class="row transaction-subheading">
|
<div>
|
||||||
<div class="col-6">
|
<div class="row transaction-subheading mt-1">
|
||||||
<span class="font-md indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
|
<div class="col-6 text-muted small mt-1">
|
||||||
{% if doc.doctype == "Quotation" and not doc.docstatus %}
|
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
|
||||||
{{ _("Pending") }}
|
{% if doc.valid_till %}
|
||||||
{% else %}
|
<p>
|
||||||
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
{{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 text-muted text-right small pt-3">
|
<div class="row indicator-container mt-2">
|
||||||
{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
|
<div class="col-10">
|
||||||
{% if doc.valid_till %}
|
<span class="indicator-pill {{ doc.indicator_color or (" blue" if doc.docstatus==1 else "darkgrey" ) }}">
|
||||||
<p>
|
{% if doc.doctype == "Quotation" and not doc.docstatus %}
|
||||||
{{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}
|
{{ _("Pending") }}
|
||||||
</p>
|
{% else %}
|
||||||
{% endif %}
|
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
|
||||||
</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>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</span>
|
||||||
<td class="text-right">
|
</div>
|
||||||
{{ d.get_formatted("amount") }}
|
<div class="text-right col-2">
|
||||||
<p class="text-muted small">{{ _("Rate:") }} {{ d.get_formatted("rate") }}</p>
|
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase
|
||||||
</td>
|
Order'] else doc.customer_name %}
|
||||||
</tr>
|
<b>{{ party_name }}</b>
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
{% if doc.contact_display and doc.contact_display != party_name %}
|
||||||
</table>
|
<br>
|
||||||
<!-- taxes -->
|
{{ doc.contact_display }}
|
||||||
<div class="order-taxes d-flex justify-content-end">
|
{% endif %}
|
||||||
<table>
|
</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" %}
|
{% include "erpnext/templates/includes/order/order_taxes.html" %}
|
||||||
</table>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0)
|
{% 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 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-collapse">
|
||||||
<div class="panel-body text-muted small">
|
<div class="panel-body text-muted small">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-column col-sm-6">
|
<div class="form-column col-sm-6">
|
||||||
{% if available_loyalty_points %}
|
{% 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="form-group">
|
||||||
<div class="h6">Enter Loyalty Points</div>
|
<div class="h6">Enter Loyalty Points</div>
|
||||||
<div class="control-input-wrapper">
|
<div class="control-input-wrapper">
|
||||||
<div class="control-input">
|
<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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -172,17 +182,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if doc.terms %}
|
{% if doc.terms %}
|
||||||
<div class="terms-and-condition text-muted small">
|
<div class="terms-and-condition text-muted small">
|
||||||
<hr><p>{{ doc.terms }}</p>
|
<hr>
|
||||||
|
<p>{{ doc.terms }}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
<script> {% include "templates/pages/order.js" %} </script>
|
<script> {% include "templates/pages/order.js" %}</script>
|
||||||
<script>
|
<script>
|
||||||
window.doc_info = {
|
window.doc_info = {
|
||||||
customer: '{{doc.customer}}',
|
customer: '{{doc.customer}}',
|
||||||
@@ -192,4 +202,4 @@
|
|||||||
currency: '{{ doc.currency }}'
|
currency: '{{ doc.currency }}'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from frappe import _
|
|||||||
def transaction_processing(data, from_doctype, to_doctype):
|
def transaction_processing(data, from_doctype, to_doctype):
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
deserialized_data = json.loads(data)
|
deserialized_data = json.loads(data)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
deserialized_data = data
|
deserialized_data = data
|
||||||
|
|
||||||
@@ -30,30 +29,29 @@ def transaction_processing(data, from_doctype, to_doctype):
|
|||||||
|
|
||||||
|
|
||||||
def job(deserialized_data, from_doctype, to_doctype):
|
def job(deserialized_data, from_doctype, to_doctype):
|
||||||
failed_history = []
|
fail_count = 0
|
||||||
i = 0
|
|
||||||
for d in deserialized_data:
|
for d in deserialized_data:
|
||||||
failed = []
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
i += 1
|
|
||||||
doc_name = d.get("name")
|
doc_name = d.get("name")
|
||||||
frappe.db.savepoint("before_creation_state")
|
frappe.db.savepoint("before_creation_state")
|
||||||
task(doc_name, from_doctype, to_doctype)
|
task(doc_name, from_doctype, to_doctype)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
frappe.db.rollback(save_point="before_creation_state")
|
frappe.db.rollback(save_point="before_creation_state")
|
||||||
failed_history.append(e)
|
fail_count += 1
|
||||||
failed.append(e)
|
|
||||||
update_logger(
|
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(
|
update_logger(
|
||||||
doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today())
|
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):
|
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 Invoice": purchase_order.make_purchase_invoice,
|
||||||
"Purchase Receipt": purchase_order.make_purchase_receipt,
|
"Purchase Receipt": purchase_order.make_purchase_receipt,
|
||||||
},
|
},
|
||||||
"Purhcase Invoice": {
|
"Purchase Invoice": {
|
||||||
"Purchase Receipt": purchase_invoice.make_purchase_receipt,
|
"Purchase Receipt": purchase_invoice.make_purchase_receipt,
|
||||||
"Payment": payment_entry.get_payment_entry,
|
"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()
|
log_doc.save()
|
||||||
|
|
||||||
|
|
||||||
def show_job_status(failed_history, deserialized_data, to_doctype):
|
def show_job_status(fail_count, deserialized_data_count, to_doctype):
|
||||||
if not failed_history:
|
if not fail_count:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("Creation of {0} successful").format(to_doctype),
|
_("Creation of {0} successful").format(to_doctype),
|
||||||
title="Successful",
|
title="Successful",
|
||||||
indicator="green",
|
indicator="green",
|
||||||
)
|
)
|
||||||
|
elif fail_count != 0 and fail_count < deserialized_data_count:
|
||||||
if len(failed_history) != 0 and len(failed_history) < len(deserialized_data):
|
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
"""Creation of {0} partially successful.
|
"""Creation of {0} partially successful.
|
||||||
@@ -167,8 +164,7 @@ def show_job_status(failed_history, deserialized_data, to_doctype):
|
|||||||
title="Partially successful",
|
title="Partially successful",
|
||||||
indicator="orange",
|
indicator="orange",
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
if len(failed_history) == len(deserialized_data):
|
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
"""Creation of {0} failed.
|
"""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):
|
def record_exists(log_doc, doc_name, status):
|
||||||
|
|
||||||
record = mark_retrired_transaction(log_doc, doc_name)
|
record = mark_retrired_transaction(log_doc, doc_name)
|
||||||
|
|
||||||
if record and status == "Failed":
|
if record and status == "Failed":
|
||||||
return False
|
return False
|
||||||
elif record and status == "Success":
|
elif record and status == "Success":
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
.time-slot.selected {
|
.time-slot.selected {
|
||||||
color: white;
|
color: white;
|
||||||
background: #5e64ff;
|
background: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-slot.selected .text-muted {
|
.time-slot.selected .text-muted {
|
||||||
|
|||||||
Reference in New Issue
Block a user