1, 'price' => $product->get_price(), ) ); if ( function_exists( 'wc_get_price_including_tax' ) ) { // WC 3.0+ $price = wc_get_price_including_tax( $product, $args ); } else { // WC < 3.0 $price = $product->get_price_including_tax( $args['qty'], $args['price'] ); } return $price; } /** * For a given product, and optionally price/qty, work out the sign-up fee with tax excluded, based on store settings. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * @param WC_Product $product * @param array $args * @return float */ function wcs_get_price_excluding_tax( $product, $args = array() ) { $args = wp_parse_args( $args, array( 'qty' => 1, 'price' => $product->get_price(), ) ); if ( function_exists( 'wc_get_price_excluding_tax' ) ) { // WC 3.0+ $price = wc_get_price_excluding_tax( $product, $args ); } else { // WC < 3.0 $price = $product->get_price_excluding_tax( $args['qty'], $args['price'] ); } return $price; } /** * Returns a 'from' prefix if you want to show where prices start at. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 * @return string */ function wcs_get_price_html_from_text( $product = '' ) { if ( function_exists( 'wc_get_price_html_from_text' ) ) { // WC 3.0+ $price_html_from_text = wc_get_price_html_from_text(); } else { // WC < 3.0 $price_html_from_text = $product->get_price_html_from_text(); } return $price_html_from_text; } /** * Get an array of the prices, used to help determine min/max values. * * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 */ function wcs_get_variation_prices( $variation, $variable_product ) { return array( 'price' => apply_filters( 'woocommerce_variation_prices_price', WC_Subscriptions_Product::get_price( $variation ), $variation, $variable_product ), 'regular_price' => apply_filters( 'woocommerce_variation_prices_regular_price', WC_Subscriptions_Product::get_regular_price( $variation, 'edit' ), $variation, $variable_product ), 'sale_price' => apply_filters( 'woocommerce_variation_prices_sale_price', WC_Subscriptions_Product::get_sale_price( $variation, 'edit' ), $variation, $variable_product ), 'sign_up_fee' => apply_filters( 'woocommerce_variation_prices_sign_up_fee', WC_Subscriptions_Product::get_sign_up_fee( $variation ), $variation, $variable_product ), ); } /** * Get an array of the minimum and maximum priced variations based on subscription billing terms. * * @param array $child_variation_ids the IDs of product variation children ids * @return array Array containing the min and max variation prices and billing data * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 */ function wcs_get_min_max_variation_data( $variable_product, $child_variation_ids = array() ) { if ( empty( $child_variation_ids ) ) { $child_variation_ids = is_callable( array( $variable_product, 'get_visible_children' ) ) ? $variable_product->get_visible_children() : $variable_product->get_children( true ); } $variations_data = array(); foreach ( $child_variation_ids as $variation_id ) { if ( $variation = wc_get_product( $variation_id ) ) { $prices = wcs_get_variation_prices( $variation, $variable_product ); foreach ( $prices as $price_key => $amount ) { if ( '' !== $amount ) { if ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) ) { $prices[ $price_key ] = wcs_get_price_including_tax( $variable_product, array( 'price' => $amount ) ); } else { $prices[ $price_key ] = wcs_get_price_excluding_tax( $variable_product, array( 'price' => $amount ) ); } } } $variations_data[ $variation_id ] = array( 'price' => $prices['price'], 'regular_price' => $prices['regular_price'], 'sale_price' => $prices['sale_price'], 'subscription' => array( 'sign_up_fee' => $prices['sign_up_fee'], 'period' => WC_Subscriptions_Product::get_period( $variation ), 'interval' => WC_Subscriptions_Product::get_interval( $variation ), 'trial_length' => WC_Subscriptions_Product::get_trial_length( $variation ), 'trial_period' => WC_Subscriptions_Product::get_trial_period( $variation ), 'length' => WC_Subscriptions_Product::get_length( $variation ), ), ); } } return wcs_calculate_min_max_variations( $variations_data ); } /** * Determine the minimum and maximum values for a set of structured subscription * price data in a form created by @see wcs_get_min_max_variation_data() * * @param array $variations_data the IDs of product variation children ids * @return array * @since 1.0.0 - Migrated from WooCommerce Subscriptions v2.2.0 */ function wcs_calculate_min_max_variations( $variations_data ) { $lowest_initial_amount = $highest_initial_amount = $lowest_price = $highest_price = ''; $shortest_initial_period = $longest_initial_period = $shortest_trial_period = $longest_trial_period = $shortest_trial_length = $longest_trial_length = ''; $longest_initial_interval = $shortest_initial_interval = $variable_subscription_period = $variable_subscription_period_interval = ''; $lowest_regular_price = $highest_regular_price = $lowest_sale_price = $highest_sale_price = $max_subscription_period = $max_subscription_period_interval = ''; $variable_subscription_sign_up_fee = $variable_subscription_trial_period = $variable_subscription_trial_length = $variable_subscription_length = $variable_subscription_sign_up_fee = $variable_subscription_trial_period = $variable_subscription_trial_length = $variable_subscription_length = ''; $min_variation_id = $max_variation_id = null; $variations_data_prices_list = array(); $variations_data_sign_up_fees_list = array(); $variations_data_periods_list = array(); $variations_data_intervals_list = array(); $variations_data_trial_lengths_list = array(); $variations_data_trial_periods_list = array(); $variations_data_lengths_list = array(); foreach ( $variations_data as $variation_id => $variation_data ) { $is_max = $is_min = false; if ( '' === $variation_data['price'] && empty( $variation_data['subscription']['sign_up_fee'] ) ) { continue; } $variations_data_prices_list = array_unique( array_merge( $variations_data_prices_list, array( $variation_data['price'] ) ) ); $variations_data_sign_up_fees_list = array_unique( array_merge( $variations_data_sign_up_fees_list, array( empty( $variation_data['subscription']['sign_up_fee'] ) ? 0 : $variation_data['subscription']['sign_up_fee'] ) ) ); $variations_data_periods_list = array_unique( array_merge( $variations_data_periods_list, array( $variation_data['subscription']['period'] ) ) ); $variations_data_intervals_list = array_unique( array_merge( $variations_data_intervals_list, array( $variation_data['subscription']['interval'] ) ) ); $variations_data_trial_lengths_list = array_unique( array_merge( $variations_data_trial_lengths_list, array( empty( $variation_data['subscription']['trial_length'] ) ? 0 : $variation_data['subscription']['trial_length'] ) ) ); $variations_data_trial_periods_list = array_unique( array_merge( $variations_data_trial_periods_list, array( $variation_data['subscription']['trial_period'] ) ) ); $variations_data_lengths_list = array_unique( array_merge( $variations_data_lengths_list, array( $variation_data['subscription']['length'] ) ) ); $has_free_trial = '' !== $variation_data['subscription']['trial_length'] && $variation_data['subscription']['trial_length'] > 0; // Determine some recurring price flags $is_lowest_price = $variation_data['price'] < $lowest_price || '' === $lowest_price; $is_longest_period = wcs_get_longest_period( $variable_subscription_period, $variation_data['subscription']['period'] ) === $variation_data['subscription']['period']; $is_longest_interval = $variation_data['subscription']['interval'] >= $variable_subscription_period_interval || '' === $variable_subscription_period_interval; // Find the amount the subscriber will have to pay up-front if ( $has_free_trial ) { $initial_amount = $variation_data['subscription']['sign_up_fee']; $initial_period = $variation_data['subscription']['trial_period']; $initial_interval = $variation_data['subscription']['trial_length']; } else { $initial_amount = (float) $variation_data['price'] + (float) $variation_data['subscription']['sign_up_fee']; $initial_period = $variation_data['subscription']['period']; $initial_interval = $variation_data['subscription']['interval']; } // We have a free trial & no sign-up fee, so need to choose the longest free trial (and maybe the shortest) if ( $has_free_trial && 0 == $variation_data['subscription']['sign_up_fee'] ) { // First variation if ( '' === $longest_trial_period ) { $is_min = true; // If two variations have the same free trial, choose the variation with the lowest recurring price for the longest period } elseif ( $variable_subscription_trial_period === $variation_data['subscription']['trial_period'] && $variation_data['subscription']['trial_length'] === $variable_subscription_trial_length ) { // If the variation has the lowest recurring price, it's the cheapest if ( $is_lowest_price ) { $is_min = true; // When current variation's free trial is the same as the lowest, it's the cheaper if it has a longer billing schedule } elseif ( $variation_data['price'] === $lowest_price ) { if ( $is_longest_period && $is_longest_interval ) { $is_min = true; // Longest with a new billing period } elseif ( $is_longest_period && $variation_data['subscription']['period'] !== $variable_subscription_trial_period ) { $is_min = true; } } // Otherwise the cheapest variation is the one with the longer trial } elseif ( $variable_subscription_trial_period === $variation_data['subscription']['trial_period'] ) { $is_min = $variation_data['subscription']['trial_length'] > $variable_subscription_trial_length; // Otherwise just a longer trial period (that isn't equal to the longest period) } elseif ( wcs_get_longest_period( $longest_trial_period, $variation_data['subscription']['trial_period'] ) === $variation_data['subscription']['trial_period'] ) { $is_min = true; } if ( $is_min ) { $longest_trial_period = $variation_data['subscription']['trial_period']; $longest_trial_length = $variation_data['subscription']['trial_length']; } // If the current cheapest variation is also free, then the shortest trial period is the most expensive if ( 0 == $lowest_price || '' === $lowest_price ) { if ( '' === $shortest_trial_period ) { $is_max = true; // Need to check trial length } elseif ( $shortest_trial_period === $variation_data['subscription']['trial_period'] ) { $is_max = $variation_data['subscription']['trial_length'] < $shortest_trial_length; // Need to find shortest period } elseif ( wcs_get_shortest_period( $shortest_trial_period, $variation_data['subscription']['trial_period'] ) === $variation_data['subscription']['trial_period'] ) { $is_max = true; } if ( $is_max ) { $shortest_trial_period = $variation_data['subscription']['trial_period']; $shortest_trial_length = $variation_data['subscription']['trial_length']; } } } else { $longest_initial_period = wcs_get_longest_period( $longest_initial_period, $initial_period ); $shortest_initial_period = wcs_get_shortest_period( $shortest_initial_period, $initial_period ); $is_lowest_initial_amount = $initial_amount < $lowest_initial_amount || '' === $lowest_initial_amount; $is_longest_initial_period = $initial_period === $longest_initial_period; $is_longest_initial_interval = $initial_interval >= $longest_initial_interval || '' === $longest_initial_interval; $is_highest_initial = $initial_amount > $highest_initial_amount || '' === $highest_initial_amount; $is_shortest_period = $initial_period === $shortest_initial_period || '' === $shortest_initial_period; $is_shortest_interval = $initial_interval < $shortest_initial_interval || '' === $shortest_initial_interval; // If we're not dealing with the lowest initial access amount, then ignore this variation if ( ! $is_lowest_initial_amount && $initial_amount !== $lowest_initial_amount ) { continue; } // If the variation has the lowest price, it's the cheapest if ( $is_lowest_initial_amount ) { $is_min = true; // When current variation's price is the same as the lowest, it's the cheapest only if it has a longer billing schedule } elseif ( $initial_amount === $lowest_initial_amount ) { // We need to check the recurring schedule when the sign-up fee & free trial periods are equal if ( $has_free_trial && $initial_period == $longest_initial_period && $initial_interval == $longest_initial_interval ) { // If the variation has the lowest recurring price, it's the cheapest if ( $is_lowest_price ) { $is_min = true; // When current variation's price is the same as the lowest, it's the cheapest only if it has a longer billing schedule } elseif ( $variation_data['price'] === $lowest_price ) { if ( $is_longest_period && $is_longest_interval ) { $is_min = true; // Longest with a new billing period } elseif ( $is_longest_period && $variation_data['subscription']['period'] !== $variable_subscription_period ) { $is_min = true; } } // Longest initial term is the cheapest } elseif ( $is_longest_initial_period && $is_longest_initial_interval ) { $is_min = true; // Longest with a new billing period } elseif ( $is_longest_initial_period && $initial_period !== $variable_subscription_period ) { $is_min = true; } } // If we have the highest price for the shortest period, we might have the maximum variation if ( $is_highest_initial && $is_shortest_period && $is_shortest_interval ) { $is_max = true; // But only if its for the shortest billing period } elseif ( $variation_data['price'] === $highest_price ) { if ( $is_shortest_period && $is_shortest_interval ) { $is_max = true; } elseif ( $is_shortest_period ) { $is_max = true; } } } // If it's the min subscription terms if ( $is_min ) { $min_variation_id = $variation_id; $lowest_price = $variation_data['price']; $lowest_regular_price = $variation_data['regular_price']; $lowest_sale_price = $variation_data['sale_price']; $lowest_regular_price = ( '' === $lowest_regular_price ) ? 0 : $lowest_regular_price; $lowest_sale_price = ( '' === $lowest_sale_price ) ? 0 : $lowest_sale_price; $lowest_initial_amount = $initial_amount; $longest_initial_period = $initial_period; $longest_initial_interval = $initial_interval; $variable_subscription_sign_up_fee = $variation_data['subscription']['sign_up_fee']; $variable_subscription_period = $variation_data['subscription']['period']; $variable_subscription_period_interval = $variation_data['subscription']['interval']; $variable_subscription_trial_length = $variation_data['subscription']['trial_length']; $variable_subscription_trial_period = $variation_data['subscription']['trial_period']; $variable_subscription_length = $variation_data['subscription']['length']; } if ( $is_max ) { $max_variation_id = $variation_id; $highest_price = $variation_data['price']; $highest_regular_price = $variation_data['regular_price']; $highest_sale_price = $variation_data['sale_price']; $highest_initial_amount = $initial_amount; $highest_regular_price = ( '' === $highest_regular_price ) ? 0 : $highest_regular_price; $highest_sale_price = ( '' === $highest_sale_price ) ? 0 : $highest_sale_price; $max_subscription_period = $variation_data['subscription']['period']; $max_subscription_period_interval = $variation_data['subscription']['interval']; } } if ( sizeof( array_unique( $variations_data_prices_list ) ) > 1 ) { $subscription_details_identical = false; } elseif ( sizeof( array_unique( $variations_data_sign_up_fees_list ) ) > 1 ) { $subscription_details_identical = false; } elseif ( sizeof( array_unique( $variations_data_periods_list ) ) > 1 ) { $subscription_details_identical = false; } elseif ( sizeof( array_unique( $variations_data_intervals_list ) ) > 1 ) { $subscription_details_identical = false; } elseif ( sizeof( array_unique( $variations_data_trial_lengths_list ) ) > 1 ) { $subscription_details_identical = false; } elseif ( sizeof( array_unique( $variations_data_trial_periods_list ) ) > 1 ) { $subscription_details_identical = false; } elseif ( sizeof( array_unique( $variations_data_lengths_list ) ) > 1 ) { $subscription_details_identical = false; } else { $subscription_details_identical = true; } return array( 'min' => array( 'variation_id' => $min_variation_id, 'price' => $lowest_price, 'regular_price' => $lowest_regular_price, 'sale_price' => $lowest_sale_price, 'period' => $variable_subscription_period, 'interval' => $variable_subscription_period_interval, ), 'max' => array( 'variation_id' => $max_variation_id, 'price' => $highest_price, 'regular_price' => $highest_regular_price, 'sale_price' => $highest_sale_price, 'period' => $max_subscription_period, 'interval' => $max_subscription_period_interval, ), 'subscription' => array( 'signup-fee' => $variable_subscription_sign_up_fee, 'trial_period' => $variable_subscription_trial_period, 'trial_length' => $variable_subscription_trial_length, 'length' => $variable_subscription_length, ), 'identical' => $subscription_details_identical, ); } /** * Generates a key for grouping subscription products with the same billing schedule. * * Used in a frontend cart and checkout context to group items by a recurring cart key for use in generating recurring carts. * Used by the orders//subscriptions REST API endpoint to group order items into subscriptions. * * @see https://woocommerce.com/document/subscriptions/develop/multiple-subscriptions/#section-3 * * @param WC_Product $product The product to generate the key for. * @param int $renewal_time The timestamp of the first renewal payment. * * @return string The subscription product grouping key. */ function wcs_get_subscription_grouping_key( $product, $renewal_time = 0 ) { $key = ''; $renewal_time = ! empty( $renewal_time ) ? $renewal_time : WC_Subscriptions_Product::get_first_renewal_payment_time( $product ); $interval = WC_Subscriptions_Product::get_interval( $product ); $period = WC_Subscriptions_Product::get_period( $product ); $length = WC_Subscriptions_Product::get_length( $product ); $trial_period = WC_Subscriptions_Product::get_trial_period( $product ); $trial_length = WC_Subscriptions_Product::get_trial_length( $product ); if ( $renewal_time > 0 ) { $key .= gmdate( 'Y_m_d_', $renewal_time ); } // First start with the billing interval and period. switch ( $interval ) { case 1: if ( 'day' === $period ) { $key .= 'daily'; } else { $key .= sprintf( '%sly', $period ); } break; case 2: $key .= sprintf( 'every_2nd_%s', $period ); break; case 3: $key .= sprintf( 'every_3rd_%s', $period ); // or sometimes two exceptions it would seem break; default: $key .= sprintf( 'every_%dth_%s', $interval, $period ); break; } if ( $length > 0 ) { $key .= '_for_'; $key .= sprintf( '%d_%s', $length, $period ); if ( $length > 1 ) { $key .= 's'; } } if ( $trial_length > 0 ) { $key .= sprintf( '_after_a_%d_%s_trial', $trial_length, $trial_period ); } return apply_filters( 'wcs_subscription_product_grouping_key', $key, $product, $renewal_time ); } Taxes Archives - Odessa Headlines https://odessaheadlines.com/tag/taxes/ Covering Ector County, Texas Sat, 01 Nov 2025 16:54:36 +0000 en-US hourly 1 https://wordpress.org/?v=7.1.4 https://odessaheadlines.com/wp-content/uploads/2021/03/cropped-MAR21-Odessa-Icon-150x150.png Taxes Archives - Odessa Headlines https://odessaheadlines.com/tag/taxes/ 32 32 HEADLINES INVESTIGATION: “We’ll Never Regain Public Trust” — Inside the City’s Tax-Rate Cover-Up https://odessaheadlines.com/2025/11/01/headlines-investigation-well-never-regain-public-trust-inside-the-citys-tax-rate-cover-up/ Sat, 01 Nov 2025 16:54:34 +0000 https://odessaheadlines.com/?p=2573 The Spark: A Citizen and a Law the City Wanted to Ignore When Odessan Kris Crow stood before the Odessa City Council on September 17, 2025, he wasn’t just voicing a concern — he had already filed a formal complaint with the Texas Attorney General’s Office, asserting that the city was in violation of Senate […]

HEADLINES INVESTIGATION: “We’ll Never Regain Public Trust” — Inside the City’s Tax-Rate Cover-Up is a story from Odessa Headlines, a publication covering Ector County, Texas. Odessa Headlines’ vision is to provide quality in-depth original coverage and commentary of local issues – predominantly politics and public policy – while cross-sharing excellent coverage of state and national issues from our media partners across Texas.

]]>
The Spark: A Citizen and a Law the City Wanted to Ignore

When Odessan Kris Crow stood before the Odessa City Council on September 17, 2025, he wasn’t just voicing a concern — he had already filed a formal complaint with the Texas Attorney General’s Office, asserting that the city was in violation of Senate Bill 1851.   That new state law prevents cities from adopting a tax rate above the “no-new-revenue” rate if their annual audit and financial statement have not been timely filed.

From the dais, city leaders pushed back hard. Mayor Cal Hendrick and Interim City Attorney Keith Stretcher, citing “over 100 years of combined legal experience,” publicly assured citizens that SB 1851 did not apply to Odessa.

But Crow’s complaint had already set a process in motion—one that would soon expose just how deeply flawed the city’s internal numbers, procedures, and leadership had become.


A Warning from Within

Just nine days later, on September 26, 2025, Councilman Craig Stoker sent an email to the Mayor, City Manager David Vela, City Attorney Stretcher, and Assistant City Manager (now Interim City Manager)  Aaron Smith. The subject line: “Re: Fixing the Tax Levy for the City of Odessa, Texas.”

Stoker’s email is remarkable — both for its bluntness and for what it reveals about the breakdown inside City Hall.

“I tried to call out the inaccuracy in our numbers and subsequent marketing,” Stoker wrote. “I mentioned that I did not like the use of that number at the budget workshop… but by that point I had been yelled at enough and demeaned by another council member that I mostly kept my mouth shut.”

He went on to blame both the Finance and Media Relations Departments for publishing and promoting false tax-rate data:

“As I expressed to you earlier this is a failure on the part of not only finance but also marketing. There are inaccuracies and conflicts all over the website regarding the budget and the data. All of them should know better.”

“Just like the mess with billing and collections, I am angry that we walk out with egg on our face and take the blame, but we don’t even have the ability to take any action to reassure the public that incompetent staff have been dealt with. We will never be able to rebuild any kind of public trust when we can’t even trust the staff ourselves.”

That single line — “We will never be able to rebuild any kind of public trust” — would prove prophetic.

Despite his frustration, Stoker concluded by saying he would still vote for the budget and the flawed tax rate “to avoid punishing the city” but vowed that “next year I will expect that we have a competent staff member or third-party budget manager. I will not suffer through this ridiculousness again.”


The Mayor’s Response: Private Calls, Public Silence

Just hours earlier that same evening, Mayor Hendrick had circulated his own message to the council — one that now raises serious Open Meetings Act concerns.

“You are each BCC’ed to avoid a violation of the Open Meetings Act,” Hendrick began, before adding: “I have talked to each of you individually this afternoon to explain the problems with the proposed tax rate and the fact that the effect of the proposed tax rate is much greater than what we understood.”

The Mayor went on to acknowledge that “we relied on inaccurate data” to communicate the new tax rate to the public — data used on the city’s website, social media, and in conversations with the press and staff.

He then described a decision point that, by his own words, appeared to have already been reached:

“We either do nothing and allow the new tax rate to become effective October 1, 2025, or we can meet next Friday and agree to a no-new-[revenue sic] tax rate… It is my understanding that Council members desire an opportunity to readdress the tax levy issue next week.”

Finally, Hendrick instructed City Manager Vela:

“Please get with the City Secretary and post a notice for a meeting for next Friday, October 3rd, at 11:00 AM… and a second notice for 11:30 AM.”

But those meetings were never posted — and never held.


The Meetings That Disappeared

A review of the city’s official agenda center confirms that no special meetings were scheduled for October 3, 2025.
No notices.
No minutes.
No explanation.

Instead, the city quietly proceeded with adopting the flawed tax rate — the same one both the mayor and Stoker had acknowledged internally was based on inaccurate data.

In doing so, they effectively hid the problem from the public.
No correction was issued.
No statement was made.
No effort was taken to fix what both the mayor and a sitting councilman knew to be wrong.


The Pattern: Conceal, Delay, Deny

The events of late September 2025 fit a now-familiar pattern at Odessa City Hall:

  1. Citizen raises a legal concern (Crow) ? dismissed.
  2. Internal acknowledgment of error (Hendrick & Stoker) ? buried.
  3. Promise of corrective action (Oct 3 meetings) ? never kept.
  4. No public disclosure ? taxpayers left in the dark.

Even after the Texas Attorney General opened an investigation, city officials publicly maintained that they had acted properly. That narrative unraveled, however, during a later press conference, when Mayor Cal Hendrick told reporters that the Council had decided in executive session to send a letter to Attorney General Ken Paxton stating that the city would not raise the 2025–26 tax rate above the no-new-revenue rate.

That admission is problematic on two fronts. First, it directly contradicts the public record — video of the meeting shows the Council reconvening from executive session and announcing that no action had been taken. Second, it appears to confirm a violation of the Texas Open Meetings Act, which prohibits taking or agreeing upon official action in closed session.

Hendrick further acknowledged that, amid the controversy, city officials had also found some problems with the way tax rates had been calculated in prior years, reinforcing the pattern of internal errors and after-the-fact disclosure that has plagued this administration.

That contradiction — between public denial and private admission — lies at the heart of Odessa’s credibility crisis.


“We’ll Never Rebuild Public Trust”

Councilman Stoker’s words now serve as both confession and epitaph for the city’s handling of this affair:

“We will never be able to rebuild any kind of public trust when we can’t even trust the staff ourselves.”

He was right. The cover-up didn’t just destroy confidence in the city’s numbers — it shattered confidence in its leaders.


The Bottom Line

Crow did his duty by invoking state law and alerting the Attorney General.
Stoker did his duty by documenting the truth in writing but failed the public trust by not bringing his very valid concerns public.
The mayor and council clearly failed in their duty — to correct the record, inform the public, and honor the Open Meetings Act they claimed to be following.

The missing October 3 meetings are more than a clerical omission; they are evidence of a deliberate decision to keep citizens in the dark.

And as the investigation continues, the question Odessa must now ask is not whether the math was wrong — it’s whether the city’s leadership was ever honest about it.


HEADLINE TAKEAWAY:

“We’ll Never Rebuild Public Trust.” — Councilman Craig Stoker, Sept. 26, 2025


HEADLINES INVESTIGATION: “We’ll Never Regain Public Trust” — Inside the City’s Tax-Rate Cover-Up is a story from Odessa Headlines, a publication covering Ector County, Texas. Odessa Headlines’ vision is to provide quality in-depth original coverage and commentary of local issues – predominantly politics and public policy – while cross-sharing excellent coverage of state and national issues from our media partners across Texas.

]]>
Councilman Hanie Holds The Line https://odessaheadlines.com/2024/09/11/councilman-hanie-holds-the-line/ Wed, 11 Sep 2024 19:18:59 +0000 https://odessaheadlines.com/?p=2517 Despite a passionate plea from Councilman Chris Hanie to consider the plight of local taxpayers, the Odessa City Council voted Tuesday to raise water, sewer, and property taxes for most Odessans, by a vote of six to one. City Manager John Beckmeyer had previously promised that the City’s use of a new zero-based budget would […]

Councilman Hanie Holds The Line is a story from Odessa Headlines, a publication covering Ector County, Texas. Odessa Headlines’ vision is to provide quality in-depth original coverage and commentary of local issues – predominantly politics and public policy – while cross-sharing excellent coverage of state and national issues from our media partners across Texas.

]]>
Despite a passionate plea from Councilman Chris Hanie to consider the plight of local taxpayers, the Odessa City Council voted Tuesday to raise water, sewer, and property taxes for most Odessans, by a vote of six to one.

City Manager John Beckmeyer had previously promised that the City’s use of a new zero-based budget would save money, yet the budget passed Tuesday night featured a 9.4% overall increase of nearly $12 million, bringing the total budget to $305,193,877.

A notable change in the budget was the addition of nearly $1 million to staff the City Manager’s office with seven public relations and communications personnel. This came alongside an almost doubling of the City Secretary’s budget and a 76% increase in the Planning and Development department’s budget. On the other hand, the budget also sharply reduced the number of police officers.

While the City Council emphasized a small two-cent reduction in the property tax rate, the data presented showed that most taxpayers will actually pay more in city taxes due to rising property values.

In addition to higher property taxes, the city also increased water and sewer rates by 3% and trash rates by 3.5%. These hikes were approved despite ongoing service failures, which have left residents without water for days and with overflowing dumpsters.

Despite the nearly 10% increase in the budget, Councilperson Denise Swanner praised city staff for their hard work and lauded the value of the new zero-based budgeting process. However, there were several budgetary issues pointed out by citizens at the meeting. For example, the water and sewer fund’s revenues were budgeted to decrease by 1.7%, even though the council voted to raise water and sewer rates by 3%. Questions were raised about how revenues could decrease while rates were increasing.

Councilman Chris Hanie had a different perspective. He urged his fellow council members to drop the property tax rate to the “no new revenue” rate or lower, and to maintain current water, sewer, and trash rates. Hanie pointed out that the city will still be sitting on a surplus of over $272 million, equivalent to almost a full year of expenses. In a comment to Odessa Headlines, Councilman Hanie said, “I was really disappointed that Mayor Joven and the rest of the council showed so little concern for the difficulties our taxpayers are facing. With inflation, everyone is struggling to make ends meet, and we could have easily found some savings in our budget or used some of the $123 million general fund balance the city currently holds. I really wanted to hold the line and help our taxpayers, but I couldn’t get any support from City Manager Beckmeyer, Mayor Joven, or the other council members.”

Councilman Hanie Holds The Line is a story from Odessa Headlines, a publication covering Ector County, Texas. Odessa Headlines’ vision is to provide quality in-depth original coverage and commentary of local issues – predominantly politics and public policy – while cross-sharing excellent coverage of state and national issues from our media partners across Texas.

]]>