| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- <?php
- declare(strict_types=1);
- namespace app\miniapi\service;
- use app\miniapi\http\Fields;
- use app\miniapi\http\Input;
- use app\miniapi\exception\ApiException;
- use plugin\saiadmin\app\model\water\WaterEnterprise;
- use plugin\saiadmin\app\model\water\WaterEnterpriseAddress;
- use plugin\saiadmin\app\model\water\WaterSku;
- use plugin\saiadmin\app\model\water\WaterDelivery;
- use plugin\saiadmin\app\service\WaterBusiness;
- final class EnterpriseCommands
- {
- public function __construct(private readonly Access $access)
- {
- }
- public function address(int $enterprise, array $input): array
- {
- $this->access->enterprise($enterprise, true);
- Fields::only($input, [
- 'address',
- 'contact',
- 'phone',
- 'revision',
- 'request_key',
- ]);
- $address = Fields::text($input, 'address', 255);
- $contact = Fields::text($input, 'contact', 60);
- $phone = Fields::phone($input);
- $revision = Fields::text($input, 'revision', 64);
- return Commands::run($this->access->actor, Fields::text($input, 'request_key', 80), 'address:' . $enterprise, $input, function () use ($enterprise, $address, $contact, $phone, $revision) {
- $this->access->enterprise($enterprise, true);
- $enterpriseRow = WaterEnterprise::query()->where('id', $enterprise)->lockForUpdate()->firstOrFail();
- if (!hash_equals(WaterPresenter::revision($enterpriseRow), $revision)) {
- throw new ApiException(40901, '企业资料已更新,请刷新', 409);
- }
- // Booking uses the same enterprise lock. A locking read sees tasks committed after
- // the earlier authorization snapshot, even under MySQL REPEATABLE READ.
- $activeTask = WaterDelivery::query()->where('enterprise_id', $enterprise)
- ->whereNotNull('active_key')->lockForUpdate()->first(['id']);
- if ($activeTask) {
- throw new ApiException(40902, '存在未完成配送,请完成或取消后修改配送地址', 409);
- }
- $values = [
- 'address' => $address,
- 'contact' => $contact,
- 'phone' => $phone,
- 'updated_at' => date('Y-m-d H:i:s'),
- ];
- if ($enterpriseRow->address !== $address) {
- $values += [
- 'longitude' => null,
- 'latitude' => null,
- ];
- }
- $enterpriseRow->fill($values)->save();
- $default = WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)->where('is_default', 1)->where('status', 1)->lockForUpdate()->first();
- if ($default) {
- $default->fill([
- 'address' => $values['address'], 'contact' => $values['contact'], 'phone' => $values['phone'],
- 'longitude' => $values['longitude'] ?? $default->longitude, 'latitude' => $values['latitude'] ?? $default->latitude,
- 'updated_at' => $values['updated_at'],
- ])->save();
- }
- return [
- 'id' => $enterprise,
- 'revision' => WaterPresenter::revision($enterpriseRow),
- ];
- }, fn () => $this->access->enterprise($enterprise, true));
- }
- private function addressValues(array $input): array
- {
- Fields::only($input, [
- 'label', 'address', 'contact', 'phone', 'region', 'longitude', 'latitude',
- 'is_default', 'revision', 'request_key',
- ]);
- $coordinates = null;
- if (array_key_exists('longitude', $input) || array_key_exists('latitude', $input)) {
- if (!array_key_exists('longitude', $input) || !array_key_exists('latitude', $input)) {
- throw new ApiException(42201, '经纬度必须同时填写', 422);
- }
- $coordinates = Fields::location($input);
- }
- return [
- 'label' => Fields::text($input, 'label', 80, false) ?: '配送地址',
- 'address' => Fields::text($input, 'address', 255),
- 'contact' => Fields::text($input, 'contact', 60),
- 'phone' => Fields::phone($input),
- 'region' => Fields::text($input, 'region', 80, false),
- 'longitude' => $coordinates['longitude'] ?? null,
- 'latitude' => $coordinates['latitude'] ?? null,
- 'is_default' => filter_var($input['is_default'] ?? false, FILTER_VALIDATE_BOOLEAN),
- 'revision' => Fields::text($input, 'revision', 64, false),
- ];
- }
- public function createAddress(int $enterprise, array $input): array
- {
- $this->access->enterprise($enterprise, true);
- Fields::only($input, [
- 'label', 'address', 'contact', 'phone', 'region', 'longitude', 'latitude',
- 'is_default', 'request_key',
- ]);
- $values = $this->addressValues($input);
- $requestKey = Fields::text($input, 'request_key', 80);
- return Commands::run($this->access->actor, $requestKey, 'address:create:' . $enterprise, $input, function () use ($enterprise, $values) {
- $enterpriseRow = WaterEnterprise::query()->where('id', $enterprise)->lockForUpdate()->firstOrFail();
- $hasDefault = WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)->where('is_default', 1)->where('status', 1)->exists();
- $makeDefault = !$hasDefault || $values['is_default'];
- if ($makeDefault) {
- WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)->update(['is_default' => 0]);
- }
- $now = date('Y-m-d H:i:s');
- $id = WaterEnterpriseAddress::query()->insertGetId([
- 'enterprise_id' => $enterprise,
- 'label' => $values['label'],
- 'contact' => $values['contact'],
- 'phone' => $values['phone'],
- 'region' => $values['region'],
- 'address' => $values['address'],
- 'longitude' => $values['longitude'],
- 'latitude' => $values['latitude'],
- 'is_default' => $makeDefault ? 1 : 0,
- 'status' => 1,
- 'created_at' => $now,
- 'updated_at' => $now,
- ]);
- if ($makeDefault) {
- $enterpriseRow->fill([
- 'address' => $values['address'], 'contact' => $values['contact'], 'phone' => $values['phone'],
- 'region' => $values['region'], 'longitude' => $values['longitude'], 'latitude' => $values['latitude'],
- 'updated_at' => $now,
- ])->save();
- }
- return ['id' => (int) $id, 'is_default' => $makeDefault];
- }, fn () => $this->access->enterprise($enterprise, true));
- }
- public function updateAddress(int $enterprise, int $address, array $input): array
- {
- $this->access->enterprise($enterprise, true);
- $values = $this->addressValues($input);
- $requestKey = Fields::text($input, 'request_key', 80);
- return Commands::run($this->access->actor, $requestKey, 'address:update:' . $address, $input, function () use ($enterprise, $address, $values) {
- $enterpriseRow = WaterEnterprise::query()->where('id', $enterprise)->lockForUpdate()->firstOrFail();
- $row = WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)->where('status', 1)->lockForUpdate()->find($address);
- if (!$row) throw new ApiException(40401, '配送地址不存在或已停用', 404);
- if ($values['revision'] === '' || !hash_equals(EnterpriseAddressPresenter::revision($row), $values['revision'])) {
- throw new ApiException(40901, '配送地址已更新,请刷新', 409);
- }
- if (WaterDelivery::query()->where('address_id', $address)->whereNotNull('active_key')->exists()
- && ($row->address !== $values['address'] || $row->longitude != $values['longitude'] || $row->latitude != $values['latitude'])) {
- throw new ApiException(40902, '该地址存在未完成配送,请完成或取消后修改位置', 409);
- }
- $makeDefault = $values['is_default'] || (int) $row->is_default === 1;
- if ($makeDefault) WaterEnterpriseAddress::query()->where('enterprise_id', $enterprise)->update(['is_default' => 0]);
- $now = date('Y-m-d H:i:s');
- $row->fill([
- 'label' => $values['label'], 'address' => $values['address'], 'contact' => $values['contact'],
- 'phone' => $values['phone'], 'region' => $values['region'], 'longitude' => $values['longitude'],
- 'latitude' => $values['latitude'], 'is_default' => $makeDefault ? 1 : 0, 'updated_at' => $now,
- ])->save();
- if ($makeDefault) {
- $enterpriseRow->fill([
- 'address' => $values['address'], 'contact' => $values['contact'], 'phone' => $values['phone'],
- 'region' => $values['region'], 'longitude' => $values['longitude'], 'latitude' => $values['latitude'],
- 'updated_at' => $now,
- ])->save();
- }
- return ['id' => $address, 'revision' => EnterpriseAddressPresenter::revision($row), 'is_default' => $makeDefault];
- }, fn () => $this->access->enterprise($enterprise, true));
- }
- public function book(int $enterprise, array $input): array
- {
- $this->access->enterprise($enterprise, true);
- Fields::only($input, ['sku_id', 'quantity', 'address_id', 'request_key']);
- $sku = Fields::id($input, 'sku_id');
- $quantity = Input::integer($input['quantity'] ?? null, 'quantity', 1, 10000);
- $address = Input::integer($input['address_id'] ?? 0, 'address_id', 0, 10000000);
- return Commands::run($this->access->actor, Fields::text($input, 'request_key', 80), 'delivery.book:' . $enterprise, $input, function () use ($enterprise, $sku, $quantity, $address) {
- // Match the existing SKU -> enterprise -> account lock order.
- WaterSku::query()->where('id', $sku)->lockForUpdate()->first();
- WaterEnterprise::query()->where('id', $enterprise)->lockForUpdate()->first();
- $this->access->enterprise($enterprise, true);
- return (new WaterBusiness())->createDelivery([
- 'enterprise_id' => $enterprise,
- 'sku_id' => $sku,
- 'quantity' => $quantity,
- 'address_id' => $address,
- ], 0);
- }, fn () => $this->access->enterprise($enterprise, true));
- }
- public function cancel(int $enterprise, int $id, array $input): array
- {
- $this->access->enterprise($enterprise, true);
- $this->access->enterpriseTask($enterprise, $id);
- Fields::only($input, ['request_key']);
- return Commands::run($this->access->actor, Fields::text($input, 'request_key', 80), 'delivery.cancel:' . $id, $input, function () use ($enterprise, $id) {
- $this->access->enterprise($enterprise, true);
- $task = $this->access->enterpriseTask($enterprise, $id);
- WaterSku::query()->where('id', $task->sku_id)->lockForUpdate()->first();
- $locked = WaterDelivery::query()->where('id', $id)->lockForUpdate()->firstOrFail();
- // Customers may cancel only unassigned requests; dispatched work requires operator handling.
- if (!in_array($locked->status, ['pending', 'cancelled'], true)) {
- throw new ApiException(40902, '已派单的配送请联系客服取消', 409);
- }
- return (new WaterBusiness())->transition([
- 'id' => $id,
- 'action' => 'cancel',
- ], 0);
- }, fn () => $this->access->enterprise($enterprise, true));
- }
- }
|