trim((string)$this->request->post('name')), 'license_no' => trim((string)$this->request->post('license_no')), 'license_image' => (string)$this->request->post('license_image'), 'legal_person' => trim((string)$this->request->post('legal_person')), 'contact' => trim((string)$this->request->post('contact')), 'mobile' => trim((string)$this->request->post('mobile')), 'province' => trim((string)$this->request->post('province')), 'city' => trim((string)$this->request->post('city')), 'district' => trim((string)$this->request->post('district')), 'address' => trim((string)$this->request->post('address')), 'invoice_title' => trim((string)$this->request->post('invoice_title')), 'invoice_tax_no' => trim((string)$this->request->post('invoice_tax_no')), 'invoice_bank' => trim((string)$this->request->post('invoice_bank')), 'invoice_account' => trim((string)$this->request->post('invoice_account')), 'settle_type' => $this->request->post('settle_type', 'prepay'), ]; if ($data['name'] === '' || $data['license_no'] === '' || $data['contact'] === '' || $data['mobile'] === '') { $this->error('请填写企业名称、统一社会信用代码、联系人和电话'); } if (!preg_match('/^1\d{10}$/', $data['mobile'])) { $this->error('联系电话格式不正确'); } if (!in_array($data['settle_type'], ['prepay', 'monthly'], true)) { $data['settle_type'] = 'prepay'; } // 已被商家绑定的关联账号,企业资料由主账号维护 if (Db::name('merchant_member')->where('user_id', $this->uid())->find()) { $this->error('当前账号已绑定商家,企业资料请由主账号维护'); } $exist = Db::name('merchant')->where('user_id', $this->uid())->find(); if ($exist && $exist['verify_status'] === 'pass') { $this->error('您已是认证商家,无需重复入驻'); } $save = array_merge($data, [ 'verify_status' => 'pending', 'audit_msg' => '', 'updatetime' => time(), ]); if ($exist) { Db::name('merchant')->where('id', $exist['id'])->update($save); $id = (int)$exist['id']; } else { $save['user_id'] = $this->uid(); $save['createtime'] = time(); $id = Db::name('merchant')->insertGetId($save); } $this->success('提交成功,请等待审核', ['merchant_id' => $id]); } /** * 商家档案与经营数据 */ public function info() { $merchant = $this->merchantInfo(); if (!$merchant) { $this->success('未入驻', ['applied' => false]); return; } $uids = $this->merchantUserIds($merchant); $isOwner = (int)$merchant['user_id'] === $this->uid(); $orderCount = (int)Db::name('order')->where('user_id', 'in', $uids)->where('pay_status', 1)->count(); $totalAmount = (float)Db::name('order')->where('user_id', 'in', $uids)->where('pay_status', 1)->sum('pay_amount'); $invoiced = (float)Db::name('invoice')->where('merchant_id', $merchant['id'])->where('status', 'issued')->sum('amount'); $this->success('', [ 'applied' => true, 'is_owner' => $isOwner, 'role' => $isOwner ? 'owner' : 'member', 'merchant' => [ 'id' => (int)$merchant['id'], 'name' => $merchant['name'], 'license_no' => $merchant['license_no'], 'legal_person' => $merchant['legal_person'], 'contact' => $merchant['contact'], 'mobile' => $merchant['mobile'], 'address' => $merchant['province'] . $merchant['city'] . $merchant['district'] . $merchant['address'], 'invoice_title' => $merchant['invoice_title'], 'invoice_tax_no' => $merchant['invoice_tax_no'], 'settle_type' => $merchant['settle_type'], 'verify_status' => $merchant['verify_status'], 'audit_msg' => $merchant['audit_msg'], 'role' => $isOwner ? 'owner' : 'member', 'is_owner' => $isOwner, 'owner_user_id' => (int)$merchant['user_id'], 'member_count' => max(0, count($uids) - 1), 'order_count' => $orderCount, 'total_amount' => round($totalAmount, 2), 'invoiced_amount' => round($invoiced, 2), 'invoiceable' => round(max(0, $totalAmount - $invoiced), 2), ], ]); } /** * 维护资料(联系人 / 地址 / 开票信息) * @ApiMethod (POST) */ public function update() { $merchant = $this->requireMerchant(); $this->requireMerchantOwner($merchant); $fields = ['contact', 'mobile', 'province', 'city', 'district', 'address', 'invoice_title', 'invoice_tax_no', 'invoice_bank', 'invoice_account']; $data = ['updatetime' => time()]; foreach ($fields as $f) { $v = $this->request->post($f, null); if ($v !== null) { $data[$f] = trim((string)$v); } } if (isset($data['mobile']) && $data['mobile'] !== '' && !preg_match('/^1\d{10}$/', $data['mobile'])) { $this->error('联系电话格式不正确'); } Db::name('merchant')->where('id', $merchant['id'])->update($data); $this->success('已保存'); } /** * 账单中心:按月汇总 + 近期订单 */ public function bills() { $merchant = $this->requireMerchant(); $uids = $this->merchantUserIds($merchant); $months = Db::name('order') ->field("FROM_UNIXTIME(createtime,'%Y-%m') as month, count(*) as cnt, sum(pay_amount) as amount") ->where('user_id', 'in', $uids)->where('pay_status', 1) ->group('month')->order('month desc')->limit(12)->select(); foreach ($months as &$m) { $m['amount'] = round((float)$m['amount'], 2); $m['cnt'] = (int)$m['cnt']; } unset($m); $orders = Db::name('order')->field('id,order_no,service_name,pay_amount,status,createtime') ->where('user_id', 'in', $uids)->order('id desc')->limit(20)->select(); foreach ($orders as &$o) { $o['pay_amount'] = (float)$o['pay_amount']; $o['status_text'] = \app\common\library\OrderStatus::label($o['status']); $o['time_text'] = date('Y-m-d H:i', (int)$o['createtime']); } unset($o); $this->success('', ['months' => $months, 'orders' => $orders]); } /** * 企业订单:主账号 + 关联账号的全部订单 */ public function orders() { $merchant = $this->requireMerchant(); $uids = $this->merchantUserIds($merchant); $status = $this->request->param('status', 'all'); $page = max(1, (int)$this->request->param('page', 1)); $limit = min(50, max(1, (int)$this->request->param('limit', 20))); $build = function () use ($uids, $status) { $q = Db::name('order')->where('user_id', 'in', $uids); if ($status === 'active') { $q->where('status', 'in', \app\common\library\OrderStatus::activeValues()); } elseif ($status === 'completed') { $q->where('status', \app\common\library\OrderStatus::COMPLETED); } elseif ($status === 'cancelled') { $q->where('status', \app\common\library\OrderStatus::CANCELLED); } elseif ($status !== 'all' && $status !== '') { $q->where('status', (int)$status); } return $q; }; $total = $build()->count(); $rows = $build()->order('id desc')->page($page, $limit)->select(); $userMap = Db::name('user')->where('id', 'in', $uids)->column('id,nickname,mobile', 'id'); foreach ($rows as &$row) { $ownerUser = (int)$row['user_id'] === (int)$merchant['user_id']; $u = isset($userMap[(int)$row['user_id']]) ? $userMap[(int)$row['user_id']] : null; $mobile = $u ? (string)$u['mobile'] : ''; $row['pay_amount'] = (float)$row['pay_amount']; $row['urgent'] = (int)$row['urgent']; $row['status_text'] = \app\common\library\OrderStatus::label((int)$row['status']); $row['appoint_text'] = $row['appoint_time'] ? date('m-d H:i', (int)$row['appoint_time']) : '尽快上门'; $row['create_text'] = date('Y-m-d H:i', (int)$row['createtime']); $row['address_text'] = $row['province'] . $row['city'] . $row['district'] . $row['address']; $row['account_name'] = $u && $u['nickname'] ? $u['nickname'] : ('用户' . substr($mobile, -4)); $row['account_role'] = $ownerUser ? 'owner' : 'member'; $row['account_role_text'] = $ownerUser ? '主账号' : '关联账号'; } unset($row); $this->success('', [ 'list' => $rows, 'total' => (int)$total, 'page' => $page, ]); } /** * 开票记录 */ public function invoices() { $merchant = $this->requireMerchant(); $list = Db::name('invoice')->where('merchant_id', $merchant['id'])->order('id desc')->limit(30)->select(); $statusText = ['wait' => '开票中', 'issued' => '已开票', 'reject' => '已驳回']; foreach ($list as &$row) { $row['amount'] = (float)$row['amount']; $row['status_text'] = isset($statusText[$row['status']]) ? $statusText[$row['status']] : $row['status']; $row['type_text'] = $row['type'] === 'special' ? '专用发票' : '普通发票'; $row['time_text'] = date('Y-m-d H:i', (int)$row['createtime']); } unset($row); $this->success('', ['list' => $list]); } /** * 申请开票 * @ApiMethod (POST) */ public function invoice() { $merchant = $this->requireMerchant(); $amount = round((float)$this->request->post('amount'), 2); $type = $this->request->post('type', 'normal'); $email = trim((string)$this->request->post('email')); if ($amount <= 0) { $this->error('请填写开票金额'); } if (!in_array($type, ['normal', 'special'], true)) { $this->error('发票类型错误'); } $paid = (float)Db::name('order')->where('user_id', 'in', $this->merchantUserIds($merchant))->where('pay_status', 1)->sum('pay_amount'); $used = (float)Db::name('invoice')->where('merchant_id', $merchant['id'])->where('status', 'in', ['wait', 'issued'])->sum('amount'); $available = round(max(0, $paid - $used), 2); if ($amount > $available) { $this->error('可开票金额不足,当前可开 ¥' . $available); } Db::name('invoice')->insert([ 'merchant_id' => (int)$merchant['id'], 'user_id' => $this->uid(), 'amount' => $amount, 'type' => $type, 'title' => $merchant['invoice_title'] ?: $merchant['name'], 'tax_no' => $merchant['invoice_tax_no'], 'email' => $email, 'status' => 'wait', 'createtime' => time(), 'updatetime' => time(), ]); $this->success('开票申请已提交,3 个工作日内开具'); } /** * 商家关联账号列表(主账号 + 已绑定账号) */ public function members() { $merchant = $this->requireMerchant(); $uids = $this->merchantUserIds($merchant); $isOwner = (int)$merchant['user_id'] === $this->uid(); $users = Db::name('user')->where('id', 'in', $uids)->column('id,nickname,mobile,avatar', 'id'); $bindRows = Db::name('merchant_member')->where('merchant_id', (int)$merchant['id'])->select(); $bindMap = []; foreach ($bindRows as $b) { $bindMap[(int)$b['user_id']] = $b; } $stats = Db::name('order')->field('user_id,count(*) as cnt,sum(pay_amount) as amount') ->where('user_id', 'in', $uids)->where('pay_status', 1) ->group('user_id')->select(); $statMap = []; foreach ($stats as $s) { $statMap[(int)$s['user_id']] = $s; } $list = []; foreach ($uids as $uid) { $u = isset($users[$uid]) ? $users[$uid] : null; $owner = (int)$uid === (int)$merchant['user_id']; $bind = isset($bindMap[(int)$uid]) ? $bindMap[(int)$uid] : null; $mobile = $u ? (string)$u['mobile'] : ''; $list[] = [ 'id' => $owner ? 0 : (int)$bind['id'], 'user_id' => (int)$uid, 'nickname' => $u ? ($u['nickname'] ?: ('用户' . substr($mobile, -4))) : '用户', 'mobile' => $mobile !== '' ? substr_replace($mobile, '****', 3, 4) : '', 'avatar' => $u ? $u['avatar'] : '', 'role' => $owner ? 'owner' : 'member', 'role_text' => $owner ? '主账号' : '关联账号', 'is_owner' => $owner, 'order_count' => isset($statMap[(int)$uid]) ? (int)$statMap[(int)$uid]['cnt'] : 0, 'total_amount' => isset($statMap[(int)$uid]) ? round((float)$statMap[(int)$uid]['amount'], 2) : 0, 'bind_time_text' => (!$owner && $bind) ? date('Y-m-d H:i', (int)$bind['createtime']) : '', ]; } // 主账号排在最前 usort($list, function ($a, $b) { return (int)$b['is_owner'] - (int)$a['is_owner']; }); $this->success('', [ 'list' => $list, 'is_owner' => $isOwner, 'total' => count($list), ]); } /** * 绑定关联账号(仅主账号) * @ApiMethod (POST) */ public function bindMember() { $merchant = $this->requireMerchant(); $this->requireMerchantOwner($merchant); $mobile = trim((string)$this->request->post('mobile')); if (!preg_match('/^1\d{10}$/', $mobile)) { $this->error('请输入正确的关联账号手机号'); } $user = Db::name('user')->where('mobile', $mobile)->find(); if (!$user) { $this->error('该手机号尚未注册,请对方先登录小程序'); } if ((int)$user['id'] === $this->uid()) { $this->error('不能绑定当前主账号'); } if (Db::name('merchant')->where('user_id', $user['id'])->find()) { $this->error('该账号已是商家主账号,不能重复绑定'); } if (Db::name('merchant_member')->where('user_id', $user['id'])->find()) { $this->error('该账号已绑定其他商家'); } $id = Db::name('merchant_member')->insertGetId([ 'merchant_id' => (int)$merchant['id'], 'user_id' => (int)$user['id'], 'remark' => '', 'createtime' => time(), 'updatetime' => time(), ]); try { $this->notify('user', (int)$user['id'], '已加入商家账号', '您的账号已关联到商家「' . $merchant['name'] . '」,下单、账单、开票数据将实时同步', 'system', 0); } catch (\Exception $e) {} $this->success('绑定成功,关联账号数据将实时同步', [ 'member_id' => (int)$id, 'user_id' => (int)$user['id'], 'nickname' => $user['nickname'] ?: ('用户' . substr($mobile, -4)), ]); } /** * 解除关联账号(仅主账号) * @ApiMethod (POST) */ public function unbindMember() { $merchant = $this->requireMerchant(); $this->requireMerchantOwner($merchant); $memberId = (int)$this->request->post('member_id'); $userId = (int)$this->request->post('user_id'); $query = Db::name('merchant_member')->where('merchant_id', (int)$merchant['id']); if ($memberId > 0) { $query->where('id', $memberId); } elseif ($userId > 0) { $query->where('user_id', $userId); } else { $this->error('缺少关联账号参数'); } $row = $query->find(); if (!$row) { $this->error('关联账号不存在或已解除'); } if ((int)$row['user_id'] === (int)$merchant['user_id']) { $this->error('不能解除主账号'); } Db::name('merchant_member')->where('id', (int)$row['id'])->delete(); try { $this->notify('user', (int)$row['user_id'], '商家关联已解除', '您已与商家「' . $merchant['name'] . '」解除关联,对方商家数据不再同步', 'system', 0); } catch (\Exception $e) {} $this->success('已解除关联,对方将不再同步商家数据'); } /** * 批量下单后绑定批次(订单本身走已验收的 /order/create) * @ApiMethod (POST) */ public function bindBatch() { $merchant = $this->requireMerchant(); $uids = $this->merchantUserIds($merchant); // 兼容三种入参:数组 / 逗号串 / 混合 $raw = $this->request->post('order_ids'); $orderIds = []; foreach ((array)$raw as $v) { foreach (explode(',', (string)$v) as $one) { $one = (int)$one; if ($one > 0) { $orderIds[] = $one; } } } $orderIds = array_values(array_unique($orderIds)); $batchNo = trim((string)$this->request->post('batch_no', '')); if (empty($orderIds)) { $this->error('缺少订单'); } if ($batchNo === '') { $batchNo = date('YmdHis') . mt_rand(100, 999); } $ok = 0; foreach ($orderIds as $oid) { $oid = (int)$oid; if (!Db::name('order')->where('id', $oid)->where('user_id', 'in', $uids)->find()) { continue; } if (Db::name('merchant_order')->where('order_id', $oid)->find()) { continue; } Db::name('merchant_order')->insert([ 'merchant_id' => (int)$merchant['id'], 'order_id' => $oid, 'batch_no' => $batchNo, 'createtime' => time(), ]); $ok++; } $this->success('已归属到批次', ['batch_no' => $batchNo, 'count' => $ok]); } /** * 当前用户商家档案:主账号直接匹配,关联账号通过绑定关系匹配 */ protected function merchantInfo() { $merchant = Db::name('merchant')->where('user_id', $this->uid())->find(); if ($merchant) { return $merchant; } $bind = Db::name('merchant_member')->where('user_id', $this->uid())->find(); if ($bind) { return Db::name('merchant')->where('id', (int)$bind['merchant_id'])->find(); } return null; } /** * 商家数据归属用户集合:主账号 + 所有关联账号 */ protected function merchantUserIds($merchant) { $ids = [(int)$merchant['user_id']]; $rows = Db::name('merchant_member')->where('merchant_id', (int)$merchant['id'])->column('user_id'); foreach ($rows as $id) { $id = (int)$id; if ($id > 0 && !in_array($id, $ids, true)) { $ids[] = $id; } } return $ids; } /** * 仅商家主账号可执行敏感操作 */ protected function requireMerchantOwner($merchant) { if ((int)$merchant['user_id'] !== $this->uid()) { $this->error('仅主账号可执行此操作', null, 403); } } /** * 要求已通过审核的商家 */ protected function requireMerchant() { $m = $this->merchantInfo(); if (!$m) { $this->error('请先完成商家入驻', ['need_apply' => 1], 2001); } if ($m['verify_status'] === 'pending') { $this->error('商家资质审核中,请耐心等待', ['verify_status' => 'pending'], 2002); } if ($m['verify_status'] === 'reject') { $this->error('资质未通过:' . $m['audit_msg'], ['verify_status' => 'reject'], 2003); } return $m; } }