|
3 | 3 |
|
4 | 4 | import frappe |
5 | 5 | from frappe import _ |
| 6 | +from frappe.query_builder.functions import Sum |
6 | 7 | from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_diff_in_hours |
7 | 8 |
|
8 | 9 | import erpnext |
@@ -308,9 +309,14 @@ def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account): |
308 | 309 | if flt(self.repair_cost) <= 0: |
309 | 310 | return |
310 | 311 |
|
311 | | - pi_expense_account = ( |
312 | | - frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account |
313 | | - ) |
| 312 | + expense_accounts = _get_expense_accounts_for_purchase_invoice(self.purchase_invoice) |
| 313 | + |
| 314 | + if not expense_accounts: |
| 315 | + frappe.throw( |
| 316 | + _("No expense accounts found for Purchase Invoice {0}").format(self.purchase_invoice) |
| 317 | + ) |
| 318 | + |
| 319 | + pi_expense_account = expense_accounts[0] |
314 | 320 |
|
315 | 321 | gl_entries.append( |
316 | 322 | self.get_gl_dict( |
@@ -473,3 +479,84 @@ def calculate_last_schedule_date_before_modification(self, asset, row, extra_mon |
473 | 479 | def get_downtime(failure_date, completion_date): |
474 | 480 | downtime = time_diff_in_hours(completion_date, failure_date) |
475 | 481 | return round(downtime, 2) |
| 482 | + |
| 483 | + |
| 484 | +@frappe.whitelist() |
| 485 | +def get_repair_cost_for_purchase_invoice(purchase_invoice: str) -> float: |
| 486 | + """ |
| 487 | + Get the total repair cost from GL entries for a purchase invoice. |
| 488 | + Only considers expense accounts for non-stock, non-fixed-asset items. |
| 489 | + """ |
| 490 | + if not purchase_invoice: |
| 491 | + return 0.0 |
| 492 | + |
| 493 | + frappe.has_permission("Purchase Invoice", "read", purchase_invoice, throw=True) |
| 494 | + |
| 495 | + expense_accounts = _get_expense_accounts_for_purchase_invoice(purchase_invoice) |
| 496 | + |
| 497 | + if not expense_accounts: |
| 498 | + return 0.0 |
| 499 | + |
| 500 | + return _get_total_expense_amount(purchase_invoice, expense_accounts) |
| 501 | + |
| 502 | + |
| 503 | +def _get_expense_accounts_for_purchase_invoice(purchase_invoice: str) -> list[str]: |
| 504 | + """ |
| 505 | + Get expense accounts for non-stock items from the purchase invoice. |
| 506 | + """ |
| 507 | + pi_items = frappe.get_all( |
| 508 | + "Purchase Invoice Item", |
| 509 | + filters={"parent": purchase_invoice}, |
| 510 | + fields=["item_code", "expense_account", "is_fixed_asset"], |
| 511 | + ) |
| 512 | + |
| 513 | + if not pi_items: |
| 514 | + return [] |
| 515 | + |
| 516 | + # Get list of stock item codes from the invoice |
| 517 | + item_codes = {item.item_code for item in pi_items if item.item_code} |
| 518 | + stock_items = set() |
| 519 | + if item_codes: |
| 520 | + stock_items = set( |
| 521 | + frappe.db.get_all( |
| 522 | + "Item", filters={"name": ["in", list(item_codes)], "is_stock_item": 1}, pluck="name" |
| 523 | + ) |
| 524 | + ) |
| 525 | + |
| 526 | + expense_accounts = set() |
| 527 | + |
| 528 | + for item in pi_items: |
| 529 | + # Skip stock items - they use warehouse accounts |
| 530 | + if item.item_code and item.item_code in stock_items: |
| 531 | + continue |
| 532 | + |
| 533 | + # Skip fixed assets - they use asset accounts |
| 534 | + if item.is_fixed_asset: |
| 535 | + continue |
| 536 | + |
| 537 | + # Use expense account from Purchase Invoice Item |
| 538 | + if item.expense_account: |
| 539 | + expense_accounts.add(item.expense_account) |
| 540 | + |
| 541 | + return list(expense_accounts) |
| 542 | + |
| 543 | + |
| 544 | +def _get_total_expense_amount(purchase_invoice: str, expense_accounts: list[str]) -> float: |
| 545 | + """Get the total expense amount from GL entries for a purchase invoice and accounts.""" |
| 546 | + if not expense_accounts: |
| 547 | + return 0.0 |
| 548 | + |
| 549 | + gl_entry = frappe.qb.DocType("GL Entry") |
| 550 | + |
| 551 | + result = ( |
| 552 | + frappe.qb.from_(gl_entry) |
| 553 | + .select((Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("total")) |
| 554 | + .where( |
| 555 | + (gl_entry.voucher_type == "Purchase Invoice") |
| 556 | + & (gl_entry.voucher_no == purchase_invoice) |
| 557 | + & (gl_entry.account.isin(expense_accounts)) |
| 558 | + & (gl_entry.is_cancelled == 0) |
| 559 | + ) |
| 560 | + ).run(as_dict=True) |
| 561 | + |
| 562 | + return flt(result[0].total) if result else 0.0 |
0 commit comments