|
|
|
|
@ -23,93 +23,209 @@ from src.overhaul_scope.service import get_prev_oh
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
setup_logging(logger=log)
|
|
|
|
|
|
|
|
|
|
async def get_all(db_session: DbSession):
|
|
|
|
|
from sqlalchemy import text
|
|
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
async def get_spareparts_paginated(db_session):
|
|
|
|
|
"""
|
|
|
|
|
Get all spare parts with their latest PR and PO information.
|
|
|
|
|
Get paginated spare parts with usage, inventory, and PR/PO information.
|
|
|
|
|
Uses two queries: one for data, one for total count.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
db_session: SQLAlchemy database session
|
|
|
|
|
assetnum: Optional asset number filter (not used in this query but kept for compatibility)
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
List of dictionaries containing spare part information
|
|
|
|
|
page (int): Page number (1-based)
|
|
|
|
|
items_per_page (int): Number of items per page
|
|
|
|
|
"""
|
|
|
|
|
# Define the SQL query
|
|
|
|
|
query = text("""
|
|
|
|
|
WITH latest_prs AS (
|
|
|
|
|
SELECT DISTINCT ON (pl.item_num)
|
|
|
|
|
pl.item_num,
|
|
|
|
|
h.num as pr_number,
|
|
|
|
|
h.issue_date as pr_issue_date,
|
|
|
|
|
h.status as pr_status,
|
|
|
|
|
pl.qty_ordered as pr_qty_ordered,
|
|
|
|
|
pl.description,
|
|
|
|
|
pl.unit_cost,
|
|
|
|
|
pl.line_cost
|
|
|
|
|
FROM public.maximo_sparepart_pr_po h
|
|
|
|
|
JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num
|
|
|
|
|
WHERE h.type = 'PR'
|
|
|
|
|
AND h.issue_date IS NOT NULL
|
|
|
|
|
AND h.num LIKE 'K%'
|
|
|
|
|
ORDER BY pl.item_num, h.issue_date DESC
|
|
|
|
|
)
|
|
|
|
|
SELECT DISTINCT ON (pr.item_num)
|
|
|
|
|
pr.item_num,
|
|
|
|
|
pr.line_cost,
|
|
|
|
|
pr.unit_cost,
|
|
|
|
|
pr.description,
|
|
|
|
|
COALESCE(i.curbaltotal, 0) as current_balance_total,
|
|
|
|
|
pr.pr_number,
|
|
|
|
|
pr.pr_issue_date,
|
|
|
|
|
pr.pr_qty_ordered,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN po.po_number IS NOT NULL THEN 'YES'
|
|
|
|
|
ELSE 'NO'
|
|
|
|
|
END as po_exists,
|
|
|
|
|
COALESCE(po.qty_received, 0) as po_qty_received,
|
|
|
|
|
COALESCE(po.qty_ordered, 0) as po_qty_ordered,
|
|
|
|
|
po.estimated_arrival_date as po_estimated_arrival_date
|
|
|
|
|
FROM latest_prs pr
|
|
|
|
|
LEFT JOIN public.maximo_inventory i ON pr.item_num = i.itemnum
|
|
|
|
|
LEFT JOIN LATERAL (
|
|
|
|
|
# calculate limit/offset
|
|
|
|
|
# limit = items_per_page
|
|
|
|
|
# offset = (page - 1) * items_per_page
|
|
|
|
|
|
|
|
|
|
# -----------------------------
|
|
|
|
|
# Query #1: Fetch paginated rows
|
|
|
|
|
# -----------------------------
|
|
|
|
|
data_query = text("""
|
|
|
|
|
WITH oh_workorders AS (
|
|
|
|
|
SELECT DISTINCT wonum, asset_location, asset_unit
|
|
|
|
|
FROM public.wo_staging_maximo_2
|
|
|
|
|
WHERE worktype = 'OH'
|
|
|
|
|
AND asset_location IS NOT NULL
|
|
|
|
|
AND EXTRACT(YEAR FROM reportdate) >= 2019
|
|
|
|
|
AND asset_unit IN ('3', '00')
|
|
|
|
|
),
|
|
|
|
|
sparepart_usage AS (
|
|
|
|
|
SELECT oh.asset_location, mwm.itemnum, mwm.itemqty, mwm.wonum
|
|
|
|
|
FROM oh_workorders oh
|
|
|
|
|
INNER JOIN public.maximo_workorder_materials mwm ON oh.wonum = mwm.wonum
|
|
|
|
|
),
|
|
|
|
|
location_sparepart_stats AS (
|
|
|
|
|
SELECT asset_location, itemnum,
|
|
|
|
|
COUNT(DISTINCT wonum) as total_wo_count,
|
|
|
|
|
SUM(itemqty) as total_qty_used,
|
|
|
|
|
AVG(itemqty) as avg_qty_per_wo,
|
|
|
|
|
MIN(itemqty) as min_qty_used,
|
|
|
|
|
MAX(itemqty) as max_qty_used
|
|
|
|
|
FROM sparepart_usage
|
|
|
|
|
GROUP BY asset_location, itemnum
|
|
|
|
|
HAVING SUM(itemqty) > 0
|
|
|
|
|
),
|
|
|
|
|
pr_lines AS (
|
|
|
|
|
SELECT
|
|
|
|
|
pl.item_num,
|
|
|
|
|
h.num as pr_number,
|
|
|
|
|
h.issue_date as pr_issue_date,
|
|
|
|
|
h.status as pr_status,
|
|
|
|
|
pl.qty_ordered as pr_qty_ordered,
|
|
|
|
|
pl.qty_requested as pr_qty_requested
|
|
|
|
|
FROM public.maximo_sparepart_pr_po h
|
|
|
|
|
JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num
|
|
|
|
|
WHERE h.type = 'PR' AND EXTRACT(YEAR FROM h.issue_date) >= 2023
|
|
|
|
|
),
|
|
|
|
|
item_descriptions AS (
|
|
|
|
|
SELECT DISTINCT
|
|
|
|
|
item_num,
|
|
|
|
|
FIRST_VALUE(description) OVER (
|
|
|
|
|
PARTITION BY item_num
|
|
|
|
|
ORDER BY created_at DESC NULLS LAST
|
|
|
|
|
) as description
|
|
|
|
|
FROM public.maximo_sparepart_pr_po_line
|
|
|
|
|
WHERE description IS NOT NULL
|
|
|
|
|
),
|
|
|
|
|
po_lines AS (
|
|
|
|
|
SELECT
|
|
|
|
|
pl.item_num,
|
|
|
|
|
h.num as po_number,
|
|
|
|
|
pl.qty_received,
|
|
|
|
|
pl.qty_ordered,
|
|
|
|
|
h.estimated_arrival_date
|
|
|
|
|
h.estimated_arrival_date as po_estimated_arrival_date,
|
|
|
|
|
h.vendeliverydate as po_vendeliverydate,
|
|
|
|
|
h.receipts as po_receipt,
|
|
|
|
|
h.status as po_status,
|
|
|
|
|
pl.qty_ordered as po_qty_ordered,
|
|
|
|
|
pl.qty_received as po_qty_received
|
|
|
|
|
FROM public.maximo_sparepart_pr_po h
|
|
|
|
|
JOIN public.maximo_sparepart_pr_po_line pl ON h.num = pl.num
|
|
|
|
|
WHERE h.type = 'PO'
|
|
|
|
|
AND h.num = pr.pr_number
|
|
|
|
|
AND pl.item_num = pr.item_num
|
|
|
|
|
LIMIT 1
|
|
|
|
|
) po ON true
|
|
|
|
|
ORDER BY pr.item_num;
|
|
|
|
|
AND (h.receipts = 'NONE')
|
|
|
|
|
AND (h.status IS NOT NULL)
|
|
|
|
|
),
|
|
|
|
|
pr_po_unified AS (
|
|
|
|
|
SELECT
|
|
|
|
|
pr.item_num,
|
|
|
|
|
pr.pr_number,
|
|
|
|
|
pr.pr_issue_date,
|
|
|
|
|
pr.pr_qty_ordered,
|
|
|
|
|
pr.pr_status,
|
|
|
|
|
po.po_number,
|
|
|
|
|
COALESCE(po.po_qty_ordered,0) as po_qty_ordered,
|
|
|
|
|
COALESCE(po.po_qty_received,0) as po_qty_received,
|
|
|
|
|
po.po_estimated_arrival_date,
|
|
|
|
|
po.po_vendeliverydate,
|
|
|
|
|
po.po_receipt,
|
|
|
|
|
po.po_status,
|
|
|
|
|
CASE WHEN po.po_number IS NOT NULL THEN 'YES' ELSE 'NO' END as po_exists
|
|
|
|
|
FROM pr_lines pr
|
|
|
|
|
LEFT JOIN po_lines po
|
|
|
|
|
ON pr.item_num = po.item_num
|
|
|
|
|
AND pr.pr_number = po.po_number
|
|
|
|
|
),
|
|
|
|
|
pr_po_agg AS (
|
|
|
|
|
SELECT
|
|
|
|
|
item_num,
|
|
|
|
|
SUM(COALESCE(pr_qty_ordered,0)) as total_pr_qty,
|
|
|
|
|
SUM(COALESCE(po_qty_ordered,0)) as total_po_qty,
|
|
|
|
|
SUM(COALESCE(po_qty_received,0)) as total_po_received,
|
|
|
|
|
JSON_AGG(
|
|
|
|
|
JSON_BUILD_OBJECT(
|
|
|
|
|
'pr_number', pr_number,
|
|
|
|
|
'pr_issue_date', pr_issue_date,
|
|
|
|
|
'pr_qty_requested', pr_qty_ordered,
|
|
|
|
|
'pr_status', pr_status,
|
|
|
|
|
'po_exists', po_exists,
|
|
|
|
|
'po_qty_ordered', po_qty_ordered,
|
|
|
|
|
'po_qty_received', po_qty_received,
|
|
|
|
|
'po_estimated_arrival_date', po_estimated_arrival_date,
|
|
|
|
|
'po_vendeliverydate', po_vendeliverydate,
|
|
|
|
|
'po_receipt', po_receipt,
|
|
|
|
|
'po_status', po_status
|
|
|
|
|
) ORDER BY pr_issue_date DESC
|
|
|
|
|
) as pr_po_details
|
|
|
|
|
FROM pr_po_unified
|
|
|
|
|
GROUP BY item_num
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
lss.itemnum,
|
|
|
|
|
COALESCE(id.description, 'No description available') as item_description,
|
|
|
|
|
lss.total_wo_count,
|
|
|
|
|
lss.total_qty_used,
|
|
|
|
|
ROUND(CAST(lss.avg_qty_per_wo AS NUMERIC), 2) as avg_qty_per_wo,
|
|
|
|
|
lss.min_qty_used,
|
|
|
|
|
lss.max_qty_used,
|
|
|
|
|
COALESCE(i.curbaltotal,0) as current_balance_total,
|
|
|
|
|
COALESCE(ap.total_pr_qty,0) as total_pr_qty,
|
|
|
|
|
COALESCE(ap.total_po_qty,0) as total_po_qty,
|
|
|
|
|
COALESCE(ap.total_po_received,0) as total_po_received,
|
|
|
|
|
ap.pr_po_details
|
|
|
|
|
FROM location_sparepart_stats lss
|
|
|
|
|
LEFT JOIN item_descriptions id ON lss.itemnum = id.item_num
|
|
|
|
|
LEFT JOIN public.maximo_inventory i ON lss.itemnum = i.itemnum
|
|
|
|
|
LEFT JOIN pr_po_agg ap ON lss.itemnum = ap.item_num
|
|
|
|
|
ORDER BY lss.asset_location, lss.itemnum
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# Execute the query
|
|
|
|
|
result = await db_session.execute(query)
|
|
|
|
|
|
|
|
|
|
# Fetch all results and convert to list of dictionaries
|
|
|
|
|
|
|
|
|
|
rows = await db_session.execute(data_query)
|
|
|
|
|
|
|
|
|
|
spare_parts = []
|
|
|
|
|
for row in result:
|
|
|
|
|
for row in rows:
|
|
|
|
|
spare_parts.append({
|
|
|
|
|
"item_num": row.item_num,
|
|
|
|
|
"description": row.description,
|
|
|
|
|
"line_cost": row.line_cost,
|
|
|
|
|
"unit_cost": row.unit_cost,
|
|
|
|
|
"current_balance_total": float(row.current_balance_total) if row.current_balance_total is not None else 0.0,
|
|
|
|
|
"pr_number": row.pr_number,
|
|
|
|
|
"pr_issue_date": row.pr_issue_date,
|
|
|
|
|
"pr_qty_ordered": float(row.pr_qty_ordered) if row.pr_qty_ordered is not None else 0.0,
|
|
|
|
|
"po_exists": row.po_exists,
|
|
|
|
|
"po_qty_received": float(row.po_qty_received) if row.po_qty_received is not None else 0.0,
|
|
|
|
|
"po_qty_ordered": float(row.po_qty_ordered) if row.po_qty_ordered is not None else 0.0,
|
|
|
|
|
"po_estimated_arrival_date": row.po_estimated_arrival_date
|
|
|
|
|
"item_num": row.itemnum,
|
|
|
|
|
"description": row.item_description,
|
|
|
|
|
"current_balance_total": float(row.current_balance_total) if row.current_balance_total else 0.0,
|
|
|
|
|
"total_required_for_oh": float(row.avg_qty_per_wo),
|
|
|
|
|
"total_pr_qty": row.total_pr_qty,
|
|
|
|
|
"total_po_qty": row.total_po_qty,
|
|
|
|
|
"total_po_received": row.total_po_received,
|
|
|
|
|
"pr_po_details": row.pr_po_details
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return spare_parts
|
|
|
|
|
|
|
|
|
|
# # -----------------------------
|
|
|
|
|
# # Query #2: Count total rows
|
|
|
|
|
# # -----------------------------
|
|
|
|
|
# count_query = text("""
|
|
|
|
|
# WITH oh_workorders AS (
|
|
|
|
|
# SELECT DISTINCT wonum, asset_location, asset_unit
|
|
|
|
|
# FROM public.wo_staging_maximo_2
|
|
|
|
|
# WHERE worktype = 'OH'
|
|
|
|
|
# AND asset_location IS NOT NULL
|
|
|
|
|
# AND EXTRACT(YEAR FROM reportdate) >= 2019
|
|
|
|
|
# AND asset_unit IN ('3', '00')
|
|
|
|
|
# ),
|
|
|
|
|
# sparepart_usage AS (
|
|
|
|
|
# SELECT oh.asset_location, mwm.itemnum, mwm.itemqty, mwm.wonum
|
|
|
|
|
# FROM oh_workorders oh
|
|
|
|
|
# INNER JOIN public.maximo_workorder_materials mwm ON oh.wonum = mwm.wonum
|
|
|
|
|
# ),
|
|
|
|
|
# location_sparepart_stats AS (
|
|
|
|
|
# SELECT asset_location, itemnum
|
|
|
|
|
# FROM sparepart_usage
|
|
|
|
|
# GROUP BY asset_location, itemnum
|
|
|
|
|
# )
|
|
|
|
|
# SELECT COUNT(*) as total_count
|
|
|
|
|
# FROM location_sparepart_stats;
|
|
|
|
|
# """)
|
|
|
|
|
|
|
|
|
|
# total_count_result = await db_session.execute(count_query)
|
|
|
|
|
# total_count = total_count_result.scalar() or 0
|
|
|
|
|
|
|
|
|
|
# # calculate total pages
|
|
|
|
|
# total_pages = math.ceil(total_count / items_per_page) if items_per_page > 0 else 1
|
|
|
|
|
|
|
|
|
|
# return {
|
|
|
|
|
# "total": total_count,
|
|
|
|
|
# "page": page,
|
|
|
|
|
# "items_per_page": items_per_page,
|
|
|
|
|
# "total_pages": total_pages,
|
|
|
|
|
# "items": spare_parts
|
|
|
|
|
# }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProcurementStatus(Enum):
|
|
|
|
|
|