diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 69296f65..21441f64 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Business; use App\Models\Banking\MarketingAccountBill; use App\Models\Banking\MarketingTaobaoAlipayBill; use App\Models\Banking\MarketingPinduoduoBill; +use App\Models\StoreReceivables\StoreZhongcaiReconciliation; use App\Utilities\SidebarPermissions; use Illuminate\Routing\Controller; @@ -70,11 +71,14 @@ class Pages extends Controller if ($tabs->isEmpty()) { abort_if($tab !== null, 404); + $content = $this->resolveContent($page, null); + return view('business.pages.show', [ 'section' => $sectionConfig, 'page' => $pageConfig, 'tabs' => $tabs, 'activeTab' => null, + 'content' => $content, ]); } @@ -100,14 +104,28 @@ class Pages extends Controller /** * 根据页面和标签加载对应的业务内容数据 */ - protected function resolveContent(string $page, string $tab): ?array + protected function resolveContent(string $page, ?string $tab): ?array { return match ($page) { 'marketing-account-bills' => $this->contentForMarketingAccountBills($tab), + 'zhongcai-reconciliation' => $this->contentForZhongcaiReconciliation(), default => null, }; } + /** + * 种菜应收对账 - 加载对账数据(21字段) + */ + protected function contentForZhongcaiReconciliation(): ?array + { + return [ + 'view' => 'store_receivables.zhongcai_reconciliations.partials.embedded-table', + 'records' => StoreZhongcaiReconciliation::orderBy('bill_date', 'desc') + ->orderBy('id', 'desc') + ->paginate(setting('default.list_limit', 25)), + ]; + } + /** * 营销账户账单 - 加载指定平台的数据 * 淘系支付宝、拼多多使用独立的流水账单表,其他平台使用通用汇总账单表 diff --git a/app/Http/Controllers/StoreReceivables/StoreZhongcaiReconciliations.php b/app/Http/Controllers/StoreReceivables/StoreZhongcaiReconciliations.php new file mode 100644 index 00000000..33d7f875 --- /dev/null +++ b/app/Http/Controllers/StoreReceivables/StoreZhongcaiReconciliations.php @@ -0,0 +1,200 @@ +middleware('permission:read-banking-accounts')->only('index'); + $this->middleware('permission:create-banking-accounts')->only('create', 'store', 'import', 'template'); + $this->middleware('permission:update-banking-accounts')->only('edit', 'update', 'export'); + $this->middleware('permission:delete-banking-accounts')->only('destroy'); + } + + public function index() + { + return view('store_receivables.zhongcai_reconciliations.index', [ + 'records' => StoreZhongcaiReconciliation::orderBy('bill_date', 'desc') + ->orderBy('id', 'desc') + ->paginate(setting('default.list_limit', 25)), + ]); + } + + public function create() + { + return view('store_receivables.zhongcai_reconciliations.form', [ + 'record' => new StoreZhongcaiReconciliation([ + 'company_id' => company_id(), + 'bill_date' => now()->toDateString(), + ]), + ]); + } + + public function template() + { + return response()->streamDownload(function () { + $csv = fopen('php://output', 'w'); + fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fputcsv($csv, self::CSV_HEADERS); + fclose($csv); + }, 'zhongcai_reconciliation_template.csv', [ + 'Content-Type' => 'text/csv; charset=UTF-8', + ]); + } + + public function export(HttpRequest $request) + { + $ids = $request->validate([ + 'ids' => ['nullable', 'array', 'max:5000'], + 'ids.*' => ['integer'], + ])['ids'] ?? []; + + $query = StoreZhongcaiReconciliation::oldest('id'); + if (!empty($ids)) { + $query->whereKey($ids); + } + $records = $query->get(); + + return response()->streamDownload(function () use ($records) { + $csv = fopen('php://output', 'w'); + fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fputcsv($csv, self::CSV_HEADERS); + + foreach ($records as $r) { + fputcsv($csv, [ + $r->bill_date->toDateString(), + $r->planting_order_no, + $r->finance_selling_price, + $r->finance_freight, + $r->finance_commission, + $r->jingxi_price_diff, + $r->deduction, + $r->finance_empty_package_fee, + $r->finance_payment_amount, + $r->correct_store, + $r->person_in_charge, + $r->creator, + $r->profit_center, + $r->responsible_platform, + $r->external_id, + $r->store, + $r->finance_notes, + $r->alipay_order, + $r->bill_item, + $r->income_amount, + $r->difference_data, + ]); + } + fclose($csv); + }, 'zhongcai_reconciliation_export.csv', [ + 'Content-Type' => 'text/csv; charset=UTF-8', + ]); + } + + public function import(HttpRequest $request): RedirectResponse + { + $validated = $request->validate([ + 'import' => ['required', 'file', 'mimes:xlsx,xls,csv', 'max:5120'], + ], [ + 'import.required' => trans('store_zhongcai_reconciliations.import_required'), + 'import.mimes' => trans('store_zhongcai_reconciliations.import_file_type'), + 'import.max' => trans('store_zhongcai_reconciliations.import_file_size'), + ]); + + $handle = fopen($validated['import']->getRealPath(), 'r'); + $bom = fread($handle, 3); + if ($bom !== chr(0xEF) . chr(0xBB) . chr(0xBF)) { + rewind($handle); + } + fgetcsv($handle); // skip header + + $count = 0; + $companyId = company_id(); + $userId = user()->id; + + while (($row = fgetcsv($handle)) !== false) { + if (empty($row[0]) && empty($row[1])) { + continue; + } + + StoreZhongcaiReconciliation::create([ + 'company_id' => $companyId, + 'bill_date' => $row[0] ?? now()->toDateString(), + 'planting_order_no' => $row[1] ?? '', + 'finance_selling_price' => (float) ($row[2] ?? 0), + 'finance_freight' => (float) ($row[3] ?? 0), + 'finance_commission' => (float) ($row[4] ?? 0), + 'jingxi_price_diff' => (float) ($row[5] ?? 0), + 'deduction' => (float) ($row[6] ?? 0), + 'finance_empty_package_fee' => (float) ($row[7] ?? 0), + 'finance_payment_amount' => (float) ($row[8] ?? 0), + 'correct_store' => $row[9] ?? null, + 'person_in_charge' => $row[10] ?? null, + 'creator' => $row[11] ?? null, + 'profit_center' => $row[12] ?? null, + 'responsible_platform' => $row[13] ?? null, + 'external_id' => $row[14] ?? null, + 'store' => $row[15] ?? null, + 'finance_notes' => $row[16] ?? null, + 'alipay_order' => $row[17] ?? null, + 'bill_item' => $row[18] ?? null, + 'income_amount' => (float) ($row[19] ?? 0), + 'difference_data' => $row[20] ?? null, + 'created_from' => 'core::ui', + 'created_by' => $userId, + ]); + $count++; + } + fclose($handle); + + flash(trans('store_zhongcai_reconciliations.import_success', ['count' => $count]))->success(); + return redirect()->route('zhongcai-reconciliations.index'); + } + + public function store(Request $request): RedirectResponse + { + StoreZhongcaiReconciliation::create(array_merge($request->validated(), [ + 'company_id' => company_id(), + 'created_from' => 'core::ui', + 'created_by' => user()->id, + ])); + + flash(trans('messages.success.created', ['type' => trans('store_zhongcai_reconciliations.title')]))->success(); + return redirect()->route('zhongcai-reconciliations.index'); + } + + public function edit(StoreZhongcaiReconciliation $zhongcaiReconciliation) + { + return view('store_receivables.zhongcai_reconciliations.form', [ + 'record' => $zhongcaiReconciliation, + ]); + } + + public function update(Request $request, StoreZhongcaiReconciliation $zhongcaiReconciliation): RedirectResponse + { + $zhongcaiReconciliation->update($request->validated()); + flash(trans('messages.success.updated', ['type' => trans('store_zhongcai_reconciliations.title')]))->success(); + return redirect()->route('zhongcai-reconciliations.index'); + } + + public function destroy(StoreZhongcaiReconciliation $zhongcaiReconciliation): RedirectResponse + { + $zhongcaiReconciliation->delete(); + flash(trans('messages.success.deleted', ['type' => trans('store_zhongcai_reconciliations.title')]))->success(); + return redirect()->route('zhongcai-reconciliations.index'); + } +} diff --git a/app/Http/Requests/StoreReceivables/StoreZhongcaiReconciliation.php b/app/Http/Requests/StoreReceivables/StoreZhongcaiReconciliation.php new file mode 100644 index 00000000..be2c2cf6 --- /dev/null +++ b/app/Http/Requests/StoreReceivables/StoreZhongcaiReconciliation.php @@ -0,0 +1,40 @@ + ['required', 'date'], + 'planting_order_no' => ['required', 'string', 'max:100'], + 'finance_selling_price' => ['nullable', 'numeric'], + 'finance_freight' => ['nullable', 'numeric'], + 'finance_commission' => ['nullable', 'numeric'], + 'jingxi_price_diff' => ['nullable', 'numeric'], + 'deduction' => ['nullable', 'numeric'], + 'finance_empty_package_fee' => ['nullable', 'numeric'], + 'finance_payment_amount' => ['nullable', 'numeric'], + 'correct_store' => ['nullable', 'string', 'max:191'], + 'person_in_charge' => ['nullable', 'string', 'max:100'], + 'creator' => ['nullable', 'string', 'max:100'], + 'profit_center' => ['nullable', 'string', 'max:100'], + 'responsible_platform' => ['nullable', 'string', 'max:100'], + 'external_id' => ['nullable', 'string', 'max:100'], + 'store' => ['nullable', 'string', 'max:191'], + 'finance_notes' => ['nullable', 'string', 'max:1000'], + 'alipay_order' => ['nullable', 'string', 'max:100'], + 'bill_item' => ['nullable', 'string', 'max:100'], + 'income_amount' => ['nullable', 'numeric'], + 'difference_data' => ['nullable', 'string', 'max:1000'], + ]; + } +} diff --git a/app/Models/StoreReceivables/StoreZhongcaiReconciliation.php b/app/Models/StoreReceivables/StoreZhongcaiReconciliation.php new file mode 100644 index 00000000..b5c4a8f7 --- /dev/null +++ b/app/Models/StoreReceivables/StoreZhongcaiReconciliation.php @@ -0,0 +1,50 @@ + 'date', + 'finance_selling_price' => 'double', + 'finance_freight' => 'double', + 'finance_commission' => 'double', + 'jingxi_price_diff' => 'double', + 'deduction' => 'double', + 'finance_empty_package_fee' => 'double', + 'finance_payment_amount' => 'double', + 'income_amount' => 'double', + 'deleted_at' => 'datetime', + ]; +} diff --git a/database/migrations/2026_07_21_120000_create_store_zhongcai_reconciliations_table.php b/database/migrations/2026_07_21_120000_create_store_zhongcai_reconciliations_table.php new file mode 100644 index 00000000..10da5e9b --- /dev/null +++ b/database/migrations/2026_07_21_120000_create_store_zhongcai_reconciliations_table.php @@ -0,0 +1,50 @@ +increments('id'); + $table->unsignedInteger('company_id'); + $table->date('bill_date')->comment('日期'); + $table->string('planting_order_no', 100)->comment('种菜订单编号'); + $table->decimal('finance_selling_price', 15, 2)->default(0)->comment('财务售价'); + $table->decimal('finance_freight', 15, 2)->default(0)->comment('财务运费'); + $table->decimal('finance_commission', 15, 2)->default(0)->comment('财务佣金'); + $table->decimal('jingxi_price_diff', 15, 2)->default(0)->comment('京喜差价'); + $table->decimal('deduction', 15, 2)->default(0)->comment('扣款'); + $table->decimal('finance_empty_package_fee', 15, 2)->default(0)->comment('财务空包费'); + $table->decimal('finance_payment_amount', 15, 2)->default(0)->comment('财务打款金额'); + $table->string('correct_store', 191)->nullable()->comment('正确店铺'); + $table->string('person_in_charge', 100)->nullable()->comment('负责人'); + $table->string('creator', 100)->nullable()->comment('创建人'); + $table->string('profit_center', 100)->nullable()->comment('所属利润中心'); + $table->string('responsible_platform', 100)->nullable()->comment('负责平台'); + $table->string('external_id', 100)->nullable()->comment('ID'); + $table->string('store', 191)->nullable()->comment('店铺'); + $table->text('finance_notes')->nullable()->comment('财务备注'); + $table->string('alipay_order', 100)->nullable()->comment('支付宝订单'); + $table->string('bill_item', 100)->nullable()->comment('账单项目'); + $table->decimal('income_amount', 15, 2)->default(0)->comment('收入金额'); + $table->text('difference_data')->nullable()->comment('差异数据'); + $table->string('created_from', 50)->nullable(); + $table->unsignedInteger('created_by')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['company_id', 'bill_date']); + $table->index('planting_order_no'); + $table->index('store'); + }); + } + + public function down(): void + { + Schema::dropIfExists('store_zhongcai_reconciliations'); + } +}; diff --git a/resources/lang/zh-CN/store_zhongcai_reconciliations.php b/resources/lang/zh-CN/store_zhongcai_reconciliations.php new file mode 100644 index 00000000..2d5843f8 --- /dev/null +++ b/resources/lang/zh-CN/store_zhongcai_reconciliations.php @@ -0,0 +1,51 @@ + '种菜应收对账', + + // 表格列标题(对应Excel表1,21个字段) + 'bill_date' => '日期', + 'planting_order_no' => '种菜订单编号', + 'finance_selling_price' => '财务售价', + 'finance_freight' => '财务运费', + 'finance_commission' => '财务佣金', + 'jingxi_price_diff' => '京喜差价', + 'deduction' => '扣款', + 'finance_empty_package_fee' => '财务空包费', + 'finance_payment_amount' => '财务打款金额', + 'correct_store' => '正确店铺', + 'person_in_charge' => '负责人', + 'creator' => '创建人', + 'profit_center' => '所属利润中心', + 'responsible_platform' => '负责平台', + 'external_id' => 'ID', + 'store' => '店铺', + 'finance_notes' => '财务备注', + 'alipay_order' => '支付宝订单', + 'bill_item' => '账单项目', + 'income_amount' => '收入金额', + 'difference_data' => '差异数据', + + // 操作 + 'add_record' => '新增对账记录', + 'edit_record' => '编辑对账记录', + 'bulk_upload' => '批量导入', + 'bulk_download' => '批量导出', + 'download_template' => '下载导入模板', + 'choose_file' => '选择文件', + 'import_success' => '成功导入 :count 条对账记录', + 'import_required' => '请选择要导入的文件', + 'import_file_type' => '仅支持 .xlsx、.xls、.csv 文件', + 'import_file_size' => '文件大小不能超过 5MB', + 'select_all' => '全选', + 'select_record' => '选择记录', + 'no_records' => '暂无对账数据', + + // 表单分组 + 'section_basic' => '基本信息', + 'section_finance' => '财务数据(元)', + 'section_store' => '店铺与人员', + 'section_payment' => '收款与差异', + +]; diff --git a/resources/views/store_receivables/zhongcai_reconciliations/form.blade.php b/resources/views/store_receivables/zhongcai_reconciliations/form.blade.php new file mode 100644 index 00000000..95ff93db --- /dev/null +++ b/resources/views/store_receivables/zhongcai_reconciliations/form.blade.php @@ -0,0 +1,157 @@ +@php $isEdit = $record->exists; @endphp + + + + {{ $isEdit ? trans('store_zhongcai_reconciliations.edit_record') : trans('store_zhongcai_reconciliations.add_record') }} + - {{ trans('store_zhongcai_reconciliations.title') }} + + + + +
+ @csrf + @if ($isEdit) @method('PATCH') @endif + + {{-- 基本信息 --}} +
+

{{ trans('store_zhongcai_reconciliations.section_basic') }}

+
+
+ + +
+
+ + +
+
+
+ + {{-- 财务数据 --}} +
+

{{ trans('store_zhongcai_reconciliations.section_finance') }}

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {{-- 店铺与人员 --}} +
+

{{ trans('store_zhongcai_reconciliations.section_store') }}

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {{-- 收款与差异 --}} +
+

{{ trans('store_zhongcai_reconciliations.section_payment') }}

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ {{ trans('general.cancel') }} + +
+
+
+
+
diff --git a/resources/views/store_receivables/zhongcai_reconciliations/index.blade.php b/resources/views/store_receivables/zhongcai_reconciliations/index.blade.php new file mode 100644 index 00000000..2c18b55b --- /dev/null +++ b/resources/views/store_receivables/zhongcai_reconciliations/index.blade.php @@ -0,0 +1,8 @@ + + + {{ trans('store_zhongcai_reconciliations.title') }} + + + @include('store_receivables.zhongcai_reconciliations.partials.embedded-table') + + diff --git a/resources/views/store_receivables/zhongcai_reconciliations/partials/embedded-table.blade.php b/resources/views/store_receivables/zhongcai_reconciliations/partials/embedded-table.blade.php new file mode 100644 index 00000000..a4472292 --- /dev/null +++ b/resources/views/store_receivables/zhongcai_reconciliations/partials/embedded-table.blade.php @@ -0,0 +1,162 @@ + + + +@php $pageIds = $records->pluck('id')->map(fn($id) => (string) $id)->values(); @endphp + +
+
+ +
+ @can('create-banking-accounts') +
+ + + {{ trans('store_zhongcai_reconciliations.add_record') }} + +
+ @csrf +
+ + +
+ +
+ + + {{ trans('store_zhongcai_reconciliations.download_template') }} + +
+ @endcan +
+ @can('update-banking-accounts') + + @endcan +
+
+ + @if ($errors->has('import')) +
+ @foreach ($errors->get('import') as $message) +

{{ $message }}

+ @endforeach +
+ @endif + + {{-- 21列数据表格 --}} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @if ($records->isEmpty()) + + + + @else + @foreach ($records as $r) + + + + + + + + + + + + + + + + + + + + + + + + + + @endforeach + @endif + +
+ isEmpty()) disabled @endif + x-on:change="selected = $event.target.checked ? @js($pageIds) : []" + x-bind:checked="selected.length === {{ $pageIds->count() }} && selected.length > 0" + class="h-4 w-4 rounded border-gray-300 text-green" /> + {{ trans('store_zhongcai_reconciliations.bill_date') }}{{ trans('store_zhongcai_reconciliations.planting_order_no') }}{{ trans('store_zhongcai_reconciliations.finance_selling_price') }}{{ trans('store_zhongcai_reconciliations.finance_freight') }}{{ trans('store_zhongcai_reconciliations.finance_commission') }}{{ trans('store_zhongcai_reconciliations.jingxi_price_diff') }}{{ trans('store_zhongcai_reconciliations.deduction') }}{{ trans('store_zhongcai_reconciliations.finance_empty_package_fee') }}{{ trans('store_zhongcai_reconciliations.finance_payment_amount') }}{{ trans('store_zhongcai_reconciliations.correct_store') }}{{ trans('store_zhongcai_reconciliations.person_in_charge') }}{{ trans('store_zhongcai_reconciliations.creator') }}{{ trans('store_zhongcai_reconciliations.profit_center') }}{{ trans('store_zhongcai_reconciliations.responsible_platform') }}{{ trans('store_zhongcai_reconciliations.external_id') }}{{ trans('store_zhongcai_reconciliations.store') }}{{ trans('store_zhongcai_reconciliations.finance_notes') }}{{ trans('store_zhongcai_reconciliations.alipay_order') }}{{ trans('store_zhongcai_reconciliations.bill_item') }}{{ trans('store_zhongcai_reconciliations.income_amount') }}{{ trans('store_zhongcai_reconciliations.difference_data') }}{{ trans('general.actions') }}
+ {{ trans('store_zhongcai_reconciliations.no_records') }} +
+ + {{ $r->bill_date->format('Y-m-d') }}{{ $r->planting_order_no }}{{ number_format($r->finance_selling_price, 2) }}{{ number_format($r->finance_freight, 2) }}{{ number_format($r->finance_commission, 2) }}{{ number_format($r->jingxi_price_diff, 2) }}{{ number_format($r->deduction, 2) }}{{ number_format($r->finance_empty_package_fee, 2) }}{{ number_format($r->finance_payment_amount, 2) }}{{ $r->correct_store }}{{ $r->person_in_charge }}{{ $r->creator }}{{ $r->profit_center }}{{ $r->responsible_platform }}{{ $r->external_id }}{{ $r->store }}{{ \Illuminate\Support\Str::limit($r->finance_notes, 15) }}{{ $r->alipay_order }}{{ $r->bill_item }}{{ number_format($r->income_amount, 2) }}{{ \Illuminate\Support\Str::limit($r->difference_data, 15) }} + @can('update-banking-accounts') + {{ trans('general.edit') }} + @endcan + @can('delete-banking-accounts') +
+ @csrf @method('DELETE') + +
+ @endcan +
+
+ + @unless ($records->isEmpty()) +
{{ $records->links() }}
+ @endunless +
diff --git a/routes/admin.php b/routes/admin.php index ada7aafc..9b1c2cf3 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -234,6 +234,18 @@ Route::group(['prefix' => 'banking'], function () { Route::resource('reconciliations', 'Banking\Reconciliations', ['middleware' => ['date.format', 'money', 'dropzone']]); }); +Route::group(['as' => 'zhongcai-reconciliations.', 'prefix' => 'zhongcai-reconciliations'], function () { + Route::get('template', 'StoreReceivables\StoreZhongcaiReconciliations@template')->name('template'); + Route::get('export', 'StoreReceivables\StoreZhongcaiReconciliations@export')->name('export'); + Route::post('import', 'StoreReceivables\StoreZhongcaiReconciliations@import')->name('import'); + Route::get('create', 'StoreReceivables\StoreZhongcaiReconciliations@create')->name('create'); + Route::post('/', 'StoreReceivables\StoreZhongcaiReconciliations@store')->name('store'); + Route::get('{zhongcaiReconciliation}/edit', 'StoreReceivables\StoreZhongcaiReconciliations@edit')->name('edit'); + Route::patch('{zhongcaiReconciliation}', 'StoreReceivables\StoreZhongcaiReconciliations@update')->name('update'); + Route::delete('{zhongcaiReconciliation}', 'StoreReceivables\StoreZhongcaiReconciliations@destroy')->name('destroy'); + Route::get('/', 'StoreReceivables\StoreZhongcaiReconciliations@index')->name('index'); +}); + Route::group(['prefix' => 'settings'], function () { Route::get('categories/{category}/enable', 'Settings\Categories@enable')->name('categories.enable'); Route::get('categories/{category}/disable', 'Settings\Categories@disable')->name('categories.disable');