I've been working on a solution this afternoon and have come up with the following. It may not be the most elegant way of doing this though, so I'm open to further comments.
function custom_tax_list_and_posts() {
$post_type = 'CUSTOM_POST_TYPE_NAME';
$taxonomy = 'CUSTOM_TAXONOMY_NAME';
// Get all of the parent terms in the custom taxonomy and include empty ones
$parent_terms = get_terms( $taxonomy, array( 'parent' => 0, 'hide_empty' => false ) );
echo '<ul>';
// Start looping parent terms
foreach( $parent_terms as $pterm ) :
// Get all of the child terms within the parent term only
$child_terms = get_terms( $taxonomy, array( 'parent' => $pterm->term_id, 'hide_empty' => false ) );
echo '<li>';
echo '<h3>' . $pterm->name . '</h3>';
echo '<ul>';
// Start looping through child terms
foreach( $child_terms as $cterm ) :
echo '<li>';
echo '<h3>' . $cterm->name . '</h3>';
// Configure custom post arguments
$args = array(
'post_type' => $post_type,
'posts_per_page' => 10, // Or however many you need
'tax_query' => array(
array(
'taxonomy' => $taxonomy,
'field' => 'slug',
'terms' => $cterm->slug
)
)
);
query_posts($args);
if ( have_posts() ) :
echo '<ul>';
// Start looping posts within child terms only
while ( have_posts() ) : the_post();
echo '<li>';
echo '<p class="post-date">' . get_the_date('j F Y') . '</p>';
echo '<h3><a href="' . get_the_permalink() . '">' . get_the_title() . '</a></h3>';
echo '<p>' . get_the_excerpt() . '</p>';
echo '<a class="read-more" href="'. get_the_permalink() . '">Read more ...</a>';
echo '</li>';
endwhile;
echo '</ul>';
endif;
endforeach;
echo '</ul>';
echo '</li>';
endforeach;
echo '</ul>';
}