middleware('auth'); $this->middleware('company.identify'); } public function template(): BinaryFileResponse { $path = $this->template->path(); abort_unless(is_file($path), 404, '对账单模板不存在。'); return response()->download($path, $this->template->filename()); } public function upload(Request $request): JsonResponse|RedirectResponse { $request->validate([ 'import' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:51200'], ]); $supplier = $this->effectiveSupplier($request); $records = $this->template->parse($request->file('import'), $supplier); $rows = $this->statements->save( $supplier, $records, (string) $request->file('import')->getClientOriginalName(), (int) (company_id() ?: 1), user_id() ? (int) user_id() : null, ); $message = "对账单已保存:{$supplier} 共 {$rows} 行。"; return $request->expectsJson() ? response()->json(['message' => $message, 'rows' => $rows]) : back()->with('success', $message); } public function confirmReturn(Request $request): RedirectResponse { return $this->markReturn($request, SupplierReturnConfirmation::STATUS_CONFIRMED, '已确认该行退货扣减。'); } public function disputeReturn(Request $request): RedirectResponse { return $this->markReturn($request, SupplierReturnConfirmation::STATUS_DISPUTED, '已提交异议,复核通过前该行暂不扣减。'); } public function confirmAllReturns(Request $request): RedirectResponse { [$supplier, $companyId] = $this->returnTarget($request); $keys = array_values(array_map( static fn (array $row): string => (string) ($row['_action_key'] ?? ''), $this->pageData->returnRowsForFilters($supplier, $companyId, $request->all()), )); $confirmed = $this->returnConfirmations->confirmAll( $supplier, $keys, $companyId, user_id() ? (int) user_id() : null, ); return back()->with('success', "已全部确认:{$confirmed} 行退货扣减生效(异议行保持暂不扣减)。"); } public function exportReturns(Request $request): StreamedResponse { [$supplier, $companyId] = $this->returnTarget($request); $rows = $this->pageData->returnRowsForFilters($supplier, $companyId, $request->query()); return $this->returnCsvExporter->download($rows, $supplier); } public function exportComparison(Request $request): StreamedResponse { $request->validate([ 'month' => ['nullable', 'string', 'regex:/^\d{4}-\d{2}$/'], 'reconciler' => ['nullable', 'string', 'max:191'], ]); [$supplier, $companyId] = $this->returnTarget($request); $detail = $this->pageData->financeDetailForFilters($supplier, $companyId, $request->query()); return $this->comparisonCsvExporter->download( $detail, $supplier, trim((string) $request->query('month', '')), trim((string) $request->query('reconciler', '')), ); } public function saveDingTalkRobot(Request $request): RedirectResponse { $request->validate([ 'supplier' => ['required', 'string', 'max:191'], 'dingtalk_webhook' => ['nullable', 'string', 'max:2048'], 'dingtalk_secret' => ['nullable', 'string', 'max:256'], 'dingtalk_at_all' => ['nullable', 'boolean'], ]); [$supplier] = $this->returnTarget($request); $this->dingTalkNotifier->save( $supplier, (string) $request->input('dingtalk_webhook', ''), (string) $request->input('dingtalk_secret', ''), $request->boolean('dingtalk_at_all'), ); return back()->with('success', "{$supplier} 的钉钉群机器人已保存。"); } public function pushDifferences(Request $request): RedirectResponse { $request->validate([ 'supplier' => ['required', 'string', 'max:191'], 'month' => ['nullable', 'string', 'regex:/^\d{4}-\d{2}$/'], 'reconciler' => ['nullable', 'string', 'max:191'], 'scope' => ['required', 'in:all,row,selected'], 'purchase_no' => ['nullable', 'string', 'max:191'], 'date' => ['nullable', 'string', 'max:32'], 'barcode' => ['nullable', 'string', 'max:191'], 'difference_type' => ['nullable', 'string', 'max:32'], 'selected_rows' => ['nullable', 'array', 'max:600'], 'selected_rows.*' => ['string', 'max:2048'], ]); [$supplier, $companyId] = $this->returnTarget($request); $detail = $this->pageData->financeDetailForFilters($supplier, $companyId, $request->all()); $differences = array_values(array_filter( $detail['rows'], static fn (array $row): bool => ($row['type'] ?? '') !== SupplierStatementMatcher::TYPE_MATCH, )); if ($request->input('scope') === 'row') { $purchaseNo = trim((string) $request->input('purchase_no')); $date = trim((string) $request->input('date')); $barcode = trim((string) $request->input('barcode')); $type = trim((string) $request->input('difference_type')); $differences = array_values(array_filter( $differences, static fn (array $row): bool => (string) ($row['purchaseNo'] ?? '') === $purchaseNo && (string) ($row['date'] ?? '') === $date && (string) ($row['barcode'] ?? '') === $barcode && (string) ($row['type'] ?? '') === $type )); } elseif ($request->input('scope') === 'selected') { $selected = []; foreach ((array) $request->input('selected_rows', []) as $token) { $row = json_decode((string) $token, true); if (is_array($row)) { $selected[$this->differenceRowKey($row)] = true; } } $differences = array_values(array_filter( $differences, fn (array $row): bool => isset($selected[$this->differenceRowKey($row)]), )); } if ($differences === []) { return back()->withErrors(['dingtalk_push' => '当前筛选没有可推送的差异。']); } try { $result = $this->dingTalkNotifier->push($supplier, $differences, [ 'month' => (string) $request->input('month', ''), 'reconciler' => (string) $request->input('reconciler', ''), ]); } catch (Throwable $exception) { return back()->withErrors(['dingtalk_push' => '推送失败:' . $exception->getMessage()]); } return back()->with( 'success', "已推送 {$result['rows']} 条差异到 {$supplier} 钉钉群({$result['messages']} 条消息)。", ); } private function markReturn(Request $request, string $status, string $message): RedirectResponse { $request->validate(['key' => ['required', 'string', 'max:191']]); [$supplier, $companyId] = $this->returnTarget($request); $key = (string) $request->input('key'); $owned = collect($this->pageData->returnRows($supplier, $companyId)) ->contains(static fn (array $row): bool => (string) ($row['_action_key'] ?? '') === $key); abort_unless($owned, 403, '该退货明细不属于当前厂家账号。'); $this->returnConfirmations->mark( $supplier, $key, $status, $companyId, user_id() ? (int) user_id() : null, ); return back()->with('success', $message); } /** @return array{0:string,1:int} */ private function returnTarget(Request $request): array { return [$this->effectiveSupplier($request), (int) (company_id() ?: 1)]; } private function effectiveSupplier(Request $request): string { $user = $request->user(); if ($user && SupplierPortalAccess::isSupplierUser($user)) { $supplier = $this->supplierPortalAccess->supplierFor($user); abort_if(! $supplier, 403, '当前钉钉账号未绑定有效供应商,请联系财务人员。'); return $supplier; } $request->validate(['supplier' => ['required', 'string', 'max:191']]); $supplier = trim((string) $request->input('supplier')); abort_if($supplier === '', 422, '请选择厂家。'); return $supplier; } /** @param array $row */ private function differenceRowKey(array $row): string { return implode("\0", [ trim((string) ($row['purchaseNo'] ?? $row['purchase_no'] ?? '')), trim((string) ($row['date'] ?? '')), trim((string) ($row['barcode'] ?? '')), trim((string) ($row['type'] ?? '')), ]); } }