sample offer emial and tempalte

Offer_Email_Format_1
ahmed.mujtaba 2026-09-16 18:24:10 +05:00
parent 5254bdb964
commit 65b39e9bff
4 changed files with 137 additions and 74 deletions

View File

@ -26,7 +26,7 @@ from g_sheet.app import router as g_sheet_router
from s3.app import router as s3_router
from department.app import router as department_router
from verification_check.app import router as verification_check_router
from reference_check.app import router as reference_check_router
# from reference_check.app import router as reference_check_router
logging.basicConfig(level=logging.INFO,format="%(levelname)-8s %(name)s: %(message)s")
logger=logging.getLogger("main")
@ -141,4 +141,4 @@ app.include_router(g_sheet_router)
app.include_router(s3_router)
app.include_router(department_router)
app.include_router(verification_check_router)
app.include_router(reference_check_router)
# app.include_router(reference_check_router)

View File

@ -1,49 +0,0 @@
-- 043_reference_check.sql
-- Annexure G - Employment Reference Check Form as its own table
-- (backend/reference_check/models.py, ReferenceCheck). One row per referee, so
-- a candidate can have several. recruiter_id and candidate_id both point at
-- users.id (recruiters and candidates are users in this system); created_by is
-- the logged-in user who filled the form.
--
-- The table name is mixed case at the user's request, so it must stay quoted
-- ("Reference_Check") in any raw SQL. FK / index names follow the db_setup
-- NAMING_CONVENTION so a dev DB that autogenerated first is a no-op.
--
-- Idempotent, applied at startup by alembic_setup.run_manual_sql(). Needed
-- because prod boots with DB_AUTOGENERATE=false and never autogenerates tables.
CREATE TABLE IF NOT EXISTS app."Reference_Check" (
id uuid PRIMARY KEY,
recruiter_id uuid,
candidate_id uuid,
position_applied varchar,
referee_name varchar,
date_time timestamptz,
referee_title varchar,
referee_organization varchar,
relationship_duration varchar,
scope_of_relationship varchar,
work_approach varchar,
leadership varchar,
communication varchar,
agility varchar,
signoff_referee_name varchar,
signoff_designation varchar,
signoff_date date,
created_by uuid,
created_at timestamptz NOT NULL DEFAULT NOW(),
updated_at timestamptz NOT NULL DEFAULT NOW(),
is_deleted boolean NOT NULL DEFAULT false,
CONSTRAINT "fk_Reference_Check_recruiter_id_users"
FOREIGN KEY (recruiter_id) REFERENCES app.users (id),
CONSTRAINT "fk_Reference_Check_candidate_id_users"
FOREIGN KEY (candidate_id) REFERENCES app.users (id),
CONSTRAINT "fk_Reference_Check_created_by_users"
FOREIGN KEY (created_by) REFERENCES app.users (id)
);
CREATE INDEX IF NOT EXISTS "ix_Reference_Check_recruiter_id"
ON app."Reference_Check" (recruiter_id);
CREATE INDEX IF NOT EXISTS "ix_Reference_Check_candidate_id"
ON app."Reference_Check" (candidate_id);

View File

@ -29,17 +29,26 @@ def _recruiter_names(recruiter_names):
return ", ".join(escape(n) for n in names[:-1]) + f" & {escape(names[-1])}"
def _package_row(label, value, bold=False):
cell = "border:1px solid #000000;padding:4px 8px;vertical-align:top;"
def _package_row(label, value, bold=False, widths=(174, 449), bold_label=False, valign="top"):
cell = f"border:1px solid #000000;padding:1px 4px;vertical-align:{valign};"
shown = f"<strong>{value}</strong>" if bold else value
label = f"<strong>{label}</strong>" if bold_label else label
return (
"<tr>"
f'<td width="174" style="width:174px;{cell}">{label}</td>'
f'<td width="449" style="width:449px;{cell}">{shown}</td>'
f'<td width="{widths[0]}" style="width:{widths[0]}px;{cell}">{label}</td>'
f'<td width="{widths[1]}" style="width:{widths[1]}px;{cell}">{shown}</td>'
"</tr>"
)
def render_offer_email_subject(candidate_name, offered_position, company_name):
"""Offer Intent <Candidate Name> - <Designation> <Entity Name>"""
name = str(candidate_name or "").strip()
position = str(offered_position or "").strip()
company = str(company_name or "").strip()
return f"Offer Intent {name} - {position} {company}".strip()
def render_offer_email_body(
candidate_name,
offered_position,
@ -53,21 +62,23 @@ def render_offer_email_body(
timings,
recruiter_names,
):
"""Annexure J - Offer Email Format as an Outlook-safe HTML body: inline styles
and a bordered table only, no <style> block or classes (Outlook strips them).
Times New Roman 11pt and the bold runs match the .docx. recruiter_names is a
list, joined as "A", "A & B" or "A, B & C"."""
font = "font-family:'Times New Roman',Times,serif;font-size:11pt;color:#000000;"
"""Annexure J - Offer format to be sent to the candidate, as an Outlook-safe
HTML body: inline styles and a bordered table only, no <style> block or
classes (Outlook strips them). Arial in grey and the bold runs match the PDF;
the offer letter itself goes out as an attachment. recruiter_names is a list,
joined as "A", "A & B" or "A, B & C"."""
font = "font-family:Arial,Helvetica,sans-serif;font-size:11pt;color:#595959;"
table_font = "font-family:Arial,Helvetica,sans-serif;font-size:11pt;color:#7F7F7F;"
paragraph = "margin:0 0 11pt 0;"
first_name = (str(candidate_name or "").strip().split() or [""])[0]
rows = (
_package_row("Monthly Gross Salary", _salary(monthly_gross_salary), bold=True)
+ _package_row("Subsidized Services", _text(subsidized_services))
+ _package_row("Probation Period", _text(probation_period))
+ _package_row("Notice Period", _text(notice_period))
+ _package_row("Location", _text(location))
+ _package_row("Timings", _text(timings))
+ _package_row("Subsidized Services", _text(subsidized_services), valign="middle")
+ _package_row("Probation Period", _text(probation_period), valign="middle")
+ _package_row("Notice Period", _text(notice_period), valign="middle")
+ _package_row("Location", _text(location), valign="middle")
+ _package_row("Timings", _text(timings), valign="middle")
)
return (
@ -75,21 +86,22 @@ def render_offer_email_body(
f'<p style="{paragraph}">Dear {escape(first_name)},</p>'
f'<p style="{paragraph}">Hope this email finds you well.</p>'
f'<p style="{paragraph}">Subsequent to your interviews and pursuant to our discussion, '
"we are pleased to offer you the role of "
f"<strong>{_text(offered_position)} in {_text(cadre)} Cadre</strong> "
f"with <strong>{_text(company_name)}</strong>, based at the Head Office in Karachi.</p>"
f"we are pleased to offer you the role of <strong>{_text(offered_position)}</strong> "
f"in <strong>{_text(cadre)}</strong> Cadre with <strong>{_text(company_name)}</strong>, "
f"based at {_text(' '.join(str(location or '').split()))}.</p>"
f'<p style="{paragraph}">As discussed, the details of the remuneration package are as follows:</p>'
'<table cellpadding="0" cellspacing="0" border="1" width="623" '
f'style="width:623px;border-collapse:collapse;border:1px solid #000000;{font}margin:0 0 11pt 0;">'
f'style="width:623px;border-collapse:collapse;border:1px solid #000000;{table_font}margin:0 0 11pt 0;">'
f"{rows}"
"</table>"
f'<p style="{paragraph}">You will also be eligible for all the benefits at your grade '
"as per the Company&rsquo;s policy.</p>"
f'<p style="{paragraph}">We look forward to the opportunity to work with you in an atmosphere '
"that is mutually challenging and rewarding. Please confirm your acceptance of the Offer by "
"replying to this e-mail within <strong>24 hours,</strong> mentioning your "
f'<p style="{paragraph}">Also attached in the email is the offer letter. Please sign the attached '
"offer letter as your acceptance within 24 hours, mentioning your "
"<strong>suggested date for joining.</strong></p>"
'<p style="margin:0;"><strong>Regards,</strong></p>'
f'<p style="margin:0;"><strong>{_recruiter_names(recruiter_names)}</strong></p>'
f'<p style="{paragraph}text-align:justify;">We look forward to the opportunity to work with you '
"in an atmosphere that is mutually challenging and rewarding.</p>"
f'<p style="{paragraph}">Regards,</p>'
f'<p style="margin:0;">{_recruiter_names(recruiter_names)}</p>'
"</div>"
)

View File

@ -0,0 +1,100 @@
# DONOT REMOVE THIS ( OFFER LETTER JUST CREATE TEMPLATE FOR NOW)
from datetime import date, datetime
from html import escape
from offer_email_template.template import _package_row, _salary, _text
def _letter_date(value):
"""<Month, DD, YYYY> -> "September 16, 2026". Accepts date/datetime or an ISO string."""
if value in (None, ""):
return ""
if isinstance(value, date):
return value.strftime("%B %d, %Y")
try:
return datetime.fromisoformat(str(value)).strftime("%B %d, %Y")
except ValueError:
return _text(value)
def render_offer_letter_body(
candidate_full_name,
candidate_cnic,
address_line,
city_country,
candidate_first_name,
job_title,
department_name,
cadre,
work_location,
monthly_gross_salary,
probation_period,
performance_bonus,
health_insurance,
life_insurance,
leave_entitlement,
letter_date,
):
"""Offer Letter Format as an Outlook-safe HTML body: inline styles and a
bordered table only, no <style> block or classes (Outlook strips them).
Arial 10pt, bold / underline runs and the two equal table columns match the
.docx; the acceptance blanks stay blank for the candidate to fill in."""
font = "font-family:Arial,Helvetica,sans-serif;font-size:10pt;color:#000000;"
heading = "font-family:Calibri,Arial,sans-serif;font-size:10pt;font-weight:bold;margin:0 0 11pt 0;"
paragraph = "margin:0 0 8pt 0;"
tight = "margin:0;"
widths = (234, 234)
header_cell = "border:1px solid #000000;padding:4px 8px;text-align:center;font-weight:bold;"
salary = _salary(monthly_gross_salary)
if salary and not salary.endswith("/-"):
salary = f"{salary}/-"
rows = (
"<tr>"
f'<td width="{widths[0]}" style="width:{widths[0]}px;{header_cell}">Component</td>'
f'<td width="{widths[1]}" style="width:{widths[1]}px;{header_cell}">Details</td>'
"</tr>"
+ _package_row("Monthly Gross Salary", salary, bold=True, widths=widths, bold_label=True)
+ _package_row("Probation Period", _text(probation_period), widths=widths)
+ _package_row("Performance Bonus", _text(performance_bonus), widths=widths)
+ _package_row("Health Insurance", _text(health_insurance), widths=widths)
+ _package_row("Life Insurance", _text(life_insurance), widths=widths)
+ _package_row("Leave Entitlement", _text(leave_entitlement), widths=widths)
)
return (
f'<div style="{font}">'
f'<p style="{paragraph}text-align:right;color:#1A1A1A;">'
f"<strong>Date: {_letter_date(letter_date)}</strong></p>"
f'<p style="{tight}">{_text(candidate_full_name)}</p>'
f'<p style="{tight}">{_text(candidate_cnic)}</p>'
f'<p style="{tight}">{_text(address_line)},</p>'
f'<p style="{paragraph}">{_text(city_country)}</p>'
f'<p style="{paragraph}text-align:center;"><strong><u>OFFER LETTER</u></strong></p>'
f'<p style="{tight}">Dear <strong>{escape(str(candidate_first_name or "").strip())}</strong>,</p>'
f'<p style="{paragraph}">We are pleased to offer you the position of <strong>{_text(job_title)}</strong> '
f"in the <strong>{_text(department_name)}</strong> department at <strong>Utopia Brands</strong> "
f"in <strong>{_text(cadre)} cadre</strong>, based in <strong>{_text(work_location)}</strong>. "
"Your employment is expected to commence, subject to the completion of onboarding "
"formalities and satisfactory reference checks.</p>"
f'<p style="{heading}">COMPENSATION &amp; BENEFITS</p>'
'<table align="center" cellpadding="0" cellspacing="0" border="1" width="468" '
f'style="width:468px;border-collapse:collapse;border:1px solid #000000;{font}margin:0 auto 11pt auto;">'
f"{rows}"
"</table>"
f'<p style="{paragraph}">We look forward to welcoming you to Utopia Brands. Kindly confirm your '
"acceptance by signing and returning this letter; a formal Letter of Appointment will follow "
"upon joining. Congratulations, and welcome to the team.</p>"
'<p style="margin:0 0 30pt 0;">Best Regards,</p>'
f'<p style="{tight}"><strong>Human Resources</strong></p>'
f'<p style="{paragraph}"><strong>Utopia Brands</strong></p>'
f'<p style="{heading}">ACCEPTANCE OF OFFER</p>'
f'<p style="{tight}">I, <strong>____________________</strong>, accept the terms and conditions '
"of employment outlined in this offer letter.</p>"
f'<p style="{paragraph}">My date of joining will be</p>'
'<p style="margin:0 0 30pt 0;color:#1A1A1A;"><strong>_____________________</strong></p>'
f'<p style="{tight}">Signature: ___________________________'
"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Date: __________________</p>"
"</div>"
)