Files
xin-procurement/app/Http/Controllers/Mini/BillController.php
T
2026-10-30 11:17:15 +08:00

212 lines
8.1 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\Mini;
use App\Exceptions\RepositoryException;
use App\Exports\BillBatchExport;
use App\Exports\BillExport;
use App\Models\BillModel;
use App\Services\BillDetailService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
use Modules\AnnoRoute\Attribute\GetRoute;
use Modules\AnnoRoute\Attribute\RequestAttribute;
use Symfony\Component\HttpFoundation\Response;
/**
* 小程序门店账单(采购单完成后由后台生成,门店端只读)
*/
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
class BillController extends BaseMiniController
{
/**
* 账单列表:当前门店强制过滤
* ?status= 支付状态(0未支付 1已支付);?payable=1 仅可发起付款(未支付且未在审核中,供合并付款选择页);
* ?start_date=&end_date= 账单日期区间;?page=&pageSize= 分页(pageSize 上限 50)
* 响应附加 summary:待支付笔数/金额(仅按门店口径,不受列表筛选影响)
*/
#[GetRoute('/bill', authorize: true)]
public function index(Request $request): JsonResponse
{
$params = $request->validate([
'status' => 'nullable|integer|in:0,1',
'payable' => 'nullable|boolean',
'start_date' => 'nullable|date_format:Y-m-d',
'end_date' => 'nullable|date_format:Y-m-d|after_or_equal:start_date',
'page' => 'nullable|integer|min:1',
'pageSize' => 'nullable|integer|min:1|max:50',
], [
'status.in' => '支付状态不正确',
'start_date.date_format' => '开始日期格式为 Y-m-d',
'end_date.date_format' => '结束日期格式为 Y-m-d',
'end_date.after_or_equal' => '结束日期不能早于开始日期',
'pageSize.max' => '每页数量最大 50',
]);
$store = $this->currentStore($request);
$query = BillModel::query()
->where('store_id', $store->id)
->with('purchase:id,purchase_no,purchase_date');
if (isset($params['status'])) {
$query->where('status', (int) $params['status']);
}
if ($request->boolean('payable')) {
$query->where('status', BillModel::STATUS_UNPAID)->where('payment_id', 0);
}
if (! empty($params['start_date'])) {
$query->whereDate('bill_date', '>=', $params['start_date']);
}
if (! empty($params['end_date'])) {
$query->whereDate('bill_date', '<=', $params['end_date']);
}
$paginator = $query->orderBy('bill_date', 'desc')
->orderBy('id', 'desc')
->paginate((int) ($params['pageSize'] ?? 10));
$cycleDays = (int) $store->payment_cycle_days;
$paginator->getCollection()->transform(
fn (BillModel $bill): array => $this->formatBill($bill, $cycleDays)
);
// 待支付汇总(合并付款入口的头部统计)
$unpaid = BillModel::query()
->where('store_id', $store->id)
->where('status', BillModel::STATUS_UNPAID)
->selectRaw('COUNT(*) as aggregate_count, COALESCE(SUM(total_amount), 0) as aggregate_amount')
->first();
$data = $paginator->toArray();
$data['summary'] = [
'unpaid_count' => (int) $unpaid->aggregate_count,
'unpaid_amount' => bcadd((string) $unpaid->aggregate_amount, '0', 2),
];
return $this->success($data);
}
/**
* 批量导出账单:仅可导出本店账单,所选账单数据合并为一张表
*/
#[GetRoute('/bill/batch-export', authorize: true)]
public function batchExport(Request $request): Response
{
$data = $request->validate([
'ids' => 'required|array|min:1|max:100',
'ids.*' => 'required|integer|distinct',
], [
'ids.required' => '请选择要导出的账单',
'ids.max' => '单次最多导出 100 张账单',
'ids.*.distinct' => '账单不能重复选择',
]);
$store = $this->currentStore($request);
$bills = BillModel::query()
->with('store:id,name')
->where('store_id', $store->id)
->whereIn('id', $data['ids'])
->orderBy('bill_date')
->orderBy('id')
->get();
if ($bills->count() !== count($data['ids'])) {
throw new RepositoryException('包含不存在或不属于当前门店的账单');
}
return Excel::download(new BillBatchExport($bills), '门店账单_批量导出.xlsx');
}
/**
* 单独导出账单:仅可导出本店账单(强制 store_id 过滤);
* ?category_id= 一级分类过滤(0/缺省=全部)
*/
#[GetRoute('/bill/{id}/export', authorize: true, where: ['id' => '[0-9]+'])]
public function export(int $id, Request $request): Response
{
$data = $request->validate([
'category_id' => 'nullable|integer|min:0',
]);
$store = $this->currentStore($request);
$bill = BillModel::with('store:id,name')
->where('store_id', $store->id)
->find($id);
if ($bill === null) {
throw new RepositoryException('账单不存在');
}
return Excel::download(
new BillExport($bill, (int) ($data['category_id'] ?? 0)),
'门店账单_' . $bill->bill_no . '.xlsx'
);
}
/** 账单详情(校验归属:仅能查看本店账单;含合并后的商品明细与关联订单) */
#[GetRoute('/bill/{id}', authorize: true, where: ['id' => '[0-9]+'])]
public function detail(int $id, Request $request): JsonResponse
{
$store = $this->currentStore($request);
$bill = BillModel::with('purchase:id,purchase_no,purchase_date')
->where('store_id', $store->id)
->find($id);
if ($bill === null) {
throw new RepositoryException('账单不存在');
}
$orders = $bill->orders()
->orderBy('id')
->get(['id', 'order_no', 'order_date', 'total_quantity', 'total_weight', 'total_amount', 'status'])
->toArray();
return $this->success([
'bill' => $this->formatBill($bill, (int) $store->payment_cycle_days),
'items' => app(BillDetailService::class)->mergedItems($bill),
'orders' => $orders,
]);
}
/**
* 账单输出格式化(列表行与详情共用)
* 推导支付进度(待支付/审核中/已支付),按门店回款周期计算应结算日期;
* 剔除收款操作人等后台字段
*
* @return array<string, mixed>
*/
private function formatBill(BillModel $bill, int $cycleDays): array
{
$payState = $bill->payState();
return [
'id' => $bill->id,
'bill_no' => $bill->bill_no,
'bill_date' => $bill->bill_date->toDateString(),
'purchase_id' => $bill->purchase_id,
'purchase' => $bill->purchase?->toArray(),
'product_amount' => $bill->product_amount,
'delivery_fee' => $bill->delivery_fee,
'box_num' => $bill->box_num,
'tray_num' => $bill->tray_num,
'box_price' => $bill->box_price,
'tray_price' => $bill->tray_price,
'added_amount' => $bill->added_amount,
'after_sale' => $bill->after_sale,
'total_amount' => $bill->total_amount,
'status' => $bill->status,
'status_name' => BillModel::STATUS_NAMES[$bill->status] ?? '',
'pay_state' => $payState,
'pay_state_name' => BillModel::PAY_STATE_NAMES[$payState],
'can_pay' => $payState === BillModel::PAY_STATE_UNPAID,
'payment_id' => $bill->payment_id,
'settlement_date' => $bill->bill_date->copy()->addDays($cycleDays)->toDateString(),
'paid_at' => $bill->paid_at?->toDateTimeString(),
'pay_remark' => $bill->pay_remark,
'remark' => $bill->remark,
'created_at' => $bill->created_at?->toDateTimeString(),
];
}
}