102 lines
4.1 KiB
PHP
102 lines
4.1 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Exceptions\RepositoryException;
|
||
use App\Models\StatementItemModel;
|
||
use App\Models\StatementModel;
|
||
use App\Models\StoreModel;
|
||
use App\Models\StoreOrderItemModel;
|
||
use App\Models\StoreOrderModel;
|
||
use Illuminate\Support\Carbon;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
/**
|
||
* 门店对账单生成(小程序端自助生成)
|
||
*
|
||
* 流程(事务内):
|
||
* 1. 拉取门店周期内的订单明细(排除已取消订单,按 order_item 去重防止重复入账)
|
||
* 2. 快照当前 payment_cycle_days,settlement_date = period_end + cycle 天
|
||
* 3. 明细快照商品名/单价/数量/重量/金额,statement_no = ST…
|
||
*/
|
||
class StatementGenerateService
|
||
{
|
||
public function __construct(private readonly BillNumberService $billNumberService)
|
||
{
|
||
}
|
||
|
||
/**
|
||
* @param StoreModel $store 门店(回款周期从此快照)
|
||
* @param string $periodStart 周期开始(Y-m-d)
|
||
* @param string $periodEnd 周期结束(Y-m-d)
|
||
*/
|
||
public function generate(StoreModel $store, string $periodStart, string $periodEnd): StatementModel
|
||
{
|
||
return DB::transaction(function () use ($store, $periodStart, $periodEnd) {
|
||
$orderItems = StoreOrderItemModel::query()
|
||
->join('store_order', 'store_order.id', '=', 'store_order_item.order_id')
|
||
->where('store_order_item.store_id', $store->id)
|
||
->whereDate('store_order.order_date', '>=', $periodStart)
|
||
->whereDate('store_order.order_date', '<=', $periodEnd)
|
||
->where('store_order.status', '<>', StoreOrderModel::STATUS_CANCELLED)
|
||
->whereNull('store_order.deleted_at')
|
||
->select('store_order_item.*')
|
||
->get();
|
||
|
||
if ($orderItems->isEmpty()) {
|
||
throw new RepositoryException('周期内本店无订单数据,无法生成对账单');
|
||
}
|
||
|
||
// 防重复入账:剔除已计入过对账单的订单明细
|
||
$usedItemIds = StatementItemModel::query()
|
||
->whereIn('statement_id', StatementModel::where('store_id', $store->id)->pluck('id'))
|
||
->pluck('order_item_id');
|
||
$orderItems = $orderItems->reject(fn ($item) => $usedItemIds->contains($item->id));
|
||
if ($orderItems->isEmpty()) {
|
||
throw new RepositoryException('周期内的订单明细均已生成过对账单');
|
||
}
|
||
|
||
// 快照回款周期 → 应结算日期
|
||
$cycleDays = (int) $store->payment_cycle_days;
|
||
$totalAmount = $orderItems->reduce(
|
||
static fn (string $carry, $item): string => bcadd($carry, (string) $item->amount, 2),
|
||
'0'
|
||
);
|
||
|
||
$statement = StatementModel::create([
|
||
'statement_no' => $this->billNumberService->make('ST'),
|
||
'store_id' => $store->id,
|
||
'period_start' => $periodStart,
|
||
'period_end' => $periodEnd,
|
||
'total_amount' => $totalAmount,
|
||
'payment_cycle_days' => $cycleDays,
|
||
'settlement_date' => Carbon::parse($periodEnd)->addDays($cycleDays)->toDateString(),
|
||
'status' => StatementModel::STATUS_PENDING,
|
||
]);
|
||
|
||
$rows = [];
|
||
$now = now();
|
||
foreach ($orderItems as $item) {
|
||
$rows[] = [
|
||
'statement_id' => $statement->id,
|
||
'order_id' => $item->order_id,
|
||
'order_item_id' => $item->id,
|
||
'product_id' => $item->product_id,
|
||
'product_name' => $item->product_name,
|
||
'price' => $item->price,
|
||
'quantity' => $item->quantity,
|
||
'weight' => $item->weight,
|
||
'amount' => $item->amount,
|
||
'is_reconciled' => StatementItemModel::NOT_RECONCILED,
|
||
'store_remark' => '',
|
||
'created_at' => $now,
|
||
'updated_at' => $now,
|
||
];
|
||
}
|
||
StatementItemModel::insert($rows);
|
||
|
||
return $statement;
|
||
});
|
||
}
|
||
}
|