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

```python
import gurobipy as gp

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

# Create variables
dale_hours = model.addVar(vtype=gp.GRB.INTEGER, name="dale_hours")
laura_hours = model.addVar(vtype=gp.GRB.INTEGER, name="laura_hours")
bobby_hours = model.addVar(vtype=gp.GRB.INTEGER, name="bobby_hours")

# Set objective function
model.setObjective(1 * dale_hours + 8 * laura_hours + 7 * bobby_hours, gp.GRB.MINIMIZE)

# Add constraints
model.addConstr(3 * dale_hours + 6 * bobby_hours >= 8, "paperwork_dale_bobby_min")
model.addConstr(7 * laura_hours + 6 * bobby_hours >= 20, "paperwork_laura_bobby_min")
model.addConstr(3 * dale_hours + 7 * laura_hours + 6 * bobby_hours >= 20, "paperwork_total_min")
model.addConstr(9 * dale_hours + 3 * laura_hours >= 31, "quality_dale_laura_min")
model.addConstr(9 * dale_hours + 3 * laura_hours + 2 * bobby_hours >= 31, "quality_total_min")
model.addConstr(6 * dale_hours - 4 * laura_hours >= 0, "dale_laura_relation")
model.addConstr(-9 * laura_hours + 4 * bobby_hours >= 0, "laura_bobby_relation")
model.addConstr(3 * dale_hours + 6 * bobby_hours <= 27, "paperwork_dale_bobby_max")
model.addConstr(3 * dale_hours + 7 * laura_hours <= 68, "paperwork_dale_laura_max")
model.addConstr(7 * laura_hours + 6 * bobby_hours <= 45, "paperwork_laura_bobby_max")
model.addConstr(9 * dale_hours + 3 * laura_hours + 2 * bobby_hours <= 77, "quality_total_max")


# Optimize model
model.optimize()

# Print results
if model.status == gp.GRB.OPTIMAL:
    print(f"Optimal solution found: {model.objVal}")
    print(f"Dale's hours: {dale_hours.x}")
    print(f"Laura's hours: {laura_hours.x}")
    print(f"Bobby's hours: {bobby_hours.x}")
elif model.status == gp.GRB.INFEASIBLE:
    print("Model is infeasible.")
else:
    print(f"Optimization ended with status: {model.status}")

```
