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

197 lines
9.2 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\EcommerceFinance\Repositories\GoodsReconciliationRepository;
use Modules\EcommerceFinance\Repositories\SupplierPayableReportRepository;
use Modules\EcommerceFinance\Repositories\SupplierPayableRepository;
use Modules\EcommerceFinance\Services\SupplierPayableMaintenanceRegistry;
use Modules\EcommerceFinance\Services\SupplierPayableImportTemplate;
use Modules\EcommerceFinance\Services\SupplierPayableOverrideStore;
use Modules\EcommerceFinance\Services\SupplierPayableWorkbookImporter;
use RuntimeException;
use Symfony\Component\HttpFoundation\StreamedResponse;
class SupplierPayableActions extends Controller
{
public function __construct(
private readonly SupplierPayableRepository $supplier,
private readonly GoodsReconciliationRepository $goods,
private readonly SupplierPayableReportRepository $reports,
private readonly SupplierPayableMaintenanceRegistry $registry,
private readonly SupplierPayableImportTemplate $template,
private readonly SupplierPayableOverrideStore $overrides,
private readonly SupplierPayableWorkbookImporter $importer,
) {
$this->middleware('auth');
$this->middleware('company.identify');
}
public function definition(Request $request): JsonResponse
{
return response()->json($this->registry->publicDefinition($this->dataset($request)));
}
public function update(Request $request, string $recordKey): JsonResponse|RedirectResponse
{
$dataset = $this->dataset($request);
$values = $this->registry->normalize($dataset, (array) $request->input('values', []));
$result = $this->overrides->save(
$dataset,
$recordKey,
$values,
(int) (company_id() ?: 1),
user_id() ? (int) user_id() : null,
);
return $request->expectsJson()
? response()->json(['message' => '保存成功。'] + $result)
: back()->with('success', '保存成功。');
}
public function import(Request $request): JsonResponse|RedirectResponse
{
$request->validate(['import' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:10240']]);
$result = $this->importer->import(
$this->dataset($request),
$request->file('import'),
(int) (company_id() ?: 1),
user_id() ? (int) user_id() : null,
);
$message = "导入完成:新增维护 {$result['created']} 条,更新维护 {$result['updated']} 条。";
return $request->expectsJson()
? response()->json(['message' => $message] + $result)
: back()->with('success', $message);
}
public function template(Request $request): StreamedResponse
{
$dataset = $this->dataset($request);
return $this->template->download($this->title($dataset), $this->registry->fields($dataset));
}
public function export(Request $request): StreamedResponse
{
$dataset = $this->dataset($request);
$fields = $this->registry->fields($dataset);
$filters = $this->filters($dataset, $request);
$companyId = (int) (company_id() ?: 1);
return response()->streamDownload(function () use ($dataset, $fields, $filters, $companyId): void {
$csv = fopen('php://output', 'w');
if ($csv === false) {
throw new RuntimeException('无法创建导出文件。');
}
fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($csv, array_merge(['记录标识'], array_column($fields, 'label')));
foreach ($this->allRows($dataset, $filters) as $batch) {
foreach ($this->overrides->decorate($dataset, $batch, $companyId) as $row) {
$values = [$this->csvText($row['_action_key'] ?? '')];
foreach ($fields as $field => $definition) {
$values[] = $definition['type'] === 'number' && is_numeric($row[$field] ?? null)
? $row[$field]
: $this->csvText($row[$field] ?? '');
}
fputcsv($csv, $values);
}
}
fclose($csv);
}, $this->title($dataset) . '_' . now()->format('Ymd_His') . '.csv', [
'Content-Type' => 'text/csv; charset=UTF-8',
'Cache-Control' => 'private, no-store',
]);
}
/** @return iterable<array<int,array<string,mixed>>> */
private function allRows(string $dataset, array $filters): iterable
{
if ($dataset === 'payment-reconciliation' && ($filters['record_type'] ?? 'all') === 'all') {
foreach (['purchase', 'sale'] as $type) {
yield from $this->allRows($dataset, array_replace($filters, ['record_type' => $type]));
}
return;
}
$page = 1;
do {
$result = $this->page($dataset, $filters, $page, 100);
$rows = array_slice((array) ($result['data'] ?? $result['rows'] ?? []), 0, 100);
if ($rows !== []) {
yield $rows;
}
$total = (int) ($result['total'] ?? -1);
$more = $rows !== [] && ($total < 0 ? count($rows) === 100 : $page * 100 < $total);
$page++;
} while ($more);
}
private function page(string $dataset, array $filters, int $page, int $limit): array
{
return match ($dataset) {
'payment-reconciliation' => $this->goods->list($filters, $page, $limit),
'return-reconciliation' => $this->supplier->returnReconciliations($filters, $page, $limit),
'invoice-deductions' => $this->supplier->invoiceDeductions($filters, $page, $limit),
'compensation-details' => $this->supplier->compensationDetails($filters, $page, $limit),
'advance-details' => $this->supplier->advanceDetails($filters, $page, $limit),
'sample-deductions' => $this->supplier->sampleDeductions($filters, $page, $limit),
'delivery-unpriced' => $this->supplier->deliveryUnpriced($filters, $page, $limit),
'return-unpriced' => $this->supplier->returnUnpriced($filters, $page, $limit),
'other-adjustments' => $this->supplier->otherAdjustments($filters, $page, $limit),
'dropshipping-express' => $this->supplier->dropshippingExpress($filters, $page, $limit),
'board-deductions' => $this->reports->boardDeductions($filters, $page, $limit),
'supplier-payables-report' => $this->reports->supplierPayables($filters, $page, $limit),
};
}
private function filters(string $dataset, Request $request): array
{
$allowed = match ($dataset) {
'payment-reconciliation' => ['record_type', 'supplier', 'warehouse', 'stock_no', 'purchase_no', 'product_code', 'express_no', 'send_time_from', 'send_time_to', 'create_time_from', 'create_time_to', 'sent_status', 'has_diff'],
'return-reconciliation', 'return-unpriced' => ['supplier', 'warehouse', 'sku_id', 'express_no', 'date_from', 'date_to'],
'invoice-deductions', 'compensation-details', 'advance-details', 'other-adjustments' => ['supplier', 'month_from', 'month_to'],
'sample-deductions' => ['supplier', 'department', 'creator', 'sku_id', 'date_from', 'date_to'],
'delivery-unpriced' => ['supplier', 'warehouse', 'purchase', 'barcode', 'express_no', 'date_from', 'date_to'],
'dropshipping-express' => ['supplier', 'express_name', 'month_from', 'month_to'],
'board-deductions' => ['stock_in_date', 'stock_out_date', 'furniture_factory', 'lamination_factory', 'deduction_type', 'material_name', 'claim_number'],
'supplier-payables-report' => ['month', 'supplier', 'checker', 'settlement_method'],
};
return collect($request->only($allowed))
->map(static fn (mixed $value): mixed => is_string($value) ? trim($value) : $value)
->reject(static fn (mixed $value): bool => $value === '' || $value === null || $value === false)
->all();
}
private function dataset(Request $request): string
{
$dataset = (string) $request->route('dataset');
$this->registry->fields($dataset);
return $dataset;
}
private function title(string $dataset): string
{
return [
'payment-reconciliation' => '货款对账', 'return-reconciliation' => '退货对账',
'invoice-deductions' => '发票扣款', 'compensation-details' => '赔偿明细',
'advance-details' => '借支明细', 'sample-deductions' => '样品扣款',
'delivery-unpriced' => '送货未定价', 'return-unpriced' => '退货未定价',
'other-adjustments' => '其他扣款补款', 'dropshipping-express' => '代发快递',
'board-deductions' => '板材扣款', 'supplier-payables-report' => '供应商应付表',
][$dataset];
}
private function csvText(mixed $value): string
{
$value = (string) $value;
return preg_match('/^[=+\-@\t\r]/u', $value) === 1 ? "\t" . $value : $value;
}
}