255 lines
12 KiB
Python
255 lines
12 KiB
Python
"""Persian message templates (the ERP's user-facing layer is Persian)."""
|
|
import html
|
|
from datetime import date
|
|
|
|
STATUS_FA = {
|
|
"offered": "🔵 در انتظار بررسی",
|
|
"accepted": "🟢 پذیرفتهشده",
|
|
"arrived": "✅ رسیده",
|
|
"rejected": "🔴 ردشده",
|
|
"expired": "⚪️ منقضی",
|
|
}
|
|
|
|
MISSING_MARKS = {True: "✅", False: "❌"}
|
|
WARNING_MARK = "⚠️"
|
|
|
|
|
|
def esc(value) -> str:
|
|
return html.escape(str(value if value is not None else "—"))
|
|
|
|
|
|
def fa_date(value: str | None) -> str:
|
|
if not value:
|
|
return "—"
|
|
return str(value)[:10]
|
|
|
|
|
|
WELCOME = (
|
|
"سلام! 👋 من دستیار تدارکات هستم.\n\n"
|
|
"میتوانی با پیام متنی یا <b>صوتی</b> فعالیتی تعریف کنی — مثلاً:\n"
|
|
"«سازان نو برای پروژه برج آسمان، ۵۰۰ کیلو میلگرد A3، هر کیلو ۴۵ تومن، پرداخت ۳ فاز»\n\n"
|
|
"من هر چه را فهمیدم بهصورت پیشنویس آماده میکنم، چیزی که فهمیدم و چیزهای خالی را نشان "
|
|
"میدهم و فقط با تأیید تو در سیستم ثبت میکنم.\n\n"
|
|
"دستورها:\n"
|
|
"/drafts — پیشنویسهای باز\n"
|
|
"/cancel — لغو پیشنویس/عملیات جاری\n"
|
|
"/whoami — اتصال من\n"
|
|
"/unlink — قطع اتصال\n"
|
|
"/help — راهنما"
|
|
)
|
|
|
|
HELP = (
|
|
"<b>چه کارهایی میتوانم بکنم؟</b>\n\n"
|
|
"🧾 <b>ثبت استعلام</b> — تأمینکننده، پروژه، اقلام (کالا/مقدار/واحد)، قیمت و شرایط پرداخت را از گفتگو درمیآورم.\n"
|
|
"🏢 <b>افزودن تأمینکننده</b> — «تأمینکننده جدید فلانی، دسته لوله و اتصالات، تلفن ...»\n"
|
|
"📦 <b>افزودن کالا</b> — «کالای جدید میلگرد A3 با واحد کیلوگرم»\n"
|
|
"📊 <b>پرسوجو</b> — «استعلامهای در انتظار؟»، «کدامها بهزودی منقضی میشوند؟»\n"
|
|
"✔️ <b>پذیرش استعلام</b> — «استعلام سازان نو را قبول کن»\n"
|
|
"🚚 <b>اعلام رسیدن</b> — «اقلام سازان نو رسید، ۵۰۰ کیلو»\n\n"
|
|
"هر ثبت فقط با دکمه ✅ انجام میشود. با /cancel هر چیز نیمهکاره را لغو کن."
|
|
)
|
|
|
|
LINK_START = (
|
|
"برای اتصال حساب Bale به حساب ERP، <b>نام کاربری Casdoor</b> خود را بفرست "
|
|
"(یا اگر رمز جدا داری همان نام کاربری سامانه). برای لغو: /cancel"
|
|
)
|
|
LINK_ASK_PASSWORD = "ممنون. حالا <b>رمز عبور</b> را بفرست (فقط برای یک بار ورود؛ ذخیره نمیشود). /cancel برای لغو"
|
|
LINK_OK = "✅ اتصال انجام شد: <b>{name}</b> ({role})\nحالا میتوانی فعالیتها را بفرستی. /help را ببین."
|
|
LINK_FAIL = "ورود ناموفق بود: {detail}\nدوباره /start بزن یا نام کاربری را بفرست."
|
|
ADMIN_NO_LINK = "حالت ادمین (بدون اتصال) فعال است؛ ثبتها بدون انتساب کاربر انجام میشود."
|
|
|
|
DRAFTS_LIST_HEADER = "📋 پیشنویسهای باز:"
|
|
DRAFTS_EMPTY = "پیشنویس بازی نداری. یک فعالیت را با متن یا وویس بفرست."
|
|
NOTHING_TO_COMMIT = "پیشنویس بازی برای ثبت نیست؛ اول یک فعالیت بفرست."
|
|
CANCELLED = "لغو شد. ✔️"
|
|
UNKNOWN_HINT = (
|
|
"متوجه منظورت نشد 🤔\n"
|
|
"میتوانی مثلاً بگویی: «ثبت استعلام از سازان نو برای ۲۰۰ عدد شیر فلکه» یا /help را ببینی."
|
|
)
|
|
UNLINKED = "ابتدا حساب خود را متصل کن: /start"
|
|
AI_NOT_CONFIGURED = (
|
|
"پردازش هوشمند تنظیم نشده است (متغیرهای PROCUREMENT_BOT_AI_*). "
|
|
"با مدیر سیستم تماس بگیر."
|
|
)
|
|
STT_FAILED = "نتوانستم پیام صوتی را تبدیل کنم: {detail}"
|
|
VOICE_EMPTY = "از پیام صوتی متنی شنیده نشد؛ دوباره بگو یا با متن بفرست."
|
|
ERR_GENERIC = "خطا: {detail}"
|
|
PICK_QUOTE = "چند استعلام پیدا شد؛ کدام را میخواهی؟"
|
|
QUOTE_NOT_FOUND = "استعلامی برای «{q}» پیدا نکردم."
|
|
ACCEPT_REPORT = (
|
|
"✅ پذیرش انجام شد\n"
|
|
"استعلام #{id}\n"
|
|
"وضعیت: {status}\n"
|
|
"اقلام تصمیمگیریشده: {count}"
|
|
)
|
|
ARRIVE_REPORT = (
|
|
"🚚 ثبت رسید انجام شد\n"
|
|
"استعلام #{id} — وضعیت: {status}\n{lines}"
|
|
)
|
|
ACCEPT_DONE_TS = "شرایط انتخابشده: {label}"
|
|
|
|
|
|
def missing_block(missing: list[dict]) -> str:
|
|
"""✅/❌ list of what's captured vs. still empty (⚠️ = nice-to-have)."""
|
|
if not missing:
|
|
return "همه فیلدها کامل است ✅"
|
|
lines = []
|
|
for m in missing:
|
|
mark = MISSING_MARKS[m["ok"]] if m["blocking"] else (MISSING_MARKS.get(m["ok"], WARNING_MARK))
|
|
if not m["blocking"] and not m["ok"]:
|
|
mark = WARNING_MARK
|
|
lines.append(f"{mark} {m['label']}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def quote_review_text(payload: dict, missing: list[dict], status_note: str | None = None) -> str:
|
|
lines = ["🧾 پیشنویس استعلام:"]
|
|
if status_note:
|
|
lines.append(status_note)
|
|
sup = payload.get("supplier_name") or "—"
|
|
if payload.get("supplier_id"):
|
|
sup_label = f"{esc(sup)} ✅"
|
|
elif payload.get("supplier_name"):
|
|
sup_label = f"{esc(sup)} ✨ (تأمینکننده جدید)"
|
|
else:
|
|
sup_label = "—"
|
|
lines.append(f"تأمینکننده: {sup_label}")
|
|
proj_names = payload.get("project_names") or ([payload["project_name"]] if payload.get("project_name") else [])
|
|
if proj_names:
|
|
lines.append(f"پروژه(ها): {esc('، '.join(proj_names))}")
|
|
lines.append(f"نوع: {'رسمی' if payload.get('is_official', True) else 'برآوردی'}")
|
|
if payload.get("expiry_date"):
|
|
lines.append(f"اعتبار تا: {fa_date(payload['expiry_date'])}")
|
|
|
|
items = payload.get("items") or []
|
|
if items:
|
|
lines.append("\n📦 اقلام:")
|
|
for i, it in enumerate(items, start=1):
|
|
name = it.get('material_name') or '?'
|
|
if it.get('brand_name'):
|
|
name += f" ({it['brand_name']})"
|
|
parts = [f"{i}. {esc(name)}"]
|
|
if it.get('specs'):
|
|
parts.append(esc("، ".join(it['specs'])))
|
|
if it.get("quantity"):
|
|
parts.append(f"مقدار {esc(it['quantity'])}")
|
|
unit = it.get("unit_name") or ("(واحد کالا)" if it.get("unit_id") else None)
|
|
if unit:
|
|
parts.append(str(unit))
|
|
if it.get("unit_price"):
|
|
parts.append(f"قیمت {esc(it['unit_price'])}")
|
|
if not it.get("material_id"):
|
|
parts.append("✨ کالای جدید")
|
|
lines.append(" — ".join(str(p) for p in parts))
|
|
else:
|
|
lines.append("\nاقلام: هنوز چیزی ثبت نشده ❌")
|
|
|
|
sheets = payload.get("termsheets") or []
|
|
if sheets:
|
|
ts_parts = []
|
|
for t in sheets:
|
|
lbl = esc(t.get('label'))
|
|
if t.get("milestones"):
|
|
steps_txt = " + ".join(f"{fa_date(m.get('due_date'))}: {esc(m.get('amount'))}" for m in t["milestones"] if m.get("amount"))
|
|
if steps_txt:
|
|
lbl += f" [{steps_txt}]"
|
|
elif t.get("discount_value"):
|
|
lbl += f" (تخفیف {esc(t.get('discount_value'))})"
|
|
ts_parts.append(lbl)
|
|
lines.append("\n💳 شرایط پرداخت: " + "؛ ".join(ts_parts))
|
|
if payload.get("notes"):
|
|
lines.append(f"یادداشت: {esc(payload['notes'])}")
|
|
|
|
lines.append("\n📋 وضعیت فیلدها:")
|
|
lines.append(missing_block(missing))
|
|
lines.append("\nبا پیام بعدی میتوانی چیزهای خالی را تکمیل کنی، یا با دکمهها ثبت/لغو کنی.")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def supplier_review_text(payload: dict, missing: list[dict]) -> str:
|
|
lines = ["🏢 <b>پیشنویس تأمینکننده جدید</b>", f"نام: <b>{esc(payload.get('name'))}</b>"]
|
|
lines.append(f"دسته: {esc(payload.get('category_name'))}")
|
|
if payload.get("phone"):
|
|
lines.append(f"تلفن: {esc(payload['phone'])}")
|
|
if payload.get("location"):
|
|
lines.append(f"موقعیت: {esc(payload['location'])}")
|
|
if payload.get("contact_person"):
|
|
lines.append(f"رابط: {esc(payload['contact_person'])}")
|
|
lines.append("\n<b>وضعیت فیلدها:</b>")
|
|
lines.append(missing_block(missing))
|
|
return "\n".join(lines)
|
|
|
|
|
|
def material_review_text(payload: dict, missing: list[dict]) -> str:
|
|
lines = ["📦 <b>پیشنویس کالای جدید</b>", f"نام: <b>{esc(payload.get('name'))}</b>"]
|
|
lines.append(f"واحد اصلی: {esc(payload.get('unit_name'))}")
|
|
if payload.get("topic"):
|
|
lines.append(f"تاپیک: {esc(payload['topic'])}")
|
|
lines.append("\n<b>وضعیت فیلدها:</b>")
|
|
lines.append(missing_block(missing))
|
|
return "\n".join(lines)
|
|
|
|
|
|
def quote_committed_report(created: dict, payload: dict, remaining: list[str]) -> str:
|
|
lines = [f"✅ <b>ثبت شد</b> — استعلام #{esc(created.get('id'))}"]
|
|
sup = (created.get("supplier") or {}).get("name") or payload.get("supplier_name")
|
|
if sup:
|
|
lines.append(f"تأمینکننده: {esc(sup)}")
|
|
items = created.get("line_items") or []
|
|
if items:
|
|
lines.append(f"اقلام: {len(items)}")
|
|
sheets = created.get("termsheets") or []
|
|
if sheets:
|
|
lines.append(f"شرایط پرداخت: {len(sheets)}")
|
|
if payload.get("project_id"):
|
|
lines.append("پروژه: پیوست شد")
|
|
if remaining:
|
|
lines.append("\n<b>خالی ماندهها (بعداً میتوانی در برنامه تکمیل کنی):</b>")
|
|
lines += [f"⚠️ {esc(r)}" for r in remaining]
|
|
lines.append("\nمرحله بعد: وقتی تصمیم گرفتی، بگو «استعلام را قبول کن» تا پذیرش را با هم انجام دهیم.")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def supplier_committed_report(created: dict) -> str:
|
|
return (
|
|
f"✅ <b>تأمینکننده ثبت شد</b> — {esc(created.get('name'))}\n"
|
|
f"دسته: {esc((created.get('category') or {}).get('name'))}"
|
|
)
|
|
|
|
|
|
def material_committed_report(created: dict) -> str:
|
|
return f"✅ <b>کالا ثبت شد</b> — {esc(created.get('name'))}"
|
|
|
|
|
|
def quote_line(quote: dict) -> str:
|
|
sup = (quote.get("supplier") or {}).get("name") or quote.get("supplier_id") or "—"
|
|
items = quote.get("line_items") or []
|
|
item_names = []
|
|
for it in items:
|
|
mat = (it.get("material") or {}).get("name")
|
|
if mat:
|
|
qty = it.get("quantity")
|
|
item_names.append(f"{mat} ({qty})" if qty else mat)
|
|
tail = f" — {'، '.join(item_names[:3])}" + ("…" if len(item_names) > 3 else "") if item_names else ""
|
|
return (
|
|
f"#{str(quote.get('id'))[:8]} — {esc(sup)} — {STATUS_FA.get(quote.get('status'), quote.get('status'))} — "
|
|
f"{fa_date(quote.get('quote_date'))}{tail}"
|
|
)
|
|
|
|
|
|
def status_report(kind: str, quotes: list[dict], extra_note: str | None = None) -> str:
|
|
titles = {
|
|
"pending": "🔵 استعلامهای در انتظار بررسی",
|
|
"accepted": "🟢 پذیرفتهشده (در انتظار تحویل)",
|
|
"arrived": "✅ رسیده",
|
|
"expiring": "⏳ بهزودی منقضی میشوند (۷ روز آینده)",
|
|
"recent": "🕘 آخرین استعلامها",
|
|
}
|
|
if not quotes:
|
|
return f"{titles.get(kind, 'نتیجهای')}\nموردی نیست."
|
|
lines = [titles.get(kind, "استعلامها")]
|
|
if extra_note:
|
|
lines.append(extra_note)
|
|
lines += [f"• {quote_line(q)}" for q in quotes[:10]]
|
|
return "\n".join(lines)
|