Compare commits

...

58 Commits

Author SHA1 Message Date
Frappe PR Bot
4c1cada222 chore(release): Bumped to Version 15.81.1
## [15.81.1](https://github.com/frappe/erpnext/compare/v15.81.0...v15.81.1) (2025-10-01)

### Bug Fixes

* Add try-finally for setting buying price list ([e10ed89](e10ed89be5))
* too many writes on patch run ([c455e73](c455e7390e))
2025-10-01 14:55:26 +00:00
Mihir Kandoi
8bb19eece4 Merge pull request #49835 from frappe/mergify/bp/version-15/pr-49833
fix: too many writes on patch run (backport #49832) (backport #49833)
2025-10-01 20:23:45 +05:30
Mihir Kandoi
e10ed89be5 fix: Add try-finally for setting buying price list
(cherry picked from commit 35a8d02866)
(cherry picked from commit e21baec246)
2025-10-01 13:44:41 +00:00
Mihir Kandoi
c455e7390e fix: too many writes on patch run
(cherry picked from commit 44ff6ed6a1)
(cherry picked from commit a2bf53ff0a)
2025-10-01 13:44:41 +00:00
Frappe PR Bot
24013ddf27 chore(release): Bumped to Version 15.81.0
# [15.81.0](https://github.com/frappe/erpnext/compare/v15.80.1...v15.81.0) (2025-09-30)

### Bug Fixes

* add date filter for getting return invoice items ([bc77704](bc77704462))
* auto batch not set for raw materials in subcontracting receipt ([6c8e838](6c8e8384d5))
* **bank transaction:** change reference number to small text ([5a0bf7a](5a0bf7a5e1))
* convert with flt ([3975627](3975627b33))
* do not validate cost center in cancelled gl entry ([366bb82](366bb8269c))
* don't recalculate stock_qty with conversion_factor ([c88a277](c88a277ad9))
* include precision in validation ([abffbca](abffbcad99))
* **payment entry:** trigger currency on account set ([84a2596](84a2596ce5))
* remove item name to avoid overriding item row name ([d0e511f](d0e511fd2f))
* set cost center in taxes if not set ([4dcd502](4dcd502b76))
* **stock entry:** set expense account from company for manufacture ([50f9521](50f9521feb))
* update item details only in draft state ([30f6b53](30f6b53240))
* update subcontracted_quantity with set_value ([17e415d](17e415d03a))
* use get_value instead of get_doc ([f706811](f706811757))
* use sales_order from data instead of doc ([7ee8281](7ee82813fd))
* use stock adjustment account  if no expense account ([9c81f44](9c81f448c8))
* valuation rate for old batch ([37a03f1](37a03f10ab))

### Features

* add show zero value filter in profit and loss and balance sheet ([c32ad97](c32ad9711f))
* service expense account in the subcontracting receipt ([ba9b63a](ba9b63af49))

### Performance Improvements

* reposting for backdated transactions ([cd5a836](cd5a836685))
2025-09-30 13:28:59 +00:00
ruthra kumar
e8ee5f8c9b Merge pull request #49801 from frappe/version-15-hotfix
chore: release v15
2025-09-30 18:56:45 +05:30
rohitwaghchaure
4847e24553 Merge branch 'version-15' into version-15-hotfix 2025-09-30 18:38:20 +05:30
rohitwaghchaure
bbbcb456ce Merge pull request #49810 from frappe/mergify/bp/version-15-hotfix/pr-49803
fix: valuation rate for old batch (backport #49803)
2025-09-30 17:50:13 +05:30
rohitwaghchaure
4c78f9b9cb Merge pull request #49807 from frappe/mergify/bp/version-15-hotfix/pr-49794
fix: don't recalculate stock_qty with conversion_factor (backport #49794)
2025-09-30 17:12:18 +05:30
Rohit Waghchaure
37a03f10ab fix: valuation rate for old batch
(cherry picked from commit d864d166f9)
2025-09-30 11:42:15 +00:00
rohitwaghchaure
c2dd37a89e Merge pull request #49808 from frappe/mergify/bp/version-15-hotfix/pr-49766
fix: use sales_order from data instead of doc (backport #49766)
2025-09-30 17:12:07 +05:30
rohitwaghchaure
f458faa819 Merge pull request #49809 from frappe/mergify/bp/version-15-hotfix/pr-49748
fix: update item details only in draft state (backport #49748)
2025-09-30 17:11:56 +05:30
Kavin
30f6b53240 fix: update item details only in draft state
(cherry picked from commit 689172ff22)
2025-09-30 11:08:59 +00:00
Kavin
7ee82813fd fix: use sales_order from data instead of doc
(cherry picked from commit 9f9120451b)
2025-09-30 11:06:48 +00:00
Kavin
c88a277ad9 fix: don't recalculate stock_qty with conversion_factor
(cherry picked from commit fed8236919)
2025-09-30 11:03:47 +00:00
rohitwaghchaure
9e73d083b5 Merge pull request #49793 from frappe/mergify/bp/version-15-hotfix/pr-49790
fix: update subcontracted_quantity with set_value (backport #49790)
2025-09-30 16:32:13 +05:30
rohitwaghchaure
03c8d92796 Merge pull request #49792 from frappe/mergify/bp/version-15-hotfix/pr-49791
refactor: convert item warehouse based reposting (backport #49791)
2025-09-30 16:31:57 +05:30
ruthra kumar
31ad9a6346 Merge pull request #49805 from frappe/mergify/bp/version-15-hotfix/pr-49639
fix(Credit-limit): consider current voucher for credit limit validation (backport #49639)
2025-09-30 16:30:17 +05:30
PRASATHRAJA
fc40a3c376 Merge pull request #49639 from aerele/credit-limit-jv
fix(Credit-limit): consider current voucher for credit limit validation

(cherry picked from commit 4a01c53cca)
2025-09-30 10:39:35 +00:00
ruthra kumar
b8e3db0179 Merge pull request #49800 from frappe/mergify/bp/version-15-hotfix/pr-49799
fix: do not validate cost center in cancelled gl entry (backport #49799)
2025-09-30 15:14:11 +05:30
ljain112
366bb8269c fix: do not validate cost center in cancelled gl entry
(cherry picked from commit 29cbddbc77)
2025-09-30 09:04:24 +00:00
venkat102
3975627b33 fix: convert with flt
(cherry picked from commit 81614939ab)
2025-09-29 16:20:27 +00:00
venkat102
f706811757 fix: use get_value instead of get_doc
(cherry picked from commit 89a603f20c)
2025-09-29 16:20:26 +00:00
venkat102
17e415d03a fix: update subcontracted_quantity with set_value
(cherry picked from commit ea63bfc9af)
2025-09-29 16:20:26 +00:00
Rohit Waghchaure
8911bad792 refactor: convert item-wh based reposting
(cherry picked from commit 8411e4c5b2)
2025-09-29 16:20:25 +00:00
ruthra kumar
a30c481ca1 Merge pull request #49787 from frappe/mergify/bp/version-15-hotfix/pr-49773
fix(bank transaction): change reference number to small text (backport #49773)
2025-09-29 17:15:06 +05:30
ravibharathi656
5a0bf7a5e1 fix(bank transaction): change reference number to small text
(cherry picked from commit 90f399d0fc)

# Conflicts:
#	erpnext/accounts/doctype/bank_transaction/bank_transaction.json
2025-09-29 16:59:31 +05:30
ruthra kumar
f135954944 Merge pull request #49784 from frappe/mergify/bp/version-15-hotfix/pr-49708
feat: add show zero value filter in profit and loss and balance sheet (backport #49708)
2025-09-29 16:47:54 +05:30
ravibharathi656
c32ad9711f feat: add show zero value filter in profit and loss and balance sheet
(cherry picked from commit 33ab24943c)

# Conflicts:
#	erpnext/accounts/report/balance_sheet/balance_sheet.js
#	erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
2025-09-29 16:32:21 +05:30
ruthra kumar
f6842041c9 Merge pull request #49782 from frappe/mergify/bp/version-15-hotfix/pr-49718
fix: set cost center in taxes if not set (backport #49718)
2025-09-29 16:26:35 +05:30
ravibharathi656
4dcd502b76 fix: set cost center in taxes if not set
(cherry picked from commit b75940bf0e)

# Conflicts:
#	erpnext/public/js/controllers/transaction.js
2025-09-29 15:53:19 +05:30
ruthra kumar
19f81c9204 Merge pull request #49777 from frappe/mergify/bp/version-15-hotfix/pr-49735
fix(payment entry): trigger currency on account set (backport #49735)
2025-09-29 14:56:14 +05:30
ruthra kumar
67b6b3779d Merge pull request #49775 from frappe/mergify/bp/version-15-hotfix/pr-49618
fix: add date filter for getting return invoice items (backport #49618)
2025-09-29 14:31:45 +05:30
ruthra kumar
c836016358 Merge pull request #49774 from frappe/mergify/bp/version-15-hotfix/pr-49640
fix: include precision in validation (backport #49640)
2025-09-29 14:29:45 +05:30
ravibharathi656
84a2596ce5 fix(payment entry): trigger currency on account set
(cherry picked from commit 096e74b1ee)
2025-09-29 08:58:03 +00:00
Navin-S-R
98bc7b41fc test: validate profit values for later period returns
(cherry picked from commit d5c457b8c5)

# Conflicts:
#	erpnext/accounts/report/gross_profit/test_gross_profit.py
2025-09-29 14:16:52 +05:30
l0gesh29
abffbcad99 fix: include precision in validation
(cherry picked from commit 1de0c46c51)
2025-09-29 08:44:29 +00:00
Navin-S-R
bc77704462 fix: add date filter for getting return invoice items
(cherry picked from commit 2abb011816)
2025-09-29 08:44:28 +00:00
rohitwaghchaure
5abb2d510d Merge pull request #49772 from frappe/mergify/bp/version-15-hotfix/pr-49770
chore: removed print statement (backport #49770)
2025-09-29 13:06:44 +05:30
Rohit Waghchaure
ae040024af chore: removed print statement
(cherry picked from commit 324bdcb177)
2025-09-29 06:32:32 +00:00
rohitwaghchaure
fe1247dd4b Merge pull request #49758 from frappe/mergify/bp/version-15-hotfix/pr-49757
feat: service expense account in the subcontracting receipt (backport #49757)
2025-09-27 10:20:23 +05:30
rohitwaghchaure
84789daf59 Merge pull request #49756 from frappe/mergify/bp/version-15-hotfix/pr-49741
fix(stock entry): set expense account from company for manufacture (backport #49741)
2025-09-27 10:04:06 +05:30
rohitwaghchaure
cecd177286 chore: fix conflicts
Removed fields related to job card and landed cost voucher amount from subcontracting receipt item.
2025-09-27 10:03:27 +05:30
rohitwaghchaure
c26ed80d67 chore: fix conflicts 2025-09-27 10:02:40 +05:30
rohitwaghchaure
15f0c28cda chore: fix conflicts 2025-09-27 10:01:53 +05:30
Rohit Waghchaure
ba9b63af49 feat: service expense account in the subcontracting receipt
(cherry picked from commit 6e597b9c42)

# Conflicts:
#	erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
#	erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
#	erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
2025-09-26 15:44:26 +00:00
ravibharathi656
9c81f448c8 fix: use stock adjustment account if no expense account
(cherry picked from commit b2e109318f)
2025-09-26 13:28:43 +00:00
ravibharathi656
50f9521feb fix(stock entry): set expense account from company for manufacture
(cherry picked from commit 06177ffaff)
2025-09-26 13:28:43 +00:00
ruthra kumar
0ff32ac554 Merge pull request #49738 from frappe/mergify/bp/version-15-hotfix/pr-49717
refactor: improve trial balance performance (backport #49717)
2025-09-26 11:58:26 +05:30
rohitwaghchaure
fe72ea6d6b Merge pull request #49739 from frappe/mergify/bp/version-15-hotfix/pr-49734
fix: auto batch not set for raw materials in subcontracting receipt (backport #49734)
2025-09-26 11:43:04 +05:30
rohitwaghchaure
62959e5a70 chore: fix conflicts 2025-09-26 11:21:16 +05:30
Rohit Waghchaure
6c8e8384d5 fix: auto batch not set for raw materials in subcontracting receipt
(cherry picked from commit 23f9d4c600)

# Conflicts:
#	erpnext/controllers/subcontracting_controller.py
2025-09-26 05:07:12 +00:00
ruthra kumar
0c8004530a refactor: improve trial balance performance
(cherry picked from commit cee3813ced)
2025-09-26 04:46:00 +00:00
Raffael Meyer
b18afa8bdd chore(Bank Guarante): move to leaner doctype definition (#49737) 2025-09-25 22:05:59 +00:00
rohitwaghchaure
6b3bb45900 Merge pull request #49728 from frappe/mergify/bp/version-15-hotfix/pr-49725
fix: remove item name in get_item_details (backport #49725)
2025-09-25 19:52:28 +05:30
Kavin
d0e511fd2f fix: remove item name to avoid overriding item row name
(cherry picked from commit 47055901c0)
2025-09-25 13:23:29 +00:00
rohitwaghchaure
d50538dbf7 Merge pull request #49724 from frappe/mergify/bp/version-15-hotfix/pr-49720
perf: reposting for backdated transactions (backport #49720)
2025-09-25 18:52:32 +05:30
Rohit Waghchaure
cd5a836685 perf: reposting for backdated transactions
(cherry picked from commit 1b0fc0541b)
2025-09-25 12:16:41 +00:00
35 changed files with 788 additions and 1156 deletions

View File

@@ -4,7 +4,7 @@ import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.80.1"
__version__ = "15.81.1"
def get_default_company(user=None):

File diff suppressed because it is too large Load Diff

View File

@@ -116,7 +116,7 @@
{
"allow_on_submit": 1,
"fieldname": "reference_number",
"fieldtype": "Data",
"fieldtype": "Small Text",
"label": "Reference Number"
},
{
@@ -238,7 +238,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-06-18 17:24:57.044666",
"modified": "2025-09-26 17:06:29.207673",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -36,7 +36,7 @@ class BankTransaction(Document):
party: DF.DynamicLink | None
party_type: DF.Link | None
payment_entries: DF.Table[BankTransactionPayments]
reference_number: DF.Data | None
reference_number: DF.SmallText | None
status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled"]
transaction_id: DF.Data | None
transaction_type: DF.Data | None

View File

@@ -253,7 +253,7 @@ class GLEntry(Document):
)
def validate_cost_center(self):
if not self.cost_center:
if not self.cost_center or self.is_cancelled:
return
is_group, company = frappe.get_cached_value("Cost Center", self.cost_center, ["is_group", "company"])

View File

@@ -189,8 +189,8 @@ class JournalEntry(AccountsController):
def on_submit(self):
self.validate_cheque_info()
self.check_credit_limit()
self.make_gl_entries()
self.check_credit_limit()
self.update_asset_value()
self.update_inter_company_jv()
self.update_invoice_discounting()

View File

@@ -11,6 +11,7 @@ from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
from erpnext.exceptions import InvalidAccountCurrency
from erpnext.selling.doctype.customer.test_customer import make_customer, set_credit_limit
class TestJournalEntry(unittest.TestCase):
@@ -592,6 +593,15 @@ class TestJournalEntry(unittest.TestCase):
self.assertEqual(jv.pay_to_recd_from, "_Test Receiver 2")
def test_credit_limit_for_customer(self):
customer = make_customer("_Test New Customer")
set_credit_limit("_Test New Customer", "_Test Company", 50)
jv = make_journal_entry(account1="Debtors - _TC", account2="_Test Cash - _TC", amount=100, save=False)
jv.accounts[0].party_type = "Customer"
jv.accounts[0].party = customer
jv.save()
self.assertRaises(frappe.ValidationError, jv.submit)
def make_journal_entry(
account1,

View File

@@ -601,6 +601,7 @@ frappe.ui.form.on("Payment Entry", {
if (frm.doc.payment_type == "Pay") {
frm.events.paid_amount(frm);
}
frm.events.paid_from_account_currency(frm);
}
);
},
@@ -624,6 +625,7 @@ frappe.ui.form.on("Payment Entry", {
frm.events.received_amount(frm);
}
}
frm.events.paid_to_account_currency(frm);
}
);
},

View File

@@ -127,7 +127,13 @@ class PaymentRequest(Document):
existing_payment_request_amount = flt(get_existing_payment_request_amount(ref_doc))
if existing_payment_request_amount + flt(self.grand_total) > ref_amount:
if (
flt(
existing_payment_request_amount + flt(self.grand_total, self.precision("grand_total")),
get_currency_precision(),
)
> ref_amount
):
frappe.throw(
_("Total Payment Request amount cannot be greater than {0} amount").format(
self.reference_doctype

View File

@@ -5,28 +5,33 @@ frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statement
erpnext.utils.add_dimensions("Balance Sheet", 10);
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
],
default: "Report",
reqd: 1,
});
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Balance Sheet"]["filters"].push({
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Balance Sheet"]["filters"].push(
{
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
],
default: "Report",
reqd: 1,
},
{
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "show_zero_values",
label: __("Show zero values"),
fieldtype: "Check",
}
);

View File

@@ -212,7 +212,7 @@ def get_data(
company_currency,
accumulated_values=filters.accumulated_values,
)
out = filter_out_zero_value_rows(out, parent_children_map)
out = filter_out_zero_value_rows(out, parent_children_map, filters.show_zero_values)
if out and total:
add_total_row(out, root_type, balance_must_be, period_list, company_currency)

View File

@@ -677,7 +677,9 @@ class GrossProfitGenerator:
si.name = si_item.parent
and si.docstatus = 1
and si.is_return = 1
and si.posting_date between %(from_date)s and %(to_date)s
""",
{"from_date": self.filters.from_date, "to_date": self.filters.to_date},
as_dict=1,
)

View File

@@ -1,9 +1,9 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, nowdate
from frappe.utils import add_days, flt, get_first_day, get_last_day, nowdate
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_delivery_note, make_sales_return
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.gross_profit.gross_profit import execute
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
@@ -392,7 +392,6 @@ class TestGrossProfit(FrappeTestCase):
"""
Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
"""
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
# Invoice with an item added twice
sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
@@ -635,3 +634,42 @@ class TestGrossProfit(FrappeTestCase):
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)
def test_profit_for_later_period_return(self):
month_start_date, month_end_date = get_first_day(nowdate()), get_last_day(nowdate())
# create sales invoice on month start date
sinv = self.create_sales_invoice(qty=1, rate=100, do_not_save=True, do_not_submit=True)
sinv.set_posting_time = 1
sinv.posting_date = month_start_date
sinv.save().submit()
# create credit note on next month start date
cr_note = make_sales_return(sinv.name)
cr_note.set_posting_time = 1
cr_note.posting_date = add_days(month_end_date, 1)
cr_note.save().submit()
# apply filters for invoiced period
filters = frappe._dict(
company=self.company, from_date=month_start_date, to_date=month_end_date, group_by="Invoice"
)
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 100.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 100.0)
self.assertEqual(total.get("gross_profit_%"), 100.0)
# extend filters upto returned period
filters.update(to_date=add_days(month_end_date, 1))
_, data = execute(filters=filters)
total = data[-1]
self.assertEqual(total.selling_amount, 0.0)
self.assertEqual(total.buying_amount, 0.0)
self.assertEqual(total.gross_profit, 0.0)
self.assertEqual(total.get("gross_profit_%"), 0.0)

View File

@@ -5,29 +5,34 @@ frappe.query_reports["Profit and Loss Statement"] = $.extend({}, erpnext.financi
erpnext.utils.add_dimensions("Profit and Loss Statement", 10);
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
{ value: "Margin", label: __("Margin View") },
],
default: "Report",
reqd: 1,
});
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Profit and Loss Statement"]["filters"].push({
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
});
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
{
fieldname: "selected_view",
label: __("Select View"),
fieldtype: "Select",
options: [
{ value: "Report", label: __("Report View") },
{ value: "Growth", label: __("Growth View") },
{ value: "Margin", label: __("Margin View") },
],
default: "Report",
reqd: 1,
},
{
fieldname: "accumulated_values",
label: __("Accumulated Values"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "include_default_book_entries",
label: __("Include Default FB Entries"),
fieldtype: "Check",
default: 1,
},
{
fieldname: "show_zero_values",
label: __("Show zero values"),
fieldtype: "Check",
}
);

View File

@@ -216,7 +216,7 @@ def get_opening_balance(
ignore_is_opening=0,
):
closing_balance = frappe.qb.DocType(doctype)
account = frappe.qb.DocType("Account")
accounts = frappe.db.get_all("Account", filters={"report_type": report_type}, pluck="name")
opening_balance = (
frappe.qb.from_(closing_balance)
@@ -228,14 +228,7 @@ def get_opening_balance(
Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
)
.where(
(closing_balance.company == filters.company)
& (
closing_balance.account.isin(
frappe.qb.from_(account).select("name").where(account.report_type == report_type)
)
)
)
.where((closing_balance.company == filters.company) & (closing_balance.account.isin(accounts)))
.groupby(closing_balance.account)
)
@@ -290,21 +283,24 @@ def get_opening_balance(
if filters.project:
opening_balance = opening_balance.where(closing_balance.project == filters.project)
if filters.get("include_default_book_entries"):
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if frappe.db.count("Finance Book"):
if filters.get("include_default_book_entries"):
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(_("To use a different finance book, please uncheck 'Include Default FB Entries'"))
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
frappe.throw(
_("To use a different finance book, please uncheck 'Include Default FB Entries'")
)
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
| (closing_balance.finance_book.isnull())
)
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
| (closing_balance.finance_book.isnull())
)
else:
opening_balance = opening_balance.where(
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
| (closing_balance.finance_book.isnull())
)
if accounting_dimensions:
for dimension in accounting_dimensions:

View File

@@ -200,7 +200,11 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items):
current_stock_qty = args.get(column)
elif args.get("return_qty_from_rejected_warehouse"):
reference_qty = ref.get("rejected_qty") * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)
current_stock_qty = (
args.get(column) * args.get("conversion_factor", 1.0)
if column != "stock_qty"
else args.get(column)
)
else:
reference_qty = ref.get(column) * ref.get("conversion_factor", 1.0)
current_stock_qty = args.get(column) * args.get("conversion_factor", 1.0)

View File

@@ -12,6 +12,8 @@ from frappe.utils import cint, flt, get_link_to_form
from erpnext.controllers.stock_controller import StockController
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
get_auto_batch_nos,
get_available_serial_nos,
get_voucher_wise_serial_batch_from_bundle,
)
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -52,9 +54,42 @@ class SubcontractingController(StockController):
if self.doctype in ["Subcontracting Order", "Subcontracting Receipt"]:
self.validate_items()
self.create_raw_materials_supplied()
self.set_valuation_rate_for_rm()
else:
super().validate()
def set_valuation_rate_for_rm(self):
rate_changed = False
if self.doctype == "Subcontracting Receipt":
for row in self.supplied_items:
kwargs = frappe._dict(
{
"item_code": row.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": flt(row.consumed_qty) * (-1 if not self.is_return else 1),
"voucher_type": self.doctype,
"voucher_no": self.name,
"company": self.company,
"serial_and_batch_bundle": row.serial_and_batch_bundle,
"voucher_detail_no": row.name,
"batch_no": row.batch_no,
"serial_no": row.serial_no,
"use_serial_batch_fields": row.use_serial_batch_fields,
}
)
rate = get_incoming_rate(kwargs)
precision = frappe.get_precision("Subcontracting Receipt Supplied Item", "rate")
if flt(rate, precision) != flt(row.rate, precision):
row.rate = rate
row.amount = flt(row.consumed_qty) * flt(rate)
rate_changed = True
if rate_changed:
self.calculate_items_qty_and_amount()
def validate_rejected_warehouse(self):
for item in self.get("items"):
if flt(item.rejected_qty) and not item.rejected_warehouse:
@@ -610,6 +645,64 @@ class SubcontractingController(StockController):
self.set_rate_for_supplied_items(rm_obj, item_row)
elif self.backflush_based_on == "BOM":
self.update_rate_for_supplied_items()
self.set_batch_for_supplied_items()
def set_batch_for_supplied_items(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
from erpnext.stock.get_item_details import get_filtered_serial_nos
for row in self.supplied_items:
item_details = frappe.get_cached_value(
"Item", row.rm_item_code, ["has_batch_no", "has_serial_no"], as_dict=1
)
if not item_details.has_batch_no and not item_details.has_serial_no:
continue
if not row.use_serial_batch_fields:
continue
kwargs = frappe._dict(
{
"item_code": row.rm_item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"qty": flt(row.consumed_qty),
}
)
if item_details.has_serial_no and not row.serial_and_batch_bundle and not row.serial_no:
serial_nos = get_available_serial_nos(kwargs)
if serial_nos:
serial_nos = [sn.get("serial_no") for sn in serial_nos]
serial_nos = get_filtered_serial_nos(serial_nos, self, "supplied_items")
row.serial_no = "\n".join(serial_nos)
elif item_details.has_batch_no and not row.serial_and_batch_bundle and not row.batch_no:
batches = get_auto_batch_nos(kwargs)
if batches:
consumed_qty = row.consumed_qty
for index, d in enumerate(batches):
if consumed_qty <= 0:
break
if index == 0:
row.batch_no = d.get("batch_no")
row.consumed_qty = d.get("qty")
consumed_qty -= d.get("qty")
else:
new_row = self.append("supplied_items", {})
new_row.update(frappe.copy_doc(row).as_dict())
new_row.update(
{
"consumed_qty": d.get("qty"),
"batch_no": d.get("batch_no"),
"rate": row.rate,
"amount": flt(d.get("qty")) * flt(row.rate),
}
)
consumed_qty -= d.get("qty")
def update_rate_for_supplied_items(self):
if self.doctype != "Subcontracting Receipt":

View File

@@ -1308,6 +1308,7 @@ def make_subcontracted_items():
"Subcontracted Item SA7": {},
"Subcontracted Item SA8": {},
"Subcontracted Item SA9": {"stock_uom": "Litre"},
"Subcontracted Item SA10": {},
}
for item, properties in sub_contracted_items.items():
@@ -1329,6 +1330,7 @@ def make_raw_materials():
"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"},
"Subcontracted SRM Item 8": {},
"Subcontracted SRM Item 9": {"stock_uom": "Litre"},
"Subcontracted SRM Item 10": {},
}
for item, properties in raw_materials.items():
@@ -1357,6 +1359,7 @@ def make_service_items():
"Subcontracted Service Item 7": {},
"Subcontracted Service Item 8": {},
"Subcontracted Service Item 9": {},
"Subcontracted Service Item 10": {},
}
for item, properties in service_items.items():
@@ -1381,6 +1384,7 @@ def make_bom_for_subcontracted_items():
"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
"Subcontracted Item SA10": ["Subcontracted SRM Item 10"],
}
for item_code, raw_materials in boms.items():

View File

@@ -1631,7 +1631,7 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
}
)
sales_order = doc.get("sales_order")
sales_order = data.get("sales_order")
for item_code, details in item_details.items():
so_item_details.setdefault(sales_order, frappe._dict())

View File

@@ -9,5 +9,10 @@ def execute():
docs = frappe.get_all(
"Material Request", filters={"buying_price_list": ["is", "not set"], "docstatus": 1}, pluck="name"
)
for doc in docs:
frappe.db.set_value("Material Request", doc, "buying_price_list", default_buying_price_list)
old_limit = frappe.db.MAX_WRITES_PER_TRANSACTION
frappe.db.MAX_WRITES_PER_TRANSACTION *= 4
try:
for doc in docs:
frappe.db.set_value("Material Request", doc, "buying_price_list", default_buying_price_list)
finally:
frappe.db.MAX_WRITES_PER_TRANSACTION = old_limit

View File

@@ -2137,16 +2137,22 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
this.frm.doc.name).options,
"master_name": this.frm.doc.taxes_and_charges
},
callback: function(r) {
if(!r.exc) {
if(me.frm.doc.shipping_rule && me.frm.doc.taxes) {
for (let tax of r.message) {
callback: function (r) {
if (!r.exc) {
let taxes = r.message;
taxes.forEach((tax) => {
if (me.frm.doc?.cost_center && !tax.cost_center) {
tax.cost_center = me.frm.doc.cost_center;
}
});
if (me.frm.doc.shipping_rule && me.frm.doc.taxes) {
for (let tax of taxes) {
me.frm.add_child("taxes", tax);
}
refresh_field("taxes");
} else {
me.frm.set_value("taxes", r.message);
me.frm.set_value("taxes", taxes);
me.calculate_taxes_and_totals();
}
}

View File

@@ -443,3 +443,14 @@ def create_internal_customer(customer_name=None, represents_company=None, allowe
customer_name = frappe.db.get_value("Customer", customer_name)
return customer_name
def make_customer(customer_name):
if not frappe.db.exists("Customer", customer_name):
customer = frappe.new_doc("Customer")
customer.customer_name = customer_name
customer.customer_type = "Individual"
customer.insert()
return customer.name
else:
return customer_name

View File

@@ -74,8 +74,11 @@ class PickList(TransactionBase):
if self.has_reserved_stock():
self.set_onload("has_reserved_stock", True)
for item in self.get("locations"):
item.update(get_item_details(item.item_code, item.uom, item.warehouse, self.company))
if self.docstatus.is_draft() and not hasattr(self, "_action"):
company = self.company
for item in self.get("locations"):
item.update(get_item_details(item.item_code, item.uom, item.warehouse, company))
def validate(self):
self.validate_expired_batches()

View File

@@ -311,7 +311,7 @@ class SerialandBatchBundle(Document):
def throw_error_message(self, message, exception=frappe.ValidationError):
frappe.throw(_(message), exception, title=_("Error"))
def set_incoming_rate(self, parent=None, row=None, save=False, allow_negative_stock=False):
def set_incoming_rate(self, parent=None, row=None, save=False, allow_negative_stock=False, prev_sle=None):
if self.type_of_transaction not in ["Inward", "Outward"] or self.voucher_type in [
"Installation Note",
"Job Card",
@@ -321,15 +321,15 @@ class SerialandBatchBundle(Document):
return
if return_against := self.get_return_against(parent=parent):
self.set_valuation_rate_for_return_entry(return_against, row, save)
self.set_valuation_rate_for_return_entry(return_against, row, save, prev_sle=prev_sle)
elif self.type_of_transaction == "Outward":
self.set_incoming_rate_for_outward_transaction(
row, save, allow_negative_stock=allow_negative_stock
)
else:
self.set_incoming_rate_for_inward_transaction(row, save)
self.set_incoming_rate_for_inward_transaction(row, save, prev_sle=prev_sle)
def set_valuation_rate_for_return_entry(self, return_against, row, save=False):
def set_valuation_rate_for_return_entry(self, return_against, row, save=False, prev_sle=None):
if valuation_details := self.get_valuation_rate_for_return_entry(return_against):
for row in self.entries:
if valuation_details:
@@ -361,7 +361,7 @@ class SerialandBatchBundle(Document):
)
elif self.type_of_transaction == "Inward":
self.set_incoming_rate_for_inward_transaction(row, save)
self.set_incoming_rate_for_inward_transaction(row, save, prev_sle=prev_sle)
def validate_returned_serial_batch_no(self, return_against, row, original_inv_details):
if frappe.flags.through_repost_item_valuation:
@@ -529,7 +529,11 @@ class SerialandBatchBundle(Document):
if save:
d.db_set(
{"incoming_rate": d.incoming_rate, "stock_value_difference": d.stock_value_difference}
{
"incoming_rate": d.incoming_rate,
"stock_value_difference": d.stock_value_difference,
"stock_queue": d.get("stock_queue"),
}
)
def validate_negative_batch(self, batch_no, available_qty):
@@ -606,7 +610,11 @@ class SerialandBatchBundle(Document):
return return_against
def set_incoming_rate_for_inward_transaction(self, row=None, save=False):
def set_incoming_rate_for_inward_transaction(self, row=None, save=False, prev_sle=None):
from erpnext.stock.utils import get_valuation_method
valuation_method = get_valuation_method(self.item_code)
valuation_field = "valuation_rate"
if self.voucher_type in ["Sales Invoice", "Delivery Note", "Quotation"]:
valuation_field = "incoming_rate"
@@ -630,19 +638,42 @@ class SerialandBatchBundle(Document):
if not rate and self.voucher_detail_no and self.voucher_no:
rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
stock_queue = []
batches = []
if prev_sle and prev_sle.stock_queue:
batches = frappe.get_all(
"Batch",
filters={
"name": ("in", [d.batch_no for d in self.entries if d.batch_no]),
"use_batchwise_valuation": 0,
},
pluck="name",
)
if batches and valuation_method == "FIFO":
stock_queue = parse_json(prev_sle.stock_queue)
for d in self.entries:
if self.is_rejected:
rate = 0.0
elif (d.incoming_rate == rate) and d.qty and d.stock_value_difference:
elif (d.incoming_rate == rate) and not stock_queue and d.qty and d.stock_value_difference:
continue
d.incoming_rate = flt(rate)
if d.qty:
d.stock_value_difference = flt(d.qty) * d.incoming_rate
if stock_queue and valuation_method == "FIFO" and d.batch_no in batches:
stock_queue.append([d.qty, d.incoming_rate])
d.stock_queue = json.dumps(stock_queue)
if save:
d.db_set(
{"incoming_rate": d.incoming_rate, "stock_value_difference": d.stock_value_difference}
{
"incoming_rate": d.incoming_rate,
"stock_value_difference": d.stock_value_difference,
"stock_queue": d.get("stock_queue"),
}
)
def set_serial_and_batch_values(self, parent, row, qty_field=None):

View File

@@ -285,6 +285,25 @@ class TestSerialandBatchBundle(FrappeTestCase):
self.assertEqual(flt(sle.stock_value_difference), 1000.00 * -1)
self.assertEqual(json.loads(sle.stock_queue), [[20, 200]])
se = make_stock_entry(
item_code=batch_item_code,
target="_Test Warehouse - _TC",
qty=10,
rate=100,
batch_no=batch_id,
use_serial_batch_fields=True,
)
sle = frappe.db.get_value(
"Serial and Batch Entry",
{"parent": se.items[0].serial_and_batch_bundle, "docstatus": 1},
["stock_value_difference", "stock_queue"],
as_dict=True,
)
self.assertEqual(flt(sle.stock_value_difference), 1000.00)
self.assertEqual(json.loads(sle.stock_queue), [[20, 200], [10, 100]])
se = make_stock_entry(
item_code=batch_item_code,
target="_Test Warehouse - _TC",
@@ -301,7 +320,7 @@ class TestSerialandBatchBundle(FrappeTestCase):
)
self.assertEqual(flt(sle.stock_value_difference), 1000.00)
self.assertEqual(json.loads(sle.stock_queue), [[20, 200]])
self.assertEqual(json.loads(sle.stock_queue), [[20, 200], [10, 100]])
se = make_stock_entry(
item_code=batch_item_code,
@@ -319,6 +338,24 @@ class TestSerialandBatchBundle(FrappeTestCase):
self.assertEqual(flt(sle.stock_value_difference), 5000.00 * -1)
self.assertFalse(json.loads(sle.stock_queue or "[]"))
self.assertEqual(flt(sle.stock_value), 1000.0)
se = make_stock_entry(
item_code=batch_item_code,
source="_Test Warehouse - _TC",
qty=10,
use_serial_batch_fields=False,
)
sle = frappe.db.get_value(
"Stock Ledger Entry",
{"item_code": batch_item_code, "is_cancelled": 0, "voucher_no": se.name},
["stock_value_difference", "stock_queue", "stock_value"],
as_dict=True,
)
self.assertEqual(flt(sle.stock_value_difference), 1000.00 * -1)
self.assertFalse(json.loads(sle.stock_queue or "[]"))
self.assertEqual(flt(sle.stock_value), 0.0)
def test_old_serial_no_valuation(self):

View File

@@ -2041,6 +2041,9 @@ class StockEntry(StockController):
# in case of BOM
to_warehouse = item.get("default_warehouse")
expense_account = item.get("expense_account")
if not expense_account:
expense_account = frappe.get_cached_value("Company", self.company, "stock_adjustment_account")
args = {
"to_warehouse": to_warehouse,
"from_warehouse": "",
@@ -2048,7 +2051,7 @@ class StockEntry(StockController):
"item_name": item.item_name,
"description": item.description,
"stock_uom": item.stock_uom,
"expense_account": item.get("expense_account"),
"expense_account": expense_account,
"cost_center": item.get("buying_cost_center"),
"is_finished_item": 1,
}

View File

@@ -84,7 +84,7 @@ def get_reposting_entries():
def get_stock_ledgers(vouchers):
return frappe.get_all(
"Stock Ledger Entry",
fields=["item_code", "warehouse", "posting_date"],
fields=["item_code", "warehouse", "posting_date", "posting_time", "posting_datetime"],
filters={"voucher_no": ("in", vouchers)},
)

View File

@@ -260,10 +260,13 @@ def filter_batches(batches, doc):
del batches[row.get("batch_no")]
def get_filtered_serial_nos(serial_nos, doc):
def get_filtered_serial_nos(serial_nos, doc, table=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for row in doc.get("items"):
if not table:
table = "items"
for row in doc.get(table):
if row.get("serial_no"):
for serial_no in get_serial_nos(row.get("serial_no")):
if serial_no in serial_nos:

View File

@@ -4,10 +4,11 @@
import frappe
from frappe import _
from frappe.utils import get_link_to_form, parse_json
from frappe.utils import get_datetime, get_link_to_form, parse_json
import erpnext
from erpnext.accounts.utils import get_currency_precision, get_stock_accounts
from erpnext.stock.doctype.stock_reposting_settings.stock_reposting_settings import get_stock_ledgers
from erpnext.stock.doctype.warehouse.warehouse import get_warehouses_based_on_account
@@ -141,18 +142,29 @@ def create_reposting_entries(rows, company):
rows = parse_json(rows)
entries = []
for row in rows:
row = frappe._dict(row)
item_wh = frappe._dict()
vouchers = [row.get("voucher_no") for row in rows]
sles = get_stock_ledgers(vouchers)
for sle in sles:
key = (sle.item_code, sle.warehouse)
if key not in item_wh:
item_wh[key] = sle
elif get_datetime(item_wh.get(key).posting_datetime) > get_datetime(sle.posting_datetime):
item_wh[key] = sle
for key, sle in item_wh.items():
item_code, warehouse = key
try:
doc = frappe.get_doc(
{
"doctype": "Repost Item Valuation",
"based_on": "Transaction",
"based_on": "Item and Warehouse",
"status": "Queued",
"voucher_type": row.voucher_type,
"voucher_no": row.voucher_no,
"posting_date": row.posting_date,
"item_code": item_code,
"warehouse": warehouse,
"posting_date": sle.posting_date,
"posting_time": sle.posting_time,
"company": company,
"allow_nagative_stock": 1,
}

View File

@@ -732,7 +732,6 @@ class update_entries_after:
elif dependant_sle.voucher_type == "Stock Entry" and is_transfer_stock_entry(
dependant_sle.voucher_no
):
print(dependant_sle.voucher_no)
self.distinct_item_warehouses[key] = val
self.new_items_found = True
@@ -1020,7 +1019,9 @@ class update_entries_after:
)
else:
doc = frappe.get_doc("Serial and Batch Bundle", sle.serial_and_batch_bundle)
doc.set_incoming_rate(save=True, allow_negative_stock=self.allow_negative_stock)
doc.set_incoming_rate(
save=True, allow_negative_stock=self.allow_negative_stock, prev_sle=self.wh_data
)
doc.calculate_qty_and_amount(save=True)
if stock_queue := frappe.get_all(

View File

@@ -252,14 +252,18 @@ class SubcontractingOrder(SubcontractingController):
if si.fg_item:
item = frappe.get_doc("Item", si.fg_item)
po_item = frappe.get_doc("Purchase Order Item", si.purchase_order_item)
available_qty = po_item.qty - po_item.subcontracted_quantity
qty, subcontracted_quantity, fg_item_qty = frappe.db.get_value(
"Purchase Order Item",
si.purchase_order_item,
["qty", "subcontracted_quantity", "fg_item_qty"],
)
available_qty = flt(qty) - flt(subcontracted_quantity)
if available_qty == 0:
continue
si.qty = available_qty
conversion_factor = po_item.qty / po_item.fg_item_qty
conversion_factor = flt(qty) / flt(fg_item_qty)
si.fg_item_qty = flt(
available_qty / conversion_factor, frappe.get_precision("Purchase Order Item", "qty")
)
@@ -338,13 +342,24 @@ class SubcontractingOrder(SubcontractingController):
def update_subcontracted_quantity_in_po(self, cancel=False):
for service_item in self.service_items:
doc = frappe.get_doc("Purchase Order Item", service_item.purchase_order_item)
doc.subcontracted_quantity = (
(doc.subcontracted_quantity + service_item.qty)
if not cancel
else (doc.subcontracted_quantity - service_item.qty)
subcontracted_quantity = flt(
frappe.db.get_value(
"Purchase Order Item", service_item.purchase_order_item, "subcontracted_quantity"
)
)
subcontracted_quantity = (
(subcontracted_quantity + service_item.qty)
if not cancel
else (subcontracted_quantity - service_item.qty)
)
frappe.db.set_value(
"Purchase Order Item",
service_item.purchase_order_item,
"subcontracted_quantity",
subcontracted_quantity,
)
doc.save()
@frappe.whitelist()

View File

@@ -116,7 +116,13 @@ class SubcontractingReceipt(SubcontractingController):
self.validate_items_qty()
self.set_items_bom()
self.set_items_cost_center()
self.set_items_expense_account()
if self.company:
default_expense_account = self.get_company_default(
"default_expense_account", ignore_validation=True
)
self.set_service_expense_account(default_expense_account)
self.set_expense_account_for_subcontracted_items(default_expense_account)
def validate(self):
self.reset_supplied_items()
@@ -196,6 +202,39 @@ class SubcontractingReceipt(SubcontractingController):
if item.subcontracting_order:
check_on_hold_or_closed_status("Subcontracting Order", item.subcontracting_order)
def set_service_expense_account(self, default_expense_account):
for row in self.get("items"):
if not row.service_expense_account and row.purchase_order_item:
service_item = frappe.db.get_value(
"Purchase Order Item", row.purchase_order_item, "item_code"
)
if service_item:
if default := (
get_item_defaults(service_item, self.company)
or get_item_group_defaults(service_item, self.company)
or get_brand_defaults(service_item, self.company)
):
if service_expense_account := default.get("expense_account"):
row.service_expense_account = service_expense_account
if not row.service_expense_account:
row.service_expense_account = default_expense_account
def set_expense_account_for_subcontracted_items(self, default_expense_account):
for row in self.get("items"):
if not row.expense_account:
if default := (
get_item_defaults(row.item_code, self.company)
or get_item_group_defaults(row.item_code, self.company)
or get_brand_defaults(row.item_code, self.company)
):
if expense_account := default.get("expense_account"):
row.expense_account = expense_account
if not row.expense_account:
row.expense_account = default_expense_account
def validate_items_qty(self):
for item in self.items:
if not (item.qty or item.rejected_qty):
@@ -242,14 +281,6 @@ class SubcontractingReceipt(SubcontractingController):
self.company,
)
def set_items_expense_account(self):
if self.company:
expense_account = self.get_company_default("default_expense_account", ignore_validation=True)
for item in self.items:
if not item.expense_account:
item.expense_account = expense_account
def set_supplied_items_expense_account(self):
for item in self.supplied_items:
if not item.expense_account:
@@ -599,13 +630,17 @@ class SubcontractingReceipt(SubcontractingController):
project=item.project,
item=item,
)
service_cost = flt(
item.service_cost_per_qty, item.precision("service_cost_per_qty")
) * flt(item.qty, item.precision("qty"))
# Expense Account (Credit)
self.add_gl_entry(
gl_entries=gl_entries,
account=item.expense_account,
cost_center=item.cost_center,
debit=0.0,
credit=stock_value_diff,
credit=flt(stock_value_diff) - service_cost,
remarks=remarks,
against_account=accepted_warehouse_account,
account_currency=get_account_currency(item.expense_account),
@@ -613,6 +648,21 @@ class SubcontractingReceipt(SubcontractingController):
item=item,
)
service_account = item.service_expense_account or item.expense_account
# Expense Account (Credit)
self.add_gl_entry(
gl_entries=gl_entries,
account=service_account,
cost_center=item.cost_center,
debit=0.0,
credit=service_cost,
remarks=remarks,
against_account=accepted_warehouse_account,
account_currency=get_account_currency(service_account),
project=item.project,
item=item,
)
if flt(item.rm_supp_cost) and supplier_warehouse_account:
for rm_item in supplied_items_details.get(item.name):
# Supplier Warehouse Account (Credit)

View File

@@ -422,6 +422,79 @@ class TestSubcontractingReceipt(FrappeTestCase):
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
def test_subcontracting_receipt_for_service_expense_account(self):
service_expense_account = (
frappe.get_doc(
{
"doctype": "Account",
"account_name": "_Test Service Expense",
"account_type": "Expense Account",
"company": "_Test Company with perpetual inventory",
"is_group": 0,
"parent_account": "Indirect Expenses - TCP1",
}
)
.insert(ignore_if_duplicate=True)
.name
)
service_item_doc = frappe.get_doc("Item", "Subcontracted Service Item 10")
service_item_doc.append(
"item_defaults",
{
"company": "_Test Company with perpetual inventory",
"expense_account": service_expense_account,
"default_warehouse": "Stores - TCP1",
},
)
service_item_doc.save()
service_items = [
{
"warehouse": "Stores - TCP1",
"item_code": "Subcontracted Service Item 10",
"qty": 10,
"rate": 100,
"fg_item": "Subcontracted Item SA10",
"fg_item_qty": 10,
},
]
sco = get_subcontracting_order(
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
supplier_warehouse="Work In Progress - TCP1",
service_items=service_items,
)
rm_items = get_rm_items(sco.supplied_items)
itemwise_details = make_stock_in_entry(rm_items=rm_items)
make_stock_transfer_entry(
sco_no=sco.name,
rm_items=rm_items,
itemwise_details=copy.deepcopy(itemwise_details),
)
scr = make_subcontracting_receipt(sco.name)
scr.submit()
for item in scr.items:
self.assertEqual(item.service_expense_account, service_expense_account)
gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
self.assertTrue(gl_entries)
fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
expense_account = scr.items[0].expense_account
expected_values = {
fg_warehouse_ac: [2000, 1000],
expense_account: [1000, 1000],
service_expense_account: [0, 1000],
}
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
@change_settings("Stock Settings", {"use_serial_batch_fields": 0})
def test_subcontracting_receipt_with_zero_service_cost(self):
warehouse = "Stores - TCP1"
@@ -740,13 +813,13 @@ class TestSubcontractingReceipt(FrappeTestCase):
for row in scr.supplied_items:
self.assertEqual(row.rate, 300.00)
self.assertTrue(row.serial_and_batch_bundle)
auto_created_serial_batch = frappe.db.get_value(
serial_and_batch_bundle = frappe.db.get_value(
"Stock Ledger Entry",
{"voucher_no": scr.name, "voucher_detail_no": row.name},
"auto_created_serial_and_batch_bundle",
"serial_and_batch_bundle",
)
self.assertTrue(auto_created_serial_batch)
self.assertTrue(serial_and_batch_bundle)
self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
self.assertEqual(scr.items[0].service_cost_per_qty, 100)

View File

@@ -64,6 +64,8 @@
"manufacturer_part_no",
"accounting_details_section",
"expense_account",
"column_break_exht",
"service_expense_account",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
@@ -580,12 +582,22 @@
"fieldname": "add_serial_batch_for_rejected_qty",
"fieldtype": "Button",
"label": "Add Serial / Batch No (Rejected Qty)"
},
{
"fieldname": "column_break_exht",
"fieldtype": "Column Break"
},
{
"fieldname": "service_expense_account",
"fieldtype": "Link",
"label": "Service Expense Account",
"options": "Account"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2024-12-06 15:23:58.680169",
"modified": "2025-09-26 12:00:38.877638",
"modified_by": "Administrator",
"module": "Subcontracting",
"name": "Subcontracting Receipt Item",

View File

@@ -55,6 +55,7 @@ class SubcontractingReceiptItem(Document):
serial_and_batch_bundle: DF.Link | None
serial_no: DF.SmallText | None
service_cost_per_qty: DF.Currency
service_expense_account: DF.Link | None
stock_uom: DF.Link
subcontracting_order: DF.Link | None
subcontracting_order_item: DF.Data | None