| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- <?php
- declare(strict_types=1);
- namespace app\miniapi\service;
- use app\miniapi\http\Fields;
- use app\miniapi\http\Pagination;
- use app\miniapi\exception\ApiException;
- use app\miniapi\model\MiniCheckout;
- use app\miniapi\model\MiniTaskRecord;
- use app\miniapi\model\MiniPhoto;
- use plugin\saiadmin\app\model\water\WaterEnterprise;
- use plugin\saiadmin\app\model\water\WaterEnterpriseAddress;
- use plugin\saiadmin\app\model\water\WaterAccount;
- use plugin\saiadmin\app\model\water\WaterOrder;
- use plugin\saiadmin\app\model\water\WaterDelivery;
- use plugin\saiadmin\app\model\water\WaterDevice;
- use plugin\saiadmin\app\model\water\WaterDeviceUsage;
- use plugin\saiadmin\app\model\water\WaterLedger;
- use plugin\saiadmin\app\service\WaterContentDashboard;
- use plugin\saiadmin\app\service\WaterFulfillment;
- use plugin\saiadmin\app\service\alerts\ReplenishmentPolicy;
- final class EnterpriseQuery
- {
- public function rewards(int $enterprise, array $input = []): array
- {
- $this->access->enterprise($enterprise);
- return (new \plugin\saiadmin\app\service\promotion\PromotionQuery())->enterprise($enterprise, $input);
- }
- public function __construct(private readonly Access $access)
- {
- }
- public function addressDetail(int $enterprise): array
- {
- $row = $this->access->enterprise($enterprise, true);
- $address = WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)
- ->where('is_default', 1)->where('status', 1)->orderBy('id')->first();
- return $address
- ? EnterpriseAddressPresenter::detail($address, $row)
- : array_replace(WaterPresenter::enterprise($row), ['phone' => $row->phone]);
- }
- public function addressDetailById(int $enterprise, int $address): array
- {
- $row = $this->access->enterprise($enterprise, true);
- $site = WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)
- ->where('status', 1)->find($address);
- if (!$site) {
- throw new ApiException(40401, '配送地址不存在或已停用', 404);
- }
- return EnterpriseAddressPresenter::detail($site, $row);
- }
- public function profile(?int $enterprise): array
- {
- $result = [
- 'user_id' => $this->access->actor->userId,
- 'identities' => $this->access->identities(),
- 'balance_cents' => null,
- 'reward_cents' => null,
- 'reward_buckets' => null,
- 'capabilities' => [
- 'balance_payment' => false,
- 'rewards' => false,
- 'withdrawals' => false,
- ],
- ];
- if ($enterprise !== null) {
- $balance = BalanceService::cents($enterprise);
- $result += [
- 'enterprise' => WaterPresenter::enterprise($this->access->enterprise($enterprise)),
- 'rights' => $this->rights($enterprise)['summary'],
- ];
- $result['balance_cents'] = $balance;
- $result['capabilities']['balance_payment'] = true;
- $promotion = \plugin\saiadmin\app\service\promotion\PromotionPolicy::current();
- $result['reward_buckets'] = $promotion['initialized']
- ? \plugin\saiadmin\app\service\promotion\PromotionQuery::issuedBuckets($enterprise) : 0;
- $result['capabilities']['rewards'] = $promotion['enabled'];
- }
- return $result;
- }
- public function addresses(): array
- {
- $ids = [];
- foreach ($this->access->identities() as $row) {
- if (str_starts_with($row['role'], 'enterprise_')) {
- $ids[] = $row['resource_id'];
- }
- }
- $enterprises = WaterEnterprise::query()->whereIn('id', array_unique($ids))->orderBy('id')->get()->keyBy('id');
- $sites = WaterEnterpriseAddress::query()->whereIn('enterprise_id', $enterprises->keys()->all())
- ->where('status', 1)->orderByDesc('is_default')->orderBy('id')->get();
- $items = [];
- foreach ($sites as $site) {
- $enterprise = $enterprises->get($site->enterprise_id);
- if ($enterprise) {
- $items[] = EnterpriseAddressPresenter::summary($site, $enterprise);
- }
- }
- foreach ($enterprises as $enterprise) {
- if (!$sites->contains(fn ($site) => (int) $site->enterprise_id === (int) $enterprise->id)) {
- $items[] = EnterpriseAddressPresenter::legacy($enterprise);
- }
- }
- return [
- 'items' => $items,
- 'capabilities' => [
- 'multiple_sites_per_enterprise' => true,
- 'self_create_enterprise' => false,
- ],
- 'service_hours' => null,
- 'coverage_verified' => false,
- ];
- }
- public function home(int $enterprise): array
- {
- $enterpriseRow = $this->access->enterprise($enterprise);
- $content = (new WaterContentDashboard())->preview([
- 'purchase_status' => WaterOrder::query()->where('enterprise_id', $enterprise)->exists() ? 'purchased' : 'new',
- 'is_enterprise_admin' => $this->access->isEnterpriseAdmin($enterprise) ? 1 : 0,
- 'region' => $enterpriseRow->region,
- ]);
- return [
- 'enterprise' => WaterPresenter::enterprise($enterpriseRow),
- 'banners' => $content['banners'],
- 'packages' => (new Catalog())->listing(['page_size' => 6])['items'],
- 'rights' => $this->rights($enterprise),
- 'devices' => $this->devices($enterprise, ['page_size' => 10]),
- 'unread_messages' => null,
- 'capabilities' => [
- 'coupon' => false,
- 'messages' => false,
- 'automatic_delivery' => false,
- ],
- ];
- }
- public function rights(int $enterprise): array
- {
- $enterpriseRow = $this->access->enterprise($enterprise);
- $policy = new ReplenishmentPolicy();
- $items = [];
- foreach (WaterAccount::query()->with('sku')->where('enterprise_id', $enterprise)->orderBy('id')->get() as $account) {
- $sku = $account->sku;
- $estimate = $policy->evaluate($account->toArray() + [
- 'employee_count' => $enterpriseRow->employee_count,
- 'liters' => $sku?->liters ?? 0,
- ]);
- $items[] = [
- 'id' => (int) $account->id,
- 'sku_id' => (int) $account->sku_id,
- 'sku_name' => $sku?->name,
- 'liters' => $sku ? (float) $sku->liters : null,
- 'remaining' => (int) $account->entitlement,
- 'frozen' => (int) $account->frozen,
- 'available' => $estimate['available_entitlement'],
- 'full_qty' => (int) $account->full_qty,
- 'empty_qty' => (int) $account->empty_qty,
- 'days_left' => $estimate['days_left'],
- 'severity' => $estimate['severity'],
- 'expires_at' => null,
- ];
- }
- return [
- 'items' => $items,
- 'summary' => [
- 'remaining' => array_sum(array_column($items, 'remaining')),
- 'available' => array_sum(array_column($items, 'available')),
- 'frozen' => array_sum(array_column($items, 'frozen')),
- 'full_qty' => array_sum(array_column($items, 'full_qty')),
- 'purchased' => (int) WaterOrder::query()->where('enterprise_id', $enterprise)->sum('buckets'),
- 'delivered' => (int) WaterDelivery::query()->where('enterprise_id', $enterprise)->sum('delivered_qty'),
- ],
- 'policy' => $policy->settings(),
- 'accounting' => 'enterprise_sku',
- 'capabilities' => [
- 'individual_package_cards' => false,
- 'expiry_deduction' => false,
- ],
- ];
- }
- public function ledger(int $enterprise, array $input): array
- {
- $this->access->enterprise($enterprise);
- $page = new Pagination($input);
- $query = WaterLedger::query()->where('enterprise_id', $enterprise)->whereIn('kind', [
- 'purchase',
- 'reserve',
- 'cancel',
- 'complete',
- 'partial_receive',
- 'delivery_return',
- 'promotion_reward',
- ]);
- $total = (clone $query)->count();
- $rows = $query->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get([
- 'id',
- 'sku_id',
- 'kind',
- 'quantity',
- 'reference',
- 'created_at',
- ])->toArray();
- return $page->result($rows, $total) + ['quantity_semantics' => '操作桶数,非每条记录均代表权益扣减'];
- }
- public function orders(int $enterprise, array $input): array
- {
- $this->access->enterprise($enterprise);
- $page = new Pagination($input);
- $kind = Fields::choice($input, 'kind', ['purchases', 'deliveries', 'checkouts'], 'deliveries');
- if ($kind === 'deliveries') {
- $query = WaterDelivery::query()->with('sku')->where('enterprise_id', $enterprise);
- $status = Fields::choice($input, 'status', [
- 'all',
- 'pending',
- 'transit',
- 'completed',
- 'cancelled',
- 'exception',
- ], 'all');
- $states = [
- 'pending' => ['pending', 'assigned'],
- 'transit' => ['transit'],
- 'completed' => ['completed'],
- 'cancelled' => ['cancelled'],
- 'exception' => ['returning', 'returned'],
- ];
- if ($status !== 'all') {
- $query->whereIn('status', $states[$status]);
- }
- $total = (clone $query)->count();
- $items = $query->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get()->map(WaterPresenter::delivery(...))->all();
- } elseif ($kind === 'purchases') {
- $query = WaterOrder::query()->where('enterprise_id', $enterprise);
- $total = (clone $query)->count();
- $items = $query->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)
- ->get([
- 'id',
- 'package_name',
- 'sku_id',
- 'buckets',
- 'amount_cents',
- 'created_at',
- ])->toArray();
- } else {
- $query = MiniCheckout::query()->where('enterprise_id', $enterprise);
- $total = (clone $query)->count();
- $items = $query->orderByDesc('id')->offset($page->offset())->limit($page->pageSize)->get()->map(fn ($record) => CheckoutService::present($record))->all();
- }
- return $page->result($items, $total) + ['kind' => $kind];
- }
- public function tracking(int $enterprise, int $id): array
- {
- $delivery = $this->access->enterpriseTask($enterprise, $id);
- $history = (new WaterFulfillment())->history($id);
- $events = array_map(fn ($row) => [
- 'action' => $row['action'],
- 'created_at' => $row['created_at'],
- ], $history);
- foreach (MiniTaskRecord::query()->where('delivery_id', $id)->whereIn('kind', ['depart', 'checkin', 'exception'])->orderBy('id')->get() as $record) {
- $events[] = [
- 'action' => $record->kind,
- 'created_at' => $record->created_at,
- ];
- }
- usort($events, fn ($account, $b) => strcmp($account['created_at'], $b['created_at']));
- return WaterPresenter::delivery($delivery) + [
- 'events' => $events,
- 'photos' => MiniPhoto::query()->where('delivery_id', $id)->orderBy('id')->get(['id', 'created_at'])->toArray(),
- 'eta' => null,
- 'distance_km' => null,
- 'capabilities' => ['live_gps' => false],
- ];
- }
- public function devices(int $enterprise, array $input): array
- {
- $this->access->enterprise($enterprise);
- $page = new Pagination($input);
- $query = WaterDevice::query()->where('enterprise_id', $enterprise);
- $total = (clone $query)->count();
- $items = [];
- foreach ($query->orderBy('id')->offset($page->offset())->limit($page->pageSize)->get() as $device) {
- $items[] = $this->device($device);
- }
- return $page->result($items, $total);
- }
- private function device($device): array
- {
- $state = trim($device->fault) !== '' ? 'fault' : ($device->last_seen === null ? 'unconnected' : ($device->last_seen < date('Y-m-d H:i:s', time() - 900) ? 'offline' : 'online'));
- return [
- 'id' => (int) $device->id,
- 'name' => $device->name,
- 'sn' => $device->sn,
- 'sku_id' => (int) $device->sku_id,
- 'state' => $state,
- 'last_seen' => $device->last_seen,
- 'today_liters' => $device->telemetry_date === date('Y-m-d') && $device->today_liters !== null ? (float) $device->today_liters : null,
- 'fault' => $device->fault,
- ];
- }
- public function deviceDetail(int $enterprise, int $id): array
- {
- $this->access->enterprise($enterprise);
- $device = WaterDevice::query()->where('enterprise_id', $enterprise)->find($id);
- if (!$device) {
- throw new ApiException(40401, '设备不存在', 404);
- }
- $known = WaterDeviceUsage::query()->where('device_id', $id)->whereBetween('day', [date('Y-m-d', strtotime('-6 days')), date('Y-m-d')])->pluck('liters', 'day');
- $trend = [];
- for ($i = 6;$i >= 0;$i--) {
- $day = date('Y-m-d', strtotime('-' . $i . ' days'));
- $trend[] = [
- 'day' => $day,
- 'liters' => isset($known[$day]) ? (float) $known[$day] : null,
- ];
- }
- return $this->device($device) + [
- 'trend' => $trend,
- 'rights' => $this->rights($enterprise),
- 'automatic_delivery' => false,
- ];
- }
- }
|