File size: 13,058 Bytes
dd6303a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
"""
Excel Generator
---------------
Generates two Excel outputs from TenderData:

1. BOQ Excel  (BOQ_541339.xlsx)
   Mirrors the layout of BOQ_541339_2.26 Less.xlsx:
   - Header block: tender ID, procuring entity, package no, work name
   - Summary row: lot no, work description, location, security, work time, quoted %
   - BOQ item rows: item no, code, description, qty, unit, bwdb rate, amount, quoted rate, amount, %
   - Totals row

2. Work Plan Excel  (Work_Plan_541339.xlsx)
   Mirrors 7. Work Plan_541339.xlsx:
   - Header: package no, tender ID, work name
   - Year/month header rows
   - Activity rows (blank Gantt cells for user to fill)
"""

from pathlib import Path
from typing import List
from ..models.tender_data import TenderData, BOQItem, WorkActivity


# ── BOQ Excel ────────────────────────────────────────────────────────────────

def generate_boq_excel(output_path: str, td: TenderData) -> str:
    """Generate BOQ comparison Excel."""
    try:
        import openpyxl
        from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
        from openpyxl.utils import get_column_letter
    except ImportError:
        raise ImportError("openpyxl is required. Run: pip install openpyxl")

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Quot"

    thin = Side(style="thin")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)
    bold = Font(bold=True)
    center = Alignment(horizontal="center", vertical="center", wrap_text=True)
    left = Alignment(horizontal="left", vertical="center", wrap_text=True)
    header_fill = PatternFill("solid", fgColor="BDD7EE")  # light blue
    yellow_fill = PatternFill("solid", fgColor="FFFF00")

    # ── Row 2: Tender ID + Procuring entity ─────────────────────────
    ws.merge_cells("B2:C2")
    _wc(ws, "B2", f"Tender ID: {td.tender_id}", bold, center, header_fill, border)
    ws.merge_cells("D2:H2")
    _wc(ws, "D2", td.procuring_entity, bold, center, header_fill, border)
    ws.merge_cells("I2:L2")
    _wc(ws, "I2", "Quoted Rate (%)", bold, center, header_fill, border)

    # ── Row 3: Departmental Estimation label ─────────────────────────
    ws.merge_cells("D3:H3")
    _wc(ws, "D3", "Departmental Estimation", bold, center, header_fill, border)

    # ── Row 5: Column headers ────────────────────────────────────────
    headers_row5 = ["", "Lot No.", "Lot No.", "Lot Description", "Location",
                    "Tender Security", "Tender Security", "Work Time",
                    "Quoted Rate (%)", "", "", ""]
    for col, val in enumerate(headers_row5, 1):
        cell = ws.cell(row=5, column=col, value=val)
        cell.font = bold
        cell.alignment = center
        cell.border = border

    # ── Row 6: Summary data row ──────────────────────────────────────
    work_period = f"{td.start_date}\nto \n{td.completion_date}\n"
    quoted_pct = td.quoted_rate_percent  # e.g. -0.02255
    row6 = ["", td.package_no, td.package_no, td.work_name, td.location,
            td.tender_security_amount, td.tender_security_amount,
            work_period, quoted_pct, quoted_pct, quoted_pct, quoted_pct]
    for col, val in enumerate(row6, 1):
        cell = ws.cell(row=6, column=col, value=val)
        cell.alignment = center
        cell.border = border

    # ── Row 8: BOQ section header ─────────────────────────────────────
    ws.merge_cells("B8:C8")
    _wc(ws, "B8", f"Tender ID: {td.tender_id}", bold, center, header_fill, border)
    ws.merge_cells("D8:H8")
    _wc(ws, "D8", f"Bill of Quantities (BOQ)_{td.rate_schedule_ref}", bold, center, header_fill, border)
    ws.merge_cells("I8:L8")
    _wc(ws, "I8", "Quoted Rate", bold, center, header_fill, border)

    # ── Row 9: BOQ column headers ─────────────────────────────────────
    boq_headers = ["", "Item No.", "Item Code", "Item Description", "Quantity", "Unit",
                   "BWDB\nRate", "BWDB\nAmount", "Quoted\nRate", "CFT Rate",
                   "Quoted\nAmount", "%"]
    for col, val in enumerate(boq_headers, 1):
        cell = ws.cell(row=9, column=col, value=val)
        cell.font = bold
        cell.alignment = center
        cell.fill = header_fill
        cell.border = border

    # ── BOQ data rows ─────────────────────────────────────────────────
    data_start = 10
    for i, item in enumerate(td.boq_items):
        r = data_start + i
        row_data = [
            "", item.item_no, item.item_code, item.description,
            item.quantity, item.unit, item.bwdb_rate, item.bwdb_amount,
            item.quoted_rate, "-", item.quoted_amount, item.percent_diff
        ]
        for col, val in enumerate(row_data, 1):
            cell = ws.cell(row=r, column=col, value=val)
            cell.border = border
            cell.alignment = left if col == 4 else center

    # ── Totals row ───────────────────────────────────────────────────
    last_r = data_start + len(td.boq_items)
    ws.cell(row=last_r, column=8, value=td.departmental_estimate).font = bold
    ws.cell(row=last_r, column=11, value=td.quoted_total).font = bold
    ws.cell(row=last_r, column=12, value=td.quoted_rate_percent).font = bold

    # ── Column widths ────────────────────────────────────────────────
    col_widths = [2, 12, 14, 55, 12, 8, 12, 16, 12, 12, 16, 10]
    for i, w in enumerate(col_widths, 1):
        ws.column_dimensions[get_column_letter(i)].width = w

    ws.row_dimensions[6].height = 60

    wb.save(output_path)
    print(f"  [OK] Generated: {Path(output_path).name}")
    return output_path


# ── Work Plan Excel ──────────────────────────────────────────────────────────

def generate_work_plan_excel(output_path: str, td: TenderData) -> str:
    """Generate Work Plan (Gantt) Excel."""
    try:
        import openpyxl
        from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
        from openpyxl.utils import get_column_letter
    except ImportError:
        raise ImportError("openpyxl is required. Run: pip install openpyxl")

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = td.tender_id

    thin = Side(style="thin")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)
    bold = Font(bold=True)
    center = Alignment(horizontal="center", vertical="center", wrap_text=True)
    left = Alignment(horizontal="left", vertical="center", wrap_text=True)
    header_fill = PatternFill("solid", fgColor="BDD7EE")

    # Determine month columns from td.work_months
    months = td.work_months if td.work_months else [
        "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
        "Jan", "Feb", "Mar", "Apr", "May", "Jun"
    ]
    n_months = len(months)

    # ── Row 2: Package + WORK SCHEDULE + Tender ID ───────────────────
    ws.merge_cells(f"A2:B2")
    _wc(ws, "A2", f"Package :-{td.package_no}", bold, center, header_fill, border)
    ws.merge_cells(f"C2:{get_column_letter(2 + n_months // 2)}2")
    _wc(ws, "C2", "WORK SCHEDULE", bold, center, header_fill, border)
    tender_id_col = get_column_letter(3 + n_months // 2)
    ws.merge_cells(f"{tender_id_col}2:{get_column_letter(2 + n_months)}2")
    _wc(ws, f"{tender_id_col}2", f"Tender ID: {td.tender_id}", bold, center, header_fill, border)

    # ── Row 3-4: Work name (merged across all cols) ───────────────────
    total_cols = 2 + n_months
    ws.merge_cells(f"A3:{get_column_letter(total_cols)}4")
    _wc(ws, "A3", f'Name of Work: "{td.work_name}"', bold, center, None, border)
    ws.row_dimensions[3].height = 50

    # ── Row 6: Year headers ───────────────────────────────────────────
    _wc(ws, "A6", "Sl. No.", bold, center, header_fill, border)
    _wc(ws, "B6", "Activity", bold, center, header_fill, border)
    # Determine year for each month
    start_year = td.work_start_year
    end_year = td.work_end_year
    year_for_month = _month_years(months, start_year, end_year)
    prev_year = None
    year_start_col = 3
    for m_idx, (month, year) in enumerate(zip(months, year_for_month)):
        col_letter = get_column_letter(3 + m_idx)
        ws.cell(row=6, column=3 + m_idx, value=year).font = bold
        ws.cell(row=6, column=3 + m_idx).alignment = center
        ws.cell(row=6, column=3 + m_idx).fill = header_fill
        ws.cell(row=6, column=3 + m_idx).border = border

    # ── Row 7: Month headers ──────────────────────────────────────────
    _wc(ws, "A7", "Sl. No.", bold, center, header_fill, border)
    _wc(ws, "B7", "Activity", bold, center, header_fill, border)
    for m_idx, month in enumerate(months):
        col_letter = get_column_letter(3 + m_idx)
        _wc(ws, f"{col_letter}7", month, bold, center, header_fill, border)

    # ── Activity rows ─────────────────────────────────────────────────
    activities = td.work_activities if td.work_activities else _default_activities()
    for a_idx, act in enumerate(activities):
        r = 8 + a_idx
        ws.cell(row=r, column=1, value=act.sl_no).border = border
        ws.cell(row=r, column=2, value=act.activity).alignment = left
        ws.cell(row=r, column=2).border = border
        ws.row_dimensions[r].height = 30
        for m_idx in range(n_months):
            ws.cell(row=r, column=3 + m_idx).border = border

    # ── Column widths ─────────────────────────────────────────────────
    ws.column_dimensions["A"].width = 8
    ws.column_dimensions["B"].width = 42
    for m_idx in range(n_months):
        ws.column_dimensions[get_column_letter(3 + m_idx)].width = 6

    wb.save(output_path)
    print(f"  [OK] Work Plan generated: {Path(output_path).name}")
    return output_path


# ── Helpers ──────────────────────────────────────────────────────────────────

def _wc(ws, cell_ref, value, font=None, alignment=None, fill=None, border=None):
    """Write cell with optional formatting."""
    c = ws[cell_ref]
    c.value = value
    if font:
        c.font = font
    if alignment:
        c.alignment = alignment
    if fill:
        c.fill = fill
    if border:
        c.border = border


def _month_years(months: List[str], start_year: int, end_year: int) -> List[int]:
    """Assign a year to each month label based on calendar order."""
    _order = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
              "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    years = []
    current_year = start_year
    prev_idx = -1
    for m in months:
        idx = next((i for i, mo in enumerate(_order) if mo.lower() == m.lower()[:3]), 0)
        if idx < prev_idx:
            current_year += 1
        years.append(current_year)
        prev_idx = idx
    return years


def _default_activities() -> List[WorkActivity]:
    from ..models.tender_data import WorkActivity
    return [
        WorkActivity("i",    "Site Preparation & Mobilization."),
        WorkActivity("ii",   "Procurement of Man power, Equipment, Materials & etc."),
        WorkActivity("iii",  "E/W in excavation/re-excavation of khal & foundation trench etc."),
        WorkActivity("iv",   "Manufacturing of C.C. Blocks"),
        WorkActivity("v",    "Supply of Sand Filter, Geo-Textile Filter, Khoa filter etc."),
        WorkActivity("vi",   "Dumping & Placing C.C Blocks"),
        WorkActivity("vii",  "Construction Of Herring Bone Bond (HBB) Brick Road."),
        WorkActivity("viii", "Supplying, Filling & Laying Geo-Tube"),
        WorkActivity("ix",   "Fine dressing and close turfing of the slopes and the crest of embankment"),
        WorkActivity("x",    "De-mobilization & Site handover"),
    ]