mrfirdauss commited on
Commit
fd85f3e
·
1 Parent(s): 0a55474

feat: schengen

Browse files
app/util/{JapanMultiEntryvisaLetterGenerator.py → japan_multientry_visa_letter_generator.py} RENAMED
@@ -1,6 +1,6 @@
1
  import datetime
2
  from fpdf import FPDF
3
- from .PDFDocumentGenerator import PDFDocumentGenerator # The fpdf2-based one
4
 
5
  class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
6
  """
@@ -26,7 +26,6 @@ class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
26
  formatted_date = today.strftime("%d, %B, %Y")
27
 
28
  pdf.set_font('Arial', '', 11)
29
- pdf.set_margins(25, 25)
30
  pdf.set_y(25)
31
  pdf.set_left_margin(25)
32
 
 
1
  import datetime
2
  from fpdf import FPDF
3
+ from .pdf_document_generator import PDFDocumentGenerator # The fpdf2-based one
4
 
5
  class JapanMultiEntryVisaLetterGenerator(PDFDocumentGenerator):
6
  """
 
26
  formatted_date = today.strftime("%d, %B, %Y")
27
 
28
  pdf.set_font('Arial', '', 11)
 
29
  pdf.set_y(25)
30
  pdf.set_left_margin(25)
31
 
app/util/{PDFDocumentGenerator.py → pdf_document_generator.py} RENAMED
@@ -17,6 +17,7 @@ class PDFDocumentGenerator(ABC):
17
  """
18
  self.data = data
19
 
 
20
  @abstractmethod
21
  def build_document(self, pdf: FPDF):
22
  """
@@ -37,24 +38,20 @@ class PDFDocumentGenerator(ABC):
37
  On failure, (None, error_message).
38
  """
39
  try:
40
- # 1. Initialize the FPDF object
41
  pdf = FPDF()
42
  pdf.add_page()
43
 
44
- # 2. Let the subclass build the document
45
  self.build_document(pdf)
46
 
47
- # 3. Define file path and save the document
48
  pdf_filename = "document.pdf"
49
  pdf_filepath = os.path.join(tempdir, pdf_filename)
50
 
51
  pdf.output(pdf_filepath)
52
 
53
- # 4. Check if PDF was successfully created
54
  if not os.path.exists(pdf_filepath):
55
  return None, "PDF file was not created by fpdf2."
56
 
57
- # Success
58
  return pdf_filepath, None
59
 
60
  except Exception as e:
 
17
  """
18
  self.data = data
19
 
20
+
21
  @abstractmethod
22
  def build_document(self, pdf: FPDF):
23
  """
 
38
  On failure, (None, error_message).
39
  """
40
  try:
 
41
  pdf = FPDF()
42
  pdf.add_page()
43
 
 
44
  self.build_document(pdf)
45
 
 
46
  pdf_filename = "document.pdf"
47
  pdf_filepath = os.path.join(tempdir, pdf_filename)
48
 
49
  pdf.output(pdf_filepath)
50
 
 
51
  if not os.path.exists(pdf_filepath):
52
  return None, "PDF file was not created by fpdf2."
53
 
54
+ pdf.set_margins(25, 25)
55
  return pdf_filepath, None
56
 
57
  except Exception as e:
app/util/schengen_visa_letter_generator.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ from fpdf import FPDF
3
+ from .pdf_document_generator import PDFDocumentGenerator
4
+
5
+
6
+ class SchengenVisaLetterGenerator(PDFDocumentGenerator):
7
+ """
8
+ Generates a Schengen Visa Cover Letter PDF (Individual or Group)
9
+ using the shared PDFDocumentGenerator base for layout consistency.
10
+ """
11
+
12
+ def __init__(self, data: dict, trip_type: str = "individual"):
13
+ super().__init__(data)
14
+ self.trip_type = trip_type.lower()
15
+
16
+ def build_document(self, pdf):
17
+ """
18
+ Builds the formatted PDF content.
19
+ This method is called by the base class `compile_pdf()`.
20
+ """
21
+ personal = self.data.get("personal_details", {})
22
+ members = self.data.get("group_members", [])
23
+
24
+ # --- Extract fields ---
25
+ city = self.data.get("city", "Jakarta")
26
+ date = self.data.get("date", "18th July 2024")
27
+ country = self.data.get("country", "[Schengen Country Name]")
28
+ purpose = self.data.get("purpose", "[tourism/family visit]")
29
+ main_dest = self.data.get("main_destination", "[country]")
30
+ event = self.data.get("event", "[event/activities]")
31
+ other_dest = self.data.get("other_destinations", "")
32
+ start = self.data.get("travel_start", "[start date]")
33
+ end = self.data.get("travel_end", "[end date]")
34
+ duration = self.data.get("duration", "[X days and Y nights]")
35
+ trip_highlight = self.data.get("trip_highlight", "[trip highlight]")
36
+ contact_email = self.data.get("contact_email", "[email]")
37
+ contact_phone = self.data.get("contact_phone", "[phone number]")
38
+ job_commitment = self.data.get("job_commitment", "[your job title or role]")
39
+ financial_status = self.data.get("financial_status", "sound")
40
+
41
+ pdf.set_font("Times", "B", 14)
42
+ # title = (
43
+ # "COVER LETTER FOR GROUP TRIP"
44
+ # if self.trip_type == "group"
45
+ # else "COVER LETTER FOR INDIVIDUAL TRIP"
46
+ # )
47
+ # pdf.cell(0, 10, title, ln=True, align="C")
48
+ # pdf.ln(10)
49
+
50
+ pdf.set_font("Times", "", 12)
51
+ pdf.multi_cell(0, 8, f"{city}, {date}")
52
+ pdf.multi_cell(0, 8, f"To: Embassy of {country} Jakarta")
53
+ pdf.multi_cell(0, 8, "Subject: Application for Schengen Visa")
54
+ pdf.ln(6)
55
+
56
+ # --- Opening Paragraph ---
57
+ body = (
58
+ f"Dear Sir/Madam,\n\n"
59
+ f"I am writing to apply for a Schengen {purpose} visa for the purpose of visiting {main_dest} on {event}. "
60
+ )
61
+ if other_dest:
62
+ body += f"Following this, I plan to continue my journey to {other_dest}. "
63
+ body += (
64
+ f"My travel period is from {start} to {end}, covering a total of {duration}."
65
+ )
66
+ pdf.multi_cell(0, 8, body)
67
+ pdf.ln(8)
68
+
69
+ # --- Personal Details ---
70
+ pdf.set_font("Times", "B", 12)
71
+ pdf.cell(0, 10, "Personal Details:", ln=True)
72
+ pdf.set_font("Times", "", 12)
73
+ pdf.multi_cell(
74
+ 0,
75
+ 8,
76
+ f"Name\t\t: {personal.get('name', '')}\n"
77
+ f"Date of Birth\t: {personal.get('dob', '')}\n"
78
+ f"Nationality\t: {personal.get('nationality', '')}\n"
79
+ f"Occupation\t: {personal.get('occupation', '')}\n"
80
+ f"Passport Number\t: {personal.get('passport_number', '')}",
81
+ )
82
+ pdf.ln(6)
83
+
84
+ # --- Group Section ---
85
+ if self.trip_type == "group" and members:
86
+ pdf.multi_cell(0, 8, f"I will be attending the {event} with my family members:\n")
87
+ for m in members:
88
+ pdf.multi_cell(
89
+ 0,
90
+ 8,
91
+ f"Relationship\t: {m.get('relationship', '')}\n"
92
+ f"Name\t\t: {m.get('name', '')}\n"
93
+ f"Date of Birth\t: {m.get('dob', '')}\n"
94
+ f"Occupation\t: {m.get('occupation', '')}\n"
95
+ f"Nationality\t: {m.get('nationality', '')}\n"
96
+ f"Passport Number\t: {m.get('passport_number', '')}\n",
97
+ )
98
+ pdf.ln(2)
99
+ pdf.multi_cell(
100
+ 0,
101
+ 8,
102
+ f"We will travel together to {trip_highlight}, with a detailed itinerary attached.\n",
103
+ )
104
+ pdf.ln(6)
105
+
106
+ # --- Supporting Documents ---
107
+ pdf.set_font("Times", "B", 12)
108
+ pdf.cell(0, 10, "We will also provide the following supporting documents:", ln=True)
109
+ pdf.set_font("Times", "", 12)
110
+ pdf.multi_cell(
111
+ 0,
112
+ 8,
113
+ "- Completed visa application form\n"
114
+ "- Recent photographs\n"
115
+ "- Copy of passport\n"
116
+ "- Proof of travel insurance\n"
117
+ "- Round-trip flight itinerary\n"
118
+ "- Hotel accommodation reservation\n"
119
+ "- Bank statements\n"
120
+ "- Statement letter and proof of current employment\n"
121
+ "- Copy of family documents or marriage certificate\n"
122
+ "- Copy of passport",
123
+ )
124
+ pdf.ln(6)
125
+
126
+ # --- Commitment & Return ---
127
+ pdf.multi_cell(
128
+ 0,
129
+ 8,
130
+ f"Commitment and Return: I assure you that I/we will adhere to all Schengen visa regulations and return to Indonesia as planned. "
131
+ f"I am a {job_commitment}, committed to my duties in Indonesia. "
132
+ f"My financial status is {financial_status}, and sufficient proof of funds is attached to cover all expenses for the trip. "
133
+ f"I guarantee our prompt return upon completion of the journey.\n\n"
134
+ f"We would be grateful if you could consider our application favorably and issue the necessary visa for the trip. "
135
+ f"Should you require any further details, please contact me at {contact_email} or {contact_phone}.",
136
+ )
137
+ pdf.ln(8)
138
+
139
+ # --- Closing ---
140
+ pdf.multi_cell(0, 8, "Thank you for your time and kind attention.\n\nBest regards,\n\n")
141
+ pdf.multi_cell(0, 8, personal.get("name", ""))
142
+
143
+ return pdf
server.py CHANGED
@@ -8,12 +8,12 @@ from dotenv import load_dotenv
8
  import json
9
  import requests
10
  import uuid
11
- import asyncio
12
  import io
13
 
14
  from app.util.gen_ai_base import GenAIBaseClient
15
  from app.util.browser_agent import BrowserAgent
16
- from app.util.JapanMultiEntryvisaLetterGenerator import JapanMultiEntryVisaLetterGenerator
17
  import sys
18
  sys.stdout.reconfigure(line_buffering=True)
19
  API = "https://api-dev.spun.global"
@@ -112,25 +112,43 @@ def create_app() -> Flask:
112
  # else:
113
  # return jsonify({"error": "Failed to retrieve visa information"}), 404
114
  # return jsonify({"error": "Unexpected error"}), 500
115
- @app.route('/generate/japan-multientry-tourist', methods=['POST'])
116
- def generate_japan_multientry_tourist():
 
117
  """
118
- Generates a Japan Visa Letter PDF and returns it as a downloadable file.
119
- Expects JSON payload:
120
- {
121
- "name": "Your Name",
122
- "address": "Your Address Line 1",
123
- "city_postal": "City, Postal Code",
124
- "email": "your.email@example.com",
125
- "phone": "+123456789"
126
- }
127
  """
128
  try:
129
  data = request.get_json()
130
  if not data:
131
  return jsonify({"error": "No JSON payload provided"}), 400
132
 
133
- letter_generator = JapanMultiEntryVisaLetterGenerator(data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
  with tempfile.TemporaryDirectory() as tempdir:
136
  pdf_filepath, error_msg = letter_generator.compile_pdf(tempdir)
@@ -147,11 +165,11 @@ def create_app() -> Flask:
147
  pdf_bytes,
148
  mimetype="application/pdf",
149
  as_attachment=True,
150
- download_name="Japan_Visa_Application_Letter.pdf"
151
  )
152
 
153
  except Exception as e:
154
- print(f"Error in route: {e}")
155
  return jsonify({"error": str(e)}), 500
156
 
157
  @app.route('/', methods=['GET'])
 
8
  import json
9
  import requests
10
  import uuid
11
+ import importlib
12
  import io
13
 
14
  from app.util.gen_ai_base import GenAIBaseClient
15
  from app.util.browser_agent import BrowserAgent
16
+ from app.util.japan_multientry_visa_letter_generator import JapanMultiEntryVisaLetterGenerator
17
  import sys
18
  sys.stdout.reconfigure(line_buffering=True)
19
  API = "https://api-dev.spun.global"
 
112
  # else:
113
  # return jsonify({"error": "Failed to retrieve visa information"}), 404
114
  # return jsonify({"error": "Unexpected error"}), 500
115
+
116
+ @app.route('/generate/<visa_type>', methods=['POST'])
117
+ def generate_visa_letter(visa_type):
118
  """
119
+ Dynamically generates visa letters based on <visa_type>.
120
+ Example:
121
+ POST /generate/schengen
122
+ POST /generate/japan-multientry-tourist
 
 
 
 
 
123
  """
124
  try:
125
  data = request.get_json()
126
  if not data:
127
  return jsonify({"error": "No JSON payload provided"}), 400
128
 
129
+ # Map visa types to their module and class
130
+ generator_map = {
131
+ "japan-multientry-tourist": (
132
+ "app.util.japan_multientry_visa_letter_generator",
133
+ "JapanMultiEntryVisaLetterGenerator",
134
+ ),
135
+ "schengen": (
136
+ "app.util.schengen_visa_letter_generator",
137
+ "SchengenVisaLetterGenerator",
138
+ ),
139
+ }
140
+
141
+ if visa_type.lower() not in generator_map:
142
+ return jsonify({"error": f"Unsupported visa type: {visa_type}"}), 400
143
+
144
+ module_path, class_name = generator_map[visa_type.lower()]
145
+
146
+ # ✅ Import dynamically using importlib
147
+ module = importlib.import_module(module_path)
148
+ generator_class = getattr(module, class_name)
149
+
150
+ # ✅ Generate the PDF
151
+ letter_generator = generator_class(data)
152
 
153
  with tempfile.TemporaryDirectory() as tempdir:
154
  pdf_filepath, error_msg = letter_generator.compile_pdf(tempdir)
 
165
  pdf_bytes,
166
  mimetype="application/pdf",
167
  as_attachment=True,
168
+ download_name=f"{visa_type.replace('-', '_')}_visa_letter.pdf",
169
  )
170
 
171
  except Exception as e:
172
+ print(f"Error in /generate/{visa_type}: {e}")
173
  return jsonify({"error": str(e)}), 500
174
 
175
  @app.route('/', methods=['GET'])