CaiWuXiTong/app/Http/Controllers/Business/ServiceCompanyReconciliation.php

326 lines
13 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\Http\Controllers\Business;
use App\Business\ServiceCompanyReconciliation\ServiceCompanyReconciliationRepository;
use App\Utilities\FinanceLedgerAccess;
use Carbon\CarbonImmutable;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\View\View;
class ServiceCompanyReconciliation extends Controller
{
public function __construct(
private readonly ServiceCompanyReconciliationRepository $repository,
) {
$this->middleware('auth');
$this->middleware('company.identify');
}
public function index(Request $request): View
{
$tenantId = (int) (company_id() ?: 1);
$period = $this->period($request);
$serviceCompanies = $this->repository->serviceCompanies($tenantId, $period);
return view('business.pages.financial-operations.service-company-reconciliation', [
'isDetail' => false,
'period' => $period,
'periodOptions' => $this->periodOptions(),
'serviceCompanies' => $serviceCompanies,
'serviceCompany' => null,
'relations' => [],
'settlements' => [],
'assignedPeople' => [],
'availablePeople' => [],
'storeCompanies' => [],
'serviceTypes' => ServiceCompanyReconciliationRepository::SERVICE_TYPES,
'baseMetrics' => ServiceCompanyReconciliationRepository::BASE_METRICS,
'companyDirectory' => $this->repository->companyDirectory(),
'canEdit' => FinanceLedgerAccess::canEditMapping(user()),
'summary' => [
'companies' => count($serviceCompanies),
'people' => array_sum(array_column($serviceCompanies, 'people_count')),
'labor_cost' => array_sum(array_column($serviceCompanies, 'labor_cost')),
'settlement_amount' => array_sum(array_column($serviceCompanies, 'settlement_amount')),
'pending' => count(array_filter(
$serviceCompanies,
static fn (array $row): bool => in_array(
$row['reconciliation_status'],
['待生成', '待确认', '未配置'],
true,
),
)),
],
]);
}
public function show(Request $request, int $serviceCompany): View
{
$tenantId = (int) (company_id() ?: 1);
$company = $this->repository->serviceCompany($tenantId, $serviceCompany);
abort_if($company === null, 404);
$period = $this->period($request);
$relations = $this->repository->relations($tenantId, $serviceCompany);
$settlements = $this->repository->settlements($tenantId, $serviceCompany, $period);
$assignedPeople = $this->repository->assignedPeople($tenantId, $serviceCompany);
$availablePeople = array_values($this->repository->availablePeople($tenantId));
$periodStart = CarbonImmutable::createFromFormat('!Y-m', $period)->startOfMonth();
$periodEnd = $periodStart->endOfMonth();
$currentPersonKeys = array_column(
array_filter($assignedPeople, static fn (array $row): bool => $row['is_current']),
'person_key',
);
return view('business.pages.financial-operations.service-company-reconciliation', [
'isDetail' => true,
'period' => $period,
'periodOptions' => $this->periodOptions(),
'serviceCompanies' => [],
'serviceCompany' => $company,
'relations' => $relations,
'settlements' => $settlements,
'assignedPeople' => $assignedPeople,
'availablePeople' => $availablePeople,
'currentPersonKeys' => array_flip($currentPersonKeys),
'storeCompanies' => $this->repository->storeCompanies($tenantId),
'serviceTypes' => ServiceCompanyReconciliationRepository::SERVICE_TYPES,
'baseMetrics' => ServiceCompanyReconciliationRepository::BASE_METRICS,
'companyDirectory' => $this->repository->companyDirectory(),
'canEdit' => FinanceLedgerAccess::canEditMapping(user()),
'summary' => [
'people' => count($currentPersonKeys),
'labor_cost' => array_sum(array_column(
array_filter($assignedPeople, static fn (array $row): bool => $row['is_current']),
'salary',
)),
'stores' => count(array_filter(
$relations,
static fn (array $row): bool => $row['status'] === 'active'
&& $row['effective_from'] <= $periodEnd->toDateString()
&& ($row['effective_to'] === null
|| $row['effective_to'] >= $periodStart->toDateString()),
)),
'settlement_amount' => array_sum(array_column($settlements, 'service_fee')),
],
]);
}
public function storeCompany(Request $request): RedirectResponse
{
$this->authorizeEdit();
$validated = $this->validateCompany($request);
$company = $this->repository->saveServiceCompany(
(int) (company_id() ?: 1),
$validated,
(string) user()->name,
);
return redirect()
->route('business.service-company-reconciliation.show', [
'serviceCompany' => $company['id'],
])
->with('success', '服务公司已登记。');
}
public function updateCompany(Request $request, int $serviceCompany): RedirectResponse
{
$this->authorizeEdit();
$validated = $this->validateCompany($request);
$this->repository->saveServiceCompany(
(int) (company_id() ?: 1),
$validated,
(string) user()->name,
$serviceCompany,
);
return back()->with('success', '服务公司信息已更新。');
}
public function storeRelation(Request $request, int $serviceCompany): RedirectResponse
{
$this->authorizeEdit();
$validated = $this->validateRelation($request);
$this->repository->saveRelation(
(int) (company_id() ?: 1),
$serviceCompany,
$validated,
(string) user()->name,
);
return back()->with('success', '店铺公司结算关系已保存。');
}
public function updateRelation(
Request $request,
int $serviceCompany,
int $relation,
): RedirectResponse {
$this->authorizeEdit();
$validated = $this->validateRelation($request);
$this->repository->saveRelation(
(int) (company_id() ?: 1),
$serviceCompany,
$validated,
(string) user()->name,
$relation,
);
return back()->with('success', '店铺公司结算关系已更新并生成新版本。');
}
public function assignPerson(Request $request, int $serviceCompany): RedirectResponse
{
$this->authorizeEdit();
$validated = $request->validate([
'person_key' => ['required', 'string', 'max:191'],
'effective_from' => ['required', 'date_format:Y-m-d'],
]);
$this->repository->assignPerson(
(int) (company_id() ?: 1),
$serviceCompany,
$validated['person_key'],
$validated['effective_from'],
(string) user()->name,
);
return back()->with('success', '人员归属已保存。');
}
public function endPerson(Request $request, int $serviceCompany, int $assignment): RedirectResponse
{
$this->authorizeEdit();
$validated = $request->validate([
'effective_to' => ['required', 'date_format:Y-m-d'],
]);
$this->repository->endPersonAssignment(
(int) (company_id() ?: 1),
$serviceCompany,
$assignment,
$validated['effective_to'],
);
return back()->with('success', '人员归属已结束。');
}
public function generateSettlements(Request $request, int $serviceCompany): RedirectResponse
{
$this->authorizeEdit();
$validated = $request->validate([
'period' => ['required', 'date_format:Y-m'],
]);
$result = $this->repository->generateSettlements(
(int) (company_id() ?: 1),
$serviceCompany,
$validated['period'],
(string) user()->name,
);
$message = "已生成 {$result['generated']} 条月度对账预览。";
if ($result['errors'] !== []) {
$message .= ' 未生成:' . implode(';', array_slice($result['errors'], 0, 5));
}
return redirect()
->route('business.service-company-reconciliation.show', [
'serviceCompany' => $serviceCompany,
'period' => $validated['period'],
'tab' => 'reconciliation',
])
->with($result['generated'] > 0 ? 'success' : 'error', $message);
}
public function updateSettlement(
Request $request,
int $serviceCompany,
int $settlement,
): RedirectResponse {
$this->authorizeEdit();
$validated = $request->validate([
'action' => ['nullable', 'in:confirm,save'],
'invoice_status' => ['nullable', 'in:pending,partial,invoiced,waived'],
'invoice_amount' => ['nullable', 'numeric', 'min:0'],
'payment_status' => ['nullable', 'in:pending,partial,paid'],
'paid_amount' => ['nullable', 'numeric', 'min:0'],
'notes' => ['nullable', 'string', 'max:1000'],
]);
$this->repository->updateSettlement(
(int) (company_id() ?: 1),
$serviceCompany,
$settlement,
$validated,
(string) user()->name,
);
return back()->with('success', '开票与到账状态已更新。');
}
private function period(Request $request): string
{
$period = trim((string) $request->query('period', ''));
if (preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $period) === 1) {
return $period;
}
return CarbonImmutable::now()->subMonthNoOverflow()->format('Y-m');
}
/** @return list<array{value:string,label:string}> */
private function periodOptions(): array
{
$current = CarbonImmutable::now()->startOfMonth();
return collect(range(0, 11))->map(static function (int $monthsAgo) use ($current): array {
$month = $current->subMonthsNoOverflow($monthsAgo);
return [
'value' => $month->format('Y-m'),
'label' => $month->format('Y年n月'),
];
})->all();
}
private function authorizeEdit(): void
{
abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '当前账号没有维护服务公司对账的权限。');
}
/** @return array<string,mixed> */
private function validateCompany(Request $request): array
{
return $request->validate([
'company_name' => ['required', 'string', 'max:200'],
'unified_social_credit_code' => ['nullable', 'string', 'max:64'],
'service_type_code' => ['required', 'in:' . implode(',', array_keys(
ServiceCompanyReconciliationRepository::SERVICE_TYPES,
))],
'taxpayer_type' => ['nullable', 'string', 'max:64'],
'company_status' => ['nullable', 'in:正常,注销中,已注销'],
'planning_target_amount' => ['nullable', 'numeric', 'min:0'],
'status' => ['required', 'in:active,inactive'],
'notes' => ['nullable', 'string', 'max:500'],
]);
}
/** @return array<string,mixed> */
private function validateRelation(Request $request): array
{
return $request->validate([
'store_company_key' => ['required', 'string', 'max:191'],
'base_metric' => ['required', 'in:' . implode(',', array_keys(
ServiceCompanyReconciliationRepository::BASE_METRICS,
))],
'fee_rate_percent' => ['required', 'numeric', 'min:0', 'max:100'],
'effective_from' => ['required', 'date_format:Y-m-d'],
'effective_to' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:effective_from'],
'contract_no' => ['nullable', 'string', 'max:64'],
'contract_status' => ['required', 'in:pending,stamped,waived'],
'status' => ['required', 'in:active,inactive'],
'notes' => ['nullable', 'string', 'max:500'],
]);
}
}