초기화
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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"}
|
||||
Reference in New Issue
Block a user