""" 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}')