Update app.py
Browse files
app.py
CHANGED
|
@@ -1,221 +1,240 @@
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
s = series.astype(str).fillna("")
|
| 11 |
-
|
| 12 |
-
if len(
|
| 13 |
return "0000-0000"
|
| 14 |
-
|
| 15 |
-
if len(
|
| 16 |
return "0000-0000"
|
| 17 |
-
return f"{
|
| 18 |
|
| 19 |
-
def
|
| 20 |
df = df.copy()
|
| 21 |
df["๋ฐ์ค๋ฒํธ"] = df["๋ฐ์ค๋ฒํธ"].astype(str).str.zfill(4)
|
| 22 |
-
|
| 23 |
-
df["์ ๋ชฉ"] = df["์ ๋ชฉ"].astype(str)
|
| 24 |
-
|
| 25 |
-
# ์์ฐ์ฐ๋(๋ฒ์) = ์ข
๋ฃ์ฐ๋ ๊ทธ๋ฃน ๋ฒ์
|
| 26 |
if "์ข
๋ฃ์ฐ๋" in df.columns:
|
| 27 |
-
|
| 28 |
-
|
| 29 |
else:
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
# ๋ชฉ๋ก(๊ด๋ฆฌ๋ฒํธ + ์ ๋ชฉ)
|
| 33 |
has_mgmt = "๊ด๋ฆฌ๋ฒํธ" in df.columns
|
| 34 |
list_rows = []
|
| 35 |
for box, g in df.groupby("๋ฐ์ค๋ฒํธ"):
|
| 36 |
-
lines = [f"- {r['๊ด๋ฆฌ๋ฒํธ']} {r
|
| 37 |
for _, r in g.iterrows()]
|
| 38 |
-
list_rows.append({"๋ฐ์ค๋ฒํธ": box, "๋ชฉ๋ก": "\
|
| 39 |
list_df = pd.DataFrame(list_rows)
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
meta_exist = [c for c in
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
)
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
out = write_hwpx_like_src(zin, writer)
|
| 138 |
-
zin.close()
|
| 139 |
-
return (out, dbg) if collect_debug else (out, None)
|
| 140 |
-
|
| 141 |
-
# ================= UI =================
|
| 142 |
-
with st.expander("์ฌ์ฉ๋ฒ", expanded=True):
|
| 143 |
-
st.markdown("""
|
| 144 |
-
- ํ
ํ๋ฆฟ์ **ํ๊ธ ํ๋์ปจํธ๋กค**์ด์ด์ผ ํฉ๋๋ค. (์: `name="๋ฐ์ค๋ฒํธ1"`)
|
| 145 |
-
- ์ด ์ฑ์ ํ๋ ๊ตฌ๊ฐ์ **ํ๋ฌธํ(ํ๋ ์ ๊ฑฐ)** ํ์ฌ ๊ฐ run๋ค๋ก ๋ฐ๊ฟ๋๋ค. โ ํ๊ธ ๋ทฐ์ด์์ **ํญ์ ๋ณด์**.
|
| 146 |
-
- ๋ผ๋ฒจ ํ ํ์ด์ง์ N๊ฐ๋ฉด, ํ๋๋ช
์ `๋ฐ์ค๋ฒํธ1..N`, `์ข
๋ฃ์ฐ๋1..N`, `๋ณด์กด๊ธฐ๊ฐ1..N`, `๋จ์์
๋ฌด1..N`, `๊ธฐ๋ก๋ฌผ์ฒ 1..N`, `๋ชฉ๋ก1..N`.
|
| 147 |
-
""")
|
| 148 |
-
|
| 149 |
-
tpl_file = st.file_uploader("๐ HWPX ํ
ํ๋ฆฟ ์
๋ก๋", type=["hwpx"])
|
| 150 |
-
batch_size = st.number_input("ํ
ํ๋ฆฟ์ ๋ผ๋ฒจ ์ธํธ ๊ฐ์ (ํ ํ์ด์ง N๊ฐ)", min_value=1, max_value=12, value=3, step=1)
|
| 151 |
-
data_file = st.file_uploader("๐ ๋ฐ์ดํฐ ์
๋ก๋ (Excel/CSV)", type=["xlsx","xls","csv"])
|
| 152 |
-
|
| 153 |
-
if tpl_file and data_file:
|
| 154 |
-
tpl_bytes = tpl_file.read()
|
| 155 |
-
df = pd.read_csv(data_file) if data_file.name.lower().endswith(".csv") else pd.read_excel(data_file)
|
| 156 |
-
|
| 157 |
-
if "๋ฐ์ค๋ฒํธ" not in df.columns:
|
| 158 |
-
st.error("โ ํ์ ์ปฌ๋ผ '๋ฐ์ค๋ฒํธ'๊ฐ ์์ต๋๋ค.")
|
| 159 |
-
st.stop()
|
| 160 |
-
|
| 161 |
-
st.success("โ
์์น ๋งคํ ์๋ฃ (์์
์ธก)")
|
| 162 |
st.dataframe(df.head(10), use_container_width=True)
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
st.
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
# 1ํ์ด์ง ํ๋ฆฌ๋ทฐ
|
| 176 |
-
st.subheader("๐งช 1ํ์ด์ง ๋งคํ ํ๋ฆฌ๋ทฐ")
|
| 177 |
-
keys = ["๋ฐ์ค๋ฒํธ","์ข
๋ฃ์ฐ๋","๋ณด์กด๊ธฐ๊ฐ","๋จ์์
๋ฌด","๊ธฐ๋ก๋ฌผ์ฒ ","๋ชฉ๋ก"]
|
| 178 |
-
n = int(batch_size)
|
| 179 |
-
preview = {}
|
| 180 |
-
for i in range(n):
|
| 181 |
-
if i < len(rows):
|
| 182 |
-
r = rows[i]
|
| 183 |
-
for k in keys:
|
| 184 |
-
preview[f"{k}{i+1}"] = r.get("์์ฐ์ฐ๋","") if k=="์ข
๋ฃ์ฐ๋" else r.get(k,"")
|
| 185 |
-
else:
|
| 186 |
-
for k in keys:
|
| 187 |
-
preview[f"{k}{i+1}"] = ""
|
| 188 |
-
st.dataframe(
|
| 189 |
-
pd.DataFrame([{"ํ๋๋ช
":k, "๊ฐ ์๋ถ๋ถ":str(v)[:120]} for k,v in sorted(preview.items())]),
|
| 190 |
-
use_container_width=True, height=320
|
| 191 |
-
)
|
| 192 |
-
|
| 193 |
-
if st.button("๐ ๋ผ๋ฒจ ์์ฑ (ํ์ด์ง๋ณ HWPX ZIP)"):
|
| 194 |
-
mem_zip = io.BytesIO()
|
| 195 |
-
zout = zipfile.ZipFile(mem_zip, "w", zipfile.ZIP_DEFLATED)
|
| 196 |
-
pages = (len(rows) + n - 1) // n
|
| 197 |
-
all_dbg = []
|
| 198 |
-
|
| 199 |
-
for p in range(pages):
|
| 200 |
-
chunk = rows[p*n:(p+1)*n]
|
| 201 |
-
mapping = {}
|
| 202 |
-
for i in range(n):
|
| 203 |
-
if i < len(chunk):
|
| 204 |
-
r = chunk[i]
|
| 205 |
-
for k in keys:
|
| 206 |
-
mapping[f"{k}{i+1}"] = r.get("์์ฐ์ฐ๋","") if k=="์ข
๋ฃ์ฐ๋" else r.get(k,"")
|
| 207 |
-
else:
|
| 208 |
-
for k in keys:
|
| 209 |
-
mapping[f"{k}{i+1}"] = ""
|
| 210 |
-
|
| 211 |
-
out_hwpx, dbg = apply_field_flatten(tpl_bytes, mapping, collect_debug=True)
|
| 212 |
-
all_dbg.append({"page": p+1, "stats": dbg})
|
| 213 |
-
name = "_".join([r.get("๋ฐ์ค๋ฒํธ","") for r in chunk]) if chunk else f"empty_{p+1}"
|
| 214 |
-
zout.writestr(f"label_{name}.hwpx", out_hwpx)
|
| 215 |
-
|
| 216 |
-
zout.close(); mem_zip.seek(0)
|
| 217 |
-
st.download_button("โฌ๏ธ ZIP ๋ค์ด๋ก๋", data=mem_zip, file_name="labels_by_page.zip", mime="application/zip")
|
| 218 |
-
st.download_button("โฌ๏ธ ๋๋ฒ๊ทธ(JSON)", data=json.dumps(all_dbg, ensure_ascii=False, indent=2),
|
| 219 |
-
file_name="debug.json", mime="application/json")
|
| 220 |
-
|
| 221 |
-
st.caption("ํ๋ ๊ตฌ๊ฐ์ ํต์งธ๋ก ๊ฐ run๋ค๋ก ๊ต์ฒดํฉ๋๋ค. (ํ๋ ์ ๊ฑฐ โ ๊ฐ์ด ํ์คํ ๋ณด์
๋๋ค)")
|
|
|
|
| 1 |
import streamlit as st
|
| 2 |
import pandas as pd
|
| 3 |
+
from reportlab.pdfgen import canvas
|
| 4 |
+
from reportlab.pdfbase import pdfmetrics
|
| 5 |
+
from reportlab.pdfbase.ttfonts import TTFont
|
| 6 |
+
from reportlab.lib.pagesizes import A4
|
| 7 |
+
from reportlab.lib.units import mm
|
| 8 |
+
from io import BytesIO
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
st.set_page_config(page_title="๐ฆ ๋ฐ์ค๋ผ๋ฒจ PDF ์ถ๋ ฅ๊ธฐ", layout="wide")
|
| 12 |
+
st.title("๐ฆ ๋ฐ์ค๋ผ๋ฒจ PDF ์ถ๋ ฅ๊ธฐ (๋ผ๋ฒจ ๊ท๊ฒฉ ์ปค์คํ
/ ํ๊ตญ์ด ํฐํธ ์
๋ก๋)")
|
| 13 |
+
|
| 14 |
+
with st.expander("์ฌ์ฉ ๋ฐฉ๋ฒ", expanded=True):
|
| 15 |
+
st.markdown("""
|
| 16 |
+
1. **์์
/CSV ์
๋ก๋** โ ํ์ ์ปฌ๋ผ: `๋ฐ์ค๋ฒํธ` / ๊ถ์ฅ: `์ข
๋ฃ์ฐ๋`, `๋ณด์กด๊ธฐ๊ฐ`, `๋จ์์
๋ฌด`, `๊ธฐ๋ก๋ฌผ์ฒ `, `์ ๋ชฉ`, `๊ด๋ฆฌ๋ฒํธ`
|
| 17 |
+
2. (์ ํ) **TTF ํฐํธ ์
๋ก๋**(์: ๋๋๊ณ ๋, ๋ณธ๊ณ ๋, ๋ง์ ๊ณ ๋ ๋ฑ). ์
๋ก๋ ์ ํ๋ฉด ๊ธฐ๋ณธ ํฐํธ ์ฌ์ฉ(์๋ฌธ ์์ฃผ).
|
| 18 |
+
3. **๋ผ๋ฒจ ๊ท๊ฒฉ**(ํ์ด์ง ์ฌ๋ฐฑ, ๋ผ๋ฒจ ๊ฐ๋ก/์ธ๋ก, ํ/์ด, ๋ผ๋ฒจ ๊ฐ๊ฒฉ)์ ์
๋ ฅ.
|
| 19 |
+
4. **ํ
์คํธ ๋ฐฐ์น**(๋ผ๋ฒจ ์์ชฝ ํจ๋ฉ, ํฐํธ ํฌ๊ธฐ, ์ค ๊ฐ๊ฒฉ ๋ฑ) ์กฐ์ .
|
| 20 |
+
5. **PDF ์์ฑ** โ ๋ผ๋ฒจ ์ฉ์ง(Formtec ๋ฑ)์ ์ธ์.
|
| 21 |
+
""")
|
| 22 |
+
|
| 23 |
+
# -----------------
|
| 24 |
+
# ๋ฐ์ดํฐ ๋ก๋
|
| 25 |
+
# -----------------
|
| 26 |
+
file = st.file_uploader("๐ ๋ฐ์ดํฐ ์
๋ก๋ (Excel/CSV)", type=["xlsx","xls","csv"])
|
| 27 |
+
df = None
|
| 28 |
+
if file:
|
| 29 |
+
if file.name.lower().endswith(".csv"):
|
| 30 |
+
df = pd.read_csv(file)
|
| 31 |
+
else:
|
| 32 |
+
df = pd.read_excel(file)
|
| 33 |
+
|
| 34 |
+
# ํ์ ์ปฌ๋ผ ๊ฒ์ฌ
|
| 35 |
+
if df is not None and "๋ฐ์ค๋ฒํธ" not in df.columns:
|
| 36 |
+
st.error("โ ํ์ ์ปฌ๋ผ '๋ฐ์ค๋ฒํธ'๊ฐ ์์ต๋๋ค.")
|
| 37 |
+
st.stop()
|
| 38 |
+
|
| 39 |
+
# -----------------
|
| 40 |
+
# ํฐํธ ์ค์
|
| 41 |
+
# -----------------
|
| 42 |
+
st.subheader("๐ค ํฐํธ ์ค์ ")
|
| 43 |
+
font_file = st.file_uploader("ํ๊ตญ์ด ํฐํธ(TTF) ์
๋ก๋ (์: NanumGothic.ttf / MalgunGothic.ttf)", type=["ttf"])
|
| 44 |
+
font_name = "BaseFont"
|
| 45 |
+
if font_file:
|
| 46 |
+
try:
|
| 47 |
+
font_bytes = font_file.read()
|
| 48 |
+
# ๋ฉ๋ชจ๋ฆฌ ๋ฑ๋ก: ReportLab์ ํ์ผ ๊ฒฝ๋ก๊ฐ ํ์ โ ์์ ํ์ผ ๋ง๋ค๊ธฐ๋ณด๋ค ๋ฉ๋ชจ๋ฆฌ ๋ ์ง์คํฐ ํธ๋ฆญ
|
| 49 |
+
# ํ์ง๋ง TTFont๋ ํ์ผ ๊ฒฝ๋ก ์๊ตฌ โ ์์ํ์ผ ์ ์ฅ
|
| 50 |
+
import tempfile
|
| 51 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".ttf")
|
| 52 |
+
tmp.write(font_bytes); tmp.flush()
|
| 53 |
+
pdfmetrics.registerFont(TTFont("UserKorean", tmp.name))
|
| 54 |
+
font_name = "UserKorean"
|
| 55 |
+
st.success("โ
ํฐํธ ๋ฑ๋ก ์๋ฃ: UserKorean")
|
| 56 |
+
except Exception as e:
|
| 57 |
+
st.warning(f"ํฐํธ ๋ฑ๋ก ์คํจ. ๊ธฐ๋ณธ ํฐํธ ์ฌ์ฉํฉ๋๋ค. (์ฌ์ : {e})")
|
| 58 |
+
else:
|
| 59 |
+
# ๋ด์ฅ ๊ธฐ๋ณธ ํฐํธ (์๋ฌธ ์ค์ฌ)
|
| 60 |
+
font_name = "Helvetica"
|
| 61 |
+
|
| 62 |
+
# -----------------
|
| 63 |
+
# ๋ผ๋ฒจ/ํ์ด์ง ๋ ์ด์์
|
| 64 |
+
# -----------------
|
| 65 |
+
st.subheader("๐ ๋ผ๋ฒจ ๊ท๊ฒฉ (mm ๋จ์)")
|
| 66 |
+
colA, colB, colC = st.columns(3)
|
| 67 |
+
with colA:
|
| 68 |
+
page_size = st.selectbox("ํ์ด์ง ํฌ๊ธฐ", ["A4"], index=0)
|
| 69 |
+
with colB:
|
| 70 |
+
margin_left = st.number_input("์ผ์ชฝ ์ฌ๋ฐฑ(mm)", 5.0, 50.0, 10.0, 0.5)
|
| 71 |
+
margin_top = st.number_input("์๋จ ์ฌ๋ฐฑ(mm)", 5.0, 50.0, 10.0, 0.5)
|
| 72 |
+
with colC:
|
| 73 |
+
rows = st.number_input("ํ ์", 1, 20, 10, 1)
|
| 74 |
+
cols = st.number_input("์ด ์", 1, 10, 3, 1)
|
| 75 |
+
|
| 76 |
+
colD, colE, colF = st.columns(3)
|
| 77 |
+
with colD:
|
| 78 |
+
label_w = st.number_input("๋ผ๋ฒจ ๊ฐ๋ก(mm)", 20.0, 210.0, 70.0, 0.5)
|
| 79 |
+
with colE:
|
| 80 |
+
label_h = st.number_input("๋ผ๋ฒจ ์ธ๋ก(mm)", 10.0, 297.0, 25.0, 0.5)
|
| 81 |
+
with colF:
|
| 82 |
+
gap_x = st.number_input("๊ฐ๋ก ๊ฐ๊ฒฉ(mm)", 0.0, 20.0, 3.0, 0.5)
|
| 83 |
+
gap_y = st.number_input("์ธ๋ก ๊ฐ๊ฒฉ(mm)", 0.0, 20.0, 3.0, 0.5)
|
| 84 |
+
|
| 85 |
+
# -----------------
|
| 86 |
+
# ๋ผ๋ฒจ ๋ด๋ถ ํ
์คํธ ๋ฐฐ์น
|
| 87 |
+
# -----------------
|
| 88 |
+
st.subheader("๐งฑ ๋ผ๋ฒจ ๋ด๋ถ ๋ ์ด์์")
|
| 89 |
+
col1, col2, col3 = st.columns(3)
|
| 90 |
+
with col1:
|
| 91 |
+
pad_x = st.number_input("๋ด๋ถ ํจ๋ฉ X(mm)", 0.0, 20.0, 2.0, 0.5)
|
| 92 |
+
pad_y = st.number_input("๋ด๋ถ ํจ๋ฉ Y(mm)", 0.0, 20.0, 2.0, 0.5)
|
| 93 |
+
with col2:
|
| 94 |
+
fs_big = st.number_input("ํฐํธ ํฌ๊ธฐ(ํฐ ์ ๋ชฉ)", 6, 40, 16, 1)
|
| 95 |
+
fs_mid = st.number_input("ํฐํธ ํฌ๊ธฐ(์ค๊ฐ)", 6, 40, 11, 1)
|
| 96 |
+
with col3:
|
| 97 |
+
fs_small = st.number_input("ํฐํธ ํฌ๊ธฐ(์๊ฒ/๋ชฉ๋ก)", 6, 20, 9, 1)
|
| 98 |
+
line_gap = st.number_input("์ค ๊ฐ๊ฒฉ(๋ฐฐ์)", 0.8, 2.0, 1.2, 0.1)
|
| 99 |
+
|
| 100 |
+
st.caption("๐ก Formtec 3203 ๋น์ทํ ์ค์ ์์: ๊ฐ๋ก 70, ์ธ๋ก 25, ์ด 3, ํ 10, ์ฌ๋ฐฑ 10/10, ๊ฐ๊ฒฉ 3/3 (ํ๋ฆฐํฐ๋ง๋ค ์ฝ๊ฐ ์กฐ์ )")
|
| 101 |
+
|
| 102 |
+
# -----------------
|
| 103 |
+
# ํ
์คํธ ์์ฑ ํจ์
|
| 104 |
+
# -----------------
|
| 105 |
+
def year_range(series):
|
| 106 |
s = series.astype(str).fillna("")
|
| 107 |
+
v = s[~s.isin(["", "0", "0000"])]
|
| 108 |
+
if len(v) == 0:
|
| 109 |
return "0000-0000"
|
| 110 |
+
nums = pd.to_numeric(v, errors="coerce").dropna().astype(int)
|
| 111 |
+
if len(nums) == 0:
|
| 112 |
return "0000-0000"
|
| 113 |
+
return f"{nums.min():04d}-{nums.max():04d}"
|
| 114 |
|
| 115 |
+
def build_records(df: pd.DataFrame):
|
| 116 |
df = df.copy()
|
| 117 |
df["๋ฐ์ค๋ฒํธ"] = df["๋ฐ์ค๋ฒํธ"].astype(str).str.zfill(4)
|
| 118 |
+
# ์์ฐ์ฐ๋(๋ฒ์)
|
|
|
|
|
|
|
|
|
|
| 119 |
if "์ข
๋ฃ์ฐ๋" in df.columns:
|
| 120 |
+
yr = df.groupby("๋ฐ์ค๋ฒํธ")["์ข
๋ฃ์ฐ๋"].apply(year_range).reset_index()
|
| 121 |
+
yr.columns = ["๋ฐ์ค๋ฒํธ", "์์ฐ์ฐ๋"]
|
| 122 |
else:
|
| 123 |
+
yr = pd.DataFrame({"๋ฐ์ค๋ฒํธ": df["๋ฐ์ค๋ฒํธ"].unique(), "์์ฐ์ฐ๋": "0000-0000"})
|
| 124 |
+
# ๋ชฉ๋ก
|
|
|
|
| 125 |
has_mgmt = "๊ด๋ฆฌ๋ฒํธ" in df.columns
|
| 126 |
list_rows = []
|
| 127 |
for box, g in df.groupby("๋ฐ์ค๋ฒํธ"):
|
| 128 |
+
lines = [f"- {r['๊ด๋ฆฌ๋ฒํธ']} {r.get('์ ๋ชฉ','')}" if has_mgmt else f"- {r.get('์ ๋ชฉ','')}"
|
| 129 |
for _, r in g.iterrows()]
|
| 130 |
+
list_rows.append({"๋ฐ์ค๋ฒํธ": box, "๋ชฉ๋ก": "\n".join(lines)})
|
| 131 |
list_df = pd.DataFrame(list_rows)
|
| 132 |
+
# ๋ํ ๋ฉํ
|
| 133 |
+
cols = ["๋ฐ์ค๋ฒํธ","๋ณด์กด๊ธฐ๊ฐ","๋จ์์
๋ฌด","๊ธฐ๋ก๋ฌผ์ฒ ","์ ๋ชฉ"]
|
| 134 |
+
meta_exist = [c for c in cols if c in df.columns]
|
| 135 |
+
meta = df.groupby("๋ฐ์ค๋ฒํธ", as_index=False).first()[meta_exist] if meta_exist else pd.DataFrame({"๋ฐ์ค๋ฒํธ": df["๋ฐ์ค๋ฒํธ"].unique()})
|
| 136 |
+
merged = meta.merge(list_df, on="๋ฐ์ค๋ฒํธ", how="left").merge(yr, on="๋ฐ์ค๋ฒํธ", how="left")
|
| 137 |
+
return merged.sort_values("๋ฐ์ค๋ฒํธ").to_dict(orient="records")
|
| 138 |
+
|
| 139 |
+
def draw_label(c: canvas.Canvas, x, y, w, h, rec, font_name, fs_big, fs_mid, fs_small, line_gap):
|
| 140 |
+
"""
|
| 141 |
+
์ขํ๊ณ: reportlab์ ์ขํ๋จ์ด ์์ .
|
| 142 |
+
x,y = ๋ผ๋ฒจ ์ขํ๋จ. w,h = ๋ผ๋ฒจ ํฌ๊ธฐ.
|
| 143 |
+
"""
|
| 144 |
+
# ์ฌ๋ฐฑ
|
| 145 |
+
inner_x = x + pad_x * mm
|
| 146 |
+
inner_y = y + pad_y * mm
|
| 147 |
+
inner_w = w - 2 * pad_x * mm
|
| 148 |
+
inner_h = h - 2 * pad_y * mm
|
| 149 |
+
|
| 150 |
+
# ์๋จ ๊ตต์ ์ค: ๋ฐ์ค๋ฒํธ
|
| 151 |
+
c.setFont(font_name, fs_big)
|
| 152 |
+
boxno = rec.get("๋ฐ์ค๋ฒํธ", "")
|
| 153 |
+
c.drawString(inner_x, inner_y + inner_h - fs_big*1.1, f"{boxno}")
|
| 154 |
+
|
| 155 |
+
# 2ํ: (์์ฐ์ฐ๋/๋ณด์กด๊ธฐ๊ฐ)
|
| 156 |
+
c.setFont(font_name, fs_mid)
|
| 157 |
+
prod = rec.get("์์ฐ์ฐ๋","")
|
| 158 |
+
keep = rec.get("๋ณด์กด๊ธฐ๊ฐ","") or ""
|
| 159 |
+
line_y = inner_y + inner_h - fs_big*1.1 - fs_mid*1.5
|
| 160 |
+
c.drawString(inner_x, line_y, f"{prod} {keep}")
|
| 161 |
+
|
| 162 |
+
# 3ํ: ๋จ์์
๋ฌด / ๊ธฐ๋ก๋ฌผ์ฒ (์์ผ๋ฉด)
|
| 163 |
+
line_y -= fs_mid * 1.2
|
| 164 |
+
unit = rec.get("๋จ์์
๋ฌด","") or ""
|
| 165 |
+
series = rec.get("๊ธฐ๋ก๋ฌผ์ฒ ","") or ""
|
| 166 |
+
if unit or series:
|
| 167 |
+
c.setFont(font_name, fs_mid)
|
| 168 |
+
c.drawString(inner_x, line_y, f"{unit} {series}")
|
| 169 |
+
line_y -= fs_mid * 1.0
|
| 170 |
+
|
| 171 |
+
# ๋ชฉ๋ก(์ฌ๋ฌ ์ค, ์์ ๊ธ์จ)
|
| 172 |
+
c.setFont(font_name, fs_small)
|
| 173 |
+
list_text = rec.get("๋ชฉ๋ก","") or ""
|
| 174 |
+
for ln in list_text.split("\n"):
|
| 175 |
+
if line_y < inner_y + fs_small * 1.2: # ๋ผ๋ฒจ ํ๋จ ๋์ด๊ฐ๋ฉด ์ค๋จ
|
| 176 |
+
break
|
| 177 |
+
c.drawString(inner_x, line_y, ln)
|
| 178 |
+
line_y -= fs_small * line_gap
|
| 179 |
+
|
| 180 |
+
def make_pdf(records):
|
| 181 |
+
buffer = BytesIO()
|
| 182 |
+
if page_size == "A4":
|
| 183 |
+
pw, ph = A4
|
| 184 |
+
else:
|
| 185 |
+
pw, ph = A4
|
| 186 |
+
|
| 187 |
+
c = canvas.Canvas(buffer, pagesize=(pw, ph))
|
| 188 |
+
c.setAuthor("BoxLabel")
|
| 189 |
+
c.setTitle("Box Labels")
|
| 190 |
+
|
| 191 |
+
pdfmetrics.getFont(font_name) # ensure registered
|
| 192 |
+
|
| 193 |
+
# ์ขํ/ํฌ๊ธฐ(mm โ pt)
|
| 194 |
+
L = margin_left * mm
|
| 195 |
+
T = margin_top * mm
|
| 196 |
+
W = label_w * mm
|
| 197 |
+
H = label_h * mm
|
| 198 |
+
GX = gap_x * mm
|
| 199 |
+
GY = gap_y * mm
|
| 200 |
+
|
| 201 |
+
per_page = int(rows * cols)
|
| 202 |
+
total_pages = math.ceil(len(records) / per_page) if records else 1
|
| 203 |
+
|
| 204 |
+
idx = 0
|
| 205 |
+
for p in range(total_pages):
|
| 206 |
+
for r in range(int(rows)):
|
| 207 |
+
for ccol in range(int(cols)):
|
| 208 |
+
if idx >= len(records):
|
| 209 |
+
break
|
| 210 |
+
# ์ขํ ๊ณ์ฐ (์ขํ๋จ ์์ ์ด๋ฏ๋ก ์๋จ์์ ๋ด๋ ค์ค๊ฒ Y๋ฅผ ์กฐ์ )
|
| 211 |
+
x = L + ccol * (W + GX)
|
| 212 |
+
y_top = ph - T - r * (H + GY)
|
| 213 |
+
y = y_top - H
|
| 214 |
+
draw_label(c, x, y, W, H, records[idx], font_name, fs_big, fs_mid, fs_small, line_gap)
|
| 215 |
+
idx += 1
|
| 216 |
+
if idx >= len(records):
|
| 217 |
+
break
|
| 218 |
+
c.showPage()
|
| 219 |
+
c.save()
|
| 220 |
+
buffer.seek(0)
|
| 221 |
+
return buffer
|
| 222 |
+
|
| 223 |
+
# -----------------
|
| 224 |
+
# ๋ฉ์ธ ๋์
|
| 225 |
+
# -----------------
|
| 226 |
+
if df is not None:
|
| 227 |
+
# ๋ฏธ๋ฆฌ๋ณด๊ธฐ
|
| 228 |
+
st.subheader("๐ ๋ฐ์ดํฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
st.dataframe(df.head(10), use_container_width=True)
|
| 230 |
|
| 231 |
+
records = build_records(df)
|
| 232 |
+
st.write(f"์ด **{len(records)}**๊ฐ ๋ฐ์ค๊ฐ ๊ฐ์ง๋์์ต๋๋ค.")
|
| 233 |
+
default_sel = [r["๋ฐ์ค๋ฒํธ"] for r in records]
|
| 234 |
+
sel = st.multiselect("์์ฑํ ๋ฐ์ค๋ฒํธ ์ ํ (๋น์ฐ๋ฉด ์ ์ฒด)", options=default_sel)
|
| 235 |
+
if sel:
|
| 236 |
+
records = [r for r in records if r["๋ฐ์ค๋ฒํธ"] in set(sel)]
|
| 237 |
+
|
| 238 |
+
if st.button("๐ PDF ์์ฑ"):
|
| 239 |
+
pdf = make_pdf(records)
|
| 240 |
+
st.download_button("โฌ๏ธ PDF ๋ค์ด๋ก๋", data=pdf.getvalue(), file_name="box_labels.pdf", mime="application/pdf")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|