迁移合作订单完整能力,并统一 AppRoleCode 角色权限与首页菜单展示。

新增 CooperationOrder 模块(双 Tab 列表、获客员绑定、主 Tab 扫码)、订单来源/带客单绑定及配套 API 测试;同步引入 roleCode 权限匹配与相关单元测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 13:33:00 +08:00
parent d310d26293
commit 1970572edd
66 changed files with 4247 additions and 201 deletions

View File

@ -7,47 +7,71 @@
import Foundation
/// URI
/// URI
struct HomeCommonMenuStore {
static let defaultStorageKey = "home.common.menu.uris"
static let defaultBaselineKey = "home.common.menu.android.baseline.v2"
static let storageKeyPrefix = "home.common.menu.uris.role."
private let defaults: UserDefaults
private let storageKey: String
private let baselineKey: String
/// UserDefaults
init(
defaults: UserDefaults = .standard,
storageKey: String = Self.defaultStorageKey,
baselineKey: String = Self.defaultBaselineKey
) {
init(defaults: UserDefaults = .standard) {
self.defaults = defaults
self.storageKey = storageKey
self.baselineKey = baselineKey
}
/// URI使
func load(menuItems: [HomeMenuItem]) -> [String] {
/// Android URI 4 4
static func defaultCommonURIs(
orderedTopLevelURIs: [String],
availableURIs: Set<String>
) -> [String] {
let resolved = orderedTopLevelURIs.compactMap { uri -> String? in
let trimmed = uri.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return nil }
let canonical = HomeMenuRouter.canonicalURI(for: trimmed, availableURIs: availableURIs)
return availableURIs.contains(canonical) ? canonical : nil
}
let deduplicated = unique(resolved)
if deduplicated.count > 4 {
return Array(deduplicated.prefix(4))
}
return deduplicated
}
/// URI Android
func load(
menuItems: [HomeMenuItem],
roleCode: String?,
legacyRoleId: Int? = nil,
topLevelPermissionURIs: [String]
) -> [String] {
guard let roleCode, !roleCode.isEmpty else { return [] }
migrateLegacyStorageIfNeeded(roleCode: roleCode, legacyRoleId: legacyRoleId)
let availableURIs = Set(menuItems.map(\.uri))
if !defaults.bool(forKey: baselineKey) {
let defaults = defaultCommonURIs(availableURIs: availableURIs)
save(defaults)
self.defaults.set(true, forKey: baselineKey)
return defaults
let key = storageKey(for: roleCode)
let saved = defaults.stringArray(forKey: key)
if let saved, !saved.isEmpty {
let filtered = Self.unique(saved.compactMap { uri -> String? in
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
return availableURIs.contains(resolved) ? resolved : nil
})
if !filtered.isEmpty {
save(filtered, roleCode: roleCode)
return filtered
}
}
let saved = defaults.stringArray(forKey: storageKey) ?? []
let filtered = unique(saved.compactMap { uri -> String? in
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
return availableURIs.contains(resolved) ? resolved : nil
})
save(filtered)
return filtered
let defaultURIs = Self.defaultCommonURIs(
orderedTopLevelURIs: topLevelPermissionURIs,
availableURIs: availableURIs
)
save(defaultURIs, roleCode: roleCode)
return defaultURIs
}
/// URI
func add(_ uri: String, current: [String], menuItems: [HomeMenuItem]) -> [String] {
func add(_ uri: String, current: [String], menuItems: [HomeMenuItem], roleCode: String?) -> [String] {
guard let roleCode, !roleCode.isEmpty else { return current }
let availableURIs = Set(menuItems.map(\.uri))
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
guard availableURIs.contains(resolved) else { return current }
@ -55,34 +79,49 @@ struct HomeCommonMenuStore {
return current
}
let next = current + [resolved]
save(next)
save(next, roleCode: roleCode)
return next
}
/// URI URI
func remove(_ uri: String, current: [String]) -> [String] {
func remove(_ uri: String, current: [String], roleCode: String?) -> [String] {
guard let roleCode, !roleCode.isEmpty else { return current }
let aliasKey = HomeMenuRouter.menuAliasKey(for: uri)
let next = current.filter { HomeMenuRouter.menuAliasKey(for: $0) != aliasKey }
save(next)
save(next, roleCode: roleCode)
return next
}
/// URI
func save(_ uris: [String]) {
defaults.set(unique(uris), forKey: storageKey)
/// URI
func save(_ uris: [String], roleCode: String) {
defaults.set(Self.unique(uris), forKey: storageKey(for: roleCode))
}
/// URI
private func defaultCommonURIs(availableURIs: Set<String>) -> [String] {
let preferred = ["registration_invitation", "location_report", "pm_manager", "pm"]
return unique(preferred.compactMap { uri in
let resolved = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
return availableURIs.contains(resolved) ? resolved : nil
})
/// key legacy role id key
func migrateLegacyStorageIfNeeded(roleCode: String, legacyRoleId: Int?) {
let newKey = storageKey(for: roleCode)
guard defaults.stringArray(forKey: newKey) == nil,
let legacyRoleId,
let legacyData = defaults.stringArray(forKey: legacyStorageKey(for: legacyRoleId)),
!legacyData.isEmpty else {
return
}
defaults.set(legacyData, forKey: newKey)
defaults.removeObject(forKey: legacyStorageKey(for: legacyRoleId))
}
/// role_code UserDefaults key
private func storageKey(for roleCode: String) -> String {
Self.storageKeyPrefix + roleCode
}
/// numeric role id key
private func legacyStorageKey(for roleId: Int) -> String {
Self.storageKeyPrefix + String(roleId)
}
/// URI URI
private func unique(_ uris: [String]) -> [String] {
private static func unique(_ uris: [String]) -> [String] {
var seen = Set<String>()
return uris.filter { uri in
seen.insert(HomeMenuRouter.menuAliasKey(for: uri)).inserted