초기화

This commit is contained in:
2026-07-15 18:37:19 +09:00
commit 94abc5461d
1268 changed files with 380198 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
# plan package marker
from .db_manager import (
add_production_plan,
add_equipment_assignments_to_plan,
get_all_production_plans,
find_available_equipment,
update_plan_assignment,
delete_production_plan,
adjust_and_distribute_assignments,
update_production_plan
)
from .helper import calculate_end_date
from .router import router
+294
View File
@@ -0,0 +1,294 @@
import json
import mysql.connector
from database.connection import get_db_connection
import logging
from datetime import datetime, timedelta, date
# 생산 계획 추가
def add_production_plan(plan_data):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
logging.info(f"DB에 추가될 최종 plan_data: {json.dumps(plan_data, indent=4, ensure_ascii=False)}")
sql = """
INSERT INTO production_plans (
plan_name, customer_name, customer_id, yarn_name, yarn_inventory_id,
bond_name, bond_inventory_id, requested_quantity_kg, start_date, end_date,
production_days, status, hourly_net_cost_per_machine, hourly_profit_per_machine,
plan_total_material_cost, yarn_diameter, yarn_k, ports, cycle_time,
cut_length, work_hours_day, work_days_month, num_machines,
plan_net_processing_cost, plan_company_margin, plan_processing_fee,
plan_yarn_cost, plan_bond_cost, plan_estimated_sales
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s
)
"""
cursor.execute(sql, (
plan_data.get('plan_name'), plan_data.get('customer_name'), plan_data.get('customer_id'),
plan_data.get('yarn_name'), plan_data.get('yarn_inventory_id'), plan_data.get('bond_name'),
plan_data.get('bond_inventory_id'), plan_data.get('requested_quantity_kg'),
plan_data.get('start_date'), plan_data.get('end_date'), plan_data.get('production_days'),
plan_data.get('status', 'Scheduled'), plan_data.get('hourly_net_cost_per_machine'),
plan_data.get('hourly_profit_per_machine'), plan_data.get('plan_total_material_cost'),
plan_data.get('yarn_diameter'), plan_data.get('yarn_k'), plan_data.get('ports'),
plan_data.get('cycle_time'), plan_data.get('cut_length'), plan_data.get('work_hours_day'),
plan_data.get('work_days_month'), plan_data.get('num_machines'),
plan_data.get('plan_net_processing_cost'), plan_data.get('plan_company_margin'),
plan_data.get('plan_processing_fee'), plan_data.get('plan_yarn_cost'),
plan_data.get('plan_bond_cost'), plan_data.get('plan_estimated_sales')
))
conn.commit()
return cursor.lastrowid
except mysql.connector.Error as err:
logging.error(f"Error adding production plan: {err}")
return None
# 특정 생산 계획에 설비 할당 정보 추가
def add_equipment_assignments_to_plan(plan_id, assignments):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
sql = """
INSERT INTO plan_assignments (
plan_id, equipment_id, start_date, end_date, works_on_saturday, works_on_sunday
) VALUES (%s, %s, %s, %s, %s, %s)
"""
assignment_data = [
(plan_id, assign['equipment_id'], assign['start_date'], assign['end_date'],
1 if assign.get('works_on_saturday') else 0, 1 if assign.get('works_on_sunday') else 0)
for assign in assignments
]
cursor.executemany(sql, assignment_data)
conn.commit()
return True
except mysql.connector.Error as err:
logging.error(f"Error adding equipment assignments: {err}")
return False
# 모든 생산 계획 목록 조회 (설비 할당 정보 포함)
def get_all_production_plans():
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT * FROM production_plans ORDER BY start_date")
plans = cursor.fetchall()
if not plans:
return []
sql_assignments = """
SELECT pa.*, e.name as equipment_name
FROM plan_assignments pa
JOIN equipment e ON pa.equipment_id = e.id
"""
cursor.execute(sql_assignments)
assignments = cursor.fetchall()
plan_map = {plan['plan_id']: plan for plan in plans}
for plan in plans:
plan['assigned_equipment'] = []
for assign in assignments:
if assign['plan_id'] in plan_map:
assign['works_on_saturday'] = bool(assign['works_on_saturday'])
assign['works_on_sunday'] = bool(assign['works_on_sunday'])
plan_map[assign['plan_id']]['assigned_equipment'].append(assign)
return list(plan_map.values())
except mysql.connector.Error as err:
logging.error(f"Error getting all production plans: {err}")
return []
# 특정 기간에 사용 가능한 설비 목록 검색
def find_available_equipment(start_date_str, end_date_str, works_on_saturday, works_on_sunday):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("SELECT id, name FROM equipment")
all_equipment = cursor.fetchall()
return all_equipment
except mysql.connector.Error as err:
logging.error(f"Error finding all equipment: {err}")
return []
# 개별 설비 할당 정보 수정
def update_plan_assignment(plan_id, assignment_data):
conn = None
try:
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
conn.start_transaction()
sql_update_assignment = """
UPDATE plan_assignments SET
start_date = %s,
end_date = %s,
works_on_saturday = %s,
works_on_sunday = %s
WHERE assignment_id = %s
"""
cursor.execute(sql_update_assignment, (
assignment_data.get('start_date'),
assignment_data.get('end_date'),
1 if assignment_data.get('works_on_saturday') else 0,
1 if assignment_data.get('works_on_sunday') else 0,
assignment_data.get('assignment_id')
))
cursor.execute("SELECT MIN(start_date) as min_start, MAX(end_date) as max_end FROM plan_assignments WHERE plan_id = %s", (plan_id,))
plan_dates = cursor.fetchone()
if plan_dates and plan_dates['min_start'] and plan_dates['max_end']:
cursor.execute("UPDATE production_plans SET start_date = %s, end_date = %s WHERE plan_id = %s",
(plan_dates['min_start'], plan_dates['max_end'], plan_id))
conn.commit()
return True
except mysql.connector.Error as err:
if conn:
conn.rollback()
logging.error(f"Error updating plan assignment for plan_id {plan_id}: {err}")
return False
finally:
if conn and conn.is_connected():
cursor.close()
conn.close()
# 생산 계획 삭제
def delete_production_plan(plan_id):
try:
with get_db_connection() as conn:
with conn.cursor(dictionary=True) as cursor:
cursor.execute("DELETE FROM plan_assignments WHERE plan_id = %s", (plan_id,))
cursor.execute("DELETE FROM production_plans WHERE plan_id = %s", (plan_id,))
conn.commit()
return True
except mysql.connector.Error as err:
logging.error(f"Error deleting production plan: {err}")
return False
# 가동일 분배 조정
def adjust_and_distribute_assignments(plan_id, source_assignment, distribution):
conn = None
try:
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
conn.start_transaction()
update_source_sql = """
UPDATE plan_assignments SET
start_date=%s, end_date=%s, works_on_saturday=%s, works_on_sunday=%s
WHERE assignment_id=%s
"""
cursor.execute(update_source_sql, (
source_assignment['start_date'], source_assignment['end_date'],
1 if source_assignment['works_on_saturday'] else 0,
1 if source_assignment['works_on_sunday'] else 0,
source_assignment['assignment_id']
))
for assign_id_str, days_to_add in distribution.items():
days_to_add = int(days_to_add)
if days_to_add <= 0:
continue
assign_id = int(assign_id_str)
cursor.execute("SELECT * FROM plan_assignments WHERE assignment_id = %s", (assign_id,))
target = cursor.fetchone()
if not target:
raise Exception(f"Target assignment with ID {assign_id} not found.")
current_end_date = target['end_date']
works_sat = bool(target['works_on_saturday'])
works_sun = bool(target['works_on_sunday'])
new_end_date = current_end_date
days_added_count = 0
while days_added_count < days_to_add:
new_end_date += timedelta(days=1)
weekday = new_end_date.weekday()
if (weekday < 5) or (weekday == 5 and works_sat) or (weekday == 6 and works_sun):
days_added_count += 1
cursor.execute("UPDATE plan_assignments SET end_date = %s WHERE assignment_id = %s", (new_end_date.isoformat(), assign_id))
cursor.execute("SELECT MIN(start_date) as min_start, MAX(end_date) as max_end FROM plan_assignments WHERE plan_id = %s", (plan_id,))
plan_dates = cursor.fetchone()
if plan_dates and plan_dates['min_start'] and plan_dates['max_end']:
cursor.execute("UPDATE production_plans SET start_date = %s, end_date = %s WHERE plan_id = %s",
(plan_dates['min_start'], plan_dates['max_end'], plan_id))
conn.commit()
return True
except Exception as err:
if conn:
conn.rollback()
logging.error(f"Error during assignment redistribution for plan_id {plan_id}: {err}")
return False
finally:
if conn and conn.is_connected():
cursor.close()
conn.close()
# 생산 계획 수정
def update_production_plan(plan_id, plan_data, assigned_machine_ids):
conn = None
try:
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
conn.start_transaction()
cursor.execute("DELETE FROM plan_assignments WHERE plan_id = %s", (plan_id,))
update_sql = """
UPDATE production_plans SET
plan_name = %s, customer_name = %s, customer_id = %s, yarn_name = %s, yarn_inventory_id = %s,
bond_name = %s, bond_inventory_id = %s, requested_quantity_kg = %s, start_date = %s, end_date = %s,
production_days = %s, status = %s, hourly_net_cost_per_machine = %s, hourly_profit_per_machine = %s,
plan_total_material_cost = %s, yarn_diameter = %s, yarn_k = %s, ports = %s, cycle_time = %s,
cut_length = %s, work_hours_day = %s, work_days_month = %s, num_machines = %s,
plan_net_processing_cost = %s, plan_company_margin = %s, plan_processing_fee = %s,
plan_yarn_cost = %s, plan_bond_cost = %s, plan_estimated_sales = %s
WHERE plan_id = %s
"""
cursor.execute(update_sql, (
plan_data.get('plan_name'), plan_data.get('customer_name'), plan_data.get('customer_id'),
plan_data.get('yarn_name'), plan_data.get('yarn_inventory_id'), plan_data.get('bond_name'),
plan_data.get('bond_inventory_id'), plan_data.get('requested_quantity_kg'),
plan_data.get('start_date'), plan_data.get('end_date'), plan_data.get('production_days'),
plan_data.get('status', 'Scheduled'), plan_data.get('hourly_net_cost_per_machine'),
plan_data.get('hourly_profit_per_machine'), plan_data.get('plan_total_material_cost'),
plan_data.get('yarn_diameter'), plan_data.get('yarn_k'), plan_data.get('ports'),
plan_data.get('cycle_time'), plan_data.get('cut_length'), plan_data.get('work_hours_day'),
plan_data.get('work_days_month'), plan_data.get('num_machines'),
plan_data.get('plan_net_processing_cost'), plan_data.get('plan_company_margin'),
plan_data.get('plan_processing_fee'), plan_data.get('plan_yarn_cost'),
plan_data.get('plan_bond_cost'), plan_data.get('plan_estimated_sales'),
plan_id
))
if assigned_machine_ids:
assign_sql = """
INSERT INTO plan_assignments (
plan_id, equipment_id, start_date, end_date, works_on_saturday, works_on_sunday
) VALUES (%s, %s, %s, %s, %s, %s)
"""
assignment_data = [
(
plan_id, eq_id, plan_data.get('start_date'), plan_data.get('end_date'),
1 if plan_data.get('works_on_saturday') else 0,
1 if plan_data.get('works_on_sunday') else 0
) for eq_id in assigned_machine_ids
]
cursor.executemany(assign_sql, assignment_data)
conn.commit()
return True
except Exception as err:
if conn:
conn.rollback()
logging.error(f"Error updating production plan for plan_id {plan_id}: {err}")
return False
finally:
if conn and conn.is_connected():
cursor.close()
conn.close()
+31
View File
@@ -0,0 +1,31 @@
import math
from datetime import date, timedelta
def calculate_end_date(start_date_str: str, work_days: float, works_on_saturday: bool, works_on_sunday: bool) -> str:
"""주말(토/일) 근무 여부 플래그를 고려하여 가동 후 실제 종료 날짜(YYYY-MM-DD)를 계산합니다."""
start_date = date.fromisoformat(start_date_str)
work_days_required = math.ceil(work_days)
current_date = start_date
days_counted = 0
if work_days_required <= 0:
return start_date.isoformat()
while days_counted < work_days_required:
weekday = current_date.weekday() # 월요일 0, 일요일 6
is_working_day = True
if weekday == 5: # 토요일
if not works_on_saturday:
is_working_day = False
elif weekday == 6: # 일요일
if not works_on_sunday:
is_working_day = False
if is_working_day:
days_counted += 1
if days_counted < work_days_required:
current_date += timedelta(days=1)
return current_date.isoformat()
+162
View File
@@ -0,0 +1,162 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from typing import List, Dict, Any, Optional
from .db_manager import (
add_production_plan,
add_equipment_assignments_to_plan,
get_all_production_plans,
find_available_equipment,
update_plan_assignment,
delete_production_plan,
adjust_and_distribute_assignments,
update_production_plan
)
from .helper import calculate_end_date
router = APIRouter(prefix="/plans", tags=["plans"])
# --- Pydantic Schemas ---
class PlanAssignmentSchema(BaseModel):
assignment_id: Optional[int] = None
plan_id: Optional[int] = None
equipment_id: int
start_date: str
end_date: str
works_on_saturday: bool
works_on_sunday: bool
class PlanSchema(BaseModel):
plan_id: Optional[int] = None
plan_name: str
customer_name: Optional[str] = None
customer_id: Optional[int] = None
yarn_name: Optional[str] = None
yarn_inventory_id: int
bond_name: Optional[str] = None
bond_inventory_id: Optional[int] = None
requested_quantity_kg: float
start_date: str
end_date: Optional[str] = None
production_days: float
status: Optional[str] = "Scheduled"
# Cost & Margin properties
hourly_net_cost_per_machine: float
hourly_profit_per_machine: float
plan_total_material_cost: float
yarn_diameter: float
yarn_k: float
ports: float
cycle_time: float
cut_length: float
work_hours_day: float
work_days_month: float
num_machines: float
plan_net_processing_cost: float
plan_company_margin: float
plan_processing_fee: float
plan_yarn_cost: float
plan_bond_cost: float
plan_estimated_sales: float
# Machine allocations
equipment_ids: List[int]
works_on_saturday: Optional[bool] = False
works_on_sunday: Optional[bool] = False
class AssignmentUpdatePayload(BaseModel):
assignment_id: int
start_date: str
end_date: str
works_on_saturday: bool
works_on_sunday: bool
class RedistributionPayload(BaseModel):
source_assignment: Dict[str, Any]
distribution: Dict[str, int]
# --- Plan APIs ---
@router.post("/", response_model=dict)
def create_production_plan(plan: PlanSchema):
plan_data = plan.dict()
assigned_machine_ids = plan.equipment_ids
start_date_str = plan.start_date
required_days = plan.production_days
works_on_saturday = plan.works_on_saturday
works_on_sunday = plan.works_on_sunday
if not assigned_machine_ids or not start_date_str or required_days is None:
raise HTTPException(status_code=400, detail="Missing required parameters: equipment_ids, start_date, production_days")
# 주말 가동 플래그를 고려하여 가용 완료 날짜 계산
end_date_str = calculate_end_date(start_date_str, required_days, works_on_saturday, works_on_sunday)
plan_data["end_date"] = end_date_str
plan_id = add_production_plan(plan_data)
if not plan_id:
raise HTTPException(status_code=500, detail="Failed to add production plan to DB")
# 설비 할당 정보 생성
assignments = [
{
'equipment_id': eq_id,
'start_date': start_date_str,
'end_date': end_date_str,
'works_on_saturday': works_on_saturday,
'works_on_sunday': works_on_sunday
} for eq_id in assigned_machine_ids
]
assign_success = add_equipment_assignments_to_plan(plan_id, assignments)
if not assign_success:
raise HTTPException(status_code=500, detail="Plan was created but equipment assignments failed.")
return {"plan_id": plan_id, "status": "success"}
@router.put("/{plan_id}", response_model=dict)
def edit_production_plan(plan_id: int, plan: PlanSchema):
plan_data = plan.dict()
assigned_machine_ids = plan.equipment_ids
start_date_str = plan.start_date
required_days = plan.production_days
works_on_saturday = plan.works_on_saturday
works_on_sunday = plan.works_on_sunday
end_date_str = calculate_end_date(start_date_str, required_days, works_on_saturday, works_on_sunday)
plan_data["end_date"] = end_date_str
success = update_production_plan(plan_id, plan_data, assigned_machine_ids)
if not success:
raise HTTPException(status_code=500, detail="Failed to update production plan")
return {"status": "success"}
@router.delete("/{plan_id}", response_model=dict)
def delete_plan(plan_id: int):
success = delete_production_plan(plan_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to delete production plan")
return {"status": "success"}
@router.get("/", response_model=List[dict])
def list_production_plans():
return get_all_production_plans()
@router.get("/find-available-equipment", response_model=List[dict])
def get_available_equipment(start_date: str, end_date: str, works_on_saturday: bool = False, works_on_sunday: bool = False):
return find_available_equipment(start_date, end_date, works_on_saturday, works_on_sunday)
@router.put("/{plan_id}/assignments", response_model=dict)
def update_assignment(plan_id: int, payload: AssignmentUpdatePayload):
success = update_plan_assignment(plan_id, payload.dict())
if not success:
raise HTTPException(status_code=500, detail="Failed to update assignment")
return {"status": "success"}
@router.post("/{plan_id}/assignments/redistribute", response_model=dict)
def redistribute_assignments(plan_id: int, payload: RedistributionPayload):
success = adjust_and_distribute_assignments(plan_id, payload.source_assignment, payload.distribution)
if not success:
raise HTTPException(status_code=500, detail="Failed to redistribute assignments")
return {"status": "success"}