diff --git a/app/Http/Controllers/Api/Ledger/LedgerController.php b/app/Http/Controllers/Api/Ledger/LedgerController.php new file mode 100644 index 0000000..04c064e --- /dev/null +++ b/app/Http/Controllers/Api/Ledger/LedgerController.php @@ -0,0 +1,197 @@ +user(); + + if (! $user->isStoreMaster()) { + throw new RuntimeException('非店长不可上报数据'); + } + + // 是否是彩票店数据上报 + $isLotteryLedger = $user->store->isLotteryStore(); + + $validated = $request->validate( + rules: [ + 'date' => ['bail', 'required', 'date_format:Y-m-d'], + 'items' => $isLotteryLedger ? ['bail', 'required', 'array'] : ['bail', 'array'], + 'new_customers' => ['bail', 'required', 'int', 'min:0'], + 'sales' => ['bail', 'required', 'numeric', 'min:0'], + 'expenditure' => ['bail', 'required', 'numeric', 'min:0'], + 'handover_amount' => ['bail', 'required', 'numeric', 'min:0'], + 'photos' => ['bail', 'required', 'array'], + ], + attributes: [ + 'date' => '日期', + 'items' => '彩种数据', + 'new_customers' => '新增客户', + 'sales' => '销售合计', + 'expenditure' => $isLotteryLedger ? '兑奖合计' : '支出合计', + 'handover_amount' => '交账金额', + 'photos' => '时段报表照片', + ], + ); + + /** @var \Illuminate\Database\Eloquent\Collection */ + $lotteryTypes = Keyword::filter(['parent_key' => 'lottery_type']) + ->oldest('sort') + ->get() + // 过滤未绑定上报数据类型的彩种 + ->filter(fn (Keyword $type) => (string) $type->value !== ''); + + // 上报数据项的格式: + // [ + // ['id' => '上报数据类型1', 'sales' => '销售金额', 'expenditure' => '兑奖金额'], + // ['id' => '上报数据类型2', 'sales' => '销售金额', 'expenditure' => '兑奖金额'], + // ] + + if ($isLotteryLedger) { + $items = collect($validated['items'])->keyBy('id'); + + /** @var \App\Models\Keyword */ + foreach ($lotteryTypes as $lotteryType) { + $item = $items->get($lotteryType->value); + + if (is_null($item)) { + throw new RuntimeException("{$lotteryType->name}未填写上报数据"); + } + + Validator::validate( + data: $item, + rules: [ + 'sales' => ['bail', 'required', 'numeric', 'min:0'], + 'expenditure' => ['bail', 'required', 'numeric', 'min:0'], + ], + attributes: [ + 'sales' => "[$lotteryType->name]销售金额", + 'expenditure' => "[$lotteryType->name]兑奖金额", + ], + ); + } + } + + /** @var \App\Models\Ledger|null */ + $ledger = Ledger::where('store_id', $user->store_id) + ->where('date', $validated['date']) + ->first(); + + if ($ledger && ! $ledger->allowReReport()) { + throw new RuntimeException('上报数据已更新,不可重新上传'); + } + + $ratio = bcdiv($user->store->profit_ratio, 100, 2); + + // 计算预期佣金 + $validated['expected_commission'] = bcmul($validated['sales'], $ratio, 2); + // 计算预期收益 + $validated['expected_income'] = bcsub($validated['expected_commission'], $validated['expenditure'], 2); + + try { + DB::beginTransaction(); + + if (is_null($ledger)) { + $ledger = Ledger::create( + array_merge($validated, ['store_id' => $user->store_id]) + ); + } else { + $ledger->update($validated); + $ledger->items()->delete(); + } + + LedgerItem::insert( + collect( + $isLotteryLedger ? $validated['items'] : [ + [ + 'id' => 'ledger_item_type_other', + 'sales' => $ledger->sales, + 'expenditure' => $ledger->expenditure, + ], + ] + )->map(fn ($item) => [ + 'date' => $ledger->date, + 'store_id' => $ledger->store_id, + 'ledger_id' => $ledger->id, + 'ledger_item_type_id' => $item['id'], + 'sales' => $item['sales'], + 'expenditure' => $item['expenditure'], + 'created_at' => $ledger->updated_at, + 'updated_at' => $ledger->updated_at, + ])->all() + ); + + DB::commit(); + } catch (Throwable $e) { + DB::rollBack(); + + throw tap($e, fn ($e) => report($e)); + } + + return $this->prepareLedger($ledger); + } + + public function show(string $date, Request $request) + { + /** @var \App\Models\Employee */ + $user = $request->user(); + + /** @var \App\Models\Ledger|null */ + $ledger = Ledger::with(['items']) + ->where('store_id', $user->store_id) + ->where('date', $date) + ->first(); + + $data = null; + + if ($ledger) { + $data = [ + 'date' => $ledger->date, + 'items' => $ledger->items->map(fn ($item) => [ + 'id' => $item->ledger_item_type_id, + 'sales' => $item->sales, + 'expenditure' => $item->expenditure, + ]), + 'new_customers' => $ledger->new_customers, + 'sales' => $ledger->sales, + 'expenditure' => $ledger->expenditure, + 'handover_amount' => $ledger->handover_amount, + 'photos' => $ledger->photos, + ]; + } + + return [ + 'data' => $ledger ? $this->prepareLedger($ledger) : null, + ]; + } + + protected function prepareLedger(Ledger $ledger) + { + return [ + 'date' => $ledger->date, + 'items' => $ledger->items->map(fn ($item) => [ + 'id' => $item->ledger_item_type_id, + 'sales' => $item->sales, + 'expenditure' => $item->expenditure, + ]), + 'new_customers' => $ledger->new_customers, + 'sales' => $ledger->sales, + 'expenditure' => $ledger->expenditure, + 'handover_amount' => $ledger->handover_amount, + 'photos' => $ledger->photos, + ]; + } +} diff --git a/app/Http/Controllers/Api/Ledger/LotteryTypeController.php b/app/Http/Controllers/Api/Ledger/LotteryTypeController.php new file mode 100644 index 0000000..822d1b0 --- /dev/null +++ b/app/Http/Controllers/Api/Ledger/LotteryTypeController.php @@ -0,0 +1,22 @@ + 'lottery_type'])->oldest('sort')->get(); + + return $lotteryTypes + ->filter(fn (Keyword $type) => (string) $type->value !== '') + ->map(fn ($item) => [ + 'id' => $item->value, + 'name' => $item->name, + ]); + } +} diff --git a/app/Http/Controllers/Api/LotteryTypeController.php b/app/Http/Controllers/Api/LotteryTypeController.php deleted file mode 100644 index 6c0ebf7..0000000 --- a/app/Http/Controllers/Api/LotteryTypeController.php +++ /dev/null @@ -1,19 +0,0 @@ - 'lottery_type'])->oldest('sort')->get(); - - return $lotteryTypes->map(fn ($item) => [ - 'id' => $item->value, - 'name' => $item->name, - ]); - } -} diff --git a/app/Models/Ledger.php b/app/Models/Ledger.php index e8afe95..cf538bf 100644 --- a/app/Models/Ledger.php +++ b/app/Models/Ledger.php @@ -19,6 +19,9 @@ class Ledger extends Model protected $attributes = [ 'new_customers' => 0, + 'ledger_amount' => null, + 'actual_commission' => null, + 'actual_income' => null, ]; protected $fillable = [ @@ -46,6 +49,14 @@ class Ledger extends Model return $this->hasMany(LedgerItem::class); } + /** + * 是否允许重新上报 + */ + public function allowReReport(): bool + { + return is_null($this->ledger_amount) && is_null($this->actual_commission) && is_null($this->actual_income); + } + protected function ledgerDifference(): Attribute { return Attribute::make( diff --git a/app/Models/Store.php b/app/Models/Store.php index 26c8e22..c7c2178 100644 --- a/app/Models/Store.php +++ b/app/Models/Store.php @@ -62,6 +62,19 @@ class Store extends Model return $this->hasMany(Employee::class, 'store_id'); } + public function ledgers() + { + return $this->hasMany(Ledger::class); + } + + /** + * 确认此门店是否是彩票店 + */ + public function isLotteryStore(): bool + { + return preg_match('/^store_category_lottery_/', $this->category_id); + } + protected function businessStatusText(): Attribute { return new Attribute( diff --git a/routes/api.php b/routes/api.php index b075b98..af64cab 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,7 +3,8 @@ use App\Http\Controllers\Api\Auth\AccessTokenController; use App\Http\Controllers\Api\ComplaintController; use App\Http\Controllers\Api\FeedbackController; -use App\Http\Controllers\Api\LotteryTypeController; +use App\Http\Controllers\Api\Ledger\LedgerController; +use App\Http\Controllers\Api\Ledger\LotteryTypeController; use App\Http\Controllers\Api\StatsController; use Illuminate\Support\Facades\Route; @@ -22,8 +23,11 @@ Route::group([ Route::get('/stats/dashboard', [StatsController::class, 'dashboard']); - // 彩种类型 - Route::get('lottery-types', [LotteryTypeController::class, 'index']); + // 数据上报 + Route::post('/ledger/ledgers', [LedgerController::class, 'store']); + Route::get('/ledger/ledgers/{date}', [LedgerController::class, 'show']); + Route::get('/ledger/lottery-types', [LotteryTypeController::class, 'index']); + // 举报投诉 Route::post('complaints', [ComplaintController::class, 'store']); // 意见箱