2929import net .democracycraft .treasury .utils .Idempotency ;
3030import org .bukkit .Bukkit ;
3131import org .bukkit .ChatColor ;
32+ import org .bukkit .block .Sign ;
3233import org .bukkit .entity .Player ;
3334import org .bukkit .event .EventHandler ;
3435import org .bukkit .event .EventPriority ;
4243import java .util .Locale ;
4344import java .util .UUID ;
4445import java .util .logging .Level ;
45- import java .util .regex .Pattern ;
4646
4747/**
4848 * Treasury economy adapter for ChestShop.
@@ -53,8 +53,6 @@ public class TreasuryListener extends EconomyAdapter {
5353 static final long BUSINESS_UUID_MSB = 0xC5B0000000000000L ;
5454 static final UUID CHESTSHOP_SYSTEM_UUID = new UUID (0xC5B0FFFFFFFFFFFEL , 0xFFFFFFFFFFFFFFFEL );
5555
56- private static final Pattern BUSINESS_NAME_PATTERN = Pattern .compile ("(?i)^B:[0-9A-Z]+$" );
57-
5856 private final TreasuryApi treasury ;
5957 private final TaxApi taxApi ;
6058 private final int systemAccountId ;
@@ -478,27 +476,86 @@ public void onAccountQuery(AccountQueryEvent event) {
478476 }
479477
480478 String name = event .getName ();
481- if (!BUSINESS_NAME_PATTERN .matcher (name ).matches ()) {
479+ // A business token is anything starting with "B:" — the native, uppercase form
480+ // written by ChestShopSign.businessAccountSignName, or the legacy lowercase "b:"
481+ // form written by the old PlayerBusinesses/PlayerTreasury chestshops. We accept
482+ // both prefixes (and any suffix, incl. firm names with spaces) here; the suffix is
483+ // disambiguated below. Player names can never contain ':' so this never collides.
484+ if (name == null || name .length () < 3 || !name .regionMatches (true , 0 , "B:" , 0 , 2 )) {
482485 return ;
483486 }
484487
485488 try {
486- int accountId = Integer .parseInt (name .substring (2 ), 36 );
487- net .democracycraft .treasury .model .economy .Account treasuryAccount = treasury .getAccountById (accountId );
489+ String token = name .substring (2 );
490+ int accountId = -1 ;
491+ net .democracycraft .treasury .model .economy .Account treasuryAccount = null ;
492+
493+ // Native form: the suffix is a base-36 Treasury account id (e.g. B:1A).
494+ try {
495+ accountId = Integer .parseInt (token , 36 );
496+ treasuryAccount = treasury .getAccountById (accountId );
497+ } catch (NumberFormatException notBase36 ) {
498+ // e.g. a legacy firm name containing spaces — fall through to the name lookup.
499+ }
500+
501+ // Legacy migration form: the suffix is an old PlayerBusinesses firm *name*
502+ // (e.g. b:My Shop). Resolve it to the firm's default BUSINESS account so the
503+ // shop keeps working; the physical sign is rewritten to the native form on
504+ // first use (see onTransactionMigrateSign). This is also the fallback when a
505+ // firm name happens to be valid base-36 but doesn't decode to a real account.
506+ if (treasuryAccount == null && businessApi != null ) {
507+ net .democracycraft .business .model .Firm firm = businessApi .firms ().getFirm (token );
508+ if (firm != null && firm .getDefaultAccountId () != null ) {
509+ accountId = firm .getDefaultAccountId ();
510+ treasuryAccount = treasury .getAccountById (accountId );
511+ }
512+ }
513+
488514 if (treasuryAccount != null ) {
489515 String displayName = treasuryAccount .getDisplayName ();
490516 String shortName = ChestShopSign .businessAccountSignName (accountId );
491517 UUID syntheticUuid = toBusinessUuid (accountId );
492518 Account csAccount = new Account (displayName , shortName , syntheticUuid );
493519 event .setAccount (csAccount );
494520 }
495- } catch (NumberFormatException e ) {
496- // Invalid base-36 number, ignore
497521 } catch (Exception e ) {
498522 ChestShop .getBukkitLogger ().log (Level .WARNING , "Treasury: Could not resolve business account for " + name , e );
499523 }
500524 }
501525
526+ /**
527+ * Lazily migrates legacy business shop signs to the native account-id format.
528+ *
529+ * <p>The old PlayerBusinesses chestshops addressed a firm by name
530+ * ({@code b:<FirmName>}); the native format is {@code B:<base36 account id>}
531+ * ({@link ChestShopSign#businessAccountSignName(int)}). By the time a shop
532+ * trades, {@link #onAccountQuery} has already resolved the owner account, and
533+ * its short name is the canonical native token. So if the physical sign still
534+ * shows the legacy text, we rewrite the owner line in place. This runs only on
535+ * a completed (non-cancelled) transaction and is a no-op for shops already in
536+ * the native form. Firms whose names were altered during the data migration
537+ * (stripped special characters) won't resolve and are intentionally left for
538+ * their owners to recreate.</p>
539+ */
540+ @ EventHandler (priority = EventPriority .MONITOR , ignoreCancelled = true )
541+ public void onTransactionMigrateSign (TransactionEvent event ) {
542+ Sign sign = event .getSign ();
543+ Account owner = event .getOwnerAccount ();
544+ if (sign == null || owner == null || owner .getUuid () == null || !isBusinessUuid (owner .getUuid ())) {
545+ return ;
546+ }
547+
548+ String canonical = owner .getShortName ();
549+ if (canonical == null || canonical .equals (ChestShopSign .getOwner (sign ))) {
550+ return ;
551+ }
552+
553+ sign .setLine (ChestShopSign .NAME_LINE , canonical );
554+ sign .update (true );
555+ ChestShop .getBukkitLogger ().info ("Migrated legacy business shop sign to " + canonical
556+ + " at " + sign .getLocation ());
557+ }
558+
502559 @ EventHandler (priority = EventPriority .LOW )
503560 public void onAccountAccess (AccountAccessEvent event ) {
504561 if (event .canAccess ()) {
0 commit comments