Here's how we can model this problem and generate the Gurobi code:

**Decision Variables:**

* `x`: Number of peanut butter sandwiches sold.
* `y`: Number of chocolate spread sandwiches sold.

**Objective Function:**

Maximize profit: `3x + 2y`

**Constraints:**

* **Bread Constraint:** `x + y <= 150` (Total sandwiches <= 150)
* **Minimum Peanut Butter Sandwich Constraint:** `x >= 45`
* **Minimum Chocolate Spread Sandwich Constraint:** `y >= 65`
* **Maximum Peanut Butter Sandwich Constraint:** `x <= 80`
* **Maximum Chocolate Spread Sandwich Constraint:** `y <= 100`
* **Non-negativity Constraints:** `x >= 0`, `y >= 0`


```python
import gurobipy as gp
from gurobipy import GRB

try:
    # Create a new model
    m = gp.Model("sandwich_optimization")

    # Create variables
    x = m.addVar(vtype=GRB.INTEGER, name="peanut_butter_sandwiches")
    y = m.addVar(vtype=GRB.INTEGER, name="chocolate_spread_sandwiches")

    # Set objective function
    m.setObjective(3*x + 2*y, GRB.MAXIMIZE)

    # Add constraints
    m.addConstr(x + y <= 150, "bread_constraint")
    m.addConstr(x >= 45, "min_peanut_butter")
    m.addConstr(y >= 65, "min_chocolate_spread")
    m.addConstr(x <= 80, "max_peanut_butter")
    m.addConstr(y <= 100, "max_chocolate_spread")


    # Optimize model
    m.optimize()

    # Print results
    if m.status == GRB.OPTIMAL:
        print(f"Optimal Solution Found:")
        print(f"Peanut Butter Sandwiches: {x.x}")
        print(f"Chocolate Spread Sandwiches: {y.x}")
        print(f"Maximum Profit: ${m.objVal}")
    elif m.status == GRB.INFEASIBLE:
        print("Model is infeasible.")
    else:
        print(f"Optimization ended with status {m.status}")


except gp.GurobiError as e:
    print(f"Error code {e.errno}: {e}")

except AttributeError:
    print('Encountered an attribute error')
```
