mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-04 14:08:29 +00:00
Merge pull request #36969 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -13,10 +13,11 @@ frappe.ui.form.on("Bank Transaction", {
|
||||
});
|
||||
},
|
||||
refresh(frm) {
|
||||
frm.add_custom_button(__('Unreconcile Transaction'), () => {
|
||||
frm.call('remove_payment_entries')
|
||||
.then( () => frm.refresh() );
|
||||
});
|
||||
if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) {
|
||||
frm.add_custom_button(__("Unreconcile Transaction"), () => {
|
||||
frm.call("remove_payment_entries").then(() => frm.refresh());
|
||||
});
|
||||
}
|
||||
},
|
||||
bank_account: function (frm) {
|
||||
set_bank_statement_filter(frm);
|
||||
|
||||
@@ -538,7 +538,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
}
|
||||
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
// set_unallocated_amount is called by below method,
|
||||
// no need trigger separately
|
||||
frm.events.set_total_allocated_amount(frm);
|
||||
}
|
||||
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
@@ -562,7 +564,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
||||
}
|
||||
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
// set_unallocated_amount is called by below method,
|
||||
// no need trigger separately
|
||||
frm.events.set_total_allocated_amount(frm);
|
||||
}
|
||||
frm.set_paid_amount_based_on_received_amount = false;
|
||||
|
||||
@@ -878,12 +882,18 @@ frappe.ui.form.on('Payment Entry', {
|
||||
},
|
||||
|
||||
set_total_allocated_amount: function(frm) {
|
||||
let exchange_rate = 1;
|
||||
if (frm.doc.payment_type == "Receive") {
|
||||
exchange_rate = frm.doc.source_exchange_rate;
|
||||
} else if (frm.doc.payment_type == "Pay") {
|
||||
exchange_rate = frm.doc.target_exchange_rate;
|
||||
}
|
||||
var total_allocated_amount = 0.0;
|
||||
var base_total_allocated_amount = 0.0;
|
||||
$.each(frm.doc.references || [], function(i, row) {
|
||||
if (row.allocated_amount) {
|
||||
total_allocated_amount += flt(row.allocated_amount);
|
||||
base_total_allocated_amount += flt(flt(row.allocated_amount)*flt(row.exchange_rate),
|
||||
base_total_allocated_amount += flt(flt(row.allocated_amount)*flt(exchange_rate),
|
||||
precision("base_paid_amount"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -809,6 +809,11 @@ class PaymentEntry(AccountsController):
|
||||
flt(d.allocated_amount) * flt(exchange_rate), self.precision("base_paid_amount")
|
||||
)
|
||||
|
||||
# on rare case, when `exchange_rate` is unset, gain/loss amount is incorrectly calculated
|
||||
# for base currency transactions
|
||||
if d.exchange_rate is None:
|
||||
d.exchange_rate = 1
|
||||
|
||||
allocated_amount_in_pe_exchange_rate = flt(
|
||||
flt(d.allocated_amount) * flt(d.exchange_rate), self.precision("base_paid_amount")
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ class PaymentReconciliation(Document):
|
||||
"Journal Entry" as reference_type, t1.name as reference_name,
|
||||
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
|
||||
{dr_or_cr} as amount, t2.is_advance, t2.exchange_rate,
|
||||
t2.account_currency as currency
|
||||
t2.account_currency as currency, t2.cost_center as cost_center
|
||||
from
|
||||
`tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where
|
||||
@@ -196,6 +196,7 @@ class PaymentReconciliation(Document):
|
||||
"amount": -(inv.outstanding_in_account_currency),
|
||||
"posting_date": inv.posting_date,
|
||||
"currency": inv.currency,
|
||||
"cost_center": inv.cost_center,
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -344,6 +345,7 @@ class PaymentReconciliation(Document):
|
||||
"allocated_amount": allocated_amount,
|
||||
"difference_amount": pay.get("difference_amount"),
|
||||
"currency": inv.get("currency"),
|
||||
"cost_center": pay.get("cost_center"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -418,6 +420,7 @@ class PaymentReconciliation(Document):
|
||||
"allocated_amount": flt(row.get("allocated_amount")),
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -590,7 +593,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
inv.dr_or_cr: abs(inv.allocated_amount),
|
||||
"reference_type": inv.against_voucher_type,
|
||||
"reference_name": inv.against_voucher,
|
||||
"cost_center": erpnext.get_default_cost_center(company),
|
||||
"cost_center": inv.cost_center or erpnext.get_default_cost_center(company),
|
||||
"user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} against {inv.against_voucher}",
|
||||
"exchange_rate": inv.exchange_rate,
|
||||
},
|
||||
@@ -605,7 +608,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
),
|
||||
"reference_type": inv.voucher_type,
|
||||
"reference_name": inv.voucher_no,
|
||||
"cost_center": erpnext.get_default_cost_center(company),
|
||||
"cost_center": inv.cost_center or erpnext.get_default_cost_center(company),
|
||||
"user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} from {inv.voucher_no}",
|
||||
"exchange_rate": inv.exchange_rate,
|
||||
},
|
||||
@@ -631,6 +634,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
|
||||
create_gain_loss_journal(
|
||||
company,
|
||||
today(),
|
||||
inv.party_type,
|
||||
inv.party,
|
||||
inv.account,
|
||||
@@ -644,4 +648,5 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
inv.against_voucher_type,
|
||||
inv.against_voucher,
|
||||
None,
|
||||
inv.cost_center,
|
||||
)
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
"currency"
|
||||
"currency",
|
||||
"cost_center"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -144,11 +145,17 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Exchange Rate",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-24 21:01:14.882747",
|
||||
"modified": "2023-09-03 07:52:33.684217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"sec_break1",
|
||||
"remark",
|
||||
"currency",
|
||||
"exchange_rate"
|
||||
"exchange_rate",
|
||||
"cost_center"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -98,11 +99,17 @@
|
||||
"fieldtype": "Float",
|
||||
"hidden": 1,
|
||||
"label": "Exchange Rate"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-08 18:18:36.268760",
|
||||
"modified": "2023-09-03 07:43:29.965353",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Payment",
|
||||
|
||||
@@ -126,7 +126,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
def make_gl_entries(self, get_opening_entries=False):
|
||||
gl_entries = self.get_gl_entries()
|
||||
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||
if len(gl_entries) > 5000:
|
||||
if len(gl_entries + closing_entries) > 3000:
|
||||
frappe.enqueue(
|
||||
process_gl_entries,
|
||||
gl_entries=gl_entries,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import collections
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -43,6 +43,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_debit_to_acc()
|
||||
self.validate_write_off_account()
|
||||
self.validate_change_amount()
|
||||
self.validate_duplicate_serial_and_batch_no()
|
||||
self.validate_change_account()
|
||||
self.validate_item_cost_centers()
|
||||
self.validate_warehouse()
|
||||
@@ -155,6 +156,27 @@ class POSInvoice(SalesInvoice):
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
def validate_duplicate_serial_and_batch_no(self):
|
||||
serial_nos = []
|
||||
batch_nos = []
|
||||
|
||||
for row in self.get("items"):
|
||||
if row.serial_no:
|
||||
serial_nos = row.serial_no.split("\n")
|
||||
|
||||
if row.batch_no and not row.serial_no:
|
||||
batch_nos.append(row.batch_no)
|
||||
|
||||
if serial_nos:
|
||||
for key, value in collections.Counter(serial_nos).items():
|
||||
if value > 1:
|
||||
frappe.throw(_("Duplicate Serial No {0} found").format("key"))
|
||||
|
||||
if batch_nos:
|
||||
for key, value in collections.Counter(batch_nos).items():
|
||||
if value > 1:
|
||||
frappe.throw(_("Duplicate Batch No {0} found").format("key"))
|
||||
|
||||
def validate_pos_reserved_batch_qty(self, item):
|
||||
filters = {"item_code": item.item_code, "warehouse": item.warehouse, "batch_no": item.batch_no}
|
||||
|
||||
|
||||
@@ -269,9 +269,7 @@ class PurchaseInvoice(BuyingController):
|
||||
stock_not_billed_account = self.get_company_default("stock_received_but_not_billed")
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
asset_items = [d.is_fixed_asset for d in self.items if d.is_fixed_asset]
|
||||
if len(asset_items) > 0:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
asset_received_but_not_billed = None
|
||||
|
||||
if self.update_stock:
|
||||
self.validate_item_code()
|
||||
@@ -365,6 +363,8 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
item.expense_account = asset_category_account
|
||||
elif item.is_fixed_asset and item.pr_detail:
|
||||
if not asset_received_but_not_billed:
|
||||
asset_received_but_not_billed = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = asset_received_but_not_billed
|
||||
elif not item.expense_account and for_validate:
|
||||
throw(_("Expense account is mandatory for item {0}").format(item.item_code or item.item_name))
|
||||
@@ -978,8 +978,9 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
def get_asset_gl_entry(self, gl_entries):
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
arbnb_account = None
|
||||
eiiav_account = None
|
||||
asset_eiiav_currency = None
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.is_fixed_asset:
|
||||
@@ -991,6 +992,8 @@ class PurchaseInvoice(BuyingController):
|
||||
"Asset Received But Not Billed",
|
||||
"Fixed Asset",
|
||||
]:
|
||||
if not arbnb_account:
|
||||
arbnb_account = self.get_company_default("asset_received_but_not_billed")
|
||||
item.expense_account = arbnb_account
|
||||
|
||||
if not self.update_stock:
|
||||
@@ -1013,7 +1016,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if item.item_tax_amount:
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -1056,7 +1062,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
if not eiiav_account or not asset_eiiav_currency:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
asset_eiiav_currency = get_account_currency(eiiav_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -1076,47 +1085,46 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
# When update stock is checked
|
||||
# Assets are bought through this document then it will be linked to this document
|
||||
if self.update_stock:
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
if flt(item.landed_cost_voucher_amount):
|
||||
if not eiiav_account:
|
||||
eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": eiiav_account,
|
||||
"against": cwip_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cwip_account,
|
||||
"against": eiiav_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": flt(item.landed_cost_voucher_amount),
|
||||
"project": item.project or self.project,
|
||||
},
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of assets bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
|
||||
return gl_entries
|
||||
|
||||
|
||||
@@ -694,3 +694,23 @@ class TestSubscription(unittest.TestCase):
|
||||
# Check the currency of the created invoice
|
||||
currency = frappe.db.get_value("Sales Invoice", subscription.invoices[0].invoice, "currency")
|
||||
self.assertEqual(currency, "USD")
|
||||
|
||||
def test_plan_rate_for_midmonth_start_date(self):
|
||||
subscription = frappe.new_doc("Subscription")
|
||||
subscription.party_type = "Supplier"
|
||||
subscription.party = "_Test Supplier"
|
||||
subscription.generate_invoice_at_period_start = 1
|
||||
subscription.follow_calendar_months = 1
|
||||
subscription.generate_new_invoices_past_due_date = 1
|
||||
subscription.start_date = "2023-04-08"
|
||||
subscription.end_date = "2024-02-27"
|
||||
subscription.append("plans", {"plan": "_Test Plan Name 4", "qty": 1})
|
||||
subscription.save()
|
||||
|
||||
subscription.process()
|
||||
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
pi = frappe.get_doc("Purchase Invoice", subscription.invoices[0].invoice)
|
||||
self.assertEqual(pi.total, 55333.33)
|
||||
|
||||
subscription.delete()
|
||||
|
||||
@@ -57,18 +57,17 @@ def get_plan_rate(
|
||||
prorate = frappe.db.get_single_value("Subscription Settings", "prorate")
|
||||
|
||||
if prorate:
|
||||
prorate_factor = flt(
|
||||
date_diff(start_date, get_first_day(start_date))
|
||||
/ date_diff(get_last_day(start_date), get_first_day(start_date)),
|
||||
1,
|
||||
)
|
||||
|
||||
prorate_factor += flt(
|
||||
date_diff(get_last_day(end_date), end_date)
|
||||
/ date_diff(get_last_day(end_date), get_first_day(end_date)),
|
||||
1,
|
||||
)
|
||||
|
||||
cost -= plan.cost * prorate_factor
|
||||
|
||||
cost -= plan.cost * get_prorate_factor(start_date, end_date)
|
||||
return cost
|
||||
|
||||
|
||||
def get_prorate_factor(start_date, end_date):
|
||||
total_days_to_skip = date_diff(start_date, get_first_day(start_date))
|
||||
total_days_in_month = int(get_last_day(start_date).strftime("%d"))
|
||||
prorate_factor = flt(total_days_to_skip / total_days_in_month)
|
||||
|
||||
total_days_to_skip = date_diff(get_last_day(end_date), end_date)
|
||||
total_days_in_month = int(get_last_day(end_date).strftime("%d"))
|
||||
prorate_factor += flt(total_days_to_skip / total_days_in_month)
|
||||
|
||||
return prorate_factor
|
||||
|
||||
@@ -37,24 +37,6 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier",
|
||||
"label": __("Supplier"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
on_change: () => {
|
||||
var supplier = frappe.query_report.get_filter_value('supplier');
|
||||
if (supplier) {
|
||||
frappe.db.get_value('Supplier', supplier, "tax_id", function(value) {
|
||||
frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
|
||||
});
|
||||
} else {
|
||||
frappe.query_report.set_filter_value('tax_id', "");
|
||||
}
|
||||
|
||||
frappe.query_report.refresh();
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "party_account",
|
||||
"label": __("Payable Account"),
|
||||
@@ -112,11 +94,38 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"fieldtype": "Link",
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"label": __("Party Type"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Party Type",
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'account_type': 'Payable'
|
||||
}
|
||||
};
|
||||
},
|
||||
on_change: () => {
|
||||
frappe.query_report.set_filter_value('party', "");
|
||||
let party_type = frappe.query_report.get_filter_value('party_type');
|
||||
frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"fieldname":"party",
|
||||
"label": __("Party"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_group",
|
||||
"label": __("Supplier Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group"
|
||||
"options": "Supplier Group",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "group_by_party",
|
||||
@@ -133,12 +142,6 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"label": __("Tax Id"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_future_payments",
|
||||
"label": __("Show Future Payments"),
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, today
|
||||
|
||||
from erpnext import get_default_cost_center
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
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.report.accounts_payable.accounts_payable import execute
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_item()
|
||||
self.create_supplier(currency="USD", supplier_name="Test Supplier2")
|
||||
self.create_usd_payable_account()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_accounts_receivable_with_supplier(self):
|
||||
pi = self.create_purchase_invoice(do_not_submit=True)
|
||||
pi.currency = "USD"
|
||||
pi.conversion_rate = 80
|
||||
pi.credit_to = self.creditors_usd
|
||||
pi = pi.save().submit()
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
data = execute(filters)
|
||||
self.assertEqual(data[1][0].get("outstanding"), 300)
|
||||
self.assertEqual(data[1][0].get("currency"), "USD")
|
||||
|
||||
def create_purchase_invoice(self, do_not_submit=False):
|
||||
frappe.set_user("Administrator")
|
||||
pi = make_purchase_invoice(
|
||||
item=self.item,
|
||||
company=self.company,
|
||||
supplier=self.supplier,
|
||||
is_return=False,
|
||||
update_stock=False,
|
||||
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
||||
do_not_save=1,
|
||||
rate=300,
|
||||
price_list_rate=300,
|
||||
qty=1,
|
||||
)
|
||||
|
||||
pi = pi.save()
|
||||
if not do_not_submit:
|
||||
pi = pi.submit()
|
||||
return pi
|
||||
@@ -46,8 +46,7 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
var customer = frappe.query_report.get_filter_value('customer');
|
||||
var company = frappe.query_report.get_filter_value('company');
|
||||
if (customer) {
|
||||
frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "payment_terms"], function(value) {
|
||||
frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
|
||||
frappe.db.get_value('Customer', customer, ["customer_name", "payment_terms"], function(value) {
|
||||
frappe.query_report.set_filter_value('customer_name', value["customer_name"]);
|
||||
frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]);
|
||||
});
|
||||
@@ -59,7 +58,6 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
}
|
||||
}, "Customer");
|
||||
} else {
|
||||
frappe.query_report.set_filter_value('tax_id', "");
|
||||
frappe.query_report.set_filter_value('customer_name', "");
|
||||
frappe.query_report.set_filter_value('credit_limit', "");
|
||||
frappe.query_report.set_filter_value('payment_terms', "");
|
||||
@@ -172,12 +170,6 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"label": __("Show Sales Person"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_id",
|
||||
"label": __("Tax Id"),
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
|
||||
@@ -211,11 +211,10 @@ class ReceivablePayableReport(object):
|
||||
return
|
||||
|
||||
# amount in "Party Currency", if its supplied. If not, amount in company currency
|
||||
for party_type in self.party_type:
|
||||
if self.filters.get(scrub(party_type)):
|
||||
amount = ple.amount_in_account_currency
|
||||
else:
|
||||
amount = ple.amount
|
||||
if self.filters.get("party_type") and self.filters.get("party"):
|
||||
amount = ple.amount_in_account_currency
|
||||
else:
|
||||
amount = ple.amount
|
||||
amount_in_account_currency = ple.amount_in_account_currency
|
||||
|
||||
# update voucher
|
||||
@@ -426,10 +425,9 @@ class ReceivablePayableReport(object):
|
||||
# customer / supplier name
|
||||
party_details = self.get_party_details(row.party) or {}
|
||||
row.update(party_details)
|
||||
for party_type in self.party_type:
|
||||
if self.filters.get(scrub(party_type)):
|
||||
row.currency = row.account_currency
|
||||
break
|
||||
|
||||
if self.filters.get("party_type") and self.filters.get("party"):
|
||||
row.currency = row.account_currency
|
||||
else:
|
||||
row.currency = self.company_currency
|
||||
|
||||
@@ -469,6 +467,10 @@ class ReceivablePayableReport(object):
|
||||
original_row = frappe._dict(row)
|
||||
row.payment_terms = []
|
||||
|
||||
# Cr Note's don't have Payment Terms
|
||||
if not payment_terms_details:
|
||||
return
|
||||
|
||||
# Advance allocated during invoicing is not considered in payment terms
|
||||
# Deduct that from paid amount pre allocation
|
||||
row.paid -= flt(payment_terms_details[0].total_advance)
|
||||
@@ -765,6 +767,7 @@ class ReceivablePayableReport(object):
|
||||
def prepare_conditions(self):
|
||||
self.qb_selection_filter = []
|
||||
self.or_filters = []
|
||||
|
||||
for party_type in self.party_type:
|
||||
party_type_field = scrub(party_type)
|
||||
self.or_filters.append(self.ple.party_type == party_type)
|
||||
@@ -800,6 +803,12 @@ class ReceivablePayableReport(object):
|
||||
if self.filters.get(party_type_field):
|
||||
self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field))
|
||||
|
||||
if self.filters.get("party_type"):
|
||||
self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
|
||||
|
||||
if self.filters.get("party"):
|
||||
self.qb_selection_filter.append(self.filters.party == self.ple.party)
|
||||
|
||||
if self.filters.party_account:
|
||||
self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
|
||||
else:
|
||||
|
||||
@@ -324,12 +324,22 @@ def get_journal_entry_party_map(journal_entries):
|
||||
|
||||
|
||||
def get_doc_info(vouchers, doctype, tax_category_map, net_total_map=None):
|
||||
common_fields = ["name", "tax_withholding_category"]
|
||||
common_fields = ["name"]
|
||||
fields_dict = {
|
||||
"Purchase Invoice": ["base_tax_withholding_net_total", "grand_total", "base_total"],
|
||||
"Purchase Invoice": [
|
||||
"tax_withholding_category",
|
||||
"base_tax_withholding_net_total",
|
||||
"grand_total",
|
||||
"base_total",
|
||||
],
|
||||
"Sales Invoice": ["base_net_total", "grand_total", "base_total"],
|
||||
"Payment Entry": ["paid_amount", "paid_amount_after_tax", "base_paid_amount"],
|
||||
"Journal Entry": ["total_amount"],
|
||||
"Payment Entry": [
|
||||
"tax_withholding_category",
|
||||
"paid_amount",
|
||||
"paid_amount_after_tax",
|
||||
"base_paid_amount",
|
||||
],
|
||||
"Journal Entry": ["tax_withholding_category", "total_amount"],
|
||||
}
|
||||
|
||||
entries = frappe.get_all(
|
||||
|
||||
@@ -126,6 +126,28 @@ class AccountsTestMixin:
|
||||
acc = frappe.get_doc("Account", name)
|
||||
self.debtors_usd = acc.name
|
||||
|
||||
def create_usd_payable_account(self):
|
||||
account_name = "Creditors USD"
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": account_name, "company": self.company}
|
||||
):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = account_name
|
||||
acc.parent_account = "Accounts Payable - " + self.company_abbr
|
||||
acc.company = self.company
|
||||
acc.account_currency = "USD"
|
||||
acc.account_type = "Payable"
|
||||
acc.insert()
|
||||
else:
|
||||
name = frappe.db.get_value(
|
||||
"Account",
|
||||
filters={"account_name": account_name, "company": self.company},
|
||||
fieldname="name",
|
||||
pluck=True,
|
||||
)
|
||||
acc = frappe.get_doc("Account", name)
|
||||
self.creditors_usd = acc.name
|
||||
|
||||
def clear_old_entries(self):
|
||||
doctype_list = [
|
||||
"GL Entry",
|
||||
|
||||
@@ -458,10 +458,12 @@ def reconcile_against_document(args, skip_ref_details_update_for_pe=False): # n
|
||||
|
||||
# update ref in advance entry
|
||||
if voucher_type == "Journal Entry":
|
||||
update_reference_in_journal_entry(entry, doc, do_not_save=True)
|
||||
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
|
||||
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
|
||||
# amount and account in args
|
||||
doc.make_exchange_gain_loss_journal(args)
|
||||
# referenced_row is used to deduplicate gain/loss journal
|
||||
entry.update({"referenced_row": referenced_row})
|
||||
doc.make_exchange_gain_loss_journal([entry])
|
||||
else:
|
||||
update_reference_in_payment_entry(
|
||||
entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe
|
||||
@@ -605,6 +607,8 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
||||
if not do_not_save:
|
||||
journal_entry.save(ignore_permissions=True)
|
||||
|
||||
return new_row.name
|
||||
|
||||
|
||||
def update_reference_in_payment_entry(
|
||||
d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False
|
||||
@@ -1720,6 +1724,7 @@ class QueryPaymentLedger(object):
|
||||
ple.posting_date,
|
||||
ple.due_date,
|
||||
ple.account_currency.as_("currency"),
|
||||
ple.cost_center.as_("cost_center"),
|
||||
Sum(ple.amount).as_("amount"),
|
||||
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
|
||||
)
|
||||
@@ -1782,6 +1787,7 @@ class QueryPaymentLedger(object):
|
||||
).as_("paid_amount_in_account_currency"),
|
||||
Table("vouchers").due_date,
|
||||
Table("vouchers").currency,
|
||||
Table("vouchers").cost_center.as_("cost_center"),
|
||||
)
|
||||
.where(Criterion.all(filter_on_outstanding_amount))
|
||||
)
|
||||
@@ -1852,6 +1858,7 @@ class QueryPaymentLedger(object):
|
||||
|
||||
def create_gain_loss_journal(
|
||||
company,
|
||||
posting_date,
|
||||
party_type,
|
||||
party,
|
||||
party_account,
|
||||
@@ -1865,12 +1872,14 @@ def create_gain_loss_journal(
|
||||
ref2_dt,
|
||||
ref2_dn,
|
||||
ref2_detail_no,
|
||||
cost_center,
|
||||
) -> str:
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = "Exchange Gain Or Loss"
|
||||
journal_entry.company = company
|
||||
journal_entry.posting_date = nowdate()
|
||||
journal_entry.posting_date = posting_date or nowdate()
|
||||
journal_entry.multi_currency = 1
|
||||
journal_entry.is_system_generated = True
|
||||
|
||||
party_account_currency = frappe.get_cached_value("Account", party_account, "account_currency")
|
||||
|
||||
@@ -1889,7 +1898,7 @@ def create_gain_loss_journal(
|
||||
"party": party,
|
||||
"account_currency": party_account_currency,
|
||||
"exchange_rate": 0,
|
||||
"cost_center": erpnext.get_default_cost_center(company),
|
||||
"cost_center": cost_center or erpnext.get_default_cost_center(company),
|
||||
"reference_type": ref1_dt,
|
||||
"reference_name": ref1_dn,
|
||||
"reference_detail_no": ref1_detail_no,
|
||||
@@ -1905,7 +1914,7 @@ def create_gain_loss_journal(
|
||||
"account": gain_loss_account,
|
||||
"account_currency": gain_loss_account_currency,
|
||||
"exchange_rate": 1,
|
||||
"cost_center": erpnext.get_default_cost_center(company),
|
||||
"cost_center": cost_center or erpnext.get_default_cost_center(company),
|
||||
"reference_type": ref2_dt,
|
||||
"reference_name": ref2_dn,
|
||||
"reference_detail_no": ref2_detail_no,
|
||||
|
||||
@@ -399,14 +399,11 @@ class AssetCapitalization(StockController):
|
||||
def get_gl_entries_for_consumed_asset_items(
|
||||
self, gl_entries, target_account, target_against, precision
|
||||
):
|
||||
self.are_all_asset_items_non_depreciable = True
|
||||
|
||||
# Consumed Assets
|
||||
for item in self.asset_items:
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.are_all_asset_items_non_depreciable = False
|
||||
depreciate_asset(asset, self.posting_date)
|
||||
asset.reload()
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
{
|
||||
"fieldname": "parent_location",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Parent Location",
|
||||
"options": "Location",
|
||||
"search_index": 1
|
||||
@@ -141,11 +142,11 @@
|
||||
],
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-08 16:11:11.375701",
|
||||
"modified": "2023-08-29 12:49:33.290527",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Location",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By fieldname",
|
||||
"nsm_parent_field": "parent_location",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
@@ -224,5 +225,6 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
"col_break_email_1",
|
||||
"html_llwp",
|
||||
"send_attached_files",
|
||||
"send_document_print",
|
||||
"sec_break_email_2",
|
||||
"message_for_supplier",
|
||||
"terms_section_break",
|
||||
@@ -283,13 +284,21 @@
|
||||
"fieldname": "send_attached_files",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Attached Files"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, a print of this document will be attached to each email",
|
||||
"fieldname": "send_document_print",
|
||||
"fieldtype": "Check",
|
||||
"label": "Send Document Print",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-08 16:30:10.870429",
|
||||
"modified": "2023-08-09 12:20:26.850623",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
|
||||
@@ -205,10 +205,24 @@ class RequestforQuotation(BuyingController):
|
||||
if preview:
|
||||
return {"message": message, "subject": subject}
|
||||
|
||||
attachments = None
|
||||
attachments = []
|
||||
if self.send_attached_files:
|
||||
attachments = self.get_attachments()
|
||||
|
||||
if self.send_document_print:
|
||||
supplier_language = frappe.db.get_value("Supplier", data.supplier, "language")
|
||||
system_language = frappe.db.get_single_value("System Settings", "language")
|
||||
attachments.append(
|
||||
frappe.attach_print(
|
||||
self.doctype,
|
||||
self.name,
|
||||
doc=self,
|
||||
print_format=self.meta.default_print_format or "Standard",
|
||||
lang=supplier_language or system_language,
|
||||
letterhead=self.letter_head,
|
||||
)
|
||||
)
|
||||
|
||||
self.send_email(data, sender, subject, message, attachments)
|
||||
|
||||
def send_email(self, data, sender, subject, message, attachments):
|
||||
@@ -218,7 +232,6 @@ class RequestforQuotation(BuyingController):
|
||||
recipients=data.email_id,
|
||||
sender=sender,
|
||||
attachments=attachments,
|
||||
print_format=self.meta.default_print_format or "Standard",
|
||||
send_email=True,
|
||||
doctype=self.doctype,
|
||||
name=self.name,
|
||||
|
||||
@@ -969,6 +969,44 @@ class AccountsController(TransactionBase):
|
||||
|
||||
d.exchange_gain_loss = difference
|
||||
|
||||
def gain_loss_journal_already_booked(
|
||||
self,
|
||||
gain_loss_account,
|
||||
exc_gain_loss,
|
||||
ref2_dt,
|
||||
ref2_dn,
|
||||
ref2_detail_no,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if gain/loss is booked
|
||||
"""
|
||||
if res := frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={
|
||||
"docstatus": 1,
|
||||
"account": gain_loss_account,
|
||||
"reference_type": ref2_dt, # this will be Journal Entry
|
||||
"reference_name": ref2_dn,
|
||||
"reference_detail_no": ref2_detail_no,
|
||||
},
|
||||
pluck="parent",
|
||||
):
|
||||
# deduplicate
|
||||
res = list({x for x in res})
|
||||
if exc_vouchers := frappe.db.get_all(
|
||||
"Journal Entry",
|
||||
filters={"name": ["in", res], "voucher_type": "Exchange Gain Or Loss"},
|
||||
fields=["voucher_type", "total_debit", "total_credit"],
|
||||
):
|
||||
booked_voucher = exc_vouchers[0]
|
||||
if (
|
||||
booked_voucher.total_debit == exc_gain_loss
|
||||
and booked_voucher.total_credit == exc_gain_loss
|
||||
and booked_voucher.voucher_type == "Exchange Gain Or Loss"
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def make_exchange_gain_loss_journal(self, args: dict = None) -> None:
|
||||
"""
|
||||
Make Exchange Gain/Loss journal for Invoices and Payments
|
||||
@@ -997,27 +1035,37 @@ class AccountsController(TransactionBase):
|
||||
|
||||
reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
|
||||
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
arg.get("party_type"),
|
||||
arg.get("party"),
|
||||
party_account,
|
||||
if not self.gain_loss_journal_already_booked(
|
||||
gain_loss_account,
|
||||
difference_amount,
|
||||
dr_or_cr,
|
||||
reverse_dr_or_cr,
|
||||
arg.get("against_voucher_type"),
|
||||
arg.get("against_voucher"),
|
||||
arg.get("idx"),
|
||||
self.doctype,
|
||||
self.name,
|
||||
arg.get("idx"),
|
||||
)
|
||||
frappe.msgprint(
|
||||
_("Exchange Gain/Loss amount has been booked through {0}").format(
|
||||
get_link_to_form("Journal Entry", je)
|
||||
arg.get("referenced_row"),
|
||||
):
|
||||
posting_date = frappe.db.get_value(arg.voucher_type, arg.voucher_no, "posting_date")
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
posting_date,
|
||||
arg.get("party_type"),
|
||||
arg.get("party"),
|
||||
party_account,
|
||||
gain_loss_account,
|
||||
difference_amount,
|
||||
dr_or_cr,
|
||||
reverse_dr_or_cr,
|
||||
arg.get("against_voucher_type"),
|
||||
arg.get("against_voucher"),
|
||||
arg.get("idx"),
|
||||
self.doctype,
|
||||
self.name,
|
||||
arg.get("referenced_row"),
|
||||
arg.get("cost_center"),
|
||||
)
|
||||
frappe.msgprint(
|
||||
_("Exchange Gain/Loss amount has been booked through {0}").format(
|
||||
get_link_to_form("Journal Entry", je)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("doctype") == "Payment Entry":
|
||||
# For Payment Entry, exchange_gain_loss field in the `references` table is the trigger for journal creation
|
||||
@@ -1077,6 +1125,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
je = create_gain_loss_journal(
|
||||
self.company,
|
||||
self.posting_date,
|
||||
self.party_type,
|
||||
self.party,
|
||||
party_account,
|
||||
@@ -1090,6 +1139,7 @@ class AccountsController(TransactionBase):
|
||||
self.doctype,
|
||||
self.name,
|
||||
d.idx,
|
||||
self.cost_center,
|
||||
)
|
||||
frappe.msgprint(
|
||||
_("Exchange Gain/Loss amount has been booked through {0}").format(
|
||||
@@ -1354,7 +1404,7 @@ class AccountsController(TransactionBase):
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: self.discount_amount,
|
||||
dr_or_cr: self.base_discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
item=self,
|
||||
@@ -1626,6 +1676,7 @@ class AccountsController(TransactionBase):
|
||||
and party_account_currency != self.company_currency
|
||||
and self.currency != party_account_currency
|
||||
):
|
||||
|
||||
frappe.throw(
|
||||
_("Accounting Entry for {0}: {1} can only be made in currency: {2}").format(
|
||||
party_type, party, party_account_currency
|
||||
|
||||
@@ -55,6 +55,7 @@ class TestAccountsController(FrappeTestCase):
|
||||
10 series - Sales Invoice against Payment Entries
|
||||
20 series - Sales Invoice against Journals
|
||||
30 series - Sales Invoice against Credit Notes
|
||||
40 series - Company default Cost center is unset
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -941,6 +942,60 @@ class TestAccountsController(FrappeTestCase):
|
||||
self.assertEqual(exc_je_for_si, [])
|
||||
self.assertEqual(exc_je_for_je, [])
|
||||
|
||||
def test_24_journal_against_multiple_invoices(self):
|
||||
si1 = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
||||
si2 = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
|
||||
|
||||
# Payment
|
||||
je = self.create_journal_entry(
|
||||
acc1=self.debit_usd,
|
||||
acc1_exc_rate=75,
|
||||
acc2=self.cash,
|
||||
acc1_amount=-2,
|
||||
acc2_amount=-150,
|
||||
acc2_exc_rate=1,
|
||||
)
|
||||
je.accounts[0].party_type = "Customer"
|
||||
je.accounts[0].party = self.customer
|
||||
je = je.save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 2)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
si1.reload()
|
||||
si2.reload()
|
||||
|
||||
self.assertEqual(si1.outstanding_amount, 0)
|
||||
self.assertEqual(si2.outstanding_amount, 0)
|
||||
self.assert_ledger_outstanding(si1.doctype, si1.name, 0.0, 0.0)
|
||||
self.assert_ledger_outstanding(si2.doctype, si2.name, 0.0, 0.0)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created
|
||||
# remove payment JE from list
|
||||
exc_je_for_si1 = [x for x in self.get_journals_for(si1.doctype, si1.name) if x.parent != je.name]
|
||||
exc_je_for_si2 = [x for x in self.get_journals_for(si2.doctype, si2.name) if x.parent != je.name]
|
||||
exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
|
||||
self.assertEqual(len(exc_je_for_si1), 1)
|
||||
self.assertEqual(len(exc_je_for_si2), 1)
|
||||
self.assertEqual(len(exc_je_for_je), 2)
|
||||
|
||||
si1.cancel()
|
||||
# Gain/Loss JE of si1 should've been cancelled
|
||||
exc_je_for_si1 = [x for x in self.get_journals_for(si1.doctype, si1.name) if x.parent != je.name]
|
||||
exc_je_for_si2 = [x for x in self.get_journals_for(si2.doctype, si2.name) if x.parent != je.name]
|
||||
exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
|
||||
self.assertEqual(len(exc_je_for_si1), 0)
|
||||
self.assertEqual(len(exc_je_for_si2), 1)
|
||||
self.assertEqual(len(exc_je_for_je), 1)
|
||||
|
||||
def test_30_cr_note_against_sales_invoice(self):
|
||||
"""
|
||||
Reconciling Cr Note against Sales Invoice, both having different exchange rates
|
||||
@@ -997,3 +1052,139 @@ class TestAccountsController(FrappeTestCase):
|
||||
si.reload()
|
||||
self.assertEqual(si.outstanding_amount, 1)
|
||||
self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
|
||||
|
||||
def test_40_cost_center_from_payment_entry(self):
|
||||
"""
|
||||
Gain/Loss JE should inherit cost center from payment if company default is unset
|
||||
"""
|
||||
# remove default cost center
|
||||
cc = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
frappe.db.set_value("Company", self.company, "cost_center", None)
|
||||
|
||||
rate_in_account_currency = 1
|
||||
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
||||
si.cost_center = None
|
||||
si.save().submit()
|
||||
|
||||
pe = get_payment_entry(si.doctype, si.name)
|
||||
pe.source_exchange_rate = 75
|
||||
pe.received_amount = 75
|
||||
pe.cost_center = self.cost_center
|
||||
pe = pe.save().submit()
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created.
|
||||
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||
exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
|
||||
self.assertNotEqual(exc_je_for_si, [])
|
||||
self.assertEqual(len(exc_je_for_si), 1)
|
||||
self.assertEqual(len(exc_je_for_pe), 1)
|
||||
self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
|
||||
|
||||
self.assertEqual(
|
||||
[self.cost_center, self.cost_center],
|
||||
frappe.db.get_all(
|
||||
"Journal Entry Account", filters={"parent": exc_je_for_si[0].parent}, pluck="cost_center"
|
||||
),
|
||||
)
|
||||
frappe.db.set_value("Company", self.company, "cost_center", cc)
|
||||
|
||||
def test_41_cost_center_from_journal_entry(self):
|
||||
"""
|
||||
Gain/Loss JE should inherit cost center from payment if company default is unset
|
||||
"""
|
||||
# remove default cost center
|
||||
cc = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
frappe.db.set_value("Company", self.company, "cost_center", None)
|
||||
|
||||
rate_in_account_currency = 1
|
||||
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
||||
si.cost_center = None
|
||||
si.save().submit()
|
||||
|
||||
je = self.create_journal_entry(
|
||||
acc1=self.debit_usd,
|
||||
acc1_exc_rate=75,
|
||||
acc2=self.cash,
|
||||
acc1_amount=-1,
|
||||
acc2_amount=-75,
|
||||
acc2_exc_rate=1,
|
||||
)
|
||||
je.accounts[0].party_type = "Customer"
|
||||
je.accounts[0].party = self.customer
|
||||
je.accounts[0].cost_center = self.cost_center
|
||||
je = je.save().submit()
|
||||
|
||||
# Reconcile
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created.
|
||||
exc_je_for_si = [x for x in self.get_journals_for(si.doctype, si.name) if x.parent != je.name]
|
||||
exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
|
||||
self.assertNotEqual(exc_je_for_si, [])
|
||||
self.assertEqual(len(exc_je_for_si), 1)
|
||||
self.assertEqual(len(exc_je_for_je), 1)
|
||||
self.assertEqual(exc_je_for_si[0], exc_je_for_je[0])
|
||||
|
||||
self.assertEqual(
|
||||
[self.cost_center, self.cost_center],
|
||||
frappe.db.get_all(
|
||||
"Journal Entry Account", filters={"parent": exc_je_for_si[0].parent}, pluck="cost_center"
|
||||
),
|
||||
)
|
||||
frappe.db.set_value("Company", self.company, "cost_center", cc)
|
||||
|
||||
def test_42_cost_center_from_cr_note(self):
|
||||
"""
|
||||
Gain/Loss JE should inherit cost center from payment if company default is unset
|
||||
"""
|
||||
# remove default cost center
|
||||
cc = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
frappe.db.set_value("Company", self.company, "cost_center", None)
|
||||
|
||||
rate_in_account_currency = 1
|
||||
si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
|
||||
si.cost_center = None
|
||||
si.save().submit()
|
||||
|
||||
cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
|
||||
cr_note.cost_center = self.cost_center
|
||||
cr_note.is_return = 1
|
||||
cr_note.save().submit()
|
||||
|
||||
# Reconcile
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
# Exchange Gain/Loss Journal should've been created.
|
||||
exc_je_for_si = self.get_journals_for(si.doctype, si.name)
|
||||
exc_je_for_cr_note = self.get_journals_for(cr_note.doctype, cr_note.name)
|
||||
self.assertNotEqual(exc_je_for_si, [])
|
||||
self.assertEqual(len(exc_je_for_si), 2)
|
||||
self.assertEqual(len(exc_je_for_cr_note), 2)
|
||||
self.assertEqual(exc_je_for_si, exc_je_for_cr_note)
|
||||
|
||||
for x in exc_je_for_si + exc_je_for_cr_note:
|
||||
with self.subTest(x=x):
|
||||
self.assertEqual(
|
||||
[self.cost_center, self.cost_center],
|
||||
frappe.db.get_all("Journal Entry Account", filters={"parent": x.parent}, pluck="cost_center"),
|
||||
)
|
||||
|
||||
frappe.db.set_value("Company", self.company, "cost_center", cc)
|
||||
|
||||
@@ -263,12 +263,13 @@
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.repay_from_salary",
|
||||
"fetch_from": "against_loan.payment_account",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "payment_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Repayment Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_36",
|
||||
@@ -294,7 +295,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-21 10:10:07.742298",
|
||||
"modified": "2023-09-04 15:44:29.148766",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Repayment",
|
||||
|
||||
@@ -338,5 +338,6 @@ erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
|
||||
erpnext.patches.v14_0.update_closing_balances #15-07-2023
|
||||
execute:frappe.defaults.clear_default("fiscal_year")
|
||||
execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
|
||||
erpnext.patches.v14_0.correct_asset_value_if_je_with_workflow
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
|
||||
119
erpnext/patches/v14_0/correct_asset_value_if_je_with_workflow.py
Normal file
119
erpnext/patches/v14_0/correct_asset_value_if_je_with_workflow.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import frappe
|
||||
from frappe.model.workflow import get_workflow_name
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
|
||||
|
||||
def execute():
|
||||
active_je_workflow = get_workflow_name("Journal Entry")
|
||||
if not active_je_workflow:
|
||||
return
|
||||
|
||||
correct_value_for_assets_with_manual_depr_entries()
|
||||
|
||||
finance_books = frappe.db.get_all("Finance Book", pluck="name")
|
||||
|
||||
if finance_books:
|
||||
for fb_name in finance_books:
|
||||
correct_value_for_assets_with_auto_depr(fb_name)
|
||||
|
||||
correct_value_for_assets_with_auto_depr()
|
||||
|
||||
|
||||
def correct_value_for_assets_with_manual_depr_entries():
|
||||
asset = frappe.qb.DocType("Asset")
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
aca = frappe.qb.DocType("Asset Category Account")
|
||||
company = frappe.qb.DocType("Company")
|
||||
|
||||
asset_details_and_depr_amount_map = (
|
||||
frappe.qb.from_(gle)
|
||||
.join(asset)
|
||||
.on(gle.against_voucher == asset.name)
|
||||
.join(aca)
|
||||
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
|
||||
.join(company)
|
||||
.on(company.name == asset.company)
|
||||
.select(
|
||||
asset.name.as_("asset_name"),
|
||||
asset.gross_purchase_amount.as_("gross_purchase_amount"),
|
||||
asset.opening_accumulated_depreciation.as_("opening_accumulated_depreciation"),
|
||||
Sum(gle.debit).as_("depr_amount"),
|
||||
)
|
||||
.where(
|
||||
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||
)
|
||||
.where(gle.debit != 0)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.where(asset.docstatus == 1)
|
||||
.where(asset.calculate_depreciation == 0)
|
||||
.groupby(asset.name)
|
||||
)
|
||||
|
||||
frappe.qb.update(asset).join(asset_details_and_depr_amount_map).on(
|
||||
asset_details_and_depr_amount_map.asset_name == asset.name
|
||||
).set(
|
||||
asset.value_after_depreciation,
|
||||
asset_details_and_depr_amount_map.gross_purchase_amount
|
||||
- asset_details_and_depr_amount_map.opening_accumulated_depreciation
|
||||
- asset_details_and_depr_amount_map.depr_amount,
|
||||
).run()
|
||||
|
||||
|
||||
def correct_value_for_assets_with_auto_depr(fb_name=None):
|
||||
asset = frappe.qb.DocType("Asset")
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
aca = frappe.qb.DocType("Asset Category Account")
|
||||
company = frappe.qb.DocType("Company")
|
||||
afb = frappe.qb.DocType("Asset Finance Book")
|
||||
|
||||
asset_details_and_depr_amount_map = (
|
||||
frappe.qb.from_(gle)
|
||||
.join(asset)
|
||||
.on(gle.against_voucher == asset.name)
|
||||
.join(aca)
|
||||
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
|
||||
.join(company)
|
||||
.on(company.name == asset.company)
|
||||
.select(
|
||||
asset.name.as_("asset_name"),
|
||||
asset.gross_purchase_amount.as_("gross_purchase_amount"),
|
||||
asset.opening_accumulated_depreciation.as_("opening_accumulated_depreciation"),
|
||||
Sum(gle.debit).as_("depr_amount"),
|
||||
)
|
||||
.where(
|
||||
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||
)
|
||||
.where(gle.debit != 0)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.where(asset.docstatus == 1)
|
||||
.where(asset.calculate_depreciation == 1)
|
||||
.groupby(asset.name)
|
||||
)
|
||||
|
||||
if fb_name:
|
||||
asset_details_and_depr_amount_map = asset_details_and_depr_amount_map.where(
|
||||
gle.finance_book == fb_name
|
||||
)
|
||||
else:
|
||||
asset_details_and_depr_amount_map = asset_details_and_depr_amount_map.where(
|
||||
(gle.finance_book.isin([""])) | (gle.finance_book.isnull())
|
||||
)
|
||||
|
||||
query = (
|
||||
frappe.qb.update(afb)
|
||||
.join(asset_details_and_depr_amount_map)
|
||||
.on(asset_details_and_depr_amount_map.asset_name == afb.parent)
|
||||
.set(
|
||||
afb.value_after_depreciation,
|
||||
asset_details_and_depr_amount_map.gross_purchase_amount
|
||||
- asset_details_and_depr_amount_map.opening_accumulated_depreciation
|
||||
- asset_details_and_depr_amount_map.depr_amount,
|
||||
)
|
||||
)
|
||||
|
||||
if fb_name:
|
||||
query = query.where(afb.finance_book == fb_name)
|
||||
else:
|
||||
query = query.where((afb.finance_book.isin([""])) | (afb.finance_book.isnull()))
|
||||
|
||||
query.run()
|
||||
@@ -28,7 +28,7 @@ erpnext.get_purchase_trends_filters = function() {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options":'Fiscal Year',
|
||||
"default": frappe.sys_defaults.fiscal_year
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
|
||||
},
|
||||
{
|
||||
"fieldname":"period_based_on",
|
||||
|
||||
@@ -48,7 +48,7 @@ erpnext.get_sales_trends_filters = function() {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options":'Fiscal Year',
|
||||
"default": frappe.sys_defaults.fiscal_year
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
|
||||
},
|
||||
{
|
||||
"fieldname":"company",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
{
|
||||
"fieldname": "parent_quality_procedure",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Parent Procedure",
|
||||
"options": "Quality Procedure"
|
||||
},
|
||||
@@ -115,7 +116,7 @@
|
||||
"link_fieldname": "procedure"
|
||||
}
|
||||
],
|
||||
"modified": "2020-10-26 15:25:39.316088",
|
||||
"modified": "2023-08-29 12:49:53.963370",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Quality Management",
|
||||
"name": "Quality Procedure",
|
||||
@@ -149,5 +150,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -25,18 +25,15 @@
|
||||
"label": "Department",
|
||||
"oldfieldname": "department_name",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parent_department",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Parent Department",
|
||||
"options": "Department",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Department"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@@ -44,9 +41,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@@ -54,17 +49,13 @@
|
||||
"fieldname": "is_group",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Group",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Is Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disabled",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"fieldname": "lft",
|
||||
@@ -72,9 +63,7 @@
|
||||
"hidden": 1,
|
||||
"label": "lft",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "rgt",
|
||||
@@ -82,9 +71,7 @@
|
||||
"hidden": 1,
|
||||
"label": "rgt",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "old_parent",
|
||||
@@ -92,22 +79,18 @@
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Old Parent",
|
||||
"print_hide": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-10 12:28:00.563272",
|
||||
"modified": "2023-08-28 17:26:46.826501",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Department",
|
||||
@@ -147,12 +130,12 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"set_user_permissions": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
frappe.provide("erpnext.item");
|
||||
|
||||
const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
|
||||
const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
|
||||
|
||||
frappe.ui.form.on("Item", {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch('attribute', 'numeric_values', 'numeric_values');
|
||||
@@ -894,7 +897,13 @@ function open_form(frm, doctype, child_doctype, parentfield) {
|
||||
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;
|
||||
if (in_list(SALES_DOCTYPES, doctype) && frm.doc.sales_uom) {
|
||||
new_child_doc.uom = frm.doc.sales_uom;
|
||||
} else if (in_list(PURCHASE_DOCTYPES, doctype) && frm.doc.purchase_uom) {
|
||||
new_child_doc.uom = frm.doc.purchase_uom;
|
||||
} else {
|
||||
new_child_doc.uom = frm.doc.stock_uom;
|
||||
}
|
||||
new_child_doc.description = frm.doc.description;
|
||||
if (!new_child_doc.qty) {
|
||||
new_child_doc.qty = 1.0;
|
||||
|
||||
@@ -706,6 +706,7 @@ def raise_work_orders(material_request):
|
||||
)
|
||||
|
||||
wo_order.set_work_order_operations()
|
||||
wo_order.flags.ignore_mandatory = True
|
||||
wo_order.save()
|
||||
|
||||
work_orders.append(wo_order.name)
|
||||
|
||||
@@ -176,10 +176,9 @@ class SubcontractingReceipt(SubcontractingController):
|
||||
item.rm_cost_per_qty = item.rm_supp_cost / item.qty
|
||||
rm_supp_cost.pop(item.name)
|
||||
|
||||
if item.recalculate_rate:
|
||||
item.rate = (
|
||||
flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
|
||||
)
|
||||
item.rate = (
|
||||
flt(item.rm_cost_per_qty) + flt(item.service_cost_per_qty) + flt(item.additional_cost_per_qty)
|
||||
)
|
||||
|
||||
item.received_qty = item.qty + flt(item.rejected_qty)
|
||||
item.amount = item.qty * item.rate
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"rate_and_amount",
|
||||
"rate",
|
||||
"amount",
|
||||
"recalculate_rate",
|
||||
"column_break_19",
|
||||
"rm_cost_per_qty",
|
||||
"service_cost_per_qty",
|
||||
@@ -196,7 +195,6 @@
|
||||
"options": "currency",
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval: doc.recalculate_rate",
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
@@ -466,18 +464,12 @@
|
||||
"fieldname": "accounting_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Details"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "recalculate_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Recalculate Rate"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-06 18:44:45.599761",
|
||||
"modified": "2023-09-03 17:04:21.214316",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Subcontracting",
|
||||
"name": "Subcontracting Receipt Item",
|
||||
|
||||
Reference in New Issue
Block a user