mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-18 22:42:12 +00:00
Merge pull request #42745 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -47,9 +47,11 @@ def validate_columns(data):
|
||||
|
||||
no_of_columns = max([len(d) for d in data])
|
||||
|
||||
if no_of_columns > 8:
|
||||
if no_of_columns != 8:
|
||||
frappe.throw(
|
||||
_("More columns found than expected. Please compare the uploaded file with standard template"),
|
||||
_(
|
||||
"Columns are not according to template. Please compare the uploaded file with standard template"
|
||||
),
|
||||
title=(_("Wrong Template")),
|
||||
)
|
||||
|
||||
|
||||
@@ -85,18 +85,16 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
|
||||
},
|
||||
|
||||
make_jv: function (frm) {
|
||||
let revaluation_journal = null;
|
||||
let zero_balance_journal = null;
|
||||
frappe.call({
|
||||
method: "make_jv_entries",
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: "Making Journal Entries...",
|
||||
freeze_message: __("Creating Journal Entries..."),
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
let response = r.message;
|
||||
if (response["revaluation_jv"] || response["zero_balance_jv"]) {
|
||||
frappe.msgprint(__("Journals have been created"));
|
||||
frappe.msgprint(__("Journal entries have been created"));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -230,7 +230,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
hide_unhide_fields: function (frm) {
|
||||
var company_currency = frm.doc.company
|
||||
? frappe.get_doc(":Company", frm.doc.company).default_currency
|
||||
? frappe.get_doc(":Company", frm.doc.company)?.default_currency
|
||||
: "";
|
||||
|
||||
frm.toggle_display(
|
||||
|
||||
@@ -338,6 +338,17 @@ class PaymentRequest(Document):
|
||||
payment_entry.received_amount = amount
|
||||
payment_entry.get("references")[0].allocated_amount = amount
|
||||
|
||||
# Update 'Paid Amount' on Forex transactions
|
||||
if self.currency != ref_doc.company_currency:
|
||||
if (
|
||||
self.payment_request_type == "Outward"
|
||||
and payment_entry.paid_from_account_currency == ref_doc.company_currency
|
||||
and payment_entry.paid_from_account_currency != payment_entry.paid_to_account_currency
|
||||
):
|
||||
payment_entry.paid_amount = payment_entry.base_paid_amount = (
|
||||
payment_entry.target_exchange_rate * payment_entry.received_amount
|
||||
)
|
||||
|
||||
for dimension in get_accounting_dimensions():
|
||||
payment_entry.update({dimension: self.get(dimension)})
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
@@ -32,7 +34,7 @@ payment_method = [
|
||||
]
|
||||
|
||||
|
||||
class TestPaymentRequest(unittest.TestCase):
|
||||
class TestPaymentRequest(FrappeTestCase):
|
||||
def setUp(self):
|
||||
if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"):
|
||||
frappe.get_doc(payment_gateway).insert(ignore_permissions=True)
|
||||
@@ -260,3 +262,19 @@ class TestPaymentRequest(unittest.TestCase):
|
||||
# Try to make Payment Request more than SO amount, should give validation
|
||||
pr2.grand_total = 900
|
||||
self.assertRaises(frappe.ValidationError, pr2.save)
|
||||
|
||||
def test_conversion_on_foreign_currency_accounts(self):
|
||||
po_doc = create_purchase_order(supplier="_Test Supplier USD", currency="USD", do_not_submit=1)
|
||||
po_doc.conversion_rate = 80
|
||||
po_doc.items[0].qty = 1
|
||||
po_doc.items[0].rate = 10
|
||||
po_doc.save().submit()
|
||||
|
||||
pr = make_payment_request(dt=po_doc.doctype, dn=po_doc.name, recipient_id="nabin@erpnext.com")
|
||||
pr = frappe.get_doc(pr).save().submit()
|
||||
|
||||
pe = pr.create_payment_entry()
|
||||
self.assertEqual(pe.base_paid_amount, 800)
|
||||
self.assertEqual(pe.paid_amount, 800)
|
||||
self.assertEqual(pe.base_received_amount, 800)
|
||||
self.assertEqual(pe.received_amount, 10)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"cost_center",
|
||||
"territory",
|
||||
"ignore_exchange_rate_revaluation_journals",
|
||||
"ignore_cr_dr_notes",
|
||||
"column_break_14",
|
||||
"to_date",
|
||||
"finance_book",
|
||||
@@ -383,10 +384,16 @@
|
||||
"fieldname": "ignore_exchange_rate_revaluation_journals",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Exchange Rate Revaluation Journals"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_cr_dr_notes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore System Generated Credit / Debit Notes"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2023-12-18 12:20:08.965120",
|
||||
"modified": "2024-08-13 10:41:18.381165",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -54,6 +54,7 @@ class ProcessStatementOfAccounts(Document):
|
||||
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
|
||||
from_date: DF.Date | None
|
||||
group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"]
|
||||
ignore_cr_dr_notes: DF.Check
|
||||
ignore_exchange_rate_revaluation_journals: DF.Check
|
||||
include_ageing: DF.Check
|
||||
include_break: DF.Check
|
||||
@@ -133,6 +134,9 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
if doc.ignore_exchange_rate_revaluation_journals:
|
||||
filters.update({"ignore_err": True})
|
||||
|
||||
if doc.ignore_cr_dr_notes:
|
||||
filters.update({"ignore_cr_dr_notes": True})
|
||||
|
||||
if doc.report == "General Ledger":
|
||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||
col, res = get_soa(filters)
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"base_net_rate",
|
||||
"base_net_amount",
|
||||
"valuation_rate",
|
||||
"sales_incoming_rate",
|
||||
"item_tax_amount",
|
||||
"landed_cost_voucher_amount",
|
||||
"rm_supp_cost",
|
||||
@@ -958,12 +959,22 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"description": "Valuation rate for the item as per Sales Invoice (Only for Internal Transfers)",
|
||||
"fieldname": "sales_incoming_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Sales Incoming Rate",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-14 11:57:07.171700",
|
||||
"modified": "2024-07-19 12:12:42.449298",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -79,6 +79,7 @@ class PurchaseInvoiceItem(Document):
|
||||
rejected_serial_no: DF.Text | None
|
||||
rejected_warehouse: DF.Link | None
|
||||
rm_supp_cost: DF.Currency
|
||||
sales_incoming_rate: DF.Currency
|
||||
sales_invoice_item: DF.Data | None
|
||||
serial_and_batch_bundle: DF.Link | None
|
||||
serial_no: DF.Text | None
|
||||
|
||||
@@ -1316,6 +1316,10 @@ class SalesInvoice(SellingController):
|
||||
|
||||
for item in self.get("items"):
|
||||
if flt(item.base_net_amount, item.precision("base_net_amount")):
|
||||
# Do not book income for transfer within same company
|
||||
if self.is_internal_transfer():
|
||||
continue
|
||||
|
||||
if item.is_fixed_asset:
|
||||
asset = self.get_asset(item)
|
||||
|
||||
@@ -1374,37 +1378,33 @@ class SalesInvoice(SellingController):
|
||||
self.set_asset_status(asset)
|
||||
|
||||
else:
|
||||
# Do not book income for transfer within same company
|
||||
if not self.is_internal_transfer():
|
||||
income_account = (
|
||||
item.income_account
|
||||
if (not item.enable_deferred_revenue or self.is_return)
|
||||
else item.deferred_revenue_account
|
||||
)
|
||||
income_account = (
|
||||
item.income_account
|
||||
if (not item.enable_deferred_revenue or self.is_return)
|
||||
else item.deferred_revenue_account
|
||||
)
|
||||
|
||||
amount, base_amount = self.get_amount_and_base_amount(
|
||||
item, enable_discount_accounting
|
||||
)
|
||||
amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
|
||||
|
||||
account_currency = get_account_currency(income_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": income_account,
|
||||
"against": self.customer,
|
||||
"credit": flt(base_amount, item.precision("base_net_amount")),
|
||||
"credit_in_account_currency": (
|
||||
flt(base_amount, item.precision("base_net_amount"))
|
||||
if account_currency == self.company_currency
|
||||
else flt(amount, item.precision("net_amount"))
|
||||
),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
account_currency,
|
||||
item=item,
|
||||
)
|
||||
account_currency = get_account_currency(income_account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": income_account,
|
||||
"against": self.customer,
|
||||
"credit": flt(base_amount, item.precision("base_net_amount")),
|
||||
"credit_in_account_currency": (
|
||||
flt(base_amount, item.precision("base_net_amount"))
|
||||
if account_currency == self.company_currency
|
||||
else flt(amount, item.precision("net_amount"))
|
||||
),
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
account_currency,
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# expense account gl entries
|
||||
if cint(self.update_stock) and erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
@@ -1479,6 +1479,10 @@ class SalesInvoice(SellingController):
|
||||
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||
payment_mode.base_amount -= flt(self.change_amount)
|
||||
|
||||
against_voucher = self.name
|
||||
if self.is_return and self.return_against and not self.update_outstanding_for_self:
|
||||
against_voucher = self.return_against
|
||||
|
||||
if payment_mode.base_amount:
|
||||
# POS, make payment entries
|
||||
gl_entries.append(
|
||||
@@ -1492,7 +1496,7 @@ class SalesInvoice(SellingController):
|
||||
"credit_in_account_currency": payment_mode.base_amount
|
||||
if self.party_account_currency == self.company_currency
|
||||
else payment_mode.amount,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": against_voucher,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import copy
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
@@ -3083,6 +3084,84 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
party_link.delete()
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
|
||||
|
||||
def test_sales_invoice_against_supplier_usd_with_dimensions(self):
|
||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||
make_customer,
|
||||
)
|
||||
from erpnext.accounts.doctype.party_link.party_link import create_party_link
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
|
||||
# create a customer
|
||||
customer = make_customer(customer="_Test Common Supplier USD")
|
||||
cust_doc = frappe.get_doc("Customer", customer)
|
||||
cust_doc.default_currency = "USD"
|
||||
cust_doc.save()
|
||||
# create a supplier
|
||||
supplier = create_supplier(supplier_name="_Test Common Supplier USD").name
|
||||
supp_doc = frappe.get_doc("Supplier", supplier)
|
||||
supp_doc.default_currency = "USD"
|
||||
supp_doc.save()
|
||||
|
||||
# create a party link between customer & supplier
|
||||
party_link = create_party_link("Supplier", supplier, customer)
|
||||
|
||||
# enable common party accounting
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 1)
|
||||
|
||||
# create a dimension and make it mandatory
|
||||
if not frappe.get_all("Accounting Dimension", filters={"document_type": "Department"}):
|
||||
dim = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Accounting Dimension",
|
||||
"document_type": "Department",
|
||||
"dimension_defaults": [{"company": "_Test Company", "mandatory_for_bs": True}],
|
||||
}
|
||||
)
|
||||
dim.save()
|
||||
else:
|
||||
dim = frappe.get_doc(
|
||||
"Accounting Dimension",
|
||||
frappe.get_all("Accounting Dimension", filters={"document_type": "Department"})[0],
|
||||
)
|
||||
dim.disabled = False
|
||||
dim.dimension_defaults = []
|
||||
dim.append("dimension_defaults", {"company": "_Test Company", "mandatory_for_bs": True})
|
||||
dim.save()
|
||||
|
||||
# create a sales invoice
|
||||
si = create_sales_invoice(
|
||||
customer=customer, parent_cost_center="_Test Cost Center - _TC", do_not_submit=True
|
||||
)
|
||||
si.department = "All Departments"
|
||||
si.save().submit()
|
||||
|
||||
# check outstanding of sales invoice
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Paid")
|
||||
self.assertEqual(flt(si.outstanding_amount), 0.0)
|
||||
|
||||
# check creation of journal entry
|
||||
jv = frappe.get_all(
|
||||
"Journal Entry Account",
|
||||
{
|
||||
"account": si.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": si.customer,
|
||||
"reference_type": si.doctype,
|
||||
"reference_name": si.name,
|
||||
"department": "All Departments",
|
||||
},
|
||||
pluck="credit_in_account_currency",
|
||||
)
|
||||
|
||||
self.assertTrue(jv)
|
||||
self.assertEqual(jv[0], si.grand_total)
|
||||
|
||||
dim.disabled = True
|
||||
dim.save()
|
||||
party_link.delete()
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_common_party_accounting", 0)
|
||||
|
||||
def test_payment_statuses(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
|
||||
@@ -3758,6 +3837,40 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_pos_returns_without_update_outstanding_for_self(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||
|
||||
pos_profile = make_pos_profile()
|
||||
pos_profile.payments = []
|
||||
pos_profile.append("payments", {"default": 1, "mode_of_payment": "Cash"})
|
||||
pos_profile.save()
|
||||
|
||||
pos = create_sales_invoice(qty=10, do_not_save=True)
|
||||
pos.is_pos = 1
|
||||
pos.pos_profile = pos_profile.name
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
|
||||
)
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500})
|
||||
pos.save().submit()
|
||||
|
||||
pos_return = make_sales_return(pos.name)
|
||||
pos_return.update_outstanding_for_self = False
|
||||
pos_return.save().submit()
|
||||
|
||||
gle = qb.DocType("GL Entry")
|
||||
res = (
|
||||
qb.from_(gle)
|
||||
.select(gle.against_voucher)
|
||||
.distinct()
|
||||
.where(
|
||||
gle.is_cancelled.eq(0) & gle.voucher_no.eq(pos_return.name) & gle.against_voucher.notnull()
|
||||
)
|
||||
.run(as_list=1)
|
||||
)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0][0], pos_return.return_against)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -38,6 +38,12 @@ frappe.ui.form.on("Subscription", {
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Force-Fetch Subscription Updates"),
|
||||
() => frm.trigger("force_fetch_subscription_updates"),
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Cancel Subscription"),
|
||||
() => frm.trigger("cancel_this_subscription"),
|
||||
@@ -82,4 +88,11 @@ frappe.ui.form.on("Subscription", {
|
||||
}
|
||||
});
|
||||
},
|
||||
force_fetch_subscription_updates: function (frm) {
|
||||
frm.call("force_fetch_subscription_updates").then((r) => {
|
||||
if (!r.exec) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -717,6 +717,31 @@ class Subscription(Document):
|
||||
self.update_subscription_period(posting_date or nowdate())
|
||||
self.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def force_fetch_subscription_updates(self):
|
||||
"""
|
||||
Process Subscription and create Invoices even if current date doesn't lie between current_invoice_start and currenct_invoice_end
|
||||
It makes use of 'Proces Subscription' to force processing in a specific 'posting_date'
|
||||
"""
|
||||
|
||||
# Don't process future subscriptions
|
||||
if nowdate() < self.current_invoice_start:
|
||||
frappe.msgprint(_("Subscription for Future dates cannot be processed."))
|
||||
return
|
||||
|
||||
processing_date = None
|
||||
if self.generate_invoice_at == "Beginning of the current subscription period":
|
||||
processing_date = self.current_invoice_start
|
||||
elif self.generate_invoice_at == "End of the current subscription period":
|
||||
processing_date = self.current_invoice_end
|
||||
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
|
||||
|
||||
process_subscription = frappe.new_doc("Process Subscription")
|
||||
process_subscription.posting_date = processing_date
|
||||
process_subscription.subscription = self.name
|
||||
process_subscription.save().submit()
|
||||
|
||||
|
||||
def is_prorate() -> int:
|
||||
return cint(frappe.db.get_single_value("Subscription Settings", "prorate"))
|
||||
|
||||
@@ -521,6 +521,18 @@ class TestSubscription(FrappeTestCase):
|
||||
subscription.process(posting_date="2023-01-22")
|
||||
self.assertEqual(len(subscription.invoices), 2)
|
||||
|
||||
def test_future_subscription(self):
|
||||
"""Force-Fetch should not process future subscriptions"""
|
||||
subscription = create_subscription(
|
||||
start_date=add_months(nowdate(), 1),
|
||||
submit_invoice=0,
|
||||
generate_new_invoices_past_due_date=1,
|
||||
party="_Test Subscription Customer John Doe",
|
||||
)
|
||||
subscription.force_fetch_subscription_updates()
|
||||
subscription.reload()
|
||||
self.assertEqual(len(subscription.invoices), 0)
|
||||
|
||||
|
||||
def make_plans():
|
||||
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
|
||||
|
||||
@@ -248,7 +248,10 @@ def get_conditions(filters):
|
||||
as_list=True,
|
||||
)
|
||||
if system_generated_cr_dr_journals:
|
||||
filters.update({"voucher_no_not_in": [x[0] for x in system_generated_cr_dr_journals]})
|
||||
vouchers_to_ignore = (filters.get("voucher_no_not_in") or []) + [
|
||||
x[0] for x in system_generated_cr_dr_journals
|
||||
]
|
||||
filters.update({"voucher_no_not_in": vouchers_to_ignore})
|
||||
|
||||
if filters.get("voucher_no_not_in"):
|
||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||
|
||||
@@ -234,7 +234,7 @@ class TestAsset(AssetSetup):
|
||||
pro_rata_amount, _, _ = _get_pro_rata_amt(
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
get_last_day(add_months(purchase_date, 1)),
|
||||
add_days(get_last_day(add_months(purchase_date, 1)), 1),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
)
|
||||
@@ -320,7 +320,7 @@ class TestAsset(AssetSetup):
|
||||
pro_rata_amount, _, _ = _get_pro_rata_amt(
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
get_last_day(add_months(purchase_date, 1)),
|
||||
add_days(get_last_day(add_months(purchase_date, 1)), 1),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
)
|
||||
|
||||
@@ -291,7 +291,9 @@ class AssetDepreciationSchedule(Document):
|
||||
if skip_row:
|
||||
continue
|
||||
|
||||
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
|
||||
schedule_date = get_last_day(
|
||||
add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
|
||||
)
|
||||
if not current_fiscal_year_end_date:
|
||||
current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
|
||||
elif getdate(schedule_date) > getdate(current_fiscal_year_end_date):
|
||||
@@ -330,8 +332,10 @@ class AssetDepreciationSchedule(Document):
|
||||
getdate(asset_doc.available_for_use_date),
|
||||
(asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
|
||||
)
|
||||
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
from_date = get_last_day(from_date)
|
||||
if self.depreciation_schedule:
|
||||
from_date = self.depreciation_schedule[-1].schedule_date
|
||||
from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
|
||||
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
@@ -353,9 +357,8 @@ class AssetDepreciationSchedule(Document):
|
||||
and not self.opening_accumulated_depreciation
|
||||
and not self.flags.wdv_it_act_applied
|
||||
):
|
||||
from_date = add_days(
|
||||
asset_doc.available_for_use_date, -1
|
||||
) # needed to calc depr amount for available_for_use_date too
|
||||
from_date = asset_doc.available_for_use_date
|
||||
# needed to calc depr amount for available_for_use_date too
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
depreciation_amount,
|
||||
@@ -406,6 +409,8 @@ class AssetDepreciationSchedule(Document):
|
||||
(n + self.opening_number_of_booked_depreciations)
|
||||
* cint(row.frequency_of_depreciation),
|
||||
)
|
||||
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
asset_doc.to_date = get_last_day(asset_doc.to_date)
|
||||
|
||||
depreciation_amount_without_pro_rata = depreciation_amount
|
||||
|
||||
@@ -421,7 +426,7 @@ class AssetDepreciationSchedule(Document):
|
||||
depreciation_amount_without_pro_rata, depreciation_amount
|
||||
)
|
||||
|
||||
schedule_date = add_days(schedule_date, days)
|
||||
schedule_date = add_days(schedule_date, days - 1)
|
||||
|
||||
if not depreciation_amount:
|
||||
continue
|
||||
@@ -504,7 +509,10 @@ class AssetDepreciationSchedule(Document):
|
||||
continue
|
||||
|
||||
if not accumulated_depreciation:
|
||||
if i > 0 and asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
if i > 0 and (
|
||||
asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment
|
||||
or asset_doc.flags.increase_in_asset_value_due_to_repair
|
||||
):
|
||||
accumulated_depreciation = self.get("depreciation_schedule")[
|
||||
i - 1
|
||||
].accumulated_depreciation_amount
|
||||
@@ -553,9 +561,11 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
|
||||
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||
# from_date = 01/01/2022
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
prev_depreciation_start_date = add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
|
||||
prev_depreciation_start_date = get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
)
|
||||
from_date = asset_doc.available_for_use_date
|
||||
days = date_diff(prev_depreciation_start_date, from_date) + 1
|
||||
@@ -610,7 +620,7 @@ def _get_pro_rata_amt(
|
||||
has_wdv_or_dd_non_yearly_pro_rata=False,
|
||||
original_schedule_date=None,
|
||||
):
|
||||
days = date_diff(to_date, from_date)
|
||||
days = date_diff(to_date, from_date) + 1
|
||||
months = month_diff(to_date, from_date)
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
total_days = get_total_days(original_schedule_date or to_date, 12)
|
||||
@@ -670,7 +680,7 @@ def get_straight_line_or_manual_depr_amount(
|
||||
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
|
||||
elif asset.flags.increase_in_asset_value_due_to_repair:
|
||||
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
|
||||
row.total_number_of_depreciations
|
||||
number_of_pending_depreciations
|
||||
)
|
||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
@@ -1034,6 +1044,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
date_of_return=None,
|
||||
value_after_depreciation=None,
|
||||
ignore_booked_entry=False,
|
||||
difference_amount=None,
|
||||
):
|
||||
for row in asset_doc.get("finance_books"):
|
||||
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
|
||||
@@ -1048,6 +1059,8 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
)
|
||||
|
||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||
if asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment and not value_after_depreciation:
|
||||
value_after_depreciation = row.value_after_depreciation + difference_amount
|
||||
|
||||
if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
|
||||
"Written Down Value",
|
||||
|
||||
@@ -29,6 +29,15 @@ frappe.ui.form.on("Asset Repair", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("purchase_invoice", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
docstatus: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("warehouse", "stock_items", function () {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -117,7 +117,9 @@ class AssetRepair(AccountsController):
|
||||
get_link_to_form(self.doctype, self.name),
|
||||
)
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
self.asset_doc, notes, ignore_booked_entry=True
|
||||
)
|
||||
self.asset_doc.save()
|
||||
|
||||
add_asset_activity(
|
||||
@@ -154,7 +156,9 @@ class AssetRepair(AccountsController):
|
||||
get_link_to_form(self.doctype, self.name),
|
||||
)
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
self.asset_doc, notes, ignore_booked_entry=True
|
||||
)
|
||||
self.asset_doc.save()
|
||||
|
||||
add_asset_activity(
|
||||
|
||||
@@ -56,7 +56,8 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_asset(self.current_asset_value)
|
||||
frappe.get_doc("Journal Entry", self.journal_entry).cancel()
|
||||
self.update_asset()
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset's value adjusted after cancellation of Asset Value Adjustment {0}").format(
|
||||
@@ -144,7 +145,7 @@ class AssetValueAdjustment(Document):
|
||||
|
||||
self.db_set("journal_entry", je.name)
|
||||
|
||||
def update_asset(self, asset_value):
|
||||
def update_asset(self, asset_value=None):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
|
||||
if not asset.calculate_depreciation:
|
||||
@@ -170,7 +171,11 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
asset, notes, value_after_depreciation=asset_value, ignore_booked_entry=True
|
||||
asset,
|
||||
notes,
|
||||
value_after_depreciation=asset_value,
|
||||
ignore_booked_entry=True,
|
||||
difference_amount=self.difference_amount,
|
||||
)
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.save()
|
||||
|
||||
@@ -12,6 +12,7 @@ from erpnext.assets.doctype.asset.test_asset import create_asset_data
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_asset_depr_schedule_doc,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_repair
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
|
||||
@@ -128,6 +129,136 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_depreciation_after_cancelling_asset_repair(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
|
||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||
asset_doc.calculate_depreciation = 1
|
||||
asset_doc.available_for_use_date = "2023-01-15"
|
||||
asset_doc.purchase_date = "2023-01-15"
|
||||
|
||||
asset_doc.append(
|
||||
"finance_books",
|
||||
{
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 12,
|
||||
"frequency_of_depreciation": 1,
|
||||
"depreciation_start_date": "2023-01-31",
|
||||
},
|
||||
)
|
||||
asset_doc.submit()
|
||||
|
||||
post_depreciation_entries(getdate("2023-08-21"))
|
||||
|
||||
# create asset repair
|
||||
asset_repair = create_asset_repair(asset=asset_doc, capitalize_repair_cost=1, submit=1)
|
||||
|
||||
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Active")
|
||||
|
||||
# create asset value adjustment
|
||||
current_value = get_asset_value_after_depreciation(asset_doc.name)
|
||||
|
||||
adj_doc = make_asset_value_adjustment(
|
||||
asset=asset_doc.name,
|
||||
current_asset_value=current_value,
|
||||
new_asset_value=50000.0,
|
||||
date="2023-08-21",
|
||||
)
|
||||
adj_doc.submit()
|
||||
|
||||
first_asset_depr_schedule.load_from_db()
|
||||
|
||||
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(second_asset_depr_schedule.status, "Active")
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
|
||||
|
||||
# Test gl entry creted from asset value adjustemnet
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 5625.29),
|
||||
("_Test Depreciations - _TC", 5625.29, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_no = %s
|
||||
order by account""",
|
||||
adj_doc.journal_entry,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
# test depreciation schedule after asset repair and asset value adjustemnet
|
||||
expected_schedules = [
|
||||
["2023-01-31", 5474.73, 5474.73],
|
||||
["2023-02-28", 9983.33, 15458.06],
|
||||
["2023-03-31", 9983.33, 25441.39],
|
||||
["2023-04-30", 9983.33, 35424.72],
|
||||
["2023-05-31", 9983.33, 45408.05],
|
||||
["2023-06-30", 9983.33, 55391.38],
|
||||
["2023-07-31", 9983.33, 65374.71],
|
||||
["2023-08-31", 2766.67, 68141.38],
|
||||
["2023-09-30", 2766.67, 70908.05],
|
||||
["2023-10-31", 2766.67, 73674.72],
|
||||
["2023-11-30", 2766.67, 76441.39],
|
||||
["2023-12-31", 2766.67, 79208.06],
|
||||
["2024-01-31", 2766.67, 81974.73],
|
||||
["2024-02-29", 2766.67, 84741.4],
|
||||
["2024-03-31", 2766.67, 87508.07],
|
||||
["2024-04-30", 2766.67, 90274.74],
|
||||
["2024-05-31", 2766.67, 93041.41],
|
||||
["2024-06-30", 2766.67, 95808.08],
|
||||
["2024-07-31", 2766.67, 98574.75],
|
||||
["2024-08-31", 2766.67, 101341.42],
|
||||
["2024-09-30", 2766.67, 104108.09],
|
||||
["2024-10-31", 2766.67, 106874.76],
|
||||
["2024-11-30", 2766.67, 109641.43],
|
||||
["2024-12-31", 2766.67, 112408.1],
|
||||
["2025-01-15", 2766.61, 115174.71],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
for d in second_asset_depr_schedule.get("depreciation_schedule")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
# Cancel asset repair
|
||||
asset_repair.cancel()
|
||||
asset_repair.load_from_db()
|
||||
second_asset_depr_schedule.load_from_db()
|
||||
|
||||
third_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(third_asset_depr_schedule.status, "Active")
|
||||
self.assertEqual(second_asset_depr_schedule.status, "Cancelled")
|
||||
|
||||
# After cancelling asset repair asset life will be decreased and new depreciation schedule should be calculated
|
||||
expected_schedules = [
|
||||
["2023-01-31", 5474.73, 5474.73],
|
||||
["2023-02-28", 9983.33, 15458.06],
|
||||
["2023-03-31", 9983.33, 25441.39],
|
||||
["2023-04-30", 9983.33, 35424.72],
|
||||
["2023-05-31", 9983.33, 45408.05],
|
||||
["2023-06-30", 9983.33, 55391.38],
|
||||
["2023-07-31", 9983.33, 65374.71],
|
||||
["2023-08-31", 8133.33, 73508.04],
|
||||
["2023-09-30", 8133.33, 81641.37],
|
||||
["2023-10-31", 8133.33, 89774.7],
|
||||
["2023-11-30", 8133.33, 97908.03],
|
||||
["2023-12-31", 8133.33, 106041.36],
|
||||
["2024-01-15", 8133.35, 114174.71],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
for d in third_asset_depr_schedule.get("depreciation_schedule")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
|
||||
def make_asset_value_adjustment(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -2433,6 +2433,15 @@ class AccountsController(TransactionBase):
|
||||
advance_entry.cost_center = self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||
advance_entry.is_advance = "Yes"
|
||||
|
||||
# update dimesions
|
||||
dimensions_dict = frappe._dict()
|
||||
active_dimensions = get_dimensions()[0]
|
||||
for dim in active_dimensions:
|
||||
dimensions_dict[dim.fieldname] = self.get(dim.fieldname)
|
||||
|
||||
reconcilation_entry.update(dimensions_dict)
|
||||
advance_entry.update(dimensions_dict)
|
||||
|
||||
if self.doctype == "Sales Invoice":
|
||||
reconcilation_entry.credit_in_account_currency = self.outstanding_amount
|
||||
advance_entry.debit_in_account_currency = self.outstanding_amount
|
||||
|
||||
@@ -314,18 +314,22 @@ class BuyingController(SubcontractingController):
|
||||
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
|
||||
)
|
||||
|
||||
net_rate = item.base_net_amount
|
||||
if item.sales_incoming_rate: # for internal transfer
|
||||
net_rate = item.qty * item.sales_incoming_rate
|
||||
|
||||
qty_in_stock_uom = flt(item.qty * item.conversion_factor)
|
||||
if self.get("is_old_subcontracting_flow"):
|
||||
item.rm_supp_cost = self.get_supplied_items_cost(item.name, reset_outgoing_rate)
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ item.rm_supp_cost
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
) / qty_in_stock_uom
|
||||
else:
|
||||
item.valuation_rate = (
|
||||
item.base_net_amount
|
||||
net_rate
|
||||
+ item.item_tax_amount
|
||||
+ flt(item.landed_cost_voucher_amount)
|
||||
+ flt(item.get("rate_difference_with_purchase_invoice"))
|
||||
@@ -336,72 +340,88 @@ class BuyingController(SubcontractingController):
|
||||
update_regional_item_valuation_rate(self)
|
||||
|
||||
def set_incoming_rate(self):
|
||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice", "Purchase Order"):
|
||||
"""
|
||||
Override item rate with incoming rate for internal stock transfer
|
||||
"""
|
||||
if self.doctype not in ("Purchase Receipt", "Purchase Invoice"):
|
||||
return
|
||||
|
||||
if not (self.doctype == "Purchase Receipt" or self.get("update_stock")):
|
||||
return
|
||||
|
||||
if cint(self.get("is_return")):
|
||||
# Get outgoing rate based on original item cost based on valuation method
|
||||
return
|
||||
|
||||
if not self.is_internal_transfer():
|
||||
return
|
||||
|
||||
allow_at_arms_length_price = frappe.get_cached_value(
|
||||
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
|
||||
)
|
||||
if allow_at_arms_length_price:
|
||||
return
|
||||
|
||||
self.set_sales_incoming_rate_for_internal_transfer()
|
||||
|
||||
for d in self.get("items"):
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
|
||||
if d.rate == d.sales_incoming_rate:
|
||||
continue
|
||||
|
||||
d.rate = d.sales_incoming_rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
|
||||
def set_sales_incoming_rate_for_internal_transfer(self):
|
||||
"""
|
||||
Set incoming rate from the sales transaction against which the
|
||||
purchase is made (internal transfer)
|
||||
"""
|
||||
ref_doctype_map = {
|
||||
"Purchase Order": "Sales Order Item",
|
||||
"Purchase Receipt": "Delivery Note Item",
|
||||
"Purchase Invoice": "Sales Invoice Item",
|
||||
}
|
||||
|
||||
ref_doctype = ref_doctype_map.get(self.doctype)
|
||||
items = self.get("items")
|
||||
for d in items:
|
||||
if not cint(self.get("is_return")):
|
||||
# Get outgoing rate based on original item cost based on valuation method
|
||||
for d in self.get("items"):
|
||||
if not d.get(frappe.scrub(ref_doctype)):
|
||||
posting_time = self.get("posting_time")
|
||||
if not posting_time:
|
||||
posting_time = nowtime()
|
||||
|
||||
if not d.get(frappe.scrub(ref_doctype)):
|
||||
posting_time = self.get("posting_time")
|
||||
if not posting_time and self.doctype == "Purchase Order":
|
||||
posting_time = nowtime()
|
||||
outgoing_rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get("from_warehouse"),
|
||||
"posting_date": self.get("posting_date") or self.get("transaction_date"),
|
||||
"posting_time": posting_time,
|
||||
"qty": -1 * flt(d.get("stock_qty")),
|
||||
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
||||
"voucher_detail_no": d.name,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
outgoing_rate = get_incoming_rate(
|
||||
{
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.get("from_warehouse"),
|
||||
"posting_date": self.get("posting_date") or self.get("transaction_date"),
|
||||
"posting_time": posting_time,
|
||||
"qty": -1 * flt(d.get("stock_qty")),
|
||||
"serial_and_batch_bundle": d.get("serial_and_batch_bundle"),
|
||||
"company": self.company,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"allow_zero_valuation": d.get("allow_zero_valuation"),
|
||||
"voucher_detail_no": d.name,
|
||||
},
|
||||
raise_error_if_no_rate=False,
|
||||
)
|
||||
|
||||
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||
else:
|
||||
field = (
|
||||
"incoming_rate"
|
||||
if self.get("is_internal_supplier") and not self.doctype == "Purchase Order"
|
||||
else "rate"
|
||||
)
|
||||
rate = flt(
|
||||
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
||||
* (d.conversion_factor or 1),
|
||||
d.precision("rate"),
|
||||
)
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if self.doctype == "Purchase Receipt" or self.get("update_stock"):
|
||||
if rate != d.rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer"
|
||||
).format(d.idx),
|
||||
alert=1,
|
||||
)
|
||||
d.discount_percentage = 0.0
|
||||
d.discount_amount = 0.0
|
||||
d.margin_rate_or_amount = 0.0
|
||||
d.sales_incoming_rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||
else:
|
||||
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
|
||||
d.sales_incoming_rate = flt(
|
||||
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
||||
* (d.conversion_factor or 1),
|
||||
d.precision("rate"),
|
||||
)
|
||||
|
||||
def validate_for_subcontracting(self):
|
||||
if self.is_subcontracted and self.get("is_old_subcontracting_flow"):
|
||||
@@ -566,11 +586,9 @@ class BuyingController(SubcontractingController):
|
||||
if d.from_warehouse:
|
||||
sle.dependant_sle_voucher_detail_no = d.name
|
||||
else:
|
||||
val_rate_db_precision = 6 if cint(self.precision("valuation_rate", d)) <= 6 else 9
|
||||
incoming_rate = flt(d.valuation_rate, val_rate_db_precision)
|
||||
sle.update(
|
||||
{
|
||||
"incoming_rate": incoming_rate,
|
||||
"incoming_rate": d.valuation_rate,
|
||||
"recalculate_rate": 1
|
||||
if (self.is_subcontracted and (d.bom or d.get("fg_item"))) or d.from_warehouse
|
||||
else 0,
|
||||
|
||||
@@ -435,6 +435,9 @@ class SellingController(StockController):
|
||||
if self.doctype not in ("Delivery Note", "Sales Invoice"):
|
||||
return
|
||||
|
||||
allow_at_arms_length_price = frappe.get_cached_value(
|
||||
"Stock Settings", None, "allow_internal_transfer_at_arms_length_price"
|
||||
)
|
||||
items = self.get("items") + (self.get("packed_items") or [])
|
||||
for d in items:
|
||||
if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
|
||||
@@ -481,6 +484,9 @@ class SellingController(StockController):
|
||||
if d.incoming_rate != incoming_rate:
|
||||
d.incoming_rate = incoming_rate
|
||||
else:
|
||||
if allow_at_arms_length_price:
|
||||
continue
|
||||
|
||||
rate = flt(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("rate"),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, getdate, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
@@ -13,6 +13,7 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_pay
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import prepare_data_for_internal_transfer
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
@@ -804,6 +805,41 @@ class TestAccountsController(FrappeTestCase):
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_pe, [])
|
||||
|
||||
@change_settings("Stock Settings", {"allow_internal_transfer_at_arms_length_price": 1})
|
||||
def test_16_internal_transfer_at_arms_length_price(self):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
company = "_Test Company with perpetual inventory"
|
||||
target_warehouse = create_warehouse("_Test Internal Warehouse New 1", company=company)
|
||||
warehouse = create_warehouse("_Test Internal Warehouse New 2", company=company)
|
||||
arms_length_price = 40
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=company,
|
||||
customer="_Test Internal Customer 2",
|
||||
debit_to="Debtors - TCP1",
|
||||
target_warehouse=target_warehouse,
|
||||
warehouse=warehouse,
|
||||
income_account="Sales - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
update_stock=True,
|
||||
do_not_save=True,
|
||||
do_not_submit=True,
|
||||
)
|
||||
|
||||
si.items[0].rate = arms_length_price
|
||||
si.save()
|
||||
# rate should not reset to incoming rate
|
||||
self.assertEqual(si.items[0].rate, arms_length_price)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "allow_internal_transfer_at_arms_length_price", 0)
|
||||
si.items[0].rate = arms_length_price
|
||||
si.save()
|
||||
# rate should reset to incoming rate
|
||||
self.assertEqual(si.items[0].rate, 100)
|
||||
|
||||
def test_20_journal_against_sales_invoice(self):
|
||||
# Invoice in Foreign Currency
|
||||
si = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
||||
|
||||
@@ -93,7 +93,26 @@ class Opportunity(TransactionBase, CRMNote):
|
||||
|
||||
def onload(self):
|
||||
ref_doc = frappe.get_doc(self.opportunity_from, self.party_name)
|
||||
|
||||
load_address_and_contact(ref_doc)
|
||||
load_address_and_contact(self)
|
||||
|
||||
ref_doc_contact_list = ref_doc.get("__onload").get("contact_list")
|
||||
opportunity_doc_contact_list = [
|
||||
contact
|
||||
for contact in self.get("__onload").get("contact_list")
|
||||
if contact not in ref_doc_contact_list
|
||||
]
|
||||
ref_doc_contact_list.extend(opportunity_doc_contact_list)
|
||||
ref_doc.set_onload("contact_list", ref_doc_contact_list)
|
||||
|
||||
ref_doc_addr_list = ref_doc.get("__onload").get("addr_list")
|
||||
opportunity_doc_addr_list = [
|
||||
addr for addr in self.get("__onload").get("addr_list") if addr not in ref_doc_addr_list
|
||||
]
|
||||
ref_doc_addr_list.extend(opportunity_doc_addr_list)
|
||||
ref_doc.set_onload("addr_list", ref_doc_addr_list)
|
||||
|
||||
self.set("__onload", ref_doc.get("__onload"))
|
||||
|
||||
def after_insert(self):
|
||||
|
||||
@@ -108,7 +108,9 @@ class OpportunitySummaryBySalesStage:
|
||||
self.grouped_data = []
|
||||
|
||||
grouping_key = lambda o: (o["sales_stage"], o[based_on]) # noqa
|
||||
for (sales_stage, _based_on), rows in groupby(self.query_result, grouping_key):
|
||||
for (sales_stage, _based_on), rows in groupby(
|
||||
sorted(self.query_result, key=grouping_key), key=grouping_key
|
||||
):
|
||||
self.grouped_data.append(
|
||||
{
|
||||
"sales_stage": sales_stage,
|
||||
|
||||
@@ -122,7 +122,9 @@ class SalesPipelineAnalytics:
|
||||
self.grouped_data = []
|
||||
|
||||
grouping_key = lambda o: (o.get(self.pipeline_by) or "Not Assigned", o[self.period_by]) # noqa
|
||||
for (pipeline_by, period_by), rows in groupby(self.query_result, grouping_key):
|
||||
for (pipeline_by, period_by), rows in groupby(
|
||||
sorted(self.query_result, key=grouping_key), grouping_key
|
||||
):
|
||||
self.grouped_data.append(
|
||||
{
|
||||
self.pipeline_by: pipeline_by,
|
||||
|
||||
@@ -357,6 +357,7 @@ erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
||||
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
|
||||
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
|
||||
erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
|
||||
erpnext.patches.v14_0.update_pos_return_ledger_entries
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
|
||||
|
||||
61
erpnext/patches/v14_0/update_pos_return_ledger_entries.py
Normal file
61
erpnext/patches/v14_0/update_pos_return_ledger_entries.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import frappe
|
||||
from frappe import qb
|
||||
|
||||
|
||||
def execute():
|
||||
sinv = qb.DocType("Sales Invoice")
|
||||
pos_returns_without_self = (
|
||||
qb.from_(sinv)
|
||||
.select(sinv.name)
|
||||
.where(
|
||||
sinv.docstatus.eq(1)
|
||||
& sinv.is_pos.eq(1)
|
||||
& sinv.is_return.eq(1)
|
||||
& sinv.return_against.notnull()
|
||||
& sinv.update_outstanding_for_self.eq(0)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
if pos_returns_without_self:
|
||||
pos_returns_without_self = [x[0] for x in pos_returns_without_self]
|
||||
|
||||
gle = qb.DocType("GL Entry")
|
||||
gl_against_references = (
|
||||
qb.from_(gle)
|
||||
.select(gle.voucher_no, gle.against_voucher)
|
||||
.where(
|
||||
gle.voucher_no.isin(pos_returns_without_self)
|
||||
& gle.against_voucher.notnull()
|
||||
& gle.against_voucher.eq(gle.voucher_no)
|
||||
& gle.is_cancelled.eq(0)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
_vouchers = list(set([x[0] for x in gl_against_references]))
|
||||
invoice_return_against = (
|
||||
qb.from_(sinv)
|
||||
.select(sinv.name, sinv.return_against)
|
||||
.where(sinv.name.isin(_vouchers) & sinv.return_against.notnull())
|
||||
.orderby(sinv.name)
|
||||
.run()
|
||||
)
|
||||
|
||||
valid_references = set(invoice_return_against)
|
||||
actual_references = set(gl_against_references)
|
||||
|
||||
invalid_references = actual_references.difference(valid_references)
|
||||
|
||||
if invalid_references:
|
||||
# Repost Accounting Ledger
|
||||
pos_for_reposting = (
|
||||
qb.from_(sinv)
|
||||
.select(sinv.company, sinv.name)
|
||||
.where(sinv.name.isin([x[0] for x in invalid_references]))
|
||||
.run(as_dict=True)
|
||||
)
|
||||
for x in pos_for_reposting:
|
||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||
ral.company = x.company
|
||||
ral.append("vouchers", {"voucher_type": "Sales Invoice", "voucher_no": x.name})
|
||||
ral.save().submit()
|
||||
@@ -379,6 +379,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None, currency=None
|
||||
target.project = timesheet.parent_project
|
||||
if customer:
|
||||
target.customer = customer
|
||||
default_price_list = frappe.get_value("Customer", customer, "default_price_list")
|
||||
if default_price_list:
|
||||
target.selling_price_list = default_price_list
|
||||
|
||||
if currency:
|
||||
target.currency = currency
|
||||
|
||||
@@ -840,6 +840,10 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
var get_party_currency = function() {
|
||||
if (me.is_a_mapped_document()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var party_type = frappe.meta.has_field(me.frm.doc.doctype, "customer") ? "Customer" : "Supplier";
|
||||
var party_name = me.frm.doc[party_type.toLowerCase()];
|
||||
if (party_name) {
|
||||
@@ -1249,16 +1253,24 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
is_a_mapped_document(item) {
|
||||
const mapped_item_field_map = {
|
||||
"Delivery Note Item": ["si_detail", "so_detail", "dn_detail"],
|
||||
"Sales Invoice Item": ["dn_detail", "so_detail", "sales_invoice_item"],
|
||||
"Purchase Receipt Item": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
|
||||
"Purchase Invoice Item": ["purchase_order_item", "pr_detail", "po_detail"],
|
||||
"Delivery Note": ["si_detail", "so_detail", "dn_detail"],
|
||||
"Sales Invoice": ["dn_detail", "so_detail", "sales_invoice_item"],
|
||||
"Purchase Receipt": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
|
||||
"Purchase Invoice": ["purchase_order_item", "pr_detail", "po_detail"],
|
||||
"Sales Order": ["prevdoc_docname", "quotation_item"],
|
||||
};
|
||||
const mappped_fields = mapped_item_field_map[item.doctype] || [];
|
||||
const mappped_fields = mapped_item_field_map[this.frm.doc.doctype] || [];
|
||||
|
||||
return mappped_fields
|
||||
.map((field) => item[field])
|
||||
.filter(Boolean).length > 0;
|
||||
if (item) {
|
||||
return mappped_fields
|
||||
.map((field) => item[field])
|
||||
.filter(Boolean).length > 0;
|
||||
} else if (this.frm.doc?.items) {
|
||||
let first_row = this.frm.doc.items[0];
|
||||
let mapped_rows = mappped_fields.filter(d => first_row[d])
|
||||
|
||||
return mapped_rows?.length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
batch_no(doc, cdt, cdn) {
|
||||
|
||||
@@ -248,7 +248,7 @@ erpnext.SalesFunnel = class SalesFunnel {
|
||||
context.fill();
|
||||
|
||||
// draw text
|
||||
context.fillStyle = "black";
|
||||
context.fillStyle = getComputedStyle(document.body).getPropertyValue("--text-color");
|
||||
context.textBaseline = "middle";
|
||||
context.font = "1.1em sans-serif";
|
||||
context.fillText(__(title), width + 20, y_mid);
|
||||
|
||||
@@ -93,7 +93,7 @@ def get_opp_by_lead_source(from_date, to_date, company):
|
||||
summary = {}
|
||||
sales_stages = set()
|
||||
group_key = lambda o: (o["source"], o["sales_stage"]) # noqa
|
||||
for (source, sales_stage), rows in groupby(cp_opportunities, group_key):
|
||||
for (source, sales_stage), rows in groupby(sorted(cp_opportunities, key=group_key), group_key):
|
||||
summary.setdefault(source, {})[sales_stage] = sum(r["compound_amount"] for r in rows)
|
||||
sales_stages.add(sales_stage)
|
||||
|
||||
|
||||
@@ -328,6 +328,9 @@ class DeliveryNote(SellingController):
|
||||
return
|
||||
|
||||
for item in self.items:
|
||||
if item.use_serial_batch_fields:
|
||||
continue
|
||||
|
||||
if item.pick_list_item and not item.serial_and_batch_bundle:
|
||||
filters = {
|
||||
"item_code": item.item_code,
|
||||
|
||||
@@ -1097,7 +1097,8 @@ def create_delivery_note(source_name, target_doc=None):
|
||||
)
|
||||
)
|
||||
|
||||
for customer, rows in groupby(sales_orders, key=lambda so: so["customer"]):
|
||||
group_key = lambda so: so["customer"] # noqa
|
||||
for customer, rows in groupby(sorted(sales_orders, key=group_key), key=group_key):
|
||||
sales_dict[customer] = {row.sales_order for row in rows}
|
||||
|
||||
if sales_dict:
|
||||
@@ -1178,6 +1179,7 @@ def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
|
||||
dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
|
||||
dn_item.batch_no = location.batch_no
|
||||
dn_item.serial_no = location.serial_no
|
||||
dn_item.use_serial_batch_fields = location.use_serial_batch_fields
|
||||
|
||||
update_delivery_note_item(source_doc, dn_item, delivery_note)
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"base_net_rate",
|
||||
"base_net_amount",
|
||||
"valuation_rate",
|
||||
"sales_incoming_rate",
|
||||
"item_tax_amount",
|
||||
"rm_supp_cost",
|
||||
"landed_cost_voucher_amount",
|
||||
@@ -1124,12 +1125,22 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Return Qty from Rejected Warehouse",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"description": "Valuation rate for the item as per Sales Invoice (Only for Internal Transfers)",
|
||||
"fieldname": "sales_incoming_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Sales Incoming Rate",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-28 09:48:24.448815",
|
||||
"modified": "2024-07-19 12:14:21.521466",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -88,6 +88,7 @@ class PurchaseReceiptItem(Document):
|
||||
return_qty_from_rejected_warehouse: DF.Check
|
||||
returned_qty: DF.Float
|
||||
rm_supp_cost: DF.Currency
|
||||
sales_incoming_rate: DF.Currency
|
||||
sales_order: DF.Link | None
|
||||
sales_order_item: DF.Data | None
|
||||
sample_quantity: DF.Int
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
"depends_on": "from_warehouse",
|
||||
"fieldname": "source_warehouse_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Source Warehouse Address",
|
||||
"label": "Source Warehouse Address Link",
|
||||
"options": "Address"
|
||||
},
|
||||
{
|
||||
@@ -333,7 +333,7 @@
|
||||
"depends_on": "to_warehouse",
|
||||
"fieldname": "target_warehouse_address",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Warehouse Address",
|
||||
"label": "Target Warehouse Address Link",
|
||||
"options": "Address"
|
||||
},
|
||||
{
|
||||
@@ -686,10 +686,10 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "tab_connections",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
"fieldname": "tab_connections",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Connections",
|
||||
"show_dashboard": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -697,7 +697,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-26 19:12:17.937088",
|
||||
"modified": "2024-08-13 19:02:42.386955",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"allow_negative_stock",
|
||||
"show_barcode_field",
|
||||
"clean_description_html",
|
||||
"allow_internal_transfer_at_arms_length_price",
|
||||
"quality_inspection_settings_section",
|
||||
"action_if_quality_inspection_is_not_submitted",
|
||||
"column_break_23",
|
||||
@@ -439,6 +440,13 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Do Not Update Serial / Batch on Creation of Auto Bundle"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, the item rate won't adjust to the valuation rate during internal transfers, but accounting will still use the valuation rate.",
|
||||
"fieldname": "allow_internal_transfer_at_arms_length_price",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Internal Transfers at Arm's Length Price"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.valuation_method === \"Moving Average\"",
|
||||
|
||||
@@ -27,6 +27,7 @@ class StockSettings(Document):
|
||||
action_if_quality_inspection_is_rejected: DF.Literal["Stop", "Warn"]
|
||||
allow_from_dn: DF.Check
|
||||
allow_from_pr: DF.Check
|
||||
allow_internal_transfer_at_arms_length_price: DF.Check
|
||||
allow_negative_stock: DF.Check
|
||||
allow_partial_reservation: DF.Check
|
||||
allow_to_edit_stock_uom_qty_for_purchase: DF.Check
|
||||
|
||||
@@ -6,13 +6,14 @@ import gzip
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe import _, bold, scrub
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
cstr,
|
||||
flt,
|
||||
format_date,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
now,
|
||||
@@ -743,9 +744,29 @@ class update_entries_after:
|
||||
|
||||
return self.distinct_item_warehouses[key].dependent_voucher_detail_nos
|
||||
|
||||
def validate_previous_sle_qty(self, sle):
|
||||
previous_sle = self.data[sle.warehouse].previous_sle
|
||||
if previous_sle and previous_sle.get("qty_after_transaction") < 0 and sle.get("actual_qty") > 0:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"The stock for the item {0} in the {1} warehouse was negative on the {2}. You should create a positive entry {3} before the date {4} and time {5} to post the correct valuation rate. For more details, please read the <a href='https://docs.erpnext.com/docs/user/manual/en/stock-adjustment-cogs-with-negative-stock'>documentation<a>."
|
||||
).format(
|
||||
bold(sle.item_code),
|
||||
bold(sle.warehouse),
|
||||
bold(format_date(previous_sle.posting_date)),
|
||||
sle.voucher_no,
|
||||
bold(format_date(previous_sle.posting_date)),
|
||||
bold(previous_sle.posting_time),
|
||||
),
|
||||
title=_("Warning on Negative Stock"),
|
||||
indicator="blue",
|
||||
)
|
||||
|
||||
def process_sle(self, sle):
|
||||
# previous sle data for this warehouse
|
||||
self.wh_data = self.data[sle.warehouse]
|
||||
|
||||
self.validate_previous_sle_qty(sle)
|
||||
self.affected_transactions.add((sle.voucher_type, sle.voucher_no))
|
||||
|
||||
if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock):
|
||||
|
||||
@@ -1334,7 +1334,7 @@ Lead to Quotation,Vom Lead zum Angebot,
|
||||
Learn,Lernen,
|
||||
Leave Management,Urlaube verwalten,
|
||||
Leave and Attendance,Urlaub und Anwesenheit,
|
||||
Leave application {0} already exists against the student {1},Verlassen der Anwendung {0} ist bereits für den Schüler {1} vorhanden,
|
||||
Leave application {0} already exists against the student {1},Abwesenheitsantrag {0} existiert bereits für den Schüler {1},
|
||||
Leaves has been granted sucessfully,Urlaub wurde genehmigt,
|
||||
Leaves must be allocated in multiples of 0.5,"Abwesenheiten müssen ein Vielfaches von 0,5 sein",
|
||||
Ledger,Hauptbuch,
|
||||
@@ -5529,10 +5529,10 @@ Guardian Details,Erziehungsberechtigten-Details,
|
||||
Guardians,Erziehungsberechtigte,
|
||||
Sibling Details,Geschwister-Details,
|
||||
Siblings,Geschwister,
|
||||
Exit,Verlassen,
|
||||
Exit,Austritt,
|
||||
Date of Leaving,Austrittsdatum,
|
||||
Leaving Certificate Number,Leaving Certificate Nummer,
|
||||
Reason For Leaving,Grund für das Verlassen,
|
||||
Reason For Leaving,Grund für den Austritt,
|
||||
Student Admission,Studenten Eintritt,
|
||||
Admission Start Date,Stichtag zum Zulassungsbeginn,
|
||||
Admission End Date,Stichtag für Zulassungsende,
|
||||
@@ -8795,13 +8795,13 @@ Warehouse wise Stock Value,Warenwert nach Lager,
|
||||
Ex Works,Ab Werk,
|
||||
Free Carrier,Frei Frachtführer,
|
||||
Free Alongside Ship,Frei Längsseite Schiff,
|
||||
Free on Board,Frei an Bord,
|
||||
Free On Board,Frei an Bord,
|
||||
Carriage Paid To,Frachtfrei,
|
||||
Carriage and Insurance Paid to,Frachtfrei versichert,
|
||||
Cost and Freight,Kosten und Fracht,
|
||||
"Cost, Insurance and Freight","Kosten, Versicherung und Fracht",
|
||||
Delivered at Place,Geliefert benannter Ort,
|
||||
Delivered at Place Unloaded,Geliefert benannter Ort entladen,
|
||||
Delivered At Place,Geliefert benannter Ort,
|
||||
Delivered At Place Unloaded,Geliefert benannter Ort entladen,
|
||||
Delivered Duty Paid,Geliefert verzollt,
|
||||
Discount Validity,Frist für den Rabatt,
|
||||
Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
|
||||
|
||||
|
Can't render this file because it is too large.
|
Reference in New Issue
Block a user