"""
Generate PO output: XLSX (copy with computed values) + PDF (formatted).
Source: Prototype/Sample_PO.xlsx
Output: Progress/PMS_HNR3_056_2026-27.xlsx + Progress/PMS_HNR3_056_2026-27.pdf
"""
import shutil, os, datetime
import openpyxl
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side, numbers
from openpyxl.utils import get_column_letter
# ── paths ────────────────────────────────────────────────────────────────────
BASE = r'C:\Users\shad0w\Documents\src\Peliagia_Portal'
SRC_XLSX = os.path.join(BASE, r'Prototype\Sample_PO.xlsx')
OUT_DIR = os.path.join(BASE, 'Progress')
OUT_XLSX = os.path.join(OUT_DIR, 'PMS_HNR3_056_2026-27.xlsx')
OUT_PDF = os.path.join(OUT_DIR, 'PMS_HNR3_056_2026-27.pdf')
os.makedirs(OUT_DIR, exist_ok=True)
# ─────────────────────────────────────────────────────────────────────────────
# Computed values (resolved from the string-formula cells)
# ─────────────────────────────────────────────────────────────────────────────
QTY = 1050
UNIT_PRICE = 182
TAXABLE = QTY * UNIT_PRICE # 191,100
GST_RATE = 0.18
GST_AMT = round(TAXABLE * GST_RATE) # 34,398
GRAND_TOTAL= TAXABLE + GST_AMT # 225,498
PO_NO = 'PMS/HNR3/056/2026-27'
PO_DATE = datetime.date(2026, 4, 29)
VENDOR = 'Apar Industries Ltd'
# ═════════════════════════════════════════════════════════════════════════════
# 1. XLSX — copy the template, replace text-formula cells with real formulas
# ═════════════════════════════════════════════════════════════════════════════
shutil.copy2(SRC_XLSX, OUT_XLSX)
wb = load_workbook(OUT_XLSX)
ws = wb.active
# The source stores these as Excel formula strings — they're already valid
# formulas in the file; openpyxl reads them back as strings starting with '='.
# We re-write them so openpyxl treats them as formulas (no data_only quirk).
ws['G16'] = '=F16*E16'
ws['I16'] = '=G16+H16*G16'
ws['H24'] = '=SUM(G16:G22)'
ws['H25'] = '=H24*18%'
ws['H26'] = '=H24+H25'
ws['D26'] = '=SUM(D16:D20)'
ws['G39'] = '=C13'
# Format date cell properly
ws['I5'] = PO_DATE
ws['I5'].number_format = 'DD-MMM-YYYY'
wb.save(OUT_XLSX)
print(f'[XLSX] Saved: {OUT_XLSX}')
# ═════════════════════════════════════════════════════════════════════════════
# 2. PDF — render with reportlab
# ═════════════════════════════════════════════════════════════════════════════
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
)
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
W, H = A4
MARGIN = 12 * mm
def mk_style(name, **kw):
return ParagraphStyle(name, **kw)
HEAD = mk_style('Head', fontName='Helvetica-Bold', fontSize=14, alignment=TA_CENTER, spaceAfter=1)
SUB = mk_style('Sub', fontName='Helvetica', fontSize=8, alignment=TA_CENTER, spaceAfter=1)
TITLE = mk_style('Title', fontName='Helvetica-Bold', fontSize=12, alignment=TA_CENTER, spaceAfter=4)
LABEL = mk_style('Label', fontName='Helvetica-Bold', fontSize=7.5, alignment=TA_LEFT)
VALUE = mk_style('Value', fontName='Helvetica', fontSize=7.5, alignment=TA_LEFT)
VALUEC = mk_style('ValueC', fontName='Helvetica', fontSize=7.5, alignment=TA_CENTER)
VALUER = mk_style('ValueR', fontName='Helvetica', fontSize=7.5, alignment=TA_RIGHT)
BOLDVAL = mk_style('BoldV', fontName='Helvetica-Bold', fontSize=7.5, alignment=TA_CENTER)
INSTRH = mk_style('InstrH', fontName='Helvetica-Bold', fontSize=7.5, alignment=TA_CENTER)
BLK = colors.black
GRAY = colors.HexColor('#D0D0D0')
LGRY = colors.HexColor('#F0F0F0')
def thin_box():
return [
('BOX', (0, 0), (-1, -1), 0.5, BLK),
('INNERGRID', (0, 0), (-1, -1), 0.3, BLK),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('FONTSIZE', (0, 0), (-1, -1), 7.5),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING',(0, 0), (-1, -1), 3),
('TOPPADDING', (0, 0), (-1, -1), 2),
('BOTTOMPADDING',(0, 0), (-1, -1), 2),
]
story = []
TW = W - 2 * MARGIN # total usable width
# ── Header ───────────────────────────────────────────────────────────────────
story.append(Paragraph('PELAGIA MARINE SERVICES PVT. LTD', HEAD))
story.append(Paragraph(
'Office address: 409-410, ZION, Plot 273, Sector-10, Kharghar, Navi Mumbai- 410210', SUB))
story.append(Paragraph(
'Tel: +91-22-6909 9028 / Email: technical@pelagiamarine.com / Mob: +91 74000 60772', SUB))
story.append(HRFlowable(width=TW, thickness=1, color=BLK, spaceAfter=3))
story.append(Paragraph('PURCHASE ORDER', TITLE))
story.append(HRFlowable(width=TW, thickness=1, color=BLK, spaceAfter=3))
# ── PO meta table ────────────────────────────────────────────────────────────
po_date_str = PO_DATE.strftime('%d-%b-%Y')
meta_data = [
[
Paragraph('Purchase Order No:', LABEL),
Paragraph(PO_NO, BOLDVAL),
Paragraph('Date:', LABEL),
Paragraph(po_date_str, VALUEC),
],
[
Paragraph('Performa Invoice / Quotation No:', LABEL),
Paragraph('Verbal', VALUEC),
Paragraph('P I / Quotation Date:', LABEL),
Paragraph('', VALUEC),
],
]
col_w = [TW * 0.32, TW * 0.24, TW * 0.22, TW * 0.22]
meta_tbl = Table(meta_data, colWidths=col_w, repeatRows=0)
meta_tbl.setStyle(TableStyle(thin_box() + [
('FONTNAME', (1, 0), (1, 0), 'Helvetica-Bold'),
]))
story.append(meta_tbl)
# ── Vessel / Requisition / Approved ──────────────────────────────────────────
vessel_data = [
[
Paragraph('Vessel Owner Name', LABEL),
Paragraph('Pelagia Marine Services Pvt. Ltd.', VALUE),
Paragraph('Budget head', LABEL),
Paragraph('700203', VALUEC),
Paragraph('Requested By', LABEL),
Paragraph('Kaushal Pal Singh', VALUE),
],
[
Paragraph('Vessel/Office Requisition No.', LABEL),
Paragraph('', VALUE),
Paragraph('Reqn. Date', LABEL),
Paragraph('', VALUEC),
Paragraph('Approved By', LABEL),
Paragraph('Kaushal Pal Singh', VALUE),
],
]
v_w = [TW*0.20, TW*0.17, TW*0.12, TW*0.10, TW*0.14, TW*0.27]
vessel_tbl = Table(vessel_data, colWidths=v_w)
vessel_tbl.setStyle(TableStyle(thin_box()))
story.append(vessel_tbl)
# ── Place of Delivery + Invoice Details ──────────────────────────────────────
delivery_data = [
[
Paragraph('Place of Delivery', LABEL),
Paragraph(
'Pelagia Marine Services Pvt. Ltd. Reti Bundar Near Konkan Bhavan, '
'CBD Belapur, Navi Mumbai - 400614', VALUE),
],
[
Paragraph('Invoice Details', LABEL),
Paragraph(
'Pelagia Marine Services Pvt Ltd, 409-410, ZION, Plot 273, Sector-10, '
'Kharghar, Navi Mumbai- 410210 (MH)
'
'Email: accounts@pelagiamarine.com GST NO: 27AAHCP5787B1Z6', VALUE),
],
]
d_w = [TW*0.22, TW*0.78]
delivery_tbl = Table(delivery_data, colWidths=d_w)
delivery_tbl.setStyle(TableStyle(thin_box()))
story.append(delivery_tbl)
# ── Vendor ────────────────────────────────────────────────────────────────────
vendor_data = [
[
Paragraph('Vendor Name & Address', LABEL),
Paragraph(VENDOR, VALUE),
Paragraph(
'18, TTC MIDC Industrial Area Thane Belapur Road, Opp Rabale Railway Stn '
'Rabale, Navi Mumbai 400701 GSTIN: 27AAACG1840M1ZL', VALUE),
],
[
Paragraph('Contact Person / Mobile', LABEL),
Paragraph(
'Mr. Nikhil Mumbaikar Ph. 7208055636 '
'Email: nikhil.mumbaikar@apar.com', VALUE),
Paragraph('', VALUE),
],
]
vd_w = [TW*0.22, TW*0.22, TW*0.56]
vendor_tbl = Table(vendor_data, colWidths=vd_w)
vendor_tbl.setStyle(TableStyle(thin_box()))
story.append(vendor_tbl)
# ── Line items ────────────────────────────────────────────────────────────────
items_header = [
Paragraph('S.N.', BOLDVAL),
Paragraph('Description', BOLDVAL),
Paragraph('Unit', BOLDVAL),
Paragraph('Qty', BOLDVAL),
Paragraph('Unit Price', BOLDVAL),
Paragraph('Taxable Cost', BOLDVAL),
Paragraph('GST %', BOLDVAL),
Paragraph('Total Cost', BOLDVAL),
]
items_row = [
Paragraph('1', VALUEC),
Paragraph('Eni EP 80W90 GEAR OIL', VALUE),
Paragraph('Ltr', VALUEC),
Paragraph(f'{QTY:,}', VALUEC),
Paragraph(f'{UNIT_PRICE:,.2f}', VALUER),
Paragraph(f'{TAXABLE:,.2f}', VALUER),
Paragraph('18%', VALUEC),
Paragraph(f'{GRAND_TOTAL:,.2f}', VALUER),
]
# blank filler rows
blank = [''] * 8
items_data = [items_header, items_row] + [blank] * 6
i_w = [TW*0.05, TW*0.29, TW*0.07, TW*0.07, TW*0.10, TW*0.14, TW*0.08, TW*0.20]
items_tbl = Table(items_data, colWidths=i_w, rowHeights=[None] + [10*mm]*7)
items_tbl.setStyle(TableStyle(thin_box() + [
('BACKGROUND', (0, 0), (-1, 0), LGRY),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
]))
story.append(items_tbl)
# ── Totals ────────────────────────────────────────────────────────────────────
totals_data = [
['', '', '', '', '', Paragraph('Total Taxable Value', LABEL), '', Paragraph(f'{TAXABLE:,.2f}', VALUER)],
['', '', '', '', '', Paragraph('GST (18%)', LABEL), '', Paragraph(f'{GST_AMT:,.2f}', VALUER)],
[
Paragraph(f'Total Qty: {QTY:,} Ltr', VALUE),
'', '', '',
'',
Paragraph('GRAND TOTAL', LABEL),
'',
Paragraph(f'{GRAND_TOTAL:,.2f}', mk_style('GT', fontName='Helvetica-Bold', fontSize=7.5, alignment=TA_RIGHT)),
],
]
totals_tbl = Table(totals_data, colWidths=i_w)
totals_tbl.setStyle(TableStyle([
('BOX', (0, 0), (-1, -1), 0.5, BLK),
('INNERGRID', (0, 0), (-1, -1), 0.3, BLK),
('SPAN', (0, 2), (3, 2)), # total qty spans cols 0-3
('SPAN', (5, 0), (6, 0)), # label spans 5-6 row 0
('SPAN', (5, 1), (6, 1)),
('SPAN', (5, 2), (6, 2)),
('ALIGN', (7, 0), (7, 2), 'RIGHT'),
('FONTSIZE', (0, 0), (-1, -1), 7.5),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
('TOPPADDING', (0, 0), (-1, -1), 2),
('BOTTOMPADDING',(0, 0), (-1, -1), 2),
('BACKGROUND', (5, 2), (7, 2), LGRY),
]))
story.append(totals_tbl)
# ── Instructions to Vendors ───────────────────────────────────────────────────
story.append(Spacer(1, 3*mm))
instr_header = [[Paragraph('INSTRUCTIONS TO VENDORS', INSTRH)]]
instr_tbl_h = Table(instr_header, colWidths=[TW])
instr_tbl_h.setStyle(TableStyle([
('BOX', (0, 0), (-1, -1), 0.5, BLK),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTSIZE', (0, 0), (-1, -1), 7.5),
('TOPPADDING', (0, 0), (-1, -1), 2),
('BOTTOMPADDING', (0, 0), (-1, -1), 2),
]))
story.append(instr_tbl_h)
instructions = [
(1, 'Please quote this purchase order no. for further communications and invoices pertaining to this indent.'),
(2, 'DELIVERY: Within 4 to 5 days'),
(3, "DISPATCH INSTRUCTIONS: To be transported to Navi Mumbai Site Address as above. Freight Supplier's A/C"),
(4, 'INSPECTION: NA'),
(5, 'TRANSIT INSURANCE: NA'),
(6, 'PAYMENT TERMS: Within 30 days from delivery.'),
(7, 'We encourage bulk packaging and avoid plastic. No asbestos to be used in any product or packing material.'),
]
instr_data = [[Paragraph(str(n), VALUEC), Paragraph(txt, VALUE)] for n, txt in instructions]
instr_tbl = Table(instr_data, colWidths=[TW*0.05, TW*0.95])
instr_tbl.setStyle(TableStyle([
('BOX', (0, 0), (-1, -1), 0.5, BLK),
('INNERGRID',(0, 0), (-1, -1), 0.3, BLK),
('FONTSIZE', (0, 0), (-1, -1), 7.5),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
('TOPPADDING', (0, 0), (-1, -1), 2),
('BOTTOMPADDING',(0, 0), (-1, -1), 2),
]))
story.append(instr_tbl)
# ── Signature block ───────────────────────────────────────────────────────────
story.append(Spacer(1, 6*mm))
sig_data = [
[
Paragraph('Kaushal Pal Singh', mk_style('SN', fontName='Helvetica', fontSize=7.5, alignment=TA_CENTER)),
'',
Paragraph(VENDOR, mk_style('SV', fontName='Helvetica-Bold', fontSize=7.5, alignment=TA_CENTER)),
],
[
Paragraph('Authorized Signatory & Stamp', mk_style('SA', fontName='Helvetica', fontSize=7, alignment=TA_CENTER)),
'',
Paragraph('Authorized Signatory & Stamp', mk_style('SB', fontName='Helvetica', fontSize=7, alignment=TA_CENTER)),
],
[
Paragraph('For, Pelagia Marine Services Pvt. Ltd.', mk_style('SF', fontName='Helvetica', fontSize=7.5, alignment=TA_CENTER)),
'',
Paragraph(f'For, {VENDOR}', mk_style('SFF', fontName='Helvetica', fontSize=7.5, alignment=TA_CENTER)),
],
]
sig_tbl = Table(sig_data, colWidths=[TW*0.45, TW*0.10, TW*0.45])
sig_tbl.setStyle(TableStyle([
('BOX', (0, 0), (0, -1), 0.5, BLK),
('BOX', (2, 0), (2, -1), 0.5, BLK),
('FONTSIZE', (0, 0), (-1, -1), 7.5),
('TOPPADDING', (0, 0), (-1, -1), 2),
('BOTTOMPADDING', (0, 0), (-1, -1), 2),
]))
story.append(sig_tbl)
# ── Build PDF ─────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUT_PDF,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=MARGIN,
)
doc.build(story)
print(f'[PDF] Saved: {OUT_PDF}')