Files
MBTI_wang/api/app/common/service/EnterpriseCooperationService.php
Ghost 830e3611d7 feat: 企业版合作模式(后端、管理端、小程序)
- API:合作模式配置、用户选择、企业版入口与迁移 SQL

- 管理端:合作方式管理、企业/用户侧设置与 CSV 导出等

- 小程序:合作模式弹窗与结果/简历等页对接;开发脚本小调整

Made-with: Cursor
2026-04-23 16:08:13 +08:00

451 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\common\service;
use think\facade\Db;
/**
* 企业版:三项完成判定 + 合作模式配置与用户选择
*/
class EnterpriseCooperationService
{
public const MODE_SALARY = 'salary';
public const MODE_STARTUP_EQUITY = 'startup_equity';
public const MODE_KNOWLEDGE_PAY = 'knowledge_pay';
/** @return array<string, array{title:string,description:string}> */
public static function builtinModeDefs(): array
{
return [
self::MODE_SALARY => [
'title' => '工资',
'description' => '全职/薪资导向合作',
],
self::MODE_STARTUP_EQUITY => [
'title' => '创业分红',
'description' => '合伙/股权激励类合作',
],
self::MODE_KNOWLEDGE_PAY => [
'title' => '知识付费',
'description' => '课程/咨询/付费内容类合作',
],
];
}
public static function validModeCodes(): array
{
return array_keys(self::builtinModeDefs());
}
/** 与表字段 modeCode varchar(32) 一致:小写字母/数字/下划线 */
public static function normalizeModeCode(string $code): string
{
return strtolower(trim($code));
}
public static function isValidModeCodeString(string $code): bool
{
if ($code === '' || strlen($code) > 32) {
return false;
}
return (bool) preg_match('/^[a-z0-9_]+$/', $code);
}
public static function getWechatEnterpriseId(int $wechatUserId): ?int
{
$row = Db::name('wechat_users')->where('id', $wechatUserId)->field('enterpriseId')->find();
if (!$row) {
return null;
}
$eid = (int) ($row['enterpriseId'] ?? 0);
return $eid > 0 ? $eid : null;
}
/**
* 简历环节:该企业下有上传记录,或有 enterpriseId 匹配的 resume 测评记录
*/
public static function resumeDone(int $userId, int $enterpriseId): bool
{
$n = (int) Db::name('enterprise_resume_uploads')
->where('userId', $userId)
->where('enterpriseId', $enterpriseId)
->count();
if ($n > 0) {
return true;
}
return (int) Db::name('test_results')
->where('userId', $userId)
->where('enterpriseId', $enterpriseId)
->where('testType', 'resume')
->count() > 0;
}
/**
* 面相/人脸testType in face|ai且 enterpriseId 一致(首版 strictly 绑定企业维度)
*/
public static function faceDone(int $userId, int $enterpriseId): bool
{
return (int) Db::name('test_results')
->where('userId', $userId)
->where('enterpriseId', $enterpriseId)
->whereIn('testType', ['face', 'ai'])
->count() > 0;
}
public static function mbtiDone(int $userId, int $enterpriseId): bool
{
return (int) Db::name('test_results')
->where('userId', $userId)
->where('enterpriseId', $enterpriseId)
->where('testType', 'mbti')
->count() > 0;
}
/** @return array{resumeDone:bool,faceDone:bool,mbtiDone:bool} */
public static function onboardingFlags(int $userId, int $enterpriseId): array
{
return [
'resumeDone' => self::resumeDone($userId, $enterpriseId),
'faceDone' => self::faceDone($userId, $enterpriseId),
'mbtiDone' => self::mbtiDone($userId, $enterpriseId),
];
}
/**
* 新企业、尚无任意合作模式时,写入三条默认(仅当该企业 0 条记录,避免管理端删行后再次读列表又被补回)
*/
public static function ensureDefaultConfigs(int $enterpriseId): void
{
$n = (int) Db::name('enterprise_cooperation_modes')
->where('enterpriseId', $enterpriseId)
->count();
if ($n > 0) {
return;
}
$defs = self::builtinModeDefs();
$now = time();
foreach ($defs as $code => $meta) {
Db::name('enterprise_cooperation_modes')->insert([
'enterpriseId' => $enterpriseId,
'modeCode' => $code,
'enabled' => 1,
'sortOrder' => (int) (array_search($code, array_keys($defs), true) * 10),
'title' => $meta['title'],
'description' => $meta['description'],
'createdAt' => $now,
'updatedAt' => $now,
]);
}
}
/**
* @return list<array{code:string,title:string,description:string,sortOrder:int,enabled:bool}>
*/
public static function listModesForEnterprise(int $enterpriseId, bool $onlyEnabled): array
{
self::ensureDefaultConfigs($enterpriseId);
$q = Db::name('enterprise_cooperation_modes')
->where('enterpriseId', $enterpriseId)
->order('sortOrder', 'asc')
->order('id', 'asc');
if ($onlyEnabled) {
$q->where('enabled', 1);
}
$rows = $q->select()->toArray();
$out = [];
$defs = self::builtinModeDefs();
foreach ($rows as $row) {
$code = (string) ($row['modeCode'] ?? '');
if ($code === '' || !self::isValidModeCodeString($code)) {
continue;
}
$def = $defs[$code] ?? null;
$title = trim((string) ($row['title'] ?? ''));
if ($title === '' && $def) {
$title = $def['title'];
}
if ($title === '') {
$title = '合作模式';
}
$desc = $row['description'] ?? null;
$description = $desc !== null && (string) $desc !== ''
? (string) $desc
: ($def['description'] ?? '');
$out[] = [
'code' => $code,
'title' => $title,
'description' => $description,
'sortOrder' => (int) ($row['sortOrder'] ?? 0),
'enabled' => (int) ($row['enabled'] ?? 0) === 1,
];
}
return $out;
}
/**
* 批量写入配置(超管 / 企业管理员)
*
* @param list<array{modeCode:string,enabled?:bool,sortOrder?:int,title?:string,description?:string}> $modes
*/
public static function saveConfigs(int $enterpriseId, array $modes): void
{
$defs = self::builtinModeDefs();
$now = time();
$byCode = [];
foreach ($modes as $item) {
$raw = (string) ($item['modeCode'] ?? $item['code'] ?? '');
$code = self::normalizeModeCode($raw);
if ($code === '' || !self::isValidModeCodeString($code)) {
continue;
}
$byCode[$code] = $item;
}
if ($byCode === []) {
throw new \InvalidArgumentException('请至少保存一条合作模式,且模式代码为 132 位小写字母、数字或下划线');
}
$codes = array_keys($byCode);
Db::startTrans();
try {
Db::name('enterprise_cooperation_modes')
->where('enterpriseId', $enterpriseId)
->whereNotIn('modeCode', $codes)
->delete();
foreach ($byCode as $code => $item) {
$row = Db::name('enterprise_cooperation_modes')
->where('enterpriseId', $enterpriseId)
->where('modeCode', $code)
->find();
$def = $defs[$code] ?? null;
$t = trim((string) ($item['title'] ?? ''));
$title = $t !== '' ? $t : ($def['title'] ?? '合作模式');
$description = array_key_exists('description', $item)
? (string) $item['description']
: ($def['description'] ?? '');
$payload = [
'enabled' => isset($item['enabled']) ? ((int) (bool) $item['enabled']) : 1,
'sortOrder' => isset($item['sortOrder']) ? (int) $item['sortOrder'] : 0,
'title' => $title,
'description' => $description,
'updatedAt' => $now,
];
if ($row) {
Db::name('enterprise_cooperation_modes')
->where('id', (int) $row['id'])
->update($payload);
} else {
$payload['enterpriseId'] = $enterpriseId;
$payload['modeCode'] = $code;
$payload['createdAt'] = $now;
Db::name('enterprise_cooperation_modes')->insert($payload);
}
}
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
throw $e;
}
}
public static function getUserChoice(int $userId, int $enterpriseId): ?array
{
$row = Db::name('user_cooperation_choices')
->where('userId', $userId)
->where('enterpriseId', $enterpriseId)
->find();
return $row ?: null;
}
public static function saveUserChoice(int $userId, int $enterpriseId, string $modeCode): void
{
$modeCode = self::normalizeModeCode($modeCode);
$now = time();
$row = Db::name('user_cooperation_choices')
->where('userId', $userId)
->where('enterpriseId', $enterpriseId)
->find();
if ($row) {
Db::name('user_cooperation_choices')
->where('id', (int) $row['id'])
->update([
'modeCode' => $modeCode,
'chosenAt' => $now,
'updatedAt' => $now,
]);
} else {
Db::name('user_cooperation_choices')->insert([
'userId' => $userId,
'enterpriseId' => $enterpriseId,
'modeCode' => $modeCode,
'chosenAt' => $now,
'updatedAt' => $now,
]);
}
}
public static function isModeEnabledForEnterprise(int $enterpriseId, string $modeCode): bool
{
$code = self::normalizeModeCode($modeCode);
$row = Db::name('enterprise_cooperation_modes')
->where('enterpriseId', $enterpriseId)
->where('modeCode', $code)
->find();
if (!$row) {
return false;
}
return (int) ($row['enabled'] ?? 0) === 1;
}
/**
* 管理端:某企业下用户合作意向查询(带筛选)
*/
private static function userCooperationChoicesBaseQuery(int $enterpriseId, string $keyword = '')
{
$q = Db::name('user_cooperation_choices')->alias('uc')
->join('wechat_users w', 'uc.userId = w.id')
->leftJoin('enterprise_cooperation_modes ecm', 'ecm.enterpriseId = uc.enterpriseId AND ecm.modeCode = uc.modeCode')
->leftJoin('enterprises ent', 'ent.id = uc.enterpriseId')
->where('uc.enterpriseId', $enterpriseId);
if ($keyword !== '') {
$like = '%' . addcslashes($keyword, '%_\\') . '%';
$q->where(function ($query) use ($like) {
$query->where('w.nickname', 'like', $like)
->whereOr('w.phone', 'like', $like)
->whereOr('uc.modeCode', 'like', $like);
});
}
return $q;
}
private static function mapUserCooperationChoiceRow(array $row): array
{
$ca = (int) ($row['chosenAt'] ?? 0);
$ua = (int) ($row['updatedAt'] ?? 0);
return [
'id' => (int) ($row['id'] ?? 0),
'userId' => (int) ($row['userId'] ?? 0),
'enterpriseId' => (int) ($row['enterpriseId'] ?? 0),
'enterpriseName' => (string) ($row['enterpriseName'] ?? ''),
'modeCode' => (string) ($row['modeCode'] ?? ''),
'modeTitle' => (string) ($row['modeTitle'] ?? ''),
'nickname' => (string) ($row['nickname'] ?? ''),
'phone' => (string) ($row['phone'] ?? ''),
'chosenAt' => $ca,
'updatedAt' => $ua,
'chosenAtText' => $ca > 0 ? date('Y-m-d H:i:s', $ca) : '',
'updatedAtText' => $ua > 0 ? date('Y-m-d H:i:s', $ua) : '',
];
}
/**
* @return array{list: list<array>, total: int}
*/
public static function listUserCooperationChoices(int $enterpriseId, int $page, int $pageSize, string $keyword = ''): array
{
$page = max(1, $page);
$pageSize = min(max($pageSize, 1), 100);
$field = 'uc.id,uc.userId,uc.enterpriseId,uc.modeCode,uc.chosenAt,uc.updatedAt,'
. 'w.nickname,w.phone,ecm.title as modeTitle,ent.name as enterpriseName';
$total = (int) self::userCooperationChoicesBaseQuery($enterpriseId, $keyword)->count();
$rows = self::userCooperationChoicesBaseQuery($enterpriseId, $keyword)
->field($field)
->order('uc.chosenAt', 'desc')
->page($page, $pageSize)
->select()
->toArray();
$list = [];
foreach ($rows as $row) {
$list[] = self::mapUserCooperationChoiceRow($row);
}
return ['list' => $list, 'total' => $total];
}
/**
* @return list<array>
*/
public static function listUserCooperationChoicesForExport(int $enterpriseId, string $keyword = '', int $maxRows = 10000): array
{
$field = 'uc.id,uc.userId,uc.enterpriseId,uc.modeCode,uc.chosenAt,uc.updatedAt,'
. 'w.nickname,w.phone,ecm.title as modeTitle,ent.name as enterpriseName';
$rows = self::userCooperationChoicesBaseQuery($enterpriseId, $keyword)
->field($field)
->order('uc.chosenAt', 'desc')
->limit(max(1, min($maxRows, 50000)))
->select()
->toArray();
$out = [];
foreach ($rows as $row) {
$out[] = self::mapUserCooperationChoiceRow($row);
}
return $out;
}
public static function buildUserCooperationChoicesCsvContent(array $rows, bool $includeEnterpriseColumns): string
{
$lines = [];
$headers = $includeEnterpriseColumns
? ['企业ID', '企业名称', '用户ID', '微信昵称', '手机号', '模式代码', '模式标题', '选择时间', '更新时间']
: ['用户ID', '微信昵称', '手机号', '模式代码', '模式标题', '选择时间', '更新时间'];
$lines[] = self::csvLine($headers);
foreach ($rows as $r) {
if ($includeEnterpriseColumns) {
$lines[] = self::csvLine([
(string) ($r['enterpriseId'] ?? ''),
(string) ($r['enterpriseName'] ?? ''),
(string) ($r['userId'] ?? ''),
(string) ($r['nickname'] ?? ''),
(string) ($r['phone'] ?? ''),
(string) ($r['modeCode'] ?? ''),
(string) ($r['modeTitle'] ?? ''),
(string) ($r['chosenAtText'] ?? ''),
(string) ($r['updatedAtText'] ?? ''),
]);
} else {
$lines[] = self::csvLine([
(string) ($r['userId'] ?? ''),
(string) ($r['nickname'] ?? ''),
(string) ($r['phone'] ?? ''),
(string) ($r['modeCode'] ?? ''),
(string) ($r['modeTitle'] ?? ''),
(string) ($r['chosenAtText'] ?? ''),
(string) ($r['updatedAtText'] ?? ''),
]);
}
}
return implode("\r\n", $lines);
}
private static function csvLine(array $cells): string
{
$parts = [];
foreach ($cells as $c) {
$s = (string) $c;
$s = str_replace('"', '""', $s);
$parts[] = '"' . $s . '"';
}
return implode(',', $parts);
}
}