Here's the Gurobi code to solve the optimization problem:

```python
import gurobipy as gp

# Create a new model
m = gp.Model("work_hour_optimization")

# Create variables
bill_hours = m.addVar(lb=0, vtype=gp.GRB.CONTINUOUS, name="bill_hours")
hank_hours = m.addVar(lb=0, vtype=gp.GRB.CONTINUOUS, name="hank_hours")
bobby_hours = m.addVar(lb=0, vtype=gp.GRB.CONTINUOUS, name="bobby_hours")
john_hours = m.addVar(lb=0, vtype=gp.GRB.CONTINUOUS, name="john_hours")

# Set objective function
m.setObjective(9 * bill_hours + 3 * hank_hours + 2 * bobby_hours + 2 * john_hours, gp.GRB.MAXIMIZE)

# Add constraints
m.addConstr(6 * bill_hours + 6 * hank_hours + 11 * john_hours >= 31, "c1")
m.addConstr(6 * bill_hours + 6 * hank_hours + 7 * bobby_hours >= 31, "c2")
m.addConstr(6 * bill_hours + 6 * hank_hours + 11 * john_hours >= 37, "c3")
m.addConstr(6 * bill_hours + 6 * hank_hours + 7 * bobby_hours >= 37, "c4")

m.addConstr(9 * hank_hours + 18 * bobby_hours >= 17, "c5")
m.addConstr(9 * hank_hours + 12 * john_hours >= 20, "c6")
m.addConstr(8 * bill_hours + 9 * hank_hours >= 27, "c7")
m.addConstr(18 * bobby_hours + 12 * john_hours >= 19, "c8")

m.addConstr(7 * bill_hours + 1 * bobby_hours + 13 * john_hours >= 26, "c9")
m.addConstr(7 * bill_hours + 4 * hank_hours + 1 * bobby_hours >= 26, "c10")
m.addConstr(7 * bill_hours + 1 * bobby_hours + 13 * john_hours >= 27, "c11")
m.addConstr(7 * bill_hours + 4 * hank_hours + 1 * bobby_hours >= 27, "c12")


m.addConstr(7 * bobby_hours + 11 * john_hours <= 99, "c13")
m.addConstr(6 * bill_hours + 11 * john_hours <= 52, "c14")
m.addConstr(6 * bill_hours + 7 * bobby_hours <= 118, "c15")
m.addConstr(6 * bill_hours + 7 * bobby_hours + 11 * john_hours <= 60, "c16")
m.addConstr(6 * bill_hours + 6 * hank_hours + 7 * bobby_hours + 11 * john_hours <= 60, "c17")

m.addConstr(8 * bill_hours + 18 * bobby_hours <= 104, "c18")
m.addConstr(9 * hank_hours + 18 * bobby_hours <= 63, "c19")
m.addConstr(8 * bill_hours + 12 * john_hours <= 34, "c20")
m.addConstr(8 * bill_hours + 9 * hank_hours <= 72, "c21")
m.addConstr(8 * bill_hours + 9 * hank_hours + 18 * bobby_hours + 12 * john_hours <= 72, "c22")

m.addConstr(7 * bill_hours + 4 * hank_hours <= 64, "c23")
m.addConstr(7 * bill_hours + 1 * bobby_hours <= 71, "c24")
m.addConstr(7 * bill_hours + 13 * john_hours <= 106, "c25")
m.addConstr(4 * hank_hours + 13 * john_hours <= 29, "c26")
m.addConstr(7 * bill_hours + 4 * hank_hours + 1 * bobby_hours + 13 * john_hours <= 29, "c27")


# Optimize model
m.optimize()

# Print results
if m.status == gp.GRB.OPTIMAL:
    print('Obj: %g' % m.objVal)
    for v in m.getVars():
        print('%s %g' % (v.varName, v.x))
elif m.status == gp.GRB.INFEASIBLE:
    print("The model is infeasible.")
else:
    print("Optimization ended with status %d" % m.status)

```
