Files
Aislo/B08_wf5_Quantity/B08_wf5_Quantity_Repository_Quantity.py
T
2026-07-25 19:09:57 +09:00

187 lines
8.9 KiB
Python

import hashlib
import json
from uuid import uuid4
from B08_wf5_Quantity.B08_wf5_Quantity_Schema_Quantity import (
DesignQuantity,
WorkBreakdown,
)
class QuantityRepository:
def __init__(self, connection):
self.db = connection
async def workspace(self, project_id: str) -> dict:
async with self.db.cursor() as cursor:
await cursor.execute(
"SELECT * FROM b08_work_breakdown WHERE project_id=%s ORDER BY level_no,sort_order",
(project_id,),
)
work_breakdown = list(await cursor.fetchall())
await cursor.execute(self._quantity_query(), (project_id,))
quantities = list(await cursor.fetchall())
await cursor.execute(
"""SELECT quantity_version, input_hash, confirmed_by, confirmed_at
FROM b08_quantity_confirmations WHERE project_id=%s
ORDER BY quantity_version DESC LIMIT 1""",
(project_id,),
)
confirmation = await cursor.fetchone()
return {
"work_breakdown": work_breakdown,
"quantities": quantities,
"confirmation": confirmation,
}
@staticmethod
def _quantity_query() -> str:
return """
SELECT q.*,
CASE q.reference_type
WHEN 'CATALOG' THEN IF(ci.cost_type='LABOR',ap.applied_price,0)
ELSE COALESCE(uc.labor_price,eq.labor_price,cb.labor_price,0)
END AS unit_labor,
CASE q.reference_type
WHEN 'CATALOG' THEN IF(ci.cost_type='MATERIAL',ap.applied_price,0)
ELSE COALESCE(uc.material_price,eq.material_price,cb.material_price,0)
END AS unit_material,
CASE q.reference_type
WHEN 'CATALOG' THEN IF(ci.cost_type='EXPENSE',ap.applied_price,0)
ELSE COALESCE(uc.expense_price,eq.expense_price,cb.expense_price,0)
END AS unit_expense,
pb.price_version
FROM b08_design_quantity_items q
LEFT JOIN b08_catalog_items ci
ON q.reference_type='CATALOG' AND ci.id=q.reference_id
LEFT JOIN b08_price_books pb
ON pb.project_id=q.project_id AND pb.status='CONFIRMED'
AND pb.effective_date=(
SELECT MAX(x.effective_date) FROM b08_price_books x
WHERE x.project_id=q.project_id AND x.status='CONFIRMED'
)
LEFT JOIN b08_applied_prices ap
ON ap.project_id=q.project_id AND ap.item_id=ci.id
AND ap.price_version=pb.price_version
LEFT JOIN b08_unit_costs uc
ON q.reference_type='UNIT_COST' AND uc.id=q.reference_id
AND uc.status='CONFIRMED'
LEFT JOIN b08_equipment_rates eq
ON q.reference_type='EQUIPMENT' AND eq.id=q.reference_id
AND eq.status='CONFIRMED'
LEFT JOIN b08_cost_basis cb
ON q.reference_type='COST_BASIS' AND cb.id=q.reference_id
AND cb.status='CONFIRMED'
WHERE q.project_id=%s
ORDER BY q.wbs_id,q.sort_order
"""
async def save_wbs(self, project_id: str, row: WorkBreakdown) -> str:
row_id = row.id or str(uuid4())
async with self.db.cursor() as cursor:
await cursor.execute(
"""INSERT INTO b08_work_breakdown(
id,project_id,parent_id,wbs_code,name,level_no,sort_order
) VALUES(%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE parent_id=VALUES(parent_id),
name=VALUES(name),level_no=VALUES(level_no),sort_order=VALUES(sort_order)""",
(row_id, project_id, row.parent_id, row.code, row.name,
row.level_no, row.sort_order),
)
await self.db.commit()
return row_id
async def save_quantity(
self, project_id: str, row: DesignQuantity, user_id: int | None
) -> str:
row_id = row.id or str(uuid4())
async with self.db.cursor() as cursor:
await cursor.execute(
"SELECT * FROM b08_design_quantity_items WHERE id=%s FOR UPDATE",
(row_id,),
)
before = await cursor.fetchone()
await cursor.execute(
"""INSERT INTO b08_design_quantity_items(
id,project_id,wbs_id,reference_type,reference_id,item_name,
specification,unit,design_quantity,adjusted_quantity,
confirmed_quantity,adjustment_reason,procurement_type,
excluded,status,sort_order,updated_by
) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
wbs_id=VALUES(wbs_id),reference_type=VALUES(reference_type),
reference_id=VALUES(reference_id),item_name=VALUES(item_name),
specification=VALUES(specification),unit=VALUES(unit),
design_quantity=VALUES(design_quantity),
adjusted_quantity=VALUES(adjusted_quantity),
confirmed_quantity=NULL,adjustment_reason=VALUES(adjustment_reason),
procurement_type=VALUES(procurement_type),excluded=VALUES(excluded),
status=VALUES(status),sort_order=VALUES(sort_order),updated_by=VALUES(updated_by)""",
(row_id, project_id, row.wbs_id, row.reference_type, row.reference_id,
row.item_name, row.specification, row.unit, row.design_quantity,
row.adjusted_quantity, row.confirmed_quantity, row.adjustment_reason,
row.procurement_type, row.excluded, row.status, row.sort_order, user_id),
)
if before:
await cursor.execute(
"""SELECT COALESCE(MAX(revision_no),0)+1 AS n
FROM b08_quantity_revisions WHERE quantity_item_id=%s""",
(row_id,),
)
revision_no = (await cursor.fetchone())["n"]
await cursor.execute(
"""INSERT INTO b08_quantity_revisions(
quantity_item_id,revision_no,before_json,after_json,
change_reason,changed_by
) VALUES(%s,%s,%s,%s,%s,%s)""",
(row_id, revision_no,
json.dumps(before, default=str, ensure_ascii=False),
row.model_dump_json(), row.adjustment_reason or "수량 변경", user_id),
)
await self.db.commit()
return row_id
async def confirm(self, project_id: str, user_id: int | None) -> int:
async with self.db.cursor() as cursor:
await cursor.execute(
"""SELECT COUNT(*) AS missing FROM b08_design_quantity_items
WHERE project_id=%s AND design_quantity IS NULL""",
(project_id,),
)
missing = (await cursor.fetchone())["missing"]
if missing:
raise ValueError(f"수량 미입력 항목 {missing}건이 있습니다.")
await cursor.execute(self._quantity_query(), (project_id,))
rows = list(await cursor.fetchall())
unavailable = [row for row in rows if all(
row.get(key) in (None, 0) for key in ("unit_labor", "unit_material", "unit_expense")
)]
if unavailable:
raise ValueError(f"확정 적용단가가 없는 항목 {len(unavailable)}건이 있습니다.")
await cursor.execute(
"""UPDATE b08_design_quantity_items
SET confirmed_quantity=COALESCE(adjusted_quantity,design_quantity),
status='CONFIRMED',updated_by=%s
WHERE project_id=%s""",
(user_id, project_id),
)
snapshot = [
{"id": row["id"], "quantity": row["adjusted_quantity"] if row["adjusted_quantity"] is not None else row["design_quantity"]}
for row in sorted(rows, key=lambda item: item["id"])
]
payload = json.dumps(snapshot, default=str, sort_keys=True).encode()
digest = hashlib.sha256(payload).hexdigest()
await cursor.execute(
"""SELECT COALESCE(MAX(quantity_version),0)+1 AS version
FROM b08_quantity_confirmations WHERE project_id=%s""",
(project_id,),
)
version = (await cursor.fetchone())["version"]
await cursor.execute(
"""INSERT INTO b08_quantity_confirmations(
project_id,quantity_version,input_hash,confirmed_by
) VALUES(%s,%s,%s,%s)""",
(project_id, version, digest, user_id),
)
await self.db.commit()
return version