初始化项目

This commit is contained in:
2025-01-11 14:16:20 +08:00
parent 1ed53bf65b
commit cf64159069
11206 changed files with 1446622 additions and 0 deletions

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

94
.env.example Normal file
View File

@@ -0,0 +1,94 @@
APP_NAME=xiaokang
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://xiaokang.test
APP_BEIAN=
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=xiaokang
DB_USERNAME=root
DB_PASSWORD=root
DB_PREFIX=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
REDIS_DB=1
REDIS_CACHE_DB=1
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
#########################################
# 附加配置
#########################################
# 微信小程序
## 用户端
MEMBER_WECHAT_MINI_APP_APPID=
MEMBER_WECHAT_MINI_APP_SECRET=
## 医生端
SERVICE_WECHAT_MINI_APP_APPID=
SERVICE_WECHAT_MINI_APP_SECRET=
# 支付
## 微信支付
PAY_WECHAT_MCH_ID=
PAY_WECHAT_MCH_SECRET_KEY=
PAY_WECHAT_MCH_SECRET_CERT=
PAY_WECHAT_MCH_PUBLIC_CERT_PATH=
PAY_WECHAT_MINI_APP_ID=
# 物流
EXPRESS_KEY=
EXPRESS_CUSTOMER=
# 内部服务API密钥
INTERNAL_SERVICE_API_TOKEN=
# 开发配置
SQL_LOGGER_ALL_QUERIES_ENABLED=false
SQL_LOGGER_SLOW_QUERIES_ENABLED=false
QUERY_DETECTOR_ENABLED=false

11
.gitattributes vendored Normal file
View File

@@ -0,0 +1,11 @@
* text=auto
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
lang/*
!lang/en/
!lang/zh_CN/
!lang/en.json
!lang/zh_CN.json
public/uploads
public/vendor
.env
.env.backup
.env.production
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
.phpstorm.meta.php
_ide_helper.php
/.idea
/.vscode
install.lock

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
## 关于
[Laravel](https://github.com/laravel/laravel)本地化(中文化)项目模板,带一键安装更新命令,目前[Laravel](https://github.com/laravel/laravel)版本是9.x版本。
## 快速开始
复制`.env.example``.env`文件,在`.env`文件里配置好数据库配置,在命令行里执行`php artisan system install`即可。
## 使用
内置了vendor环境如不需要请去`.gitignore`内添加`/vendor`,在命令行里执行`git rm -r --cached ./vendor`然后再提交到git库即可。
如果需要使用内置vendor环境请在开发前更新一下环境执行`composer update`即可。
一键安装/更新命令 `php artisan system install``php artisan system update`
`php artisan system install`会在项目根目录产生`install.lock`文件,如果无权限,请自行更改命令代码。
建议查看一下`app/Console/Commands/SystemCommand.php`(`php artisan system`)逻辑,以便更好的使用该命令。
目前问题:在`production`环境下无法正确执行询问形命令,目前尚无解决方法。
其他命令`php artisan`查看。

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Console\Commands;
use App\Models\YiiModels\Platform;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Console\Command;
class DataSyncCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'data:sync {--type=} {--data=}';
/**
* The console command description.
*
* @var string
*/
protected $description = '数据同步';
/**
* Execute the console command.
*
* @return int
* @throws GuzzleException
*/
public function handle()
{
$type = $this->option('type');
$data = $this->option('data');
log_s('数据同步 => 开始发送:' . $type, 'sync', 'send');
log_s($data, 'sync-old', 'send');
try {
$platform = Platform::first();
if (!$platform) {
log_s('数据同步 => 发送结果:无平台数据', 'sync', 'send');
return Command::SUCCESS;
}
if ($platform->status != Platform::STATUS_PASS) {
log_s('数据同步 => 发送结果:权限不足', 'sync', 'send');
return Command::SUCCESS;
}
$response = $this->newRequest()->post($platform->url_new, [
'form_params' => [
'platform_token' => $platform->token,
'type' => $type,
'data' => json_encode($data),
]
]);
$contents = $response->getBody()->getContents();
$result = json_decode($contents, true);
log_s('数据同步 => 发送结果:' . var_export($result, true), 'sync', 'send');
} catch (Exception $exception) {
log_s('数据同步 => 错误信息:' . $exception->getMessage(), 'sync', 'send');
}
return Command::SUCCESS;
}
public function newRequest(): Client
{
return new Client([
'timeout' => 10,
'verify' => false,
'http_errors' => false,
]);
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use App\Models\YiiModels\Platform;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Console\Command;
class DataSyncOldCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'data:sync-old {--type=} {--data=}';
/**
* The console command description.
*
* @var string
*/
protected $description = '数据同步(老)';
/**
* Execute the console command.
*
* @return int
* @throws GuzzleException
*/
public function handle()
{
$type = $this->option('type');
$data = $this->option('data');
log_s('数据同步(老) => 开始发送:' . $type, 'sync-old', 'send');
log_s($data, 'sync-old', 'send');
try {
$platform = Platform::first();
if (!$platform) {
log_s('数据同步(老) => 发送结果:无平台数据', 'sync-old', 'send');
return Command::SUCCESS;
}
if ($platform->status != Platform::STATUS_PASS) {
log_s('数据同步(老) => 发送结果:权限不足', 'sync-old', 'send');
return Command::SUCCESS;
}
[$uri, $form_params] = $this->$type($data);
$response = $this->newRequest()->post(rtrim($platform->url, '/') . '/' . $uri, [
'headers' => [
'Authorization' => "Bearer $platform->token",
],
'form_params' => $form_params
]);
$contents = $response->getBody()->getContents();
console_info($contents);
$result = json_decode($contents, true);
log_s('数据同步(老) => 发送结果:' . var_export($result, true), 'sync-old', 'send');
} catch (Exception $exception) {
log_s('数据同步(老) => 错误信息:' . $exception->getMessage(), 'sync-old', 'send');
}
return Command::SUCCESS;
}
public function newRequest(): Client
{
return new Client([
'timeout' => 10,
'verify' => false,
'http_errors' => false,
]);
}
private function syncOrder($data): array
{
return ['platform/v1/sync/order', json_decode($data, true)];
}
}

View File

@@ -0,0 +1,197 @@
<?php
namespace App\Console\Commands;
use App\Models\ExpressCompany;
use App\Models\ExpressDetail;
use App\Models\ExpressNo;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use PhpOffice\PhpSpreadsheet\IOFactory;
class ExpressCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'express {type?} {--code=} {--no=} {--id=}';
/**
* The console command description.
*
* @var string
*/
protected $description = '物流相关';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$type = $this->argument('type');
switch ($type) {
case 'update': // 物流更新
$this->update();
break;
case 'import': // 快递公司导入 快递公司没有对应API只有文件所以需要手动更新文件并执行
$this->import();
break;
default:
$this->error('参数错误');
break;
}
return Command::SUCCESS;
}
private function update()
{
$code = $this->option('code');
$no = $this->option('no');
$id = $this->option('id');
log_s("物流更新请求参数:--code=$code --no=$no --id=$id", 'express', 'message', true);
if ($code && $no) {
$expressNo = ExpressNo::firstOrCreate([
'express_company_code' => $code,
'express_no' => $no,
]);
try {
$this->updateHandle($expressNo);
} catch (Exception $exception) {
$message = "物流请求错误:$expressNo->express_company_name $expressNo->express_no => {$exception->getMessage()}";
console_error($message);
log_s($message, 'express', 'error', true);
}
} elseif ($id) {
$expressNo = ExpressNo::find($id);
if (!$expressNo) {
$message = "物流请求错误:未找到{$id}对应单号";
console_error($message);
log_s($message, 'express', 'error', true);
return true;
}
try {
$this->updateHandle($expressNo);
} catch (Exception $exception) {
$message = "物流请求错误:$expressNo->express_company_name $expressNo->express_no => {$exception->getMessage()}";
console_error($message);
log_s($message, 'express', 'error', true);
}
} else {
ExpressNo::with(['detail'])
->whereNotNull('express_company_code')
->whereNotNull('express_no')
->chunk(100, function (Collection $collection) {
/* @var ExpressNo $expressNo */
foreach ($collection as $expressNo) {
try {
if ($expressNo->detail) {
if ($expressNo->state == ExpressNo::STATE_SIGN) {
if (now()->diffInHours($expressNo->detail->detail_at) > 720) {
$expressNo->update(['sync_at' => now()]);
$message = "物流请求跳过已签收且超过30天";
console_info($message);
continue;
}
}
} else {
if (now()->diffInDays($expressNo->created_at) > 15) {
$expressNo->update(['sync_at' => now()]);
$message = "物流请求跳过该件15天无物流信息";
console_info($message);
continue;
}
}
$this->updateHandle($expressNo);
} catch (Exception $exception) {
$message = "物流请求错误:$expressNo->express_company_name $expressNo->express_no => {$exception->getMessage()}";
console_error($message);
log_s($message, 'express', 'error', true);
}
}
});
}
}
public function updateHandle(ExpressNo $expressNo)
{
console_comment("物流请求开始:$expressNo->express_company_name $expressNo->express_no");
if (!$expressNo->express_company_name) {
$expressNo->load(['company']);
if ($expressNo->company) {
$expressNo->express_company_name = $expressNo->company->name;
$expressNo->save();
}
}
if ($expressNo->sync_at && now()->diffInMinutes($expressNo->sync_at) < 60) {
$message = "物流请求跳过距离上次获取不到60分钟";
console_info($message);
return true;
}
$response = app('express')->track($expressNo->express_no, $expressNo->express_company_code, ['phone' => $expressNo->mobile]);
$result = json_decode($response, true);
if ($result === false) {
$message = "物流请求错误:$expressNo->express_company_name $expressNo->express_no => $response";
console_error($message);
log_s($message, 'express', 'error', true);
return true;
}
if (($result['status'] ?? '') != 200) {
$message = "物流请求失败:$expressNo->express_company_name $expressNo->express_no =>" . var_export($result, true);
console_info($message);
log_s($message, 'express', 'fail', true);
return true;
}
$message = "物流请求成功:$expressNo->express_company_name $expressNo->express_no";
console_info($message);
log_s($message, 'express', 'success', true);
$detailIds = [];
foreach ($result['data'] as $value) {
/* @var ExpressDetail $detail */
$detail = $expressNo->details()->updateOrCreate([
'status' => $value['status'],
'detail_at' => $value['ftime'],
'detail' => $value['context'],
]);
console_info("$expressNo->express_company_name $expressNo->express_no => {$value['context']}");
$detailIds[] = $detail->id;
}
$expressNo->details()->whereNotIn('id', $detailIds)->delete();
$expressNo->update([
'state' => $result['state'],
'sync_at' => now(),
]);
console_info("$expressNo->express_company_name $expressNo->express_no => {$expressNo->state_string}");
}
private function import()
{
$spreadsheet = IOFactory::load(storage_path('files/快递100快递公司标准编码.xlsx'));
$sheet = $spreadsheet->setActiveSheetIndex(0);
$data = $sheet->toArray();
$ids = [];
foreach ($data as $k => $value) {
if ($k < 2) {
continue;
}
$expressCompany = ExpressCompany::updateOrCreate([
'code' => $value[1],
], [
'name' => $value[0],
'type' => $value[2],
'sort' => $k,
]);
$ids[] = $expressCompany->id;
console_info("$value[1] => $value[0]");
}
ExpressCompany::whereNotIn('id', $ids)->delete();
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
class SystemCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'system {type?}';
/**
* The console command description.
*
* @var string
*/
protected $description = '系统安装/更新';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return false|void
*/
public function handle()
{
$type = $this->argument('type');
switch ($type) {
case 'ready':
if (!app()->isLocal()) {
$this->error('非法环境');
return 0;
}
$this->language();
$this->comment('准备发布完成:)');
break;
case 'install':
if (file_exists(__DIR__ . '/../../../install.lock')) {
$this->error('如需重装请删除“install.lock”文件');
return 0;
}
$this->migrate();
$this->keyGenerate();
$this->queueRestart();
file_put_contents('install.lock', 'Install on ' . date('Y-m-d H:i:s'));
$this->comment('安装完成:)');
break;
case 'update':
$this->migrate();
$this->queueRestart();
$this->comment('更新完成:)');
break;
default:
$this->error('只允许type参数类型为“ready”、“install”、“update”');
break;
}
}
private function keyGenerate(): void
{
if ($this->laravel['config']['app.key']) {
$this->comment('Laravel Key 已存在' . PHP_EOL);
} else {
Artisan::call('key:generate', [
'--ansi' => true,
], $this->output);
$this->comment('Laravel Key 已生成' . PHP_EOL);
}
}
private function migrate(): void
{
Artisan::call('migrate', [], $this->output);
$this->comment('数据库迁移完成' . PHP_EOL);
}
private function queueRestart(): void
{
Artisan::call('queue:restart');
$this->comment('队列已重启' . PHP_EOL);
}
private function language(): void
{
Artisan::call('lang:add', [
'locales' => ['en', 'zh_CN'],
], $this->output);
$this->comment('Laravel语言包更新完成' . PHP_EOL);
}
}

35
app/Console/Kernel.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* Define the application's command schedule.
*
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('express update')
->name('定时刷新物流')
->hourlyAt(12)
// ->everyMinute() // fixme test
->withoutOverlapping(30);
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__ . '/Commands');
require base_path('routes/console.php');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
*/
protected $levels = [
];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
});
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Handlers;
use BeyondCode\QueryDetector\Outputs\Output;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log as LaravelLog;
use Symfony\Component\HttpFoundation\Response;
class QueryDetectorLogHandler implements Output
{
public function boot()
{
}
public function output(Collection $detectedQueries, Response $response)
{
$this->log('Detected N+1 Query');
foreach ($detectedQueries as $detectedQuery) {
$logOutput = 'Model: ' . $detectedQuery['model'] . PHP_EOL;
$logOutput .= 'Relation: ' . $detectedQuery['relation'] . PHP_EOL;
$logOutput .= 'Num-Called: ' . $detectedQuery['count'] . PHP_EOL;
$logOutput .= 'Call-Stack:' . PHP_EOL;
foreach ($detectedQuery['sources'] as $source) {
$logOutput .= '#' . $source->index . ' ' . $source->name . ':' . $source->line . PHP_EOL;
}
$this->log($logOutput);
}
}
private function log(string $message)
{
LaravelLog::channel('query_detector')->info($message);
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
/**
* 验证
*
* @param array $rules
* @param array $messages
* @param array $customAttributes
* @param bool|array $only
* @return array
*/
public function toValidator(array $rules, array $messages = [], array $customAttributes = [], bool|array $only = []): array
{
$input = request()->all();
$validator = Validator::make($input, $rules, $messages, $customAttributes)->stopOnFirstFailure();
if ($validator->fails()) {
$messages = $validator->messages()->getMessages();
json_error(Arr::first($messages)[0]);
}
if ($only === true) {
return Arr::only($input, array_keys($rules));
}
if (count($only)) {
return Arr::only($input, $only);
}
return $input;
}
/**
* ID验证
*
* @param string $idField
* @return int
*/
public function getId(string $idField = 'id'): int
{
$id = request()->input($idField);
$validator = Validator::make(['id' => $id], ['id' => 'required|integer|min:1'], ['id' => '数据传参错误'])->stopOnFirstFailure();
if ($validator->fails()) {
$messages = $validator->messages()->getMessages();
json_error(Arr::first($messages)[0]);
}
return $id;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\InternalService;
use App\Http\Controllers\Controller;
use App\Jobs\ProductOrderCancelJob;
use App\Models\YiiModels\Platform;
use App\Models\YiiModels\ProductOrder;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
/**
* 内部通讯API
*/
class ApiController extends Controller
{
private ?Platform $platform = null;
/**
* 内部服务
*
* @param Request $request
* @return void
*/
public function handle(Request $request)
{
$token = $request->input('token');
log_i($token, 'token', 'internal_services');
if ($token != config('services.internal_service.api_token')) {
json_error('通讯错误');
}
$type = $request->input('type');
if (method_exists($this, $type)) {
log_i(' 开始执行:' . $type, 'token', 'internal_services');
log_i(json_encode(request()->all()), $type, 'internal_services');
$this->{$type}();
} else {
log_i('内部服务数据错误 ' . $type, 'token', 'internal_services');
json_error('内部服务数据错误 ' . $type);
}
}
/**
* 创建订单自动取消任务
*
* @return void
*/
public function createOrderAutoCancel()
{
$product_order_id = request()->input('product_order_id');
$minutes = request()->input('minutes');
ProductOrderCancelJob::dispatch($product_order_id)->delay(now()->addMinutes($minutes));
json_success('自动取消队列启动成功');
}
/**
* 创建退款请求
*
* @return void
* @throws \Throwable
*/
public function createOrderRefundHandle()
{
$product_order_id = request()->input('product_order_id');
$refund_no = request()->input('refund_no');
$productOrder = ProductOrder::where('id', $product_order_id)->first();
if (!$productOrder) {
json_error('订单信息不存在');
}
DB::beginTransaction();
$refundResult = [];
try {
$refundResult = $productOrder->toRefund($productOrder->total_pay_price, $refund_no);
DB::commit();
} catch (Exception $exception) {
DB::rollBack();
log_s('申请错误:' . var_export(request()->all(), true) . $exception->getMessage() . $exception->getTraceAsString(), 'order', 'error');
json_error('申请错误,请重试');
}
response()->json($refundResult)->throwResponse();
}
}

View File

@@ -0,0 +1,151 @@
<?php
namespace App\Http\Controllers\Member;
use App\Models\Cart;
use App\Models\YiiModels\Drug;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
/**
* 购物车
*/
class CartController extends MemberBaseController
{
/**
* 添加购物车
*
* @return void
*/
public function cartAdd()
{
$input = $this->toValidator([
'drug_id' => 'required|integer',
'change_qty' => 'nullable|integer',
'true_qty' => 'nullable|integer',
], [], [
'drug_id' => '商品',
'change_qty' => '数量',
'true_qty' => '数量',
], true);
if (!$input['change_qty'] && !$input['true_qty']) {
json_error('商品数量不正确');
}
/* @var Drug $drug */
$drug = Drug::with([
'drugStoreRelation' => function (Relation $relation) {
$relation->select(['drug_id', 'total_sales_volume', 'status'])
->where('store_id', $this->getStoreId());
},
'drugstoreDrug' => function (Relation $relation) {
$relation->select(['drug_id', 'price', 'stock'])
->where('drugstore_id', $this->getDrugstoreId());
},
])
->select([
'id', 'status',
])
->whereHasIn('drugStoreRelation', function (Builder $builder) {
$builder->where('store_id', $this->getStoreId());
})
->where('id', $input['drug_id'])
->first();
if (!$drug && !$drug->drugStoreRelation && !$drug->drugstoreDrug) {
json_error('商品不存在');
}
if ($drug->status != Drug::STATUS_ON || $drug->drugStoreRelation->status != Drug::STATUS_ON) {
json_error('商品已下架');
}
$cart = $this->user()->carts()->firstOrCreate([
'drug_id' => $input['drug_id'],
'store_id' => $this->getStoreId(),
]);
if ($input['change_qty']) {
if ($input['change_qty'] > $drug->drugstoreDrug->stock) {
json_error("库存不足,最多可加入{$drug->drugstoreDrug->stock}");
}
$cart->increment('qty', $input['change_qty'], ['join_at' => now()]);
if ($cart->qty <= 0) {
$cart->delete();
json_success('购物车商品已删除');
}
} else {
if (($input['true_qty'] - $cart['qty']) > $drug->drugstoreDrug->stock) {
json_error("库存不足,最多可加入{$drug->drugstoreDrug->stock}");
}
$cart->update([
'qty' => $input['true_qty'],
'join_at' => now(),
]);
if ($cart->qty <= 0) {
$cart->delete();
json_success('购物车商品已删除');
}
}
json_success('已加入购物车');
}
/**
* 清空购物车
*
* @return void
*/
public function clearAll()
{
Cart::where('store_id', $this->getStoreId())
->where('user_id', $this->userId())
->delete();
json_success('已清空购物车');
}
/**
* 购物车列表
*
* @return void
*/
public function cartLists()
{
$carts = Cart::with([
'drug' => function (Relation $relation) {
$relation->with([
'drugStoreRelation' => function (Relation $relation) {
$relation->select(['drug_id', 'total_sales_volume', 'status'])
->where('store_id', $this->getStoreId());
},
'drugstoreDrug' => function (Relation $relation) {
$relation->select(['drug_id', 'price', 'stock'])
->where('drugstore_id', $this->getDrugstoreId());
},
])
->select(['id', 'drug_name', 'source', 'type', 'is_otc', 'small_info', 'usage', 'specification', 'image', 'status']);
}
])
->whereHasIn('drug')
->where('store_id', $this->getStoreId())
->where('user_id', $this->userId())
->orderByDesc('join_at')
->get();
$valid = $invalid = [];
/* @var Cart $cart */
foreach ($carts as $cart) {
if (!$cart->drug || !$cart->drug->drugStoreRelation || !$cart->drug->drugstoreDrug) {
$invalid[] = $cart;
continue;
}
if ($cart->drug->status != Drug::STATUS_ON || $cart->drug->drugStoreRelation->status != Drug::STATUS_ON) {
$invalid[] = $cart;
continue;
}
$valid[] = $cart;
}
json_success([
'store_name' => $this->getStore()->name,
'valid' => $valid,
'invalid' => $invalid,
]);
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Http\Controllers\Member;
use App\Models\DrugCategory;
use App\Models\DrugStoreRelation;
use App\Models\YiiModels\Drug;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
/**
* 药品
*/
class DrugController extends MemberBaseController
{
/**
* 药品分类
*
* @return void
*/
public function drugCategory()
{
json_success(DrugCategory::getCategory());
}
/**
* 商品列表(带库存)
*
* @return void
*/
public function drugLists()
{
$category_first = request('category_first');
$category_second = request('category_second');
$keyword = request('keyword');
// $this->toValidator([
// 'category_first' => 'required|integer',
// 'category_second' => 'required|integer',
// ], [], [
// 'category_first' => '一级分类',
// 'category_second' => '二级分类',
// ]);
$drugs = Drug::with([
'drugstoreDrug' => function (Relation $relation) {
$relation->select(['drug_id', 'price', 'stock'])
->where('drugstore_id', $this->getDrugstoreId());
},
])
->select([
'id', 'drug_name', 'source', 'type', 'is_otc', 'category_first', 'category_second',
'usage', 'small_info', 'specification', 'image',
])
->whereHasIn('drugStoreRelation', function (Builder $builder) {
$builder->where('status', DrugStoreRelation::STATUS_ON)
->where('store_id', $this->getStoreId());
})
->when($keyword, function (Builder $builder) use ($keyword) {
$builder->where('drug_name', 'like', "%$keyword%");
})
->where('status', Drug::STATUS_ON)
->where('type_id', [Drug::TYPE_CHINESE_DRUG, Drug::TYPE_PELLET_PATENT])
->where('is_otc', Drug::IS_OTC_FALSE)
->where('category_first', $category_first)
->where('category_second', $category_second)
->get();
json_success($drugs);
}
/**
* 商品详情
*
* @return void
*/
public function drugDetail()
{
$id = $this->getId();
/* @var Drug $drug */
$drug = Drug::with([
'drugStoreRelation' => function (Relation $relation) {
$relation->select(['store_id', 'drug_id', 'total_sales_volume', 'status'])
->with(['store:id,name'])
->where('store_id', $this->getStoreId());
},
'drugstoreDrug' => function (Relation $relation) {
$relation->select(['drugstore_id', 'drug_id', 'price', 'stock'])
->where('drugstore_id', $this->getDrugstoreId());
},
])
->select([
'id', 'drug_name', 'source', 'type', 'is_otc', 'category_first', 'category_second',
'usage', 'specification', 'image', 'small_info', 'info', 'content', 'status',
])
->whereHasIn('drugStoreRelation', function (Builder $builder) {
$builder->where('store_id', $this->getStoreId());
})
->where('id', $id)
->first();
if (!$drug || !$drug->drugStoreRelation || !$drug->drugstoreDrug) {
json_error('商品不存在');
}
if ($drug->status != Drug::STATUS_ON || $drug->drugStoreRelation->status != Drug::STATUS_ON) {
json_error('商品已下架');
}
json_success($drug);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Member;
use App\Models\ExpressFee;
/**
* 物流
*/
class ExpressController extends MemberBaseController
{
public function feeInfo()
{
$expressFeeInfo = ExpressFee::getDefaultExpressFee();
if (!$expressFeeInfo) {
json_error('运费信息错误');
}
json_success($expressFeeInfo);
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Member;
use App\Http\Controllers\Controller;
use App\Models\YiiModels\Drugstore;
use App\Models\YiiModels\Store;
use App\Models\YiiModels\User;
use Illuminate\Support\Facades\Auth;
/**
* 用户端基类
*/
class MemberBaseController extends Controller
{
protected ?Store $store = null;
protected ?Drugstore $drugstore = null;
protected ?int $store_id = 0;
protected ?int $drugstore_id = 0;
/**
* @comment 用户
* @return \App\Models\YiiModels\User|\Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
return Auth::guard(User::GUARD)->user();
}
/**
* @comment 用户id
* @return int
*/
public function userId(): int
{
return $this->user()->id;
}
/**
* 当前仓库ID
*
* @return int
*/
public function getDrugstoreId(): int
{
if (!$this->drugstore_id) {
$this->drugstore_id = $this->getStore()->drugstore_id;
}
return $this->drugstore_id;
}
/**
* 当前仓库
*
* @return Drugstore
*/
public function getDrugstore(): Drugstore
{
$this->drugstore = Drugstore::where('id', $this->getDrugstoreId())->first();
if (!$this->drugstore) {
json_error('药房信息获取失败');
}
return $this->drugstore;
}
/**
* 获取门店ID
*
* @return int
*/
public function getStoreId(): int
{
if (!$this->store_id) {
$this->store_id = (int)request()->input('store_id');
}
return $this->store_id;
}
/**
* 获取门店
*
* @return Store
*/
public function getStore(): Store
{
$this->store = Store::where('id', $this->getStoreId())->first();
if (!$this->store) {
json_error('门店信息获取失败');
}
return $this->store;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Member;
use App\Models\YiiModels\User;
/**
* @deprecated fixme delete 测试接口
*/
class TestController extends MemberBaseController
{
public function testLogin()
{
if (app()->isLocal() && file_exists(base_path('./../dev.ini'))) {
/* @var User $user */
$user = User::where('mobile', request('mobile'))->first();
if ($user) {
json_success('登陆成功', ['token' => $user->token, 'user_id' => $user->id]);
}
}
json_error('错误示例');
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Models\Pay\Bill;
use App\Models\Pay\RefundBill;
use Carbon\Carbon;
use Exception;
use GuzzleHttp\Psr7\Response;
use Illuminate\Support\Facades\DB;
use Yansongda\LaravelPay\Facades\Pay;
class NotifyController
{
/**
* 微信异步通知入口
*
* @param $config_name
* @return \Psr\Http\Message\ResponseInterface|void
* @throws \Throwable
* @throws \Yansongda\Pay\Exception\ContainerException
* @throws \Yansongda\Pay\Exception\InvalidParamsException
*/
public function payWechat($config_name)
{
$result = Pay::wechat()->callback(null, ['_config' => $config_name]);
DB::beginTransaction();
try {
switch ($result['event_type']) {
case 'TRANSACTION.SUCCESS': // 支付成功
$payData = $result['resource']['ciphertext'];
if ($payData['trade_state'] == 'SUCCESS') {
$data = [
'mchid' => $payData['mchid'],
'appid' => $payData['appid'],
'pay_no' => $payData['out_trade_no'],
'pay_service_no' => $payData['transaction_id'],
'pay_amount' => $payData['amount']['total'] / 100,
'pay_at' => Carbon::parseFromLocale($payData['success_time'])->toDateTimeString(),
];
$handleResult = Bill::handleNotify($data, Bill::PAY_WAY_WECHAT);
if ($handleResult === true) {
DB::commit();
return Pay::wechat()->success();
} else {
log_i('支付成功处理失败-' . $handleResult . "\n" . var_export($result, true), 'pay-handle-error', 'wechat');
}
} else {
log_i('支付通知失败' . "\n" . var_export($result, true), 'pay-notify-error', 'wechat');
}
break;
case 'REFUND.SUCCESS': // 退款成功
$payData = $result['resource']['ciphertext'];
if ($payData['refund_status'] == 'SUCCESS') {
$data = [
'refund_no' => $payData['out_refund_no'],
'refund_service_no' => $payData['refund_id'],
'pay_no' => $payData['out_trade_no'],
'pay_service_no' => $payData['transaction_id'],
'refund_amount' => $payData['amount']['refund'] / 100,
'refund_at' => Carbon::parseFromLocale($payData['success_time'])->toDateTimeString(),
];
$handleResult = RefundBill::handleNotify($data);
if ($handleResult === true) {
DB::commit();
return Pay::wechat()->success();
} else {
log_i('退款成功处理失败-' . $handleResult . "\n" . var_export($result, true), 'refund-handle-error', 'wechat');
}
} else {
log_i('退款通知失败' . "\n" . var_export($result, true), 'refund-notify-error', 'wechat');
}
break;
default: // 其他情况
log_i('其他通知失败' . "\n" . var_export($result, true), 'other-notify-error', 'wechat');
break;
}
} catch (Exception $exception) {
log_i('通知处理失败-程序出错' . $exception->getMessage() . "\n" . $exception->getTraceAsString() . "\n" . var_export($result, true), 'all-handle-exception', 'wechat');
}
DB::rollBack();
return new Response(
500,
['Content-Type' => 'application/json'],
json_encode(['code' => 'FAIL', 'message' => '失败']),
);
}
public function messageWechatMini($config_name)
{
$server = app('easywechat.official_account.' . $config_name)->getServer();
$message = $server->getDecryptedMessage();
log_s($message);
return $server->serve();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\Platform;
use App\Http\Controllers\Controller;
use App\Models\YiiModels\Platform;
use Illuminate\Http\Request;
/**
* 平台通讯API
*/
class PlatformApi extends Controller
{
private ?Platform $platform = null;
/**
* 同步
*
* @param Request $request
* @return void
*/
public function handle(Request $request)
{
$platform_token = $request->input('platform_token');
$this->platform = Platform::where('token', $platform_token)->first();
log_i($platform_token, 'token', 'sync');
if (!$this->platform) {
log_i($platform_token . ' 未找到', 'token', 'sync');
json_error('通讯错误');
}
if ($this->platform->status != Platform::STATUS_PASS) {
log_i($platform_token . ' 无权限', 'token', 'sync');
json_error('无权限');
}
$type = $request->input('type');
if (method_exists($this, $type)) {
log_i($platform_token . ' 开始执行:' . $type, 'token', 'sync');
log_i(json_encode(request()->all()), $type, 'sync');
$this->{$type}();
} else {
log_i($platform_token . '同步数据错误 ' . $type, 'token', 'sync');
json_error('同步数据错误 ' . $type);
}
}
private function prescriptionSync()
{
$data = request('data');
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Service;
use App\Http\Controllers\Controller;
use App\Models\YiiModels\ServiceUser;
use Illuminate\Support\Facades\Auth;
/**
* 用户端基类
*/
class ServiceBaseController extends Controller
{
/**
* @comment 用户
* @return \App\Models\YiiModels\ServiceUser|\Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
return Auth::guard(ServiceUser::GUARD)->user();
}
/**
* @comment 用户id
* @return int
*/
public function userId()
{
return $this->user()->id;
}
/**
* @comment 当前仓库id
* @return int
*/
public function currentDrugstoreId()
{
$serviceUser = $this->user();
$serviceUser->loadMissing(['storeDoctorOnline.store']);
return $serviceUser->storeDoctorOnline->store->drugstore_id ?? 0;
}
/**
* @comment 当前门店id
* @return int
*/
public function currentStoreId()
{
$serviceUser = $this->user();
$serviceUser->loadMissing(['storeDoctorOnline']);
return $serviceUser->storeDoctorOnline->store_id ?? 0;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Service;
use App\Models\YiiModels\ServiceUserToken;
use Illuminate\Database\Eloquent\Builder;
/**
* @deprecated fixme delete 测试接口
*/
class TestController extends ServiceBaseController
{
public function testLogin()
{
if (app()->isLocal() && file_exists(base_path('./../dev.ini'))) {
/* @var ServiceUserToken $token */
$token = ServiceUserToken::whereHasIn('serviceUser', fn(Builder $builder) => $builder->where('mobile', request('mobile')))
->where('is_disable', ServiceUserToken::IS_DISABLE_IS_ABLE)
->first();
if ($token) {
json_success('登陆成功', ['token' => $token->token, 'user_id' => $token->su_id]);
}
}
json_error('错误示例');
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers\Tests;
use App\Http\Controllers\Controller;
use App\Models\ExpressNo;
use Illuminate\Support\Facades\Artisan;
class TestController extends Controller
{
public const ALLOW_LISTS = [
'express',
'express_update',
];
/**
* TestController constructor.
*/
public function __construct()
{
if (!app()->isLocal() && in_array(__FUNCTION__, self::ALLOW_LISTS)) {
abort(404);
}
}
public function t($fun = '')
{
if (0 == strlen($fun)) {
$fun = 'a';
}
$result = $this->$fun();
if (!empty($result)) {
return $result;
}
dd('结束运行方法:' . $fun);
}
public function express()
{
$expressNo = ExpressNo::where('express_no', request('no'))->first();
if (!$expressNo) {
dda('单号对应数据库不存在');
}
$response = app('express')->track($expressNo->express_no, $expressNo->express_company_code, ['phone' => $expressNo->mobile]);
$result = json_decode($response, true);
dda($expressNo, $result);
}
public function express_update()
{
$id = request('id');
$parameters = [
'type' => 'update',
];
if ($id) {
$parameters['--id'] = $id;
$expressNo = ExpressNo::where('id', $id)->first();
if (!$expressNo) {
json_error("{$id}数据不存在");
}
}
Artisan::call('express', $parameters);
json_success("{$id}更新完成");
}
// public function a()
// {
// //
// }
public function a()
{
//
}
}

71
app/Http/Kernel.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
namespace App\Http;
use App\Http\Middleware\AuthMemberMiddleware;
use App\Http\Middleware\AuthServiceMiddleware;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'auth.member' => AuthMemberMiddleware::class,
'auth.service' => AuthServiceMiddleware::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Middleware;
use App\Models\YiiModels\User;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthMemberMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
if (!$request->input('store_id')) {
json_error('缺少门店参数');
}
$token = $request->bearerToken();
$user = User::where('token', $token)->first();
if (!$user) {
json_guest();
}
Auth::guard(User::GUARD)->login($user);
return $next($request);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use App\Models\YiiModels\ServiceUser;
use App\Models\YiiModels\ServiceUserToken;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AuthServiceMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next)
{
$token = $request->bearerToken();
$serviceUserToken = ServiceUserToken::with('serviceUser')
->where('is_disable', ServiceUserToken::IS_DISABLE_IS_ABLE)
->where('token', $token)
->first();
if (!$serviceUserToken || !$serviceUserToken->serviceUser) {
json_guest();
}
Auth::guard(ServiceUser::GUARD)->login($serviceUserToken->serviceUser);
return $next($request);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
*
* @return string|null
*/
protected function redirectTo($request)
{
if (!$request->expectsJson()) {
return route('login');
}
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array<int, string>
*/
protected $except = [
];
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array<int, string>
*/
protected $except = [
];
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* @param string|null ...$guards
*
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, ...$guards)
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array<int, string>
*/
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustHosts as Middleware;
class TrustHosts extends Middleware
{
/**
* Get the host patterns that should be trusted.
*
* @return array<int, string|null>
*/
public function hosts()
{
return [
$this->allSubdomainsOfApplicationUrl(),
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array<int, string>|string|null
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
class ValidateSignature extends Middleware
{
/**
* The names of the query string parameters that should be ignored.
*
* @var array<int, string>
*/
protected $except = [
// 'fbclid',
// 'utm_campaign',
// 'utm_content',
// 'utm_medium',
// 'utm_source',
// 'utm_term',
];
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
'wechat',
'notify/*',
'return/*',
'platformApi/*',
'internalServiceApi/*',
];
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Http\Resources;
use App\Models\ProductOrderItem;
use App\Models\YiiModels\Prescription;
use App\Models\YiiModels\ProductOrder;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin ProductOrder
*/
class ProductOrderDetailResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'id' => $this->id,
'store_id' => $this->store_id,
'su_id' => $this->su_id,
'user_id' => $this->user_id,
'up_id' => $this->up_id,
'order_no' => $this->order_no,
'sync_order_no' => $this->sync_order_no,
'p_id' => $this->p_id,
'order_type' => $this->order_type,
'type' => $this->type,
'type_string' => $this->type_string,
'is_pay' => $this->is_pay,
'items_price' => $this->items_price,
'total_pay_price' => $this->total_pay_price,
'pay_time' => $this->pay_time,
'address_id' => $this->address_id,
'address' => $this->address,
'cancel_status' => $this->cancel_status,
'cancel_status_string' => $this->cancel_status_string,
'cancel_time' => $this->cancel_time,
'cancel_remark' => $this->cancel_remark,
'refund_status' => $this->refund_status,
'refund_time' => $this->refund_time,
'received_time' => $this->received_time,
'auto_cancel_time' => $this->auto_cancel_time,
'status' => $this->status,
'pay_method' => $this->pay_method,
'trans_expenses' => $this->trans_expenses,
'free_ship' => $this->free_ship,
'remark' => $this->remark,
'express_name' => $this->express_name,
'express_mobile' => $this->express_mobile,
'express_region' => $this->express_region,
'express_address' => $this->express_address,
'express_no_id' => $this->express_no_id,
'created_at_format' => $this->created_at_format,
'items' => $this->items->map(function (ProductOrderItem $item) {
return [
'id' => $item->id,
'product_order_id' => $item->product_order_id,
'drug_id' => $item->drug_id,
'drug_image' => $item->drug_image,
'number' => $item->number,
'price' => $item->price,
'drug_name' => $item->drug_name,
'small_info' => $item->small_info,
];
}),
'prescription' => $this->getPrescription(),
];
}
private function getPrescription()
{
if (!$this->prescription) {
return [];
}
if (in_array($this->prescription->prescription_type, [Prescription::CHINESE_MEDICINES_DECOCTION_PIECE, Prescription::DISPENSING_GRANULES])) {
return [
'id' => $this->prescription->id,
'dosage_price' => $this->items->sum('price'),
'dosage' => $this->prescription->content['repice'][0]['dosage'],
'prescription_type' => $this->prescription->prescription_type,
'prescription_type_string' => $this->prescription->prescription_type_string,
];
}
if (in_array($this->prescription->prescription_type, (array)Prescription::WESTERN_ZHONG_CHENG_MEDICINE)) {
return [
'id' => $this->prescription->id,
'dosage_price' => $this->items->sum('price'),
'prescription_type' => $this->prescription->prescription_type,
'prescription_type_string' => $this->prescription->prescription_type_string,
];
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Traits;
use App\Models\YiiModels\ProductOrder;
use App\Models\YiiModels\SystemNotice;
use App\Models\YiiModels\FundWater;
trait ProductOrderTrait
{
public function createNotice(ProductOrder $productOrder, $message): void
{
SystemNotice::create([
'store_id' => $productOrder->store_id, // 门店id
'content' => $message, // 通知内容
// 'url' => '', // 链接
// 'url_type' => '', // 链接类型
'base_type' => SystemNotice::BASE_TYPE_ORDER, // 通知类型1订单通知2挂号通知3拒诊通知4处方开具消息5处方审核消息 99系统通知
'scene_type' => SystemNotice::SCENE_TYPE_MEMBER, // 场景类型:1用户端,2服务端(角色)
// 'user_role' => '', // 通知用户类型
'user_id' => $productOrder->user_id, // 用户id
'notice_at' => now(), // 通知时间
]);
}
public function createFundWater(ProductOrder $productOrder): void
{
FundWater::create([
'store_id' => $productOrder->store_id, // 门店id
'order_id' => $productOrder->id, // 订单id
'order_type' => FundWater::BASE_TYPE_ORDER, // 订单类型 1产品订单 2挂号订单
'user_id' => $productOrder->user_id, // 用户id
'type' => FundWater::FUND_WATER_TYPE_ENTER,//入账
'price' => $productOrder->total_pay_price,
'order_no' => $productOrder->order_no,
'refund_no' => 0,
'pay_type' => $productOrder->type, // 支付类型 1微信 2易票联
'created_at' => time(),
'updated_at' => time()
]);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Jobs;
use App\Models\YiiModels\ProductOrder;
use Carbon\Carbon;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
class ProductOrderCancelJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private ?int $productOrderId = null;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($productOrderId)
{
$this->productOrderId = $productOrderId;
}
/**
* Execute the job.
*
* @return bool
* @throws \Throwable
*/
public function handle()
{
$productOrder = ProductOrder::find($this->productOrderId);
if (!$productOrder) {
log_s('订单信息不存在:' . "[productOrderId:$this->productOrderId]", 'job', 'order-cancel');
return true;
}
if ($productOrder->is_pay == ProductOrder::IS_PAY_TRUE) {
log_s('订单已支付:' . "[productOrderId:$this->productOrderId]", 'job', 'order-cancel');
return true;
}
if ($productOrder->status != ProductOrder::STATUS_UNPAID) {
log_s('取消失败,订单状态为' . $productOrder->status_string . "[productOrderId:$this->productOrderId]", 'job', 'order-cancel');
return true;
}
DB::beginTransaction();
try {
$productOrder->status = ProductOrder::STATUS_CANCEL;
$productOrder->cancel_status = ProductOrder::CANCEL_STATUS_TRUE;
$productOrder->cancel_time = time();
$productOrder->cancel_remark = '未支付超时取消';
$productOrder->save();
$productOrder->logs()->create([
'content' => '未支付超时取消订单'
]);
$this->createNotice($productOrder, '您的订单因超时已取消');
// // 同步数据
// $productOrder->refresh();
// $productOrder->load(['items']);
// Artisan::call('data:sync', [
// '--type' => 'product_order_sync',
// '--data' => $productOrder->toArray(),
// ]);
if ($productOrder->is_online == ProductOrder::IS_ONLINE_TRUE && $productOrder->sync_order_no) {
// 同步数据(老)
Artisan::call('data:sync-old', [
'--type' => 'syncOrder',
'--data' => json_encode([
'order_no' => $productOrder->sync_order_no,
'status' => 2, // 1已支付 2已取消 3已退款
'time' => Carbon::createFromTimestamp($productOrder->cancel_time)->toDateTimeString(),
]),
]);
}
DB::commit();
} catch (Exception $exception) {
DB::rollBack();
log_s('超时取消错误:' . "[productOrderId:$this->productOrderId]" . $exception->getMessage() . $exception->getTraceAsString(), 'order', 'error');
}
return true;
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use App\Models\BaseModelTrait;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Str;
/**
* App\Models\BaseAuthModel
*
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\BaseAuthModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\BaseAuthModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\BaseAuthModel query()
* @mixin \Eloquent
*/
class BaseAuthModel extends Authenticatable
{
protected $dateFormat = 'U';
use BaseModelTrait;
use HasFactory;
public $connection = 'mysql'; // 使用数据库链接
protected $guarded = [];
/**
* created_at_format
*
* @comment 创建时间的格式化
* @return string
*/
public function getCreatedAtFormatAttribute(): string
{
return $this[self::CREATED_AT]->toDateTimeString();
}
/**
* updated_at_format
*
* @comment 更新时间的格式化
* @return string
*/
public function getUpdatedAtFormatAttribute(): string
{
return $this[self::UPDATED_AT]->toDateTimeString();
}
/**
* 为数组 / JSON 序列化准备日期。
*/
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
}
}

74
app/Models/BaseModel.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
namespace App\Models;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* App\Models\BaseModel
*
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\BaseModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\BaseModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\BaseModel query()
* @mixin \Eloquent
*/
class BaseModel extends Model
{
use BaseModelTrait;
use HasFactory;
public $connection = 'mysql'; // 使用数据库链接
protected $guarded = [];
public const SELECT_ARR_KEY = '-selectArr';
public const STATUS = [
0 => '无效',
1 => '有效',
];
/**
* status_string
*
* @comment 状态名称
* @return string
*/
public function getStatusStringAttribute(): string
{
return self::STATUS[$this['status']] ?? '';
}
/**
* created_at_format
*
* @comment 创建时间的格式化
* @return string
*/
public function getCreatedAtFormatAttribute(): string
{
return $this[self::CREATED_AT]->toDateTimeString();
}
/**
* updated_at_format
*
* @comment 更新时间的格式化
* @return string
*/
public function getUpdatedAtFormatAttribute(): string
{
return $this[self::UPDATED_AT]->toDateTimeString();
}
/**
* 为数组 / JSON 序列化准备日期。
*/
protected function serializeDate(DateTimeInterface $date): string
{
return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
trait BaseModelTrait
{
/**
* 创建不重复新订单号.
*/
public static function getNewNumber(string $fieldName = 'number'): string
{
$number = now()->format('ymdHis') . rand(10000, 99999);
if (self::findNumber($number, $fieldName)) {
return self::getNewNumber();
}
return $number;
}
/**
* 根据订单号查询订单.
*/
public static function findNumber(int|string $number, string $fieldName = 'number'): ?Model
{
return self::where($fieldName, $number)->first();
}
/**
* 选项数字.
*
* @param bool|string $appendNull 是否添加空选项
* @param string $key 键名
* @param string $value 键值
* @param array $suffixData 后缀选项 ['status', 1, '[无效]', []] [字段名, 等于判断值, 真返回值, 假返回值]
*/
public static function selectArr(bool|string $appendNull = '无', string $key = 'id', string $value = 'name', array $suffixData = []): array
{
$cacheKey = get_called_class() . self::SELECT_ARR_KEY . $key . $value . md5(implode('|', $suffixData));
return Cache::remember($cacheKey, 60, function () use ($appendNull, $key, $value, $suffixData) {
$data = [];
if (false !== $appendNull) {
$data[''] = $appendNull;
}
foreach (self::get() as $model) {
$suffix = '';
if ($suffixData && 4 == count($suffixData)) {
$suffix = $model[$suffixData[0]] == $suffixData[1] ? $suffixData[2] : $suffixData[3];
}
$data[$model[$key]] = $model[$value] . $suffix;
}
return $data;
});
}
}

66
app/Models/Cart.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
namespace App\Models;
use App\Models\YiiModels\Drug;
use App\Models\YiiModels\DrugstoreDrug;
use App\Models\YiiModels\Store;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\Cart
*
* @property int $id
* @property int $user_id 用户ID
* @property int $drug_id 药品ID
* @property int $store_id 门店ID
* @property int $qty 数量
* @property string|null $join_at 加入时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\YiiModels\Drug|null $drug 药品
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \App\Models\YiiModels\Store|null $store 门店
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Cart newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Cart newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Cart query()
* @mixin \Eloquent
*/
class Cart extends BaseModel
{
protected $attributes = [
'qty' => 0,
];
protected static function boot()
{
parent::boot();
parent::creating(function (self $model) {
if (!isset($model->join_at) && !$model->join_at) {
$model->join_at = now();
}
});
}
/**
* @comment 药品
*
* @return BelongsTo
*/
public function drug(): BelongsTo
{
return $this->belongsTo(Drug::class, 'drug_id');
}
/**
* @comment 门店
*
* @return BelongsTo
*/
public function store(): BelongsTo
{
return $this->belongsTo(Store::class, 'store_id');
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Cache;
/**
* App\Models\DrugCategory
*
* @property int $id
* @property int $level 分类等级
* @property int $parent_id 父级id
* @property string $category_name 分类名称
* @property int $sort 排序
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\DrugCategory[] $subs 子分类
* @property-read int|null $subs_count
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\DrugCategory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\DrugCategory newQuery()
* @method static \Illuminate\Database\Query\Builder|\App\Models\DrugCategory onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\DrugCategory query()
* @method static \Illuminate\Database\Query\Builder|\App\Models\DrugCategory withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Models\DrugCategory withoutTrashed()
* @mixin \Eloquent
*/
class DrugCategory extends BaseModel
{
use SoftDeletes;
/**
* 药品分类
*
* @param $cache
* @return \Illuminate\Cache\|mixed|mixed[]
*/
public static function getCategory($cache = true)
{
$cacheKey = __CLASS__ . '_data_cache';
if (Cache::has($cacheKey) && $cache) {
return Cache::get($cacheKey, []);
}
$data = self::with(['subs:id,parent_id,category_name'])
->select(['id', 'parent_id', 'category_name'])
->where('level', 1)
->orderBy('sort')
->get()
->toArray();
if ($data) {
Cache::put($cacheKey, $data, 60);
}
return $data;
}
/**
* @comment 子分类
* @return HasMany
*/
public function subs(): HasMany
{
return $this->hasMany(self::class, 'parent_id', 'id')
->where('level', 2)
->orderBy('sort');
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Models;
use App\Models\YiiModels\Store;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\DrugStoreRelation
*
* @property int $id
* @property int $store_id 门店id
* @property int $drug_id 药品id
* @property string $price 销售价
* @property int $status 状态1下架2上架
* @property int $base_sales_volume 基础销量
* @property int $true_sales_volume 真实销量
* @property int $total_sales_volume 总销量
* @property string|null $buy_price 进货(采购)
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \App\Models\YiiModels\Store|null $store 门店
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\DrugStoreRelation newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\DrugStoreRelation newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\DrugStoreRelation query()
* @mixin \Eloquent
*/
class DrugStoreRelation extends BaseModel
{
// 状态
public const STATUS_OFF = 1;
public const STATUS_ON = 2;
public const STATUS = [
self::STATUS_OFF => '下架',
self::STATUS_ON => '上架',
];
protected static function boot()
{
parent::boot();
parent::creating(function (self $model) {
$model->total_sales_volume = $model->base_sales_volume + $model->true_sales_volume;
});
parent::updating(function (self $model) {
if (
isset($model->total_sales_volume)
&& isset($model->base_sales_volume)
&& isset($model->true_sales_volume)
) {
$model->total_sales_volume = $model->base_sales_volume + $model->true_sales_volume;
}
});
}
/**
* @comment 门店
* @return BelongsTo
*/
public function store(): BelongsTo
{
return $this->belongsTo(Store::class, 'store_id');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\ExpressCompany
*
* @property int $id
* @property string $name 公司名称
* @property string $code 公司编码
* @property string $type 公司类型
* @property int $sort 排序
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressCompany newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressCompany newQuery()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ExpressCompany onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressCompany query()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ExpressCompany withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ExpressCompany withoutTrashed()
* @mixin \Eloquent
*/
class ExpressCompany extends BaseModel
{
use SoftDeletes;
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* App\Models\ExpressDetail
*
* @property int $id
* @property int $express_no_id 快递单号
* @property string|null $status 状态
* @property string|null $detail_at 时间
* @property string|null $detail 详情
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressDetail newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressDetail newQuery()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ExpressDetail onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressDetail query()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ExpressDetail withTrashed()
* @method static \Illuminate\Database\Query\Builder|\App\Models\ExpressDetail withoutTrashed()
* @mixin \Eloquent
*/
class ExpressDetail extends BaseModel
{
use SoftDeletes;
}

41
app/Models/ExpressFee.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
namespace App\Models;
/**
* App\Models\ExpressFee
*
* @property int $id
* @property string $base_express_fee 基础运费
* @property string $free_express_fee 免运费门槛
* @property int $is_default 是否默认
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressFee newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressFee newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressFee query()
* @mixin \Eloquent
*/
class ExpressFee extends BaseModel
{
public const IS_DEFAULT_FALSE = 0;
public const IS_DEFAULT_TRUE = 1;
public const IS_DEFAULT = [
self::IS_DEFAULT_FALSE => '未使用',
self::IS_DEFAULT_TRUE => '使用中',
];
/**
* 获取默认运费模板
* @return static|null
*/
public static function getDefaultExpressFee(): ?self
{
return self::where('is_default', self::IS_DEFAULT_TRUE)
->select(['base_express_fee', 'free_express_fee'])
->first();
}
}

85
app/Models/ExpressNo.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* App\Models\ExpressNo
*
* @property int $id
* @property string|null $express_company_name 公司名称
* @property string $express_company_code 公司编码
* @property string $express_no 快递单号
* @property string|null $mobile 手机号码
* @property int|null $state 物流状态
* @property string|null $sync_at 同步时间
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\ExpressCompany|null $company 快递公司
* @property-read \App\Models\ExpressDetail|null $detail 物流详情[]
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ExpressDetail[] $details 物流详情[]
* @property-read int|null $details_count
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $state_string 物流状态
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressNo newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressNo newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ExpressNo query()
* @mixin \Eloquent
*/
class ExpressNo extends BaseModel
{
const STATE_SIGN = 3;
const STATE = [
1 => '揽收',
0 => '在途',
5 => '派件',
self::STATE_SIGN => '签收',
6 => '退回',
4 => '退签',
7 => '转投',
2 => '疑难',
8 => '清关',
14 => '拒签',
];
/**
* @comment 物流详情[]
* @return HasOne
*/
public function detail(): HasOne
{
return $this->hasOne(ExpressDetail::class, 'express_no_id')->orderByDesc('detail_at');
}
/**
* @comment 物流详情[]
* @return HasMany
*/
public function details(): HasMany
{
return $this->hasMany(ExpressDetail::class, 'express_no_id')->orderByDesc('detail_at');
}
/**
* @comment 快递公司
* @return BelongsTo
*/
public function company(): BelongsTo
{
return $this->belongsTo(ExpressCompany::class, 'express_company_code', 'code');
}
/**
* @comment 物流状态
* @return string
*/
public function getStateStringAttribute(): string
{
return self::STATE[$this->state] ?? '';
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace App\Models\Extend;
/**
* @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withTrashed(bool $withTrashed = true)
* @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyTrashed()
* @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutTrashed()
*/
trait SoftDeletesInt
{
/**
* Indicates if the model is currently force deleting.
*
* @var bool
*/
protected $forceDeleting = false;
/**
* Boot the soft deleting trait for a model.
*
* @return void
*/
public static function bootSoftDeletesInt()
{
static::addGlobalScope(new SoftDeletingScopeInt);
}
/**
* Initialize the soft deleting trait for an instance.
*
* @return void
*/
public function initializeSoftDeletes()
{
// if (! isset($this->casts[$this->getDeletedAtColumn()])) {
// $this->casts[$this->getDeletedAtColumn()] = 'datetime';
// }
}
/**
* Force a hard delete on a soft deleted model.
*
* @return bool|null
*/
public function forceDelete()
{
if ($this->fireModelEvent('forceDeleting') === false) {
return false;
}
$this->forceDeleting = true;
return tap($this->delete(), function ($deleted) {
$this->forceDeleting = false;
if ($deleted) {
$this->fireModelEvent('forceDeleted', false);
}
});
}
/**
* Force a hard delete on a soft deleted model without raising any events.
*
* @return bool|null
*/
public function forceDeleteQuietly()
{
return static::withoutEvents(fn() => $this->forceDelete());
}
/**
* Perform the actual delete query on this model instance.
*
* @return mixed
*/
protected function performDeleteOnModel()
{
if ($this->forceDeleting) {
return tap($this->setKeysForSaveQuery($this->newModelQuery())->forceDelete(), function () {
$this->exists = false;
});
}
return $this->runSoftDelete();
}
/**
* Perform the actual delete query on this model instance.
*
* @return void
*/
protected function runSoftDelete()
{
$query = $this->setKeysForSaveQuery($this->newModelQuery());
$time = $this->freshTimestamp();
// $columns = [$this->getDeletedAtColumn() => $this->fromDateTime($time)];
$columns = [$this->getDeletedAtColumn() => 1];
// $this->{$this->getDeletedAtColumn()} = $time;
$this->{$this->getDeletedAtColumn()} = 1;
if ($this->usesTimestamps() && !is_null($this->getUpdatedAtColumn())) {
$this->{$this->getUpdatedAtColumn()} = $time;
$columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time);
}
$query->update($columns);
$this->syncOriginalAttributes(array_keys($columns));
$this->fireModelEvent('trashed', false);
}
/**
* Restore a soft-deleted model instance.
*
* @return bool
*/
public function restore()
{
// If the restoring event does not return false, we will proceed with this
// restore operation. Otherwise, we bail out so the developer will stop
// the restore totally. We will clear the deleted timestamp and save.
if ($this->fireModelEvent('restoring') === false) {
return false;
}
// $this->{$this->getDeletedAtColumn()} = null;
$this->{$this->getDeletedAtColumn()} = 0;
// Once we have saved the model, we will fire the "restored" event so this
// developer will do anything they need to after a restore operation is
// totally finished. Then we will return the result of the save call.
$this->exists = true;
$result = $this->save();
$this->fireModelEvent('restored', false);
return $result;
}
/**
* Restore a soft-deleted model instance without raising any events.
*
* @return bool
*/
public function restoreQuietly()
{
return static::withoutEvents(fn() => $this->restore());
}
/**
* Determine if the model instance has been soft-deleted.
*
* @return bool
*/
public function trashed()
{
// return ! is_null($this->{$this->getDeletedAtColumn()});
return $this->{$this->getDeletedAtColumn()} == 1;
}
/**
* Register a "softDeleted" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function softDeleted($callback)
{
static::registerModelEvent('trashed', $callback);
}
/**
* Register a "restoring" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function restoring($callback)
{
static::registerModelEvent('restoring', $callback);
}
/**
* Register a "restored" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function restored($callback)
{
static::registerModelEvent('restored', $callback);
}
/**
* Register a "forceDeleting" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function forceDeleting($callback)
{
static::registerModelEvent('forceDeleting', $callback);
}
/**
* Register a "forceDeleted" model event callback with the dispatcher.
*
* @param \Closure|string $callback
* @return void
*/
public static function forceDeleted($callback)
{
static::registerModelEvent('forceDeleted', $callback);
}
/**
* Determine if the model is currently force deleting.
*
* @return bool
*/
public function isForceDeleting()
{
return $this->forceDeleting;
}
/**
* Get the name of the "deleted at" column.
*
* @return string
*/
public function getDeletedAtColumn()
{
return defined(static::class . '::DELETED_AT') ? static::DELETED_AT : 'is_delete';
}
/**
* Get the fully qualified "deleted at" column.
*
* @return string
*/
public function getQualifiedDeletedAtColumn()
{
return $this->qualifyColumn($this->getDeletedAtColumn());
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Models\Extend;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class SoftDeletingScopeInt implements Scope
{
/**
* All of the extensions to be added to the builder.
*
* @var string[]
*/
protected $extensions = ['Restore', 'RestoreOrCreate', 'WithTrashed', 'WithoutTrashed', 'OnlyTrashed'];
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
* @return void
*/
public function apply(Builder $builder, Model $model)
{
$builder->where($model->getQualifiedDeletedAtColumn(), 0);
}
/**
* Extend the query builder with the needed functions.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
public function extend(Builder $builder)
{
foreach ($this->extensions as $extension) {
$this->{"add{$extension}"}($builder);
}
$builder->onDelete(function (Builder $builder) {
$column = $this->getDeletedAtColumn($builder);
return $builder->update([
// $column => $builder->getModel()->freshTimestampString(),
$column => 1,
]);
});
}
/**
* Get the "deleted at" column for the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return string
*/
protected function getDeletedAtColumn(Builder $builder)
{
if (count((array)$builder->getQuery()->joins) > 0) {
return $builder->getModel()->getQualifiedDeletedAtColumn();
}
return $builder->getModel()->getDeletedAtColumn();
}
/**
* Add the restore extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addRestore(Builder $builder)
{
$builder->macro('restore', function (Builder $builder) {
$builder->withTrashed();
// return $builder->update([$builder->getModel()->getDeletedAtColumn() => null]);
return $builder->update([$builder->getModel()->getDeletedAtColumn() => 0]);
});
}
/**
* Add the restore-or-create extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addRestoreOrCreate(Builder $builder)
{
$builder->macro('restoreOrCreate', function (Builder $builder, array $attributes = [], array $values = []) {
$builder->withTrashed();
return tap($builder->firstOrCreate($attributes, $values), function ($instance) {
$instance->restore();
});
});
}
/**
* Add the with-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addWithTrashed(Builder $builder)
{
$builder->macro('withTrashed', function (Builder $builder, $withTrashed = true) {
if (!$withTrashed) {
return $builder->withoutTrashed();
}
return $builder->withoutGlobalScope($this);
});
}
/**
* Add the without-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addWithoutTrashed(Builder $builder)
{
$builder->macro('withoutTrashed', function (Builder $builder) {
$model = $builder->getModel();
// $builder->withoutGlobalScope($this)->whereNull(
// $model->getQualifiedDeletedAtColumn()
// );
$builder->withoutGlobalScope($this)->where(
$model->getQualifiedDeletedAtColumn(), 0
);
return $builder;
});
}
/**
* Add the only-trashed extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @return void
*/
protected function addOnlyTrashed(Builder $builder)
{
$builder->macro('onlyTrashed', function (Builder $builder) {
$model = $builder->getModel();
// $builder->withoutGlobalScope($this)->whereNotNull(
// $model->getQualifiedDeletedAtColumn()
// );
$builder->withoutGlobalScope($this)->where(
$model->getQualifiedDeletedAtColumn(), 1
);
return $builder;
});
}
}

272
app/Models/Pay/Bill.php Normal file
View File

@@ -0,0 +1,272 @@
<?php
namespace App\Models\Pay;
use App\Models\BaseModel;
use App\Models\BaseModelTrait;
use App\Models\YiiModels\User;
use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* App\Models\Pay\Bill
*
* @property int $id
* @property string|null $mchid 商户ID
* @property string|null $appid 应用ID
* @property string|null $billable_type 多态模型名称
* @property int|null $billable_id 多态模型ID
* @property int|null $user_id 用户ID
* @property string|null $openid Openid(微信支付涉及)
* @property string|null $title 订单名称
* @property int|null $pay_way 支付方式
* @property string $pay_amount 支付发起金额
* @property string $pay_no 商户订单号
* @property string|null $pay_service_no 支付商订单号
* @property string|null $pay_at 支付成功时间
* @property int $bill_status 账单状态
* @property int $pay_status 支付状态
* @property string|null $extra 额外数据
* @property string|null $remark 备注
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $billable
* @property-read string $bill_status_string 账单状态名称
* @property-read float $can_refund_amount 可退款的金额
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $pay_status_string 支付状态名称
* @property-read string $pay_way_alias 支付方式别名
* @property-read string $pay_way_string 支付方式名称
* @property-read float $refunded_amount 已退款的金额
* @property-read float $refunding_amount 退款中的金额
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Pay\RefundBill[] $refunds 退款单
* @property-read int|null $refunds_count
* @property-read \App\Models\YiiModels\User|null $user 用户
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Pay\Bill newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Pay\Bill newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Pay\Bill query()
* @mixin \Eloquent
*/
class Bill extends BaseModel
{
use BaseModelTrait;
protected $attributes = [
'bill_status' => self::BILL_STATUS_UNSUCCESSFUL,
'pay_status' => self::PAY_STATUS_UN_PAY,
];
// 支付方式
const PAY_WAY_ALIAS = [
self::PAY_WAY_WECHAT => 'wechat',
self::PAY_WAY_ALIPAY => 'alipay',
self::PAY_WAY_UNIPAY => 'unipay',
];
const PAY_WAY = [
self::PAY_WAY_WECHAT => '微信',
self::PAY_WAY_ALIPAY => '支付宝',
self::PAY_WAY_UNIPAY => '银联',
];
const PAY_WAY_WECHAT = 1;
const PAY_WAY_ALIPAY = 2;
const PAY_WAY_UNIPAY = 3;
// 账单状态
const BILL_STATUS_LABEL = [
self::BILL_STATUS_UNSUCCESSFUL => 'warning',
self::BILL_STATUS_SUCCESSFUL => 'success',
];
const BILL_STATUS = [
self::BILL_STATUS_UNSUCCESSFUL => '未支付成功过',
self::BILL_STATUS_SUCCESSFUL => '支付成功过',
];
const BILL_STATUS_UNSUCCESSFUL = 0;
const BILL_STATUS_SUCCESSFUL = 1;
// 支付状态
const PAY_STATUS_LABEL = [
self::PAY_STATUS_UN_PAY => 'info',
self::PAY_STATUS_SUCCESS => 'success',
self::PAY_STATUS_FAIL => 'primary',
self::PAY_STATUS_CANCEL => 'default',
self::PAY_STATUS_REFUND_PART => 'warning',
self::PAY_STATUS_REFUND_FULL => 'danger',
];
const PAY_STATUS = [
self::PAY_STATUS_UN_PAY => '未支付',
self::PAY_STATUS_SUCCESS => '付款成功',
self::PAY_STATUS_FAIL => '付款失败',
self::PAY_STATUS_CANCEL => '付款取消',
self::PAY_STATUS_REFUND_PART => '部分退款',
self::PAY_STATUS_REFUND_FULL => '全额退款',
];
const PAY_STATUS_UN_PAY = 1;
const PAY_STATUS_SUCCESS = 2;
const PAY_STATUS_FAIL = 4;
const PAY_STATUS_CANCEL = 5;
const PAY_STATUS_REFUND_PART = 7;
const PAY_STATUS_REFUND_FULL = 8;
protected static function boot()
{
parent::boot();
static::creating(function (self $bill) {
if (!$bill->pay_no) {
$bill->pay_no = self::getNewNumber('pay_no');
}
});
}
public function billable(): MorphTo
{
return $this->morphTo();
}
/**
* @comment 用户
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* @comment 退款单
* @return HasMany
*/
public function refunds(): HasMany
{
return $this->hasMany(RefundBill::class, 'bill_id');
}
/**
* pay_way_string
*
* @comment 支付方式名称
* @return string
*/
public function getPayWayStringAttribute(): string
{
return self::PAY_WAY[$this->pay_way] ?? '';
}
/**
* pay_way_alias
*
* @comment 支付方式别名
* @return string
*/
public function getPayWayAliasAttribute(): string
{
return self::PAY_WAY_ALIAS[$this->pay_way] ?? '';
}
/**
* bill_status_string
*
* @comment 账单状态名称
* @return string
*/
public function getBillStatusStringAttribute(): string
{
return self::BILL_STATUS[$this->bill_status] ?? '';
}
/**
* pay_status_string
*
* @comment 支付状态名称
* @return string
*/
public function getPayStatusStringAttribute(): string
{
return self::PAY_STATUS[$this->pay_status] ?? '';
}
/**
* refunded_amount
*
* @comment 已退款的金额
* @return float
*/
public function getRefundedAmountAttribute(): float
{
return $this->refunds->where('refund_status', 2)->sum('refund_amount');
}
/**
* refunding_amount
*
* @comment 退款中的金额
* @return float
*/
public function getRefundingAmountAttribute(): float
{
return $this->refunds->where('refund_status', 1)->sum('refund_amount');
}
/**
* can_refund_amount
*
* @comment 可退款的金额
* @return float
*/
public function getCanRefundAmountAttribute(): float
{
return $this->pay_amount - $this->refunded_amount - $this->refunding_amount;
}
/**
* 支付回调通知处理 - 异步.
*
* @param array $data
* @param int $payWay
* @return bool|string
*/
public static function handleNotify(array $data, int $payWay): bool|string
{
$bill = self::where('pay_no', $data['pay_no'])->where('pay_way', $payWay)->first();
if (!$bill) {
log_i("找不到支付订单信息:{$data['pay_no']}", 'pay-bill-error', $bill->pay_way_alias);
return '找不到支付订单信息';
}
if ($bill->pay_status != self::PAY_STATUS_UN_PAY) {
// [付款失败, 付款取消] 安排退款
if (in_array($bill->pay_status, [self::PAY_STATUS_FAIL, self::PAY_STATUS_CANCEL])) {
// todo
return true;
}
log_i("订单状态非未支付:{$data['pay_no']},订单状态:{$bill->pay_status_string}", 'pay-bill-error', $bill->pay_way_alias);
return '订单状态非未支付';
}
if ($bill->pay_amount != $data['pay_amount']) {
log_i("订单支付金额不一致:{$data['pay_no']},订单金额:{$bill->pay_amount},回调金额:{$data['pay_amount']}", 'pay-bill-error', $bill->pay_way_alias);
return '订单支付金额不一致';
}
$bill->fill([
'mchid' => $data['mchid'],
'appid' => $data['appid'],
'pay_service_no' => $data['pay_service_no'],
'pay_at' => $data['pay_at'],
'bill_status' => self::BILL_STATUS_SUCCESSFUL,
'pay_status' => self::PAY_STATUS_SUCCESS,
]);
$bill->save();
try {
$bill->loadMissing(['billable']);
if ($bill->billable) {
$bill->billable->handlePied($bill);
}
} catch (Exception $exception) {
}
return true;
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace App\Models\Pay;
use App\Models\BaseModel;
use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
/**
* App\Models\Pay\RefundBill
*
* @property int $id
* @property int $bill_id 支付订单号id
* @property string|null $refund_billable_type 多态模型名称
* @property int|null $refund_billable_id 支付订单号id
* @property string $refund_amount 退款发起金额
* @property string $refund_no 退款商户订单号
* @property string|null $refund_service_no 退款支付商订单号
* @property string|null $refund_at 退款到账时间
* @property int $refund_status 退款状态
* @property string|null $remark 备注
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Pay\Bill|null $bill 支付单
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $refund_status_string 退款状态名称
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \Illuminate\Database\Eloquent\Model|\Eloquent $refundBillable
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Pay\RefundBill newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Pay\RefundBill newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Pay\RefundBill query()
* @mixin \Eloquent
*/
class RefundBill extends BaseModel
{
protected $attributes = [
'refund_status' => self::REFUND_STATUS_REFUNDING,
];
// 退款状态
const REFUND_STATUS_LABEL = [
self::REFUND_STATUS_REFUNDING => 'info',
self::REFUND_STATUS_SUCCESS => 'success',
self::REFUND_STATUS_FAIL => 'error',
self::REFUND_STATUS_CANCEL => 'default',
];
const REFUND_STATUS = [
self::REFUND_STATUS_REFUNDING => '退款中',
self::REFUND_STATUS_SUCCESS => '退款成功',
self::REFUND_STATUS_FAIL => '退款失败',
self::REFUND_STATUS_CANCEL => '退款取消',
];
const REFUND_STATUS_REFUNDING = 1;
const REFUND_STATUS_SUCCESS = 2;
const REFUND_STATUS_FAIL = 3;
const REFUND_STATUS_CANCEL = 4;
protected static function boot()
{
parent::boot();
static::creating(function (self $refundBill) {
if (!$refundBill->refund_no) {
$refundBill->refund_no = self::getNewNumber('refund_no');
}
});
}
/**
* @comment 支付单
* @return BelongsTo
*/
public function bill(): BelongsTo
{
return $this->belongsTo(Bill::class, 'bill_id');
}
public function refundBillable(): MorphTo
{
return $this->morphTo();
}
/**
* refund_status_string
*
* @comment 退款状态名称
* @return string
*/
public function getRefundStatusStringAttribute(): string
{
return self::REFUND_STATUS[$this->refund_status] ?? '';
}
/**
* 退款回调通知处理 - 异步.
*
* @param array $data
* @return bool|string
*/
public static function handleNotify(array $data): bool|string
{
/* @var RefundBill $refund */
$refund = self::with('bill')->where('refund_no', $data['refund_no'])->first();
if (!$refund) {
log_i("找不到退款订单信息:退款单 {$data['refund_no']} 支付单 {$data['pay_no']}", 'refund-bill-error', $refund->bill->pay_way_alias);
return '找不到退款订单信息';
}
if ($refund->refund_status != self::REFUND_STATUS_REFUNDING) {
log_i("订单状态非退款中:{$data['refund_no']},订单状态:{$refund->refund_status_string}", 'refund-bill-error', $refund->bill->pay_way_alias);
return '订单状态非退款中';
}
if ($refund->refund_amount != $data['refund_amount']) {
log_i("订单退款金额不一致:{$data['refund_no']},订单金额:{$refund->refund_amount},回调金额:{$data['refund_amount']}", 'refund-bill-error', $refund->bill->pay_way_alias);
return '订单退款金额不一致';
}
$refund->fill([
'refund_service_no' => $data['refund_service_no'],
'refund_at' => $data['refund_at'],
'refund_status' => self::REFUND_STATUS_SUCCESS,
]);
$refund->save();
$refund->bill->load(['refunds' => function (Relation $relation) {
$relation->where('refund_status', self::REFUND_STATUS_SUCCESS);
}]);
$refund->bill->fill([
'pay_status' => $refund->bill->refunds->sum('refund_amount') == $refund->bill->pay_amount
? Bill::PAY_STATUS_REFUND_FULL
: Bill::PAY_STATUS_REFUND_PART
]);
$refund->bill->save();
try {
if ($refund->refund_billable_type && $refund->refund_billable_id) {
$refund->loadMissing(['refundBillable']);
if ($refund->refundBillable) {
$refund->refundBillable->handleRefunded($refund);
}
} else {
$refund->bill->loadMissing(['billable']);
if ($refund->bill->billable) {
$refund->bill->billable->handleRefunded($refund);
}
}
} catch (Exception $exception) {
}
return true;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace App\Models;
use App\Models\YiiModels\Drug;
use App\Models\YiiModels\DrugstoreDrug;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\ProductOrderItem
*
* @property int $id
* @property int $product_order_id 商品订单id
* @property int $drug_id 商品id
* @property string|null $drug_image 图片
* @property int $number 数量
* @property string $price 价格
* @property string $drug_name 商品名称
* @property string|null $small_info 商品简介
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\YiiModels\Drug|null $drug 药品
* @property-read \App\Models\DrugStoreRelation|null $drugStoreRelation 门店售卖药品关系表[]
* @property-read \App\Models\YiiModels\DrugstoreDrug|null $drugstoreDrug 门店药品关系(库存)
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductOrderItem newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductOrderItem newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\ProductOrderItem query()
* @mixin \Eloquent
*/
class ProductOrderItem extends BaseModel
{
/**
* @comment 药品
* @return BelongsTo
*/
public function drug(): BelongsTo
{
return $this->belongsTo(Drug::class, 'drug_id');
}
/**
* @comment 门店售卖药品关系表[]
* @return BelongsTo
*/
public function drugStoreRelation(): BelongsTo
{
return $this->belongsTo(DrugStoreRelation::class, 'drug_id', 'drug_id');
}
/**
* @comment 门店药品关系(库存)
* @return BelongsTo
*/
public function drugstoreDrug(): BelongsTo
{
return $this->belongsTo(DrugstoreDrug::class, 'drug_id', 'drug_id');
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\ADemo
*
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ADemo newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ADemo newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ADemo query()
* @mixin \Eloquent
*/
class ADemo extends YiiBaseModel
{
use SoftDeletesInt;
// public $table = 'a_demo';
// public $timestamps = false;
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\Address
*
* @property int $id
* @property int $user_id
* @property string $name
* @property string $mobile
* @property string $region 地区
* @property string $detail_address 详细地址
* @property int $is_default 是否默认地址0否1是
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_delete
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Address newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Address newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Address query()
* @mixin \Eloquent
*/
class Address extends YiiBaseModel
{
use SoftDeletesInt;
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\ChineseRecipe
*
* @property int $id
* @property string|null $content 药方内容
* @property int $deployment 调配 1煎煮2外配
* @property int $dosage 剂数
* @property int $consumption 每天几次
* @property int|null $usage 用法
* @property int|null $fufa_id 关联使用时间表
* @property int $volume
* @property int|null $is_deepfry 是否浓煎 0否1是
* @property string $cm_id 中药处方的中药表id
* @property string $total_price 价格
* @property string|null $remark 备注
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ChineseRecipe newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ChineseRecipe newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ChineseRecipe query()
* @mixin \Eloquent
*/
class ChineseRecipe extends YiiBaseModel
{
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\DoctorArticle
*
* @property int $id
* @property int $su_id 医生id
* @property string|null $cover 封面
* @property string $title 文章标题
* @property string|null $intro 简介
* @property string|null $content 内容
* @property string|null $video_url 视频链接
* @property int $read_num 阅读量
* @property int $collection
* @property int $is_draft 是否是草稿0否1是
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_delete
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DoctorArticle newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DoctorArticle newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DoctorArticle query()
* @mixin \Eloquent
*/
class DoctorArticle extends YiiBaseModel
{
use SoftDeletesInt;
}

View File

@@ -0,0 +1,165 @@
<?php
namespace App\Models\YiiModels;
use App\Models\DrugStoreBase;
use App\Models\DrugStoreRelation;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* App\Models\YiiModels\Drug
*
* @property int $id
* @property string $drug_name 药名
* @property string $pinyin_simple 拼音首拼
* @property string|null $source 货源(厂家)
* @property string $type 类型 1中药2西药3颗粒配方
* @property int $is_otc 是否处方药
* @property int $category_first 一级分类
* @property int $category_second 二级分类
* @property string|null $small_info 简介
* @property string|null $info 基础信息
* @property string|null $content 详情信息
* @property string|null $usage 用法
* @property string|null $function 功能主治
* @property string $specification 规格
* @property string|null $image 药品图片
* @property int $time_id 使用时间id
* @property int $type_id 使用方法id
* @property int $frequency_id 频率id
* @property int|null $unit_id 单位id
* @property int $number 数量
* @property int $status 状态 1草稿 2下架 3上架
* @property string|null $drug_number 药品编号
* @property string|null $bar_code 条形码
* @property string|null $guozi_no 国字准号
* @property string|null $drug_alias 药品别名
* @property string|null $place 产地
* @property string|null $decotion 煎法
* @property string|null $instruction 说明书图片
* @property \Illuminate\Support\Carbon $updated_at
* @property \Illuminate\Support\Carbon $created_at
* @property-read \App\Models\DrugStoreRelation|null $drugStoreRelation 门店售卖药品关系表[]
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\DrugStoreRelation[] $drugStoreRelations 门店售卖药品关系表[]
* @property-read int|null $drug_store_relations_count
* @property-read \App\Models\YiiModels\DrugstoreDrug|null $drugstoreDrug 门店药品关系(库存)[单关联]
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\YiiModels\DrugstoreDrug[] $drugstoreDrugs 门店药品关系(库存)[多关联]
* @property-read int|null $drugstore_drugs_count
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \App\Models\YiiModels\WestUnit|null $unit 单位
* @property-read \App\Models\YiiModels\DrugUseTime|null $useTime 使用时间
* @property-read \App\Models\YiiModels\DrugUseFrequency|null $userFrequency 频率
* @property-read \App\Models\YiiModels\DrugUseType|null $userType 使用方法
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Drug newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Drug newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Drug query()
* @mixin \Eloquent
*/
class Drug extends YiiBaseModel
{
// 药品类型
public const TYPE_CHINESE_DRUG = 1;
public const TYPE_WEST_DRUG = 2;
public const TYPE_PELLET_DRUG = 3;
public const TYPE_PELLET_PATENT = 4;
public const TYPE = [
self::TYPE_CHINESE_DRUG => '中药',
self::TYPE_WEST_DRUG => '西药',
self::TYPE_PELLET_DRUG => '颗粒配方',
self::TYPE_PELLET_PATENT => '中成药',
];
// 处方类型
public const IS_OTC_FALSE = 0;
public const IS_OTC_TRUE = 1;
public const IS_OTC = [
self::IS_OTC_FALSE => '非处方药',
self::IS_OTC_TRUE => '处方药',
];
// 状态
public const STATUS_DRAFT = 0;
public const STATUS_OFF = 1;
public const STATUS_ON = 2;
public const STATUS = [
self::STATUS_DRAFT => '草稿',
self::STATUS_OFF => '下架',
self::STATUS_ON => '上架',
];
/**
* @comment 门店药品关系(库存)[单关联]
* @return HasOne
*/
public function drugstoreDrug(): HasOne
{
return $this->hasOne(DrugstoreDrug::class, 'drug_id');
}
/**
* @comment 门店药品关系(库存)[多关联]
* @return HasMany
*/
public function drugstoreDrugs(): HasMany
{
return $this->hasMany(DrugstoreDrug::class, 'drug_id');
}
/**
* @comment 门店售卖药品关系表[]
* @return HasOne
*/
public function drugStoreRelation(): HasOne
{
return $this->hasOne(DrugStoreRelation::class, 'drug_id');
}
/**
* @comment 门店售卖药品关系表[]
* @return HasMany
*/
public function drugStoreRelations(): HasMany
{
return $this->hasMany(DrugStoreRelation::class, 'drug_id');
}
/**
* @comment 使用时间
* @return BelongsTo
*/
public function useTime(): BelongsTo
{
return $this->belongsTo(DrugUseTime::class, 'time_id');
}
/**
* @comment 使用方法
* @return BelongsTo
*/
public function userType(): BelongsTo
{
return $this->belongsTo(DrugUseType::class, 'type_id');
}
/**
* @comment 频率
* @return BelongsTo
*/
public function userFrequency(): BelongsTo
{
return $this->belongsTo(DrugUseFrequency::class, 'frequency_id');
}
/**
* @comment 单位
* @return BelongsTo
*/
public function unit(): BelongsTo
{
return $this->belongsTo(WestUnit::class, 'unit_id');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\DrugDosage
*
* @property int $id
* @property int $num
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugDosage newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugDosage newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugDosage query()
* @mixin \Eloquent
*/
class DrugDosage extends YiiBaseModel
{
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\DrugUseFrequency
*
* @property int $id
* @property string $name
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseFrequency newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseFrequency newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseFrequency query()
* @mixin \Eloquent
*/
class DrugUseFrequency extends YiiBaseModel
{
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\DrugUseNum
*
* @property int $id
* @property string $name
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseNum newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseNum newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseNum query()
* @mixin \Eloquent
*/
class DrugUseNum extends YiiBaseModel
{
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\DrugUseTime
*
* @property int $id
* @property string $name
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseTime newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseTime newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseTime query()
* @mixin \Eloquent
*/
class DrugUseTime extends YiiBaseModel
{
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\DrugUseType
*
* @property int $id
* @property string $name
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseType newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseType newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugUseType query()
* @mixin \Eloquent
*/
class DrugUseType extends YiiBaseModel
{
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\Drugstore
*
* @property int $id 主键id
* @property string $name 药房名称
* @property string $position 药房位置
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Drugstore newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Drugstore newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Drugstore query()
* @mixin \Eloquent
*/
class Drugstore extends YiiBaseModel
{
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Models\YiiModels;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\YiiModels\DrugstoreDrug
*
* @property int $id
* @property int $drugstore_id 药房id
* @property int $drug_id 药id
* @property int $type 类型 1中药2西药3颗粒配方
* @property string $price 销售价格
* @property string|null $market_price 市场价
* @property int $stock 库存
* @property int|null $status 状态1下架2上架
* @property int $frozen_number 冻结数量
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read \App\Models\YiiModels\Drug|null $drug 药品
* @property-read \App\Models\YiiModels\Drugstore|null $drugstore 药房
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugstoreDrug newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugstoreDrug newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\DrugstoreDrug query()
* @mixin \Eloquent
*/
class DrugstoreDrug extends YiiBaseModel
{
// 药品类型
public const TYPE_CHINESE_DRUG = Drug::TYPE_CHINESE_DRUG;
public const TYPE_WEST_DRUG = Drug::TYPE_WEST_DRUG;
public const TYPE_PELLET_DRUG = Drug::TYPE_PELLET_DRUG;
public const TYPE_PELLET_PATENT = Drug::TYPE_PELLET_PATENT;
public const TYPE = Drug::TYPE;
/**
* @comment 药品
* @return BelongsTo
*/
public function drug(): BelongsTo
{
return $this->belongsTo(Drug::class, 'drug_id');
}
/**
* @comment 药房
* @return BelongsTo
*/
public function drugstore(): BelongsTo
{
return $this->belongsTo(Drugstore::class, 'drugstore_id');
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\Address
*
* @property int $id
* @property int $store_id
* @property int $user_id
* @property int $order_id
* @property int $order_type
* @property string $type
* @property string $order_no
* @property string $refund_no
* @property number $price
* @property int $pay_type
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Address newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Address newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Address query()
* @mixin \Eloquent
*/
class FundWater extends YiiBaseModel
{
use SoftDeletesInt;
// 订单类型
const BASE_TYPE_ORDER = 1;
const BASE_TYPE_REGISTER = 2;
const BASE_TYPE = [
self::BASE_TYPE_ORDER => '产品订单',
self::BASE_TYPE_REGISTER => '挂号订单',
];
//流水类型
const FUND_WATER_TYPE_ENTER = 'enter';
const FUND_WATER_TYPE_REFUND = 'refund';
const FUND_WATER_TYPE = [
self::FUND_WATER_TYPE_ENTER => '入账',
self::FUND_WATER_TYPE_REFUND => '出账',
];
//支付类型
const PAY_TYPE_WECHAT = 1;
const PAY_TYPE_EPLPAY = 2;
const PAY_TYPE = [
self::PAY_TYPE_WECHAT => '微信',
self::PAY_TYPE_EPLPAY => '易票联',
];
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\GranularRecipe
*
* @property int $id
* @property string|null $content 药方内容
* @property int $deployment 调配 1煎煮2外配
* @property int $dosage 剂数
* @property int $consumption 用量
* @property int|null $usage 用法
* @property int|null $fufa_id 关联使用时间表
* @property int|null $is_deepfry 是否浓煎 0否1是
* @property int|null $volume 容量
* @property string $gm_id 颗粒药处方的颗粒药表id
* @property string $total_price 价格
* @property string|null $remark 备注
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\GranularRecipe newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\GranularRecipe newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\GranularRecipe query()
* @mixin \Eloquent
*/
class GranularRecipe extends YiiBaseModel
{
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\Platform
*
* @property int $id
* @property string $name 平台名称
* @property string $token 平台密钥(后期使用)
* @property string|null $url 通讯地址
* @property string|null $url_new 通讯地址(新服务使用)
* @property string $callbackurl
* @property int $status 1使用 0不使用
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Platform newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Platform newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Platform query()
* @mixin \Eloquent
*/
class Platform extends YiiBaseModel
{
// 状态
public const STATUS_BLOCK = 0;
public const STATUS_PASS = 1;
public const STATUS = [
self::STATUS_BLOCK => '封禁',
self::STATUS_PASS => '正常',
];
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\Prescription
*
* @property int $id
* @property int $store_id 门店id
* @property string $prescription_no 处方编号
* @property int $register_id 挂号订单id
* @property int $su_id su_id
* @property int $user_id 用户id
* @property int $up_id 就诊人id
* @property int $status 状态 0待审核,1已通过,2未通过
* @property int|null $valid_hours
* @property int $is_online 1线上 0线下
* @property int|null $is_dispense
* @property array|null $content 处方快照
* @property int $prescription_type 1中药处方 2西药处方 3颗粒药处方
* @property int $category 类别 1自费2医保
* @property string $doctor_order 医嘱
* @property string|null $clinical_diagnose 临床诊断
* @property string|null $doctor_sign 医生签名
* @property int|null $pharmacist_id
* @property int|null $pharmacist_view_time
* @property int $first_view 初审药师id
* @property string|null $first_sign 初审药师签名
* @property int $first_time 初审时间
* @property int $again_view 复审药师id
* @property string|null $again_sign 复审药师签名
* @property int $again_time 复审时间
* @property int $type 类型 1普通方 2常用方
* @property string $cr_ids 中药药方ids
* @property string $gr_ids 颗粒药药方ids
* @property string $wr_ids 西药药方ids
* @property int $order_type 订单类型1处方2医技
* @property string $total_pay_price
* @property int $is_pay 是否支付0否1是
* @property int $pay_time 支付时间
* @property int $pay_type 支付类型 1微信2支付宝3银行卡4其他
* @property int $cancel_status 取消状态0否1是
* @property int $cancel_time 取消时间
* @property string $cancel_remark 取消备注(未支付超时取消,主动取消,拒绝取消)
* @property int $refund_status 退款状态
* @property int $refund_time
* @property int $auto_expire_time
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_deleted
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $prescription_type_string 处方类型
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Prescription newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Prescription newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Prescription query()
* @mixin \Eloquent
*/
class Prescription extends YiiBaseModel
{
use SoftDeletesInt;
protected $casts = [
'content' => 'array',
];
const DELETED_AT = 'is_deleted';
// 处方类型
const CHINESE_MEDICINES_DECOCTION_PIECE = 1;
const WESTERN_ZHONG_CHENG_MEDICINE = 2;
const DISPENSING_GRANULES = 3;
const PRESCRIPTION_TYPE = [
self::CHINESE_MEDICINES_DECOCTION_PIECE => '中药饮片',
self::WESTERN_ZHONG_CHENG_MEDICINE => '西(中成)药',
self::DISPENSING_GRANULES => '配方颗粒',
];
/**
* prescription_type_string
*
* @comment 处方类型
* @return string
*/
public function getPrescriptionTypeStringAttribute(): string
{
return self::PRESCRIPTION_TYPE[$this->prescription_type] ?? '';
}
}

View File

@@ -0,0 +1,494 @@
<?php
namespace App\Models\YiiModels;
use App\Models\ExpressNo;
use App\Models\Pay\Bill;
use App\Models\Pay\MultiBill;
use App\Models\Pay\RefundBill;
use App\Models\ProductOrderItem;
use Exception;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Yansongda\LaravelPay\Facades\Pay;
/**
* App\Models\YiiModels\ProductOrder
*
* @property int $id
* @property int $store_id 门店id
* @property int|null $drugstore_id 仓库id
* @property int $su_id
* @property int $user_id 用户id
* @property int $up_id 就诊人id
* @property string $order_no 订单号
* @property int|null $is_online 是否互医订单 1
* @property int|null $online_prescription_status 互医处方状态 1已通过 2已作废
* @property string|null $sync_order_no
* @property int $p_id 处方id
* @property int $order_type 订单类型 1处方订单 2商城订单 2商城处方订单
* @property int $type 支付类型 1微信2支付宝3银行卡4其他
* @property int $is_pay 是否支付0否1是
* @property string $total_pay_price 支付总价
* @property string $items_price 商品总价
* @property int $pay_time
* @property int $address_id
* @property string|null $address
* @property int $cancel_status
* @property int $cancel_time
* @property string|null $cancel_remark 取消备注(未支付超时取消,主动取消,拒绝取消)
* @property int $refund_status
* @property int $refund_time
* @property int $received_time
* @property int $auto_cancel_time
* @property int $status 产品订单状态0未支付1待发货2待收货3待评价4已退款5退款中6已收货7确认收货
* @property int $pay_method 支付方式1线上2线下
* @property string $trans_expenses 运费(默认包邮)
* @property int $free_ship 是否包邮
* @property string|null $remark 留言备注
* @property string|null $express_name 收货人
* @property string|null $express_mobile 收货电话
* @property string|null $express_region 收货省市区
* @property string|null $express_address 收货地址
* @property int|null $express_no_id 快递单信息id
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read \App\Models\Pay\Bill|null $billed 已支付的支付单
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Pay\Bill[] $bills 支付单
* @property-read int|null $bills_count
* @property-read \App\Models\ExpressNo|null $expressNo 物流信息
* @property-read string $cancel_status_string 取消状态
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $refund_status_string 退款状态
* @property-read string $status_string 状态名称
* @property-read string $type_string 支付类型
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ProductOrderItem[] $items 订单商品明细
* @property-read int|null $items_count
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\YiiModels\ProductOrderLog[] $logs 订单日志
* @property-read int|null $logs_count
* @property-read \App\Models\YiiModels\Prescription|null $prescription 处方
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\YiiModels\ProductOrderRefund[] $refunds 退款单
* @property-read int|null $refunds_count
* @property-read \App\Models\YiiModels\Store|null $store 门店
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrder newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrder newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrder query()
* @mixin \Eloquent
*/
class ProductOrder extends YiiBaseModel
{
// 订单类型
public const ORDER_TYPE_PRESCRIPTION = 1;
public const ORDER_TYPE_SHOP = 2;
public const ORDER_TYPE_SHOP_PRESCRIPTION = 3;
public const ORDER_TYPE = [
self::ORDER_TYPE_PRESCRIPTION => '处方订单',
self::ORDER_TYPE_SHOP => '商城订单',
self::ORDER_TYPE_SHOP_PRESCRIPTION => '商城处方订单',
];
// 订单类型
public const TYPE_WECHAT = 1;
public const TYPE_ALIPAY = 2;
public const TYPE_BANK = 3;
public const TYPE_OTHER = 4;
public const TYPE = [
self::TYPE_WECHAT => '微信',
self::TYPE_ALIPAY => '支付宝',
self::TYPE_BANK => '银行卡',
self::TYPE_OTHER => '其他',
];
// 是否支付
public const IS_PAY_FALSE = 0;
public const IS_PAY_TRUE = 1;
public const IS_PAY = [
self::IS_PAY_FALSE => '否',
self::IS_PAY_TRUE => '是',
];
// 是否互医订单
public const IS_ONLINE_FALSE = 0;
public const IS_ONLINE_TRUE = 1;
public const IS_ONLINE = [
self::IS_ONLINE_FALSE => '否',
self::IS_ONLINE_TRUE => '是',
];
// 订单状态
const STATUS_UNPAID = 0;
const STATUS_WAIT_SEND = 1;
const STATUS_WAIT_ACCEPT = 2;
const STATUS_WAIT_COMMENT = 3;
const STATUS_REFUND = 4;
const STATUS_REFUNDING = 5;
const STATUS_ACCEPTED = 6;
const STATUS_CONFIRM = 7;
const STATUS_CANCEL = 9;
const STATUS = [
self::STATUS_UNPAID => '未支付',
self::STATUS_WAIT_SEND => '待发货',
self::STATUS_WAIT_ACCEPT => '待收货',
self::STATUS_WAIT_COMMENT => '待评价',
self::STATUS_REFUND => '已退款',
self::STATUS_REFUNDING => '退款中',
self::STATUS_ACCEPTED => '已收货',
self::STATUS_CONFIRM => '确认收货',
self::STATUS_CANCEL => '已取消',
];
// 取消状态
const CANCEL_STATUS_FALSE = 0;
const CANCEL_STATUS_TRUE = 1;
const CANCEL_STATUS = [
self::CANCEL_STATUS_FALSE => '未取消',
self::CANCEL_STATUS_TRUE => '已取消',
];
// 退款状态
const REFUND_STATUS_NORMAL = 0;
const REFUND_STATUS_APPLYING = 1;
const REFUND_STATUS_AGREE = 2;
const REFUND_STATUS_END = 3;
const REFUND_STATUS_PART = 4;
const REFUND_STATUS_CANCEL = 9;
const REFUND_STATUS = [
self::REFUND_STATUS_NORMAL => '正常',
self::REFUND_STATUS_APPLYING => '申请中',
self::REFUND_STATUS_AGREE => '同意申请',
self::REFUND_STATUS_END => '退款完成',
self::REFUND_STATUS_PART => '部分退款完成',
self::REFUND_STATUS_CANCEL => '取消退款申请',
];
// 支付方式
public const PAY_METHOD_ONLINE = 1;
public const PAY_METHOD_OFFLINE = 2;
public const PAY_METHOD = [
self::PAY_METHOD_ONLINE => '线上',
self::PAY_METHOD_OFFLINE => '线下',
];
// 支付方式
public const FREE_SHIP_FALSE = 0;
public const FREE_SHIP_TRUE = 1;
public const FREE_SHIP = [
self::FREE_SHIP_FALSE => '不包邮',
self::FREE_SHIP_TRUE => '包邮',
];
/**
* @comment 门店
* @return BelongsTo
*/
public function store(): BelongsTo
{
return $this->belongsTo(Store::class, 'store_id');
}
/**
* @comment 订单商品明细
* @return HasMany
*/
public function items(): HasMany
{
return $this->hasMany(ProductOrderItem::class, 'product_order_id');
}
/**
* @comment 退款单
* @return HasMany
*/
public function refunds(): HasMany
{
return $this->hasMany(ProductOrderRefund::class, 'order_id');
}
/**
* @comment 订单日志
* @return HasMany
*/
public function logs(): HasMany
{
return $this->hasMany(ProductOrderLog::class, 'p_id');
}
/**
* @comment 支付单
* @return MorphMany
*/
public function bills(): MorphMany
{
return $this->morphMany(Bill::class, 'billable');
}
/**
* @comment 已支付的支付单
* @return MorphOne
*/
public function billed(): MorphOne
{
return $this->morphOne(Bill::class, 'billable')->where('bill_status', Bill::BILL_STATUS_SUCCESSFUL)->latest();
}
/**
* @comment 物流信息
* @return BelongsTo
*/
public function expressNo(): BelongsTo
{
return $this->belongsTo(ExpressNo::class, 'express_no_id');
}
/**
* @comment 处方
* @return BelongsTo
*/
public function prescription(): BelongsTo
{
return $this->belongsTo(Prescription::class, 'p_id');
}
/**
* status_string
*
* @comment 状态名称
* @return string
*/
public function getStatusStringAttribute(): string
{
return self::STATUS[$this['status']] ?? '';
}
/**
* type_string
*
* @comment 支付类型
* @return string
*/
public function getTypeStringAttribute(): string
{
return self::TYPE[$this->type] ?? '';
}
/**
* cancel_status_string
*
* @comment 取消状态
* @return string
*/
public function getCancelStatusStringAttribute(): string
{
return self::CANCEL_STATUS[$this->cancel_status] ?? '';
}
/**
* refund_status_string
*
* @comment 退款状态
* @return string
*/
public function getRefundStatusStringAttribute(): string
{
return self::CANCEL_STATUS[$this->refund_status] ?? '';
}
/**
* 支付成功处理
*
* @param Bill $bill
* @return void
*/
public function handlePied(Bill $bill): void
{
$this->is_pay = self::IS_PAY_TRUE;
$this->pay_time = strtotime($bill->pay_at);
$this->status = self::STATUS_WAIT_SEND;
$this->save();
$this->logs()->create([
'content' => '订单支付成功 - ' . $this->type_string,
]);
$this->createFundWater($this);
$this->createNotice($this, '您的订单已支付成功');
$this->loadMissing(['items.drugStoreRelation' => fn(Relation $relation) => $relation->where('store_id', $this->store_id)]);
foreach ($this->items as $item) {
if ($item->drugStoreRelation) {
$item->drugStoreRelation->true_sales_volume += $item->number;
$item->drugStoreRelation->save();
}
}
// // 同步数据
// $this->refresh();
// $this->load(['items']);
// Artisan::call('data:sync', [
// '--type' => 'product_order_sync',
// '--data' => $this->toArray(),
// ]);
if ($this->is_online == self::IS_ONLINE_TRUE && $this->sync_order_no) {
// 同步数据(老)
Artisan::call('data:sync-old', [
'--type' => 'syncOrder',
'--data' => json_encode([
'order_no' => $this->sync_order_no,
'status' => 1, // 1已支付 2已取消 3已退款
'time' => $bill->pay_at,
]),
]);
}
}
/**
* 退款操作
*
* @throws \Throwable
*/
public function toRefund(int|string|float $refundAmount, $refundNo = null): array
{
if ($this->is_pay == ProductOrder::IS_PAY_FALSE) {
$message = '订单未支付:' . "[productOrderId:$this->id]";
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
if ($this->cancel_status == ProductOrder::CANCEL_STATUS_TRUE) {
$message = '订单已取消:' . "[productOrderId:$this->id]";
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
if ($this->refund_status == ProductOrder::REFUND_STATUS_PART) {
$message = '订单已退款完成:' . "[productOrderId:$this->id]";
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
if (in_array($this->status, [
ProductOrder::STATUS_UNPAID,
ProductOrder::STATUS_REFUND,
ProductOrder::STATUS_CANCEL,
])) {
$message = '退款失败,订单状态为:' . $this->status_string . "[productOrderId:$this->id]";
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
$this->loadMissing('billed');
if (!$this->billed) {
$message = '退款失败,无已支付的支付单:' . "[productOrderId:$this->id]";
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
if ($this->billed->can_refund_amount < $refundAmount) {
$message = '退款失败,申请金额超过可退金额不足:' . "[productOrderId:$this->id]";
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
DB::beginTransaction();
try {
$data = [
'refund_amount' => $refundAmount,
];
if ($refundNo) {
$data['refund_no'] = $refundNo;
}
/* @var RefundBill $refundBill */
$refundBill = $this->billed->refunds()->create($data);
$refundNo = $refundBill->refund_no;
$order = [
'out_trade_no' => $this->billed->pay_no,
'out_refund_no' => $refundBill->refund_no,
'amount' => [
'refund' => $refundBill->refund_amount * 100,
'total' => $this->billed->pay_amount * 100,
'currency' => 'CNY',
],
];
$result = Pay::wechat()->refund($order);
if (isset($result['code'])) {
$message = '退款失败:' . ($result['message'] ?? '');
log_s($message, 'order', 'refund');
return json_result($message, [], 1);
}
$this->logs()->create([
'content' => "开始退款:{$this->billed->pay_way_string}[$refundBill->refund_amount]"
]);
DB::commit();
$message = '退款申请成功:' . var_export($result, true);
log_s($message, 'order', 'refund');
return json_result('退款申请成功', ['refund_no' => $refundNo]);
} catch (Exception $exception) {
DB::rollBack();
$message = '退款错误:' . "[productOrderId:$this->id]" . $exception->getMessage() . $exception->getTraceAsString();
log_s($message, 'order', 'error');
return json_result($message, [], 1);
}
}
/**
* 退款成功处理
*
* @param RefundBill $refundBill
* @return void
*/
public function handleRefunded(RefundBill $refundBill): void
{
$this->status = self::STATUS_REFUND;
$this->refund_status = ProductOrder::REFUND_STATUS_END;
$this->save();
$refund = ProductOrderRefund::where('refund_no', $refundBill->refund_no)->first();
$refund->update([
'is_refund' => ProductOrderRefund::IS_REFUND_TRUE,
'refund_time' => $refundBill->refund_at,
'status' => ProductOrderRefund::STATUS_PASS,
]);
$this->logs()->create([
'content' => '订单退款成功 - ' . $this->type_string,
]);
$this->createNotice($this, '您的订单已退款成功');
$this->loadMissing(['items.drugstoreDrug' => fn(Relation $relation) => $relation->where('drugstore_id', $this->drugstore_id)]);
foreach ($this->items as $item) {
if ($item->drugstoreDrug) {
$item->drugstoreDrug->stock += $item->number;
$item->drugstoreDrug->frozen_number -= $item->number;
$item->drugstoreDrug->save();
}
}
// // 同步数据
// $this->refresh();
// $this->load(['items']);
// Artisan::call('data:sync', [
// '--type' => 'product_order_sync',
// '--data' => $this->toArray(),
// ]);
if ($this->is_online == self::IS_ONLINE_TRUE && $this->sync_order_no) {
// 同步数据(老)
Artisan::call('data:sync-old', [
'--type' => 'syncOrder',
'--data' => json_encode([
'order_no' => $this->sync_order_no,
'status' => 3, // 1已支付 2已取消 3已退款
'time' => $refundBill->refund_at,
]),
]);
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\ProductOrderLog
*
* @property int $id
* @property int $p_id 关联产品订单
* @property string $content
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrderLog newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrderLog newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrderLog query()
* @mixin \Eloquent
*/
class ProductOrderLog extends YiiBaseModel
{
}

View File

@@ -0,0 +1,75 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\ProductOrderRefund
*
* @property int $id
* @property int $user_id 用户id
* @property int $order_id 关联产品订单
* @property string $reason
* @property string|null $remark 备注
* @property string|null $refund_images
* @property int $refund_type 0仅退款 1退货退款
* @property string $refund_no 退款单号
* @property string $refund_price 金额
* @property int $is_refund 0待退款 1已退款
* @property int $refund_time
* @property int $status 0退款申请中 1申请通过 2撤销申请
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_deleted 1撤销申请
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrderRefund newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrderRefund newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ProductOrderRefund query()
* @mixin \Eloquent
*/
class ProductOrderRefund extends YiiBaseModel
{
use SoftDeletesInt;
const DELETED_AT = 'is_deleted';
// 原因类型
const REASON = [
1 => '发错货',
2 => '少发/漏发',
3 => '拍错了',
4 => '商品破损',
5 => '不想要了',
];
// 退款类型
const REFUND_TYPE_REFUND_ONLY = 0;
const REFUND_TYPE_RETURNS_REFUNDS = 1;
const REFUND_TYPE = [
self::REFUND_TYPE_REFUND_ONLY => '仅退款',
self::REFUND_TYPE_RETURNS_REFUNDS => '退货退款',
];
// 退款状态
const IS_REFUND_FALSE = 0;
const IS_REFUND_TRUE = 1;
const IS_REFUND_FAIL = -1;
const IS_REFUND = [
self::IS_REFUND_FALSE => '待退款',
self::IS_REFUND_TRUE => '已退款',
self::IS_REFUND_FAIL => '退款失败',
];
// 状态
const STATUS_APPLY = 0;
const STATUS_PASS = 1;
const STATUS_REVOKE = 2;
const STATUS = [
self::STATUS_APPLY => '退款申请中',
self::STATUS_PASS => '申请通过',
self::STATUS_REVOKE => '撤销申请',
];
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
/**
* App\Models\YiiModels\ServiceUser
*
* @property int $id
* @property string $openid
* @property string $session_key
* @property string $unionid
* @property string $mobile 手机号
* @property string $password 密码
* @property string $nickname 昵称
* @property int $role 角色1医生2药师3导医4客服
* @property int $status 状态0待激活1待审核2已认证3拒绝(需要修改激活资料)
* @property string|null $reason 拒绝原因
* @property int $im_status 客服或者导医的在线状态0下线1上线
* @property int $plate_type 平台:1线上2线下
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_delete
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ServiceUser newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ServiceUser newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ServiceUser query()
* @mixin \Eloquent
*/
class ServiceUser extends YiiBaseAuthModel
{
use SoftDeletesInt;
const GUARD = 'service';
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Models\YiiModels;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\YiiModels\ServiceUserToken
*
* @property int $id
* @property int $su_id 服务端用户id
* @property string $token 用户token
* @property int $is_disable 是否禁用,主动退出后=1
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \App\Models\YiiModels\ServiceUser|null $serviceUser 用户
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ServiceUserToken newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ServiceUserToken newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\ServiceUserToken query()
* @mixin \Eloquent
*/
class ServiceUserToken extends YiiBaseAuthModel
{
public const IS_DISABLE_IS_ABLE = 0;
/**
* @comment 用户
* @return BelongsTo
*/
public function serviceUser(): BelongsTo
{
return $this->belongsTo(ServiceUser::class, 'su_id');
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
/**
* App\Models\YiiModels\Store
*
* @property int $id
* @property int $plate_id 平台id
* @property int $drugstore_id 仓库id
* @property string $name
* @property string $position 位置
* @property string|null $pic 图片
* @property string $contact 联系人
* @property string $mobile 联系电话
* @property string|null $qr_code 门店二维码
* @property int $start_time 开始营业时间
* @property int $end_time 结束营业时间
* @property string|null $z_buy_percent 中药采购比例
* @property string|null $z_sale_percent 中药销售比例
* @property string|null $g_bug_percent 配方颗粒采购比例
* @property string|null $g_sale_percent 配方颗粒销售比例
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_delete 是否删除0否1是
* @property-read \App\Models\YiiModels\Drugstore|null $drugstore 仓库
* @property-read \App\Models\YiiModels\DrugstoreDrug|null $drugstoreDrug 库存()
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Store newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Store newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\Store query()
* @mixin \Eloquent
*/
class Store extends YiiBaseModel
{
use SoftDeletesInt;
/**
* @comment 仓库
* @return BelongsTo
*/
public function drugstore(): BelongsTo
{
return $this->belongsTo(Drugstore::class, 'drugstore_id');
}
/**
* @comment 库存()
*
* @return HasOne
*/
public function drugstoreDrug(): HasOne
{
return $this->hasOne(DrugstoreDrug::class, 'drugstore_id', 'drugstore_id');
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Extend\SoftDeletesInt;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* App\Models\YiiModels\StoreDoctor
*
* @property int $id
* @property int $store_id 门店id
* @property int $su_id 关联服务端用户表su_id
* @property int $is_online 是否在该门店0否1是
* @property int $last_login_time 最近登录时间
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_delete 是否删除0否1是
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @property-read \App\Models\YiiModels\Store|null $store
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\StoreDoctor newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\StoreDoctor newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\StoreDoctor query()
* @mixin \Eloquent
*/
class StoreDoctor extends YiiBaseModel
{
use SoftDeletesInt;
public const IS_ONLINE_OFFLINE = 0;
public const IS_ONLINE_ONLINE = 1;
public const IS_ONLINE = [
self::IS_ONLINE_OFFLINE => '离线',
self::IS_ONLINE_ONLINE => '在线',
];
/**
* @comment
* @return BelongsTo
*/
public function store(): BelongsTo
{
return $this->belongsTo(Store::class, 'store_id');
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\SystemNotice
*
* @property int $id ID
* @property int $store_id 门店id
* @property string $content 通知内容
* @property string|null $url 链接
* @property int|null $url_type 链接类型
* @property int|null $base_type 通知类型1订单通知2挂号通知3拒诊通知4处方开具消息5处方审核消息 99系统通知
* @property int $scene_type 场景类型:1用户端,2服务端(角色)
* @property int|null $user_role 通知用户类型
* @property int|null $user_id 用户id
* @property string|null $notice_at 通知时间
* @property int $read_status 是否读取0否1是
* @property \Illuminate\Support\Carbon $created_at 创建时间
* @property \Illuminate\Support\Carbon $updated_at 更新时间
* @property-read string $base_type_string 通知类型
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $read_status_string 通知类型
* @property-read string $scene_type_string 场景类型
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\SystemNotice newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\SystemNotice newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\SystemNotice query()
* @mixin \Eloquent
*/
class SystemNotice extends YiiBaseModel
{
protected $attributes = [
'read_status' => 0,
'user_role' => 0,
];
// 链接类型
const URL_TYPE = [];
// 通知类型
const BASE_TYPE_ORDER = 1;
const BASE_TYPE_REGISTER = 2;
const BASE_TYPE_REFUSAL = 3;
const BASE_TYPE_PRESCRIPTION_ISSUANCE = 4;
const BASE_TYPE_PRESCRIPTION_AUDIT = 5;
const BASE_TYPE_SYSTEM = 99;
const BASE_TYPE = [
self::BASE_TYPE_ORDER => '订单通知',
self::BASE_TYPE_REGISTER => '挂号通知',
self::BASE_TYPE_REFUSAL => '拒诊通知',
self::BASE_TYPE_PRESCRIPTION_ISSUANCE => '处方开具消息',
self::BASE_TYPE_PRESCRIPTION_AUDIT => '处方审核消息',
self::BASE_TYPE_SYSTEM => '系统通知',
];
// 场景类型
const SCENE_TYPE_MEMBER = 1;
const SCENE_TYPE_SERVICE = 2;
const SCENE_TYPE = [
self::SCENE_TYPE_MEMBER => '用户端',
self::SCENE_TYPE_SERVICE => '服务端(角色)',
];
// 通知用户类型
const USER_ROLE = [];
// 是否读取
const READ_STATUS_FALSE = 0;
const READ_STATUS_TRUE = 1;
const READ_STATUS = [
self::READ_STATUS_FALSE => '未读',
self::READ_STATUS_TRUE => '已读',
];
/**
* base_type_string
*
* @comment 通知类型
* @return string
*/
public function getBaseTypeStringAttribute(): string
{
return self::BASE_TYPE[$this->base_type];
}
/**
* scene_type_string
*
* @comment 场景类型
* @return string
*/
public function getSceneTypeStringAttribute(): string
{
return self::SCENE_TYPE[$this->scene_type];
}
/**
* read_status_string
*
* @comment 通知类型
* @return string
*/
public function getReadStatusStringAttribute(): string
{
return self::READ_STATUS[$this->read_status];
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Models\YiiModels;
use App\Models\Cart;
use App\Models\Extend\SoftDeletesInt;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* App\Models\YiiModels\User
*
* @property int $id
* @property string $openid
* @property string $session_key
* @property string $unionid
* @property string $mobile 手机号
* @property string $password 密码
* @property string $nickname 昵称
* @property int $gender 性别值为1时是男性值为2时是女性值为0时是未知
* @property string $country 国家
* @property string $province 省份
* @property string $city 城市
* @property string $avatarurl 头像
* @property string $token
* @property string $idcard 身份证号
* @property int $sex 性别0默认1男2女
* @property int|null $age 年龄
* @property int $status 身份状态 0待激活待完善1待审核2已认证3已拒绝
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property int $is_delete 是否删除0否
* @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Cart[] $carts 购物车
* @property-read int|null $carts_count
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\User query()
* @mixin \Eloquent
*/
class User extends YiiBaseAuthModel
{
use SoftDeletesInt;
const GUARD = 'member';
/**
* @comment 购物车
* @return HasMany
*/
public function carts(): HasMany
{
return $this->hasMany(Cart::class, 'user_id');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\WestRecipe
*
* @property int $id
* @property string|null $content
* @property int $number 药品数量
* @property string|null $instruction 说明书
* @property int|null $time_id
* @property int $type_id
* @property int $grain_number 每天几粒
* @property int $wu_id 西药单位id
* @property int $available_days 可用天数
* @property int $f_id 频率id
* @property string $total_price 价格
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at 价格
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\WestRecipe newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\WestRecipe newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\WestRecipe query()
* @mixin \Eloquent
*/
class WestRecipe extends YiiBaseModel
{
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Models\YiiModels;
/**
* App\Models\YiiModels\WestUnit
*
* @property int $id
* @property string $name
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\WestUnit newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\WestUnit newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\WestUnit query()
* @mixin \Eloquent
*/
class WestUnit extends YiiBaseModel
{
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Models\YiiModels;
use App\Models\BaseAuthModel;
use Illuminate\Support\Str;
/**
* App\Models\YiiModels\YiiBaseAuthModel
*
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\YiiBaseAuthModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\YiiBaseAuthModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\YiiBaseAuthModel query()
* @mixin \Eloquent
*/
class YiiBaseAuthModel extends BaseAuthModel
{
protected $dateFormat = 'U';
public function getTable(): string
{
return $this->table ?? Str::snake(class_basename($this));
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Models\YiiModels;
use App\Models\BaseModel;
use Illuminate\Support\Str;
/**
* App\Models\YiiModels\YiiBaseModel
*
* @property-read string $created_at_format 创建时间的格式化
* @property-read string $status_string 状态名称
* @property-read string $updated_at_format 更新时间的格式化
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\YiiBaseModel newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\YiiBaseModel newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\YiiModels\YiiBaseModel query()
* @mixin \Eloquent
*/
class YiiBaseModel extends BaseModel
{
protected $dateFormat = 'U';
public function getTable(): string
{
return $this->table ?? Str::snake(class_basename($this));
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Exception;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Providers;
// use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Providers;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event to listener mappings for the application.
*
* @var array<class-string, array<int, class-string>>
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
}
/**
* Determine if events and listeners should be automatically discovered.
*
* @return bool
*/
public function shouldDiscoverEvents()
{
return false;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Providers;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to the "home" route for your application.
*
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, and other route configuration.
*
* @return void
*/
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
// 用户端
Route::middleware('api')
->prefix('member')
->group(base_path('routes/member.php'));
// 服务端
Route::middleware('api')
->prefix('service')
->group(base_path('routes/service.php'));
// Route::middleware('api')
// ->prefix('api')
// ->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
});
}
/**
* Configure the rate limiters for the application.
*
* @return void
*/
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
}

53
artisan Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env php
<?php
define('LARAVEL_START', microtime(true));
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any of our classes manually. It's great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
$app = require_once __DIR__.'/bootstrap/app.php';
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);

55
bootstrap/app.php Normal file
View File

@@ -0,0 +1,55 @@
<?php
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

2
bootstrap/cache/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

195
bootstrap/helpers.php Normal file
View File

@@ -0,0 +1,195 @@
<?php
if (!function_exists('is_wechat')) {
/**
* 判断是否是微信访问.
*
* @return bool
*/
function is_wechat(): bool
{
return str_contains(\Jenssegers\Agent\Facades\Agent::getUserAgent(), 'MicroMessenger');
}
}
if (!function_exists('is_mobile')) {
/**
* 是否手机
*
* @return bool
*/
function is_mobile(): bool
{
$agent = new Jenssegers\Agent\Agent();
return $agent->isMobile();
}
}
if (!function_exists('json_success')) {
/**
* 成功
*
* @param mixed $message
* @param mixed $data
* @return void
*/
function json_success(mixed $message = '', mixed $data = []): void
{
if (is_string($message)) {
$result = [
'msg' => $message,
'data' => $data,
];
} else {
$result = [
'msg' => '',
'data' => $message,
];
}
$result['code'] = 0;
response()->json($result)->throwResponse();
}
}
if (!function_exists('json_page')) {
/**
* 分页
*
* @param $data
* @param mixed $pageData
* @param mixed $message
* @return void
*/
function json_page($data, mixed $pageData = [], mixed $message = ''): void
{
$pageData['total'] = method_exists($data, 'total') ? $data->total() : null;
$pageData['count'] = $data->count();
$pageData['page'] = $data->currentPage();
$pageData['per_page'] = $data->perPage();
$pageData['last'] = method_exists($data, 'lastPage') ? $data->lastPage() : null;
json_success($message, [
'page_info' => $pageData,
'page_data' => $data->toArray()['data'],
]);
}
}
if (!function_exists('json_error')) {
/**
* 失败
*
* @param mixed $message
* @param mixed $data
* @param int $code
* @return void
*/
function json_error(mixed $message = '', mixed $data = [], int $code = 1): void
{
$result = [
'code' => $code,
'msg' => $message,
'data' => $data,
];
response()->json($result)->throwResponse();
}
}
if (!function_exists('json_guest')) {
/**
* 失败 - 未登录
*
* @param mixed $message
* @param int $code
* @return void
*/
function json_guest(mixed $message = '未登录,请先登录', int $code = 99): void
{
json_error($message, [], $code);
}
}
if (!function_exists('json_result')) {
/**
* 结果
*
* @param mixed $message
* @param mixed $data
* @param int $code
* @return array
*/
function json_result(mixed $message = '', mixed $data = [], int $code = 0): array
{
return [
'code' => $code,
'msg' => $message,
'data' => $data,
];
}
}
if (!function_exists('url_to_uri')) {
/**
* 网址去除域名段 获取 url
*
* @param $url
* @return string|null
*/
function url_to_uri($url): ?string
{
if (!$url) {
return null;
}
$arr = parse_url($url);
if (isset($arr['scheme']) && isset($arr['host'])) {
$domains = [config('filesystems.disks.' . config('filesystems.default') . '.domain')];
$domain = $arr['scheme'] . '://' . $arr['host'];
if (in_array($domain, $domains)) {
$url = str_replace($arr['scheme'] . '://' . $arr['host'], '', $url);
$url = preg_replace('/!.*/', '', $url);
}
} elseif (!str_starts_with($url, '/')) {
$url = '/' . $url;
}
return $url;
}
}
if (!function_exists('uri_to_image_url')) {
/**
* 还原图片网址
*
* @param $uri
* @return string|null
*/
function uri_to_image_url($uri): ?string
{
if (!$uri) {
return null;
}
return config('filesystems.disks.' . config('filesystems.default') . '.domain') . url_to_uri($uri);
}
}
if (!function_exists('string_to_array')) {
/**
* 字符串转数组针对非0传值场景
*
* @param mixed $string 原始字符串
* @param bool $sort 是否重新排序
* @param string $separator 切割字符
* @return array
*/
function string_to_array(mixed $string, bool $sort = false, string $separator = ','): array
{
$arr = explode($separator, $string);
foreach ($arr as &$value) {
$value = trim($value);
}
$arr = array_filter($arr);
if ($sort) {
sort($arr);
}
return $arr;
}
}

88
composer.json Normal file
View File

@@ -0,0 +1,88 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The Laravel Framework.",
"keywords": [
"framework",
"laravel"
],
"license": "MIT",
"require": {
"php": "^8.0.2",
"asundust/laravel-helpers": "^9.0",
"dcat/laravel-wherehasin": "^0.8.0",
"guzzlehttp/guzzle": "^7.2",
"jenssegers/agent": "^2.6",
"laravel/framework": "^9.19",
"laravel/sanctum": "^3.0",
"laravel/tinker": "^2.7",
"overtrue/laravel-wechat": "^7.0",
"phpoffice/phpspreadsheet": "^1.28",
"tudouer/express": "^1.0",
"yansongda/laravel-pay": "^3.2"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.12",
"beyondcode/laravel-dump-server": "^1.8",
"beyondcode/laravel-query-detector": "^1.6",
"fakerphp/faker": "^1.9.1",
"kitloong/laravel-migrations-generator": "^6.8",
"laravel-lang/common": "^3.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.0.1",
"mnabialek/laravel-sql-logger": "^2.2",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^6.1",
"phpunit/phpunit": "^9.5.10",
"spatie/laravel-ignition": "^1.0"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
},
"files": [
"bootstrap/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"Illuminate\\Foundation\\ComposerScripts::postUpdate",
"@php artisan ide-helper:generate",
"@php artisan ide-helper:meta"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"easywechat-composer/easywechat-composer": true
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

12631
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

213
config/app.php Normal file
View File

@@ -0,0 +1,213 @@
<?php
use Illuminate\Support\Facades\Facade;
return [
/*
|--------------------------------------------------------------------------
| Application Name
|--------------------------------------------------------------------------
|
| This value is the name of your application. This value is used when the
| framework needs to place the application's name in a notification or
| any other location as required by the application or its packages.
|
*/
'name' => env('APP_NAME', 'Laravel'),
/*
|--------------------------------------------------------------------------
| Application Environment
|--------------------------------------------------------------------------
|
| This value determines the "environment" your application is currently
| running in. This may determine how you prefer to configure various
| services the application utilizes. Set this in your ".env" file.
|
*/
'env' => env('APP_ENV', 'production'),
/*
|--------------------------------------------------------------------------
| Application Debug Mode
|--------------------------------------------------------------------------
|
| When your application is in debug mode, detailed error messages with
| stack traces will be shown on every error that occurs within your
| application. If disabled, a simple generic error page is shown.
|
*/
'debug' => (bool) env('APP_DEBUG', false),
/*
|--------------------------------------------------------------------------
| Application URL
|--------------------------------------------------------------------------
|
| This URL is used by the console to properly generate URLs when using
| the Artisan command line tool. You should set this to the root of
| your application so that it is used when running Artisan tasks.
|
*/
'url' => env('APP_URL', 'http://localhost'),
'asset_url' => env('ASSET_URL'),
'beian' => env('APP_BEIAN', ''),
/*
|--------------------------------------------------------------------------
| Application Timezone
|--------------------------------------------------------------------------
|
| Here you may specify the default timezone for your application, which
| will be used by the PHP date and date-time functions. We have gone
| ahead and set this to a sensible default for you out of the box.
|
*/
'timezone' => 'Asia/Shanghai',
/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/
'locale' => 'zh_CN',
/*
|--------------------------------------------------------------------------
| Application Fallback Locale
|--------------------------------------------------------------------------
|
| The fallback locale determines the locale to use when the current one
| is not available. You may change the value to correspond to any of
| the language folders that are provided through your application.
|
*/
'fallback_locale' => 'en',
/*
|--------------------------------------------------------------------------
| Faker Locale
|--------------------------------------------------------------------------
|
| This locale will be used by the Faker PHP library when generating fake
| data for your database seeds. For example, this will be used to get
| localized telephone numbers, street address information and more.
|
*/
'faker_locale' => 'en_US',
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, 32 character string, otherwise these encrypted strings
| will not be safe. Please do this before deploying an application!
|
*/
'key' => env('APP_KEY'),
'cipher' => 'AES-256-CBC',
/*
|--------------------------------------------------------------------------
| Maintenance Mode Driver
|--------------------------------------------------------------------------
|
| These configuration options determine the driver used to determine and
| manage Laravel's "maintenance mode" status. The "cache" driver will
| allow maintenance mode to be controlled across multiple machines.
|
| Supported drivers: "file", "cache"
|
*/
'maintenance' => [
'driver' => 'file',
// 'store' => 'redis',
],
/*
|--------------------------------------------------------------------------
| Autoloaded Service Providers
|--------------------------------------------------------------------------
|
| The service providers listed here will be automatically loaded on the
| request to your application. Feel free to add your own services to
| this array to grant expanded functionality to your applications.
|
*/
'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
],
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. However, feel free to register as many as you wish as
| the aliases are "lazy" loaded so they don't hinder performance.
|
*/
'aliases' => Facade::defaultAliases()->merge([
// 'ExampleClass' => App\Example\ExampleClass::class,
])->toArray(),
];

129
config/auth.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option controls the default authentication "guard" and password
| reset options for your application. You may change these defaults
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| here which uses session storage and the Eloquent user provider.
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| Supported: "session"
|
*/
'guards' => [
'member' => [
'driver' => 'session',
'provider' => 'member_users',
],
'service' => [
'driver' => 'session',
'provider' => 'service_users',
],
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
|
| All authentication drivers have a user provider. This defines how the
| users are actually retrieved out of your database or other storage
| mechanisms used by this application to persist your user's data.
|
| If you have multiple user tables or models you may configure multiple
| sources which represent each model / table. These sources may then
| be assigned to any extra authentication guards you have defined.
|
| Supported: "database", "eloquent"
|
*/
'providers' => [
'member_users' => [
'driver' => 'eloquent',
'model' => \App\Models\YiiModels\User::class,
],
'service_users' => [
'driver' => 'eloquent',
'model' => \App\Models\YiiModels\ServiceUser::class,
],
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| You may specify multiple password reset configurations if you have more
| than one user table or model in the application and you want to have
| separate password reset settings based on the specific user types.
|
| The expire time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
/*
|--------------------------------------------------------------------------
| Password Confirmation Timeout
|--------------------------------------------------------------------------
|
| Here you may define the amount of seconds before a password confirmation
| times out and the user is prompted to re-enter their password via the
| confirmation screen. By default, the timeout lasts for three hours.
|
*/
'password_timeout' => 10800,
];

66
config/broadcasting.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_DRIVER', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over websockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => 'https' === env('PUSHER_SCHEME', 'https'),
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

106
config/cache.php Normal file
View File

@@ -0,0 +1,106 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache connection that gets used while
| using this caching library. This connection is used when another is
| not explicitly specified when executing a given caching function.
|
*/
'default' => env('CACHE_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Cache Stores
|--------------------------------------------------------------------------
|
| Here you may define all of the cache "stores" for your application as
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file",
| "memcached", "redis", "dynamodb", "octane", "null"
|
*/
'stores' => [
'apc' => [
'driver' => 'apc',
],
'array' => [
'driver' => 'array',
'serialize' => false,
],
'database' => [
'driver' => 'database',
'table' => 'cache',
'connection' => null,
'lock_connection' => null,
],
'file' => [
'driver' => 'file',
'path' => storage_path('framework/cache/data'),
],
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', '127.0.0.1'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],
'dynamodb' => [
'driver' => 'dynamodb',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
'endpoint' => env('DYNAMODB_ENDPOINT'),
],
'octane' => [
'driver' => 'octane',
],
],
/*
|--------------------------------------------------------------------------
| Cache Key Prefix
|--------------------------------------------------------------------------
|
| When utilizing the APC, database, memcached, Redis, or DynamoDB cache
| stores there might be other applications using the same cache. For
| that reason, you may prefix every cache key to avoid collisions.
|
*/
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache_'),
];

37
config/cors.php Normal file
View File

@@ -0,0 +1,37 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => [
'api/*',
'member/*',
'service/*',
'sanctum/csrf-cookie',
],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

145
config/database.php Normal file
View File

@@ -0,0 +1,145 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
'strict' => false,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'),
],
'default' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_DB', '1'),
],
'cache' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', '6379'),
'database' => env('REDIS_CACHE_DB', '1'),
],
],
];

Some files were not shown because too many files have changed in this diff Show More