sample offer emial and tempalte
parent
5254bdb964
commit
65b39e9bff
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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’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>"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 & 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: ___________________________'
|
||||
" Date: __________________</p>"
|
||||
"</div>"
|
||||
)
|
||||
Loading…
Reference in New Issue