> 快递代码 => 覆盖值 */ public function overrides(array $codes): array { $codes = array_values(array_unique(array_filter(array_map('strval', $codes), 'filled'))); if ($codes === [] || ! Schema::hasTable('business_courier_profiles')) { return []; } return CourierProfile::query()->whereIn('expcode', $codes)->get() ->mapWithKeys(static fn (CourierProfile $profile): array => [(string) $profile->expcode => (array) $profile->overrides]) ->all(); } /** 把覆盖值盖到快递资料行上,`_overridden` 记下改过的字段。 */ public function apply(object $row, array $overrides): object { $row = clone $row; $changed = []; foreach ($overrides as $field => $value) { if (array_key_exists($field, CourierProfile::EDITABLE)) { $row->{$field} = $value; $changed[] = $field; } } $row->_overridden = $changed; return $row; } /** 财务系统入驻(还没有工作平台记录)的快递,按快递资料的列铺成一行。 */ public function onboardingRow(array $info): object { /** @var CourierOnboarding $onboarding */ $onboarding = $info['onboarding']; return (object) [ 'Id' => null, 'expcode' => $onboarding->workplatform_expcode, 'expname' => $onboarding->courier_name, 'paymethod' => $onboarding->settlement_cycle, 'status' => $onboarding->status === CourierOnboarding::STATUS_ACTIVATED ? '1' : '0', 'kuaiyunsgn' => $onboarding->service_type === '快运' ? '1' : '0', 'tradebtime' => $info['contract_start'] ?? null, 'tradeetime' => $info['contract_end'] ?? null, 'linker' => $onboarding->contact_name, 'linkmobile' => $onboarding->contact_phone, 'dzqzjbr' => $onboarding->esign_account, 'undefined3' => $onboarding->deduction_method, 'modified' => $onboarding->updated_at?->toDateTimeString(), '_onboarding_only' => true, '_info' => $info, ]; } /** 按快递公司代码取当前显示的一行主信息(工作平台镜像优先,没有则用财务系统入驻),已盖上编辑覆盖值。 */ public function currentRow(string $code): ?object { $mirror = app(\Modules\EcommerceFinance\Repositories\CourierInformationMirrorRepository::class); $overview = app(CourierOverviewData::class); $row = null; try { if ($mirror->available() && $mirror->ready()) { $found = $mirror->paginate(array_merge( array_fill_keys(['expname', 'undefined1', 'undefined2', 'undefined3', 'tradebtime_from', 'tradebtime_to', 'status'], ''), ['expcode' => $code], ), 1, 1)['data'][0] ?? null; if ($found !== null) { $row = clone $found; $row->_info = $overview->infoFor($code, $found->expname ?? null); } } } catch (Throwable $exception) { report($exception); } if ($row === null && ($info = $overview->infoFor($code, null)) !== null) { $row = $this->onboardingRow($info); } return $row !== null ? $this->apply($row, $this->overrides([$code])[$code] ?? []) : null; } /** * 协议签署页预填:打款信息新增 → 收款账户证明;变更 → 收款账户变更。 * * @return array|null */ public static function agreementPrefill(mixed $id): ?array { if (! is_numeric($id) || ! Schema::hasTable('business_courier_payment_accounts')) { return null; } $record = CourierPaymentAccount::query()->find((int) $id); if ($record === null) { return null; } $onboarding = app(CourierOverviewData::class)->infoFor($record->expcode, $record->courier_name)['onboarding'] ?? null; $fields = $record->action === CourierPaymentAccount::ACTION_CHANGE ? [ 'account_change_date' => $record->change_date?->toDateString(), 'old_account_name' => $record->old_account_name, 'old_account_number' => $record->old_account_number, 'old_account_stop_date' => $record->old_stop_date?->toDateString(), 'new_account_effective_date' => $record->effective_date?->toDateString(), 'new_account_name' => $record->account_name, 'new_bank_name' => $record->bank_name, 'new_bank_account' => $record->account_number, ] : [ 'account_proof_effective_date' => $record->effective_date?->toDateString(), 'account_proof_account_name' => $record->account_name, 'account_proof_bank_name' => $record->bank_name, 'account_proof_bank_account' => $record->account_number, ]; if ($onboarding !== null && filled($onboarding->company_full_name)) { $fields['counterparty_organization_name'] = $onboarding->company_full_name; } return [ 'id' => $record->id, 'action' => $record->actionLabel(), 'courier_name' => $record->courier_name, 'expcode' => $record->expcode, 'document_type' => $record->documentType(), 'signer_type' => $onboarding !== null && $onboarding->entity_type === CourierOnboarding::ENTITY_PERSON ? 'person' : 'organization', 'esign_account' => $onboarding?->esign_account, 'fields' => array_filter($fields, static fn ($value): bool => filled($value)), ]; } /** 快递资料右侧接的入驻字段(我方填写;扣款方式对齐到左边「扣款方式」列,不重复显示)。 */ public const APPENDED_PROFILE_FIELDS = [ 'courier_category', 'cooperation_mode', 'courier_attribute', 'waybill_type', 'shipping_mode', 'need_logistics_check', 'workplatform_account', 'courier_email', 'network_code', 'network_region', ]; /** 工作平台的行:扣款方式没填时用入驻里我方填的。 */ public function fillFromOnboarding(object $row): object { $onboarding = $row->_info['onboarding'] ?? null; if ($onboarding !== null && blank($row->undefined3 ?? null) && filled($onboarding->deduction_method)) { $row = clone $row; $row->undefined3 = $onboarding->deduction_method; } return $row; } /** 入驻里填的网点,按工作平台网点表的列铺成一行(排在网点明细最前面)。 */ public function onboardingNetwork(CourierOnboarding $onboarding): ?object { if (blank($onboarding->network_code) && blank($onboarding->network_name) && blank($onboarding->network_region)) { return null; } return (object) [ 'id' => '入驻 ' . $onboarding->number, 'expcode' => $onboarding->workplatform_expcode, 'netcode' => $onboarding->network_code, 'netname' => $onboarding->network_name, 'address' => $onboarding->network_region, 'linker' => $onboarding->contact_name, 'linkermobile' => $onboarding->contact_phone, 'status' => $onboarding->status === CourierOnboarding::STATUS_ACTIVATED ? '1' : '0', 'created' => $onboarding->created_at?->toDateTimeString(), ]; } /** 保存编辑:只记和当前显示值不同的字段,并写一条操作日志。 */ public function update(string $expcode, ?string $courierName, object $current, array $values, int $userId): int { $profile = CourierProfile::query()->firstOrNew(['company_id' => company_id(), 'expcode' => $expcode]); $overrides = (array) ($profile->overrides ?? []); $changes = []; foreach (CourierProfile::EDITABLE as $field => $label) { if (! array_key_exists($field, $values)) { continue; } $new = $this->normalize($field, $values[$field]); $old = $this->normalize($field, $current->{$field} ?? null); if ($new === $old) { continue; } $overrides[$field] = $new; $changes[$field] = ['label' => $label, 'old' => $old, 'new' => $new]; } if ($changes === []) { return 0; } $profile->fill([ 'courier_name' => $courierName ?: $profile->courier_name, 'overrides' => $overrides, 'updated_by' => $userId, ]); $profile->created_by ??= $userId; $profile->save(); $this->log($expcode, 'edit', '修改 ' . implode('、', array_column($changes, 'label')), $changes, $userId); return count($changes); } public function log(string $expcode, string $action, string $summary, ?array $changes, int $userId): void { CourierProfileEvent::query()->create([ 'company_id' => company_id(), 'expcode' => $expcode, 'action' => $action, 'summary' => mb_substr($summary, 0, 500), 'changes' => $changes, 'created_by' => $userId, ]); } /** @return Collection */ public function events(?string $expcode = null, int $limit = 100): Collection { if (! Schema::hasTable('business_courier_profile_events')) { return collect(); } return CourierProfileEvent::query() ->with('creator') ->when(filled($expcode), static fn ($query) => $query->where('expcode', $expcode)) ->latest('id') ->limit($limit) ->get(); } /** * 财务系统这一侧的打款信息(完整显示):入驻时填的对公/对私账户 + 打款信息新增/变更记录。 * * @return list> */ public function financeAccounts(?string $expcode = null, ?CourierOnboarding $onboarding = null): array { $rows = []; if (Schema::hasTable('business_courier_payment_accounts')) { $records = CourierPaymentAccount::query() ->when(filled($expcode), static fn ($query) => $query->where('expcode', $expcode)) ->latest('id') ->get(); foreach ($records as $record) { $status = $this->paymentStatus($record); $rows[] = [ 'source' => $record->actionLabel(), 'expcode' => $record->expcode, 'courier_name' => $record->courier_name, 'type' => $record->account_type, 'name' => $record->account_name, 'bank' => $record->bank_name, 'number' => $record->account_number, 'effective_date' => $record->effective_date?->toDateString(), 'old' => $record->action === CourierPaymentAccount::ACTION_CHANGE ? trim($record->old_account_name . ' ' . $record->old_account_number) . ($record->old_stop_date ? '(' . $record->old_stop_date->toDateString() . ' 停用)' : '') : null, 'status' => $status['label'], 'tone' => $status['tone'], 'erp_account_id' => null, 'created_at' => $record->created_at?->toDateTimeString(), 'record' => $record, ]; } } $onboardings = $onboarding !== null ? collect([$onboarding]) : (Schema::hasTable('business_courier_onboardings') ? CourierOnboarding::query() ->whereNotIn('status', [CourierOnboarding::STATUS_CANCELLED, CourierOnboarding::STATUS_REJECTED]) ->when(filled($expcode), static fn ($query) => $query->where('workplatform_expcode', $expcode)) ->latest('id') ->get() : collect()); foreach ($onboardings as $item) { $checks = $item->accountChecks(); foreach ([ ['对公', $item->bank_account_name, $item->bank_name, $item->bank_account_number, $checks['corporate'] ?? null], ['对私', $item->private_account_name, $item->private_bank_name, $item->private_account_number, $checks['private'] ?? null], ] as [$type, $name, $bank, $number, $check]) { if (! filled($name) && ! filled($number)) { continue; } $rows[] = [ 'source' => '快递入驻 ' . $item->number, 'expcode' => $item->workplatform_expcode, 'courier_name' => $item->courier_name, 'type' => $type, 'name' => $name, 'bank' => $bank, 'number' => $number, 'effective_date' => $item->activated_at?->toDateString(), 'old' => null, 'status' => $check === false ? ($type === '对公' ? '户名与营业执照主体不一致' : '收款人与法定代表人不一致') : $item->statusLabel(), 'tone' => $check === false ? 'is-bad' : ($item->status === CourierOnboarding::STATUS_ACTIVATED ? 'is-ok' : 'is-warn'), 'erp_account_id' => $item->erp_account_id, 'created_at' => $item->created_at?->toDateTimeString(), 'onboarding' => $item, ]; } } return $rows; } /** * 打款信息新增/变更的状态:按协议签署里同一快递、同类文件、同一新账号的签署任务推导。 * * @return array{label:string,tone:string,signature:?CourierAgreementSignature} */ public function paymentStatus(CourierPaymentAccount $record): array { $signature = null; if (Schema::hasTable('business_courier_agreement_signatures') && filled($record->courier_name)) { $field = $record->action === CourierPaymentAccount::ACTION_CHANGE ? 'new_bank_account' : 'account_proof_bank_account'; $number = $this->digits($record->account_number); $signature = CourierAgreementSignature::query() ->where('courier_name', $record->courier_name) ->where('document_type', $record->documentType()) ->where('created_at', '>=', $record->created_at) ->latest('id') ->get() ->first(function (CourierAgreementSignature $signature) use ($field, $number): bool { try { return $this->digits((string) data_get($signature->payload, $field)) === $number; } catch (Throwable) { return false; } }); } return match (true) { $signature === null => ['label' => '待推送协议', 'tone' => 'is-warn', 'signature' => null], in_array($signature->status, [CourierAgreementSignature::STATUS_COMPLETED, CourierAgreementSignature::STATUS_ARCHIVED], true) => ['label' => '已生效', 'tone' => 'is-ok', 'signature' => $signature], in_array($signature->status, [CourierAgreementSignature::STATUS_REVOKED, CourierAgreementSignature::STATUS_EXPIRED, CourierAgreementSignature::STATUS_REJECTED, CourierAgreementSignature::STATUS_FAILED], true) => ['label' => '签署未完成,需重新推送', 'tone' => 'is-bad', 'signature' => $signature], default => ['label' => '签署中', 'tone' => 'is-warn', 'signature' => $signature], }; } private function digits(?string $value): string { return (string) preg_replace('/[\s\-]+/u', '', (string) $value); } private function normalize(string $field, mixed $value): ?string { if ($value === null) { return null; } $value = trim((string) $value); if ($value === '' || $value === '-') { return null; } if (in_array($field, CourierProfile::BOOLEAN_FIELDS, true)) { return in_array(strtolower($value), ['1', 'true', 'yes', 'y', '是'], true) ? '1' : '0'; } if (in_array($field, CourierProfile::DATE_FIELDS, true)) { return substr($value, 0, 10); } return $value; } }