Two product objects can represent the same stock item even when they occupy different memory locations. The default Object.equals uses reference identity. A value class supplies an equality rule based on the fields that identify the value, then supplies a matching hashCode. Equal values must have equal hashes; unequal values can collide. HashMap uses the hash to choose a bucket and equals to identify the key within it, so a collision must never merge unrelated items.
Keep the identity fields stable while an object is a hash key. Changing a field used by hashCode after insertion can make the entry difficult to find. A useful toString is a separate concern: it displays state but doesn't define equality. You will test distinct instances, repeated values, case differences and deliberately colliding labels.