feat: 店铺应收账款 - 种菜应收对账页面(21字段)

- 新增种菜应收对账完整CRUD:21字段对账数据表
- 字段:日期、种菜订单编号、财务售价/运费/佣金/空包费/打款金额、
  京喜差价、扣款、正确店铺、负责人、创建人、所属利润中心、
  负责平台、ID、店铺、财务备注、支付宝订单、账单项目、
  收入金额、差异数据
- 表单分4组:基本信息、财务数据、店铺与人员、收款与差异
- 修改Business\Pages支持无tabs页面加载内容
- 路由:zhongcai-reconciliations 路由组
This commit is contained in:
15061959991 2026-07-21 19:20:55 +08:00
parent 752f26bf30
commit 308e7eb479
10 changed files with 749 additions and 1 deletions

View File

@ -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)),
];
}
/**
* 营销账户账单 - 加载指定平台的数据
* 淘系支付宝、拼多多使用独立的流水账单表,其他平台使用通用汇总账单表

View File

@ -0,0 +1,200 @@
<?php
namespace App\Http\Controllers\StoreReceivables;
use App\Abstracts\Http\Controller;
use App\Http\Requests\StoreReceivables\StoreZhongcaiReconciliation as Request;
use App\Models\StoreReceivables\StoreZhongcaiReconciliation;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request as HttpRequest;
class StoreZhongcaiReconciliations extends Controller
{
private const CSV_HEADERS = [
'日期', '种菜订单编号', '财务售价', '财务运费', '财务佣金', '京喜差价',
'扣款', '财务空包费', '财务打款金额', '正确店铺', '负责人', '创建人',
'所属利润中心', '负责平台', 'ID', '店铺', '财务备注', '支付宝订单',
'账单项目', '收入金额', '差异数据',
];
public function __construct()
{
$this->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');
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\Requests\StoreReceivables;
use App\Abstracts\Http\FormRequest;
class StoreZhongcaiReconciliation extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'bill_date' => ['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'],
];
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Models\StoreReceivables;
use App\Abstracts\Model;
class StoreZhongcaiReconciliation extends Model
{
protected $table = 'store_zhongcai_reconciliations';
protected $fillable = [
'company_id',
'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',
'store',
'finance_notes',
'alipay_order',
'bill_item',
'income_amount',
'difference_data',
'created_from',
'created_by',
];
protected $casts = [
'bill_date' => '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',
];
}

View File

@ -0,0 +1,50 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('store_zhongcai_reconciliations', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,51 @@
<?php
return [
'title' => '种菜应收对账',
// 表格列标题(对应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' => '收款与差异',
];

View File

@ -0,0 +1,157 @@
@php $isEdit = $record->exists; @endphp
<x-layouts.admin>
<x-slot name="title">
{{ $isEdit ? trans('store_zhongcai_reconciliations.edit_record') : trans('store_zhongcai_reconciliations.add_record') }}
- {{ trans('store_zhongcai_reconciliations.title') }}
</x-slot>
<x-slot name="content">
<x-form.container>
<form method="POST"
action="{{ $isEdit ? route('zhongcai-reconciliations.update', $record) : route('zhongcai-reconciliations.store') }}"
class="space-y-6">
@csrf
@if ($isEdit) @method('PATCH') @endif
{{-- 基本信息 --}}
<div class="rounded-lg border border-gray-200 bg-gray-50 px-5 py-4">
<h3 class="mb-4 text-sm font-semibold text-gray-700">{{ trans('store_zhongcai_reconciliations.section_basic') }}</h3>
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.bill_date') }} <span class="text-red-500">*</span></label>
<input type="date" name="bill_date" value="{{ old('bill_date', $record->bill_date ? $record->bill_date->toDateString() : now()->toDateString()) }}" required
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.planting_order_no') }} <span class="text-red-500">*</span></label>
<input name="planting_order_no" value="{{ old('planting_order_no', $record->planting_order_no) }}" required
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
</div>
</div>
{{-- 财务数据 --}}
<div class="rounded-lg border border-gray-200 bg-white px-5 py-4">
<h3 class="mb-4 text-sm font-semibold text-gray-700">{{ trans('store_zhongcai_reconciliations.section_finance') }}</h3>
<div class="grid gap-5 sm:grid-cols-3 lg:grid-cols-4">
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.finance_selling_price') }}</label>
<input type="number" step="0.01" name="finance_selling_price" value="{{ old('finance_selling_price', $record->finance_selling_price) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.finance_freight') }}</label>
<input type="number" step="0.01" name="finance_freight" value="{{ old('finance_freight', $record->finance_freight) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.finance_commission') }}</label>
<input type="number" step="0.01" name="finance_commission" value="{{ old('finance_commission', $record->finance_commission) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.jingxi_price_diff') }}</label>
<input type="number" step="0.01" name="jingxi_price_diff" value="{{ old('jingxi_price_diff', $record->jingxi_price_diff) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.deduction') }}</label>
<input type="number" step="0.01" name="deduction" value="{{ old('deduction', $record->deduction) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.finance_empty_package_fee') }}</label>
<input type="number" step="0.01" name="finance_empty_package_fee" value="{{ old('finance_empty_package_fee', $record->finance_empty_package_fee) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.finance_payment_amount') }}</label>
<input type="number" step="0.01" name="finance_payment_amount" value="{{ old('finance_payment_amount', $record->finance_payment_amount) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
</div>
</div>
{{-- 店铺与人员 --}}
<div class="rounded-lg border border-gray-200 bg-white px-5 py-4">
<h3 class="mb-4 text-sm font-semibold text-gray-700">{{ trans('store_zhongcai_reconciliations.section_store') }}</h3>
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.correct_store') }}</label>
<input name="correct_store" value="{{ old('correct_store', $record->correct_store) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.store') }}</label>
<input name="store" value="{{ old('store', $record->store) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.person_in_charge') }}</label>
<input name="person_in_charge" value="{{ old('person_in_charge', $record->person_in_charge) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.creator') }}</label>
<input name="creator" value="{{ old('creator', $record->creator) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.profit_center') }}</label>
<input name="profit_center" value="{{ old('profit_center', $record->profit_center) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.responsible_platform') }}</label>
<input name="responsible_platform" value="{{ old('responsible_platform', $record->responsible_platform) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.external_id') }}</label>
<input name="external_id" value="{{ old('external_id', $record->external_id) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
</div>
</div>
{{-- 收款与差异 --}}
<div class="rounded-lg border border-gray-200 bg-white px-5 py-4">
<h3 class="mb-4 text-sm font-semibold text-gray-700">{{ trans('store_zhongcai_reconciliations.section_payment') }}</h3>
<div class="grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.alipay_order') }}</label>
<input name="alipay_order" value="{{ old('alipay_order', $record->alipay_order) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.bill_item') }}</label>
<input name="bill_item" value="{{ old('bill_item', $record->bill_item) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green" />
</div>
<div>
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.income_amount') }}</label>
<input type="number" step="0.01" name="income_amount" value="{{ old('income_amount', $record->income_amount) }}"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm text-right focus:border-green focus:ring-green" />
</div>
<div class="sm:col-span-2 lg:col-span-3">
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.finance_notes') }}</label>
<textarea name="finance_notes" rows="2"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green">{{ old('finance_notes', $record->finance_notes) }}</textarea>
</div>
<div class="sm:col-span-2 lg:col-span-3">
<label class="mb-1 block text-xs font-medium text-gray-600">{{ trans('store_zhongcai_reconciliations.difference_data') }}</label>
<textarea name="difference_data" rows="2"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-green focus:ring-green">{{ old('difference_data', $record->difference_data) }}</textarea>
</div>
</div>
</div>
<div class="flex justify-end gap-4">
<a href="{{ route('zhongcai-reconciliations.index') }}" class="px-5 py-2 text-gray-600 text-sm">{{ trans('general.cancel') }}</a>
<button type="submit" class="rounded-lg bg-green px-6 py-2 text-sm text-white hover:bg-green-700">{{ trans('general.save') }}</button>
</div>
</form>
</x-form.container>
</x-slot>
</x-layouts.admin>

View File

@ -0,0 +1,8 @@
<x-layouts.admin>
<x-slot name="title">
{{ trans('store_zhongcai_reconciliations.title') }}
</x-slot>
<x-slot name="content">
@include('store_receivables.zhongcai_reconciliations.partials.embedded-table')
</x-slot>
</x-layouts.admin>

View File

@ -0,0 +1,162 @@
<!-- 种菜应收对账 - 嵌入业务页面的21列表格 -->
<style>
.zhongcai-table-wrapper { overflow-x: auto; border-radius: 0.5rem; border: 1px solid #e5e7eb; }
.zhongcai-table { min-width: 100%; white-space: nowrap; font-size: 0.8125rem; }
.zhongcai-table thead th { position: sticky; top: 0; background: #f9fafb; text-align: left; color: #6b7280; padding: 0.5rem 0.6rem; border-bottom: 1px solid #e5e7eb; font-weight: 600; }
.zhongcai-table tbody td { padding: 0.5rem 0.6rem; border-bottom: 1px solid #f3f4f6; }
.zhongcai-table tbody tr:hover { background: #f9fafb; }
.amount-cell { text-align: right; font-variant-numeric: tabular-nums; }
</style>
@php $pageIds = $records->pluck('id')->map(fn($id) => (string) $id)->values(); @endphp
<div class="w-full" x-data="{ selected: [] }">
<form id="zhongcai-export-form" method="GET" action="{{ route('zhongcai-reconciliations.export') }}"></form>
<div class="mb-4 flex flex-wrap items-end justify-between gap-3 border-b border-gray-200 pb-4">
@can('create-banking-accounts')
<div class="flex flex-wrap items-end gap-3">
<a href="{{ route('zhongcai-reconciliations.create') }}"
class="inline-flex h-10 items-center gap-2 rounded-lg bg-green px-4 text-sm text-white hover:bg-green-700">
<span class="material-icons text-base" aria-hidden="true">add</span>
{{ trans('store_zhongcai_reconciliations.add_record') }}
</a>
<form method="POST" action="{{ route('zhongcai-reconciliations.import') }}"
enctype="multipart/form-data" class="flex flex-wrap items-end gap-3">
@csrf
<div>
<label for="zhongcai-import" class="mb-1 block text-xs font-medium text-gray-600">
{{ trans('store_zhongcai_reconciliations.choose_file') }}
</label>
<input id="zhongcai-import" type="file" name="import" accept=".xlsx,.xls,.csv" required
class="block max-w-xs text-sm text-gray-600" />
</div>
<button type="submit"
class="inline-flex h-10 items-center gap-2 rounded-lg bg-green px-4 text-sm text-white hover:bg-green-700">
<span class="material-icons text-base" aria-hidden="true">upload_file</span>
{{ trans('store_zhongcai_reconciliations.bulk_upload') }}
</button>
</form>
<a href="{{ route('zhongcai-reconciliations.template') }}"
class="inline-flex h-10 items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 text-sm text-gray-700 hover:bg-gray-50">
<span class="material-icons text-base" aria-hidden="true">file_download</span>
{{ trans('store_zhongcai_reconciliations.download_template') }}
</a>
</div>
@endcan
<div class="flex items-center gap-3">
@can('update-banking-accounts')
<button type="submit" form="zhongcai-export-form"
class="inline-flex h-10 items-center gap-2 rounded-lg border border-gray-300 bg-white px-4 text-sm text-gray-700 hover:bg-gray-50">
<span class="material-icons text-base" aria-hidden="true">download</span>
{{ trans('store_zhongcai_reconciliations.bulk_download') }}
</button>
@endcan
</div>
</div>
@if ($errors->has('import'))
<div class="mb-4 border-l-4 border-red-500 bg-red-50 px-4 py-3 text-sm text-red-700">
@foreach ($errors->get('import') as $message)
<p>{{ $message }}</p>
@endforeach
</div>
@endif
{{-- 21列数据表格 --}}
<div class="zhongcai-table-wrapper">
<table class="zhongcai-table divide-y divide-gray-200">
<thead>
<tr>
<th class="w-12 text-center">
<input type="checkbox" aria-label="{{ trans('store_zhongcai_reconciliations.select_all') }}"
@if ($pageIds->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" />
</th>
<th>{{ trans('store_zhongcai_reconciliations.bill_date') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.planting_order_no') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.finance_selling_price') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.finance_freight') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.finance_commission') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.jingxi_price_diff') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.deduction') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.finance_empty_package_fee') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.finance_payment_amount') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.correct_store') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.person_in_charge') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.creator') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.profit_center') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.responsible_platform') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.external_id') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.store') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.finance_notes') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.alipay_order') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.bill_item') }}</th>
<th class="text-right">{{ trans('store_zhongcai_reconciliations.income_amount') }}</th>
<th>{{ trans('store_zhongcai_reconciliations.difference_data') }}</th>
<th class="text-right">{{ trans('general.actions') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 bg-white">
@if ($records->isEmpty())
<tr>
<td colspan="23" class="px-5 py-10 text-center text-gray-400">
{{ trans('store_zhongcai_reconciliations.no_records') }}
</td>
</tr>
@else
@foreach ($records as $r)
<tr>
<td class="text-center">
<input type="checkbox" name="ids[]" value="{{ $r->id }}" form="zhongcai-export-form"
x-model="selected" aria-label="{{ trans('store_zhongcai_reconciliations.select_record') }}"
class="h-4 w-4 rounded border-gray-300 text-green" />
</td>
<td>{{ $r->bill_date->format('Y-m-d') }}</td>
<td>{{ $r->planting_order_no }}</td>
<td class="amount-cell">{{ number_format($r->finance_selling_price, 2) }}</td>
<td class="amount-cell">{{ number_format($r->finance_freight, 2) }}</td>
<td class="amount-cell">{{ number_format($r->finance_commission, 2) }}</td>
<td class="amount-cell">{{ number_format($r->jingxi_price_diff, 2) }}</td>
<td class="amount-cell">{{ number_format($r->deduction, 2) }}</td>
<td class="amount-cell">{{ number_format($r->finance_empty_package_fee, 2) }}</td>
<td class="amount-cell font-semibold">{{ number_format($r->finance_payment_amount, 2) }}</td>
<td>{{ $r->correct_store }}</td>
<td>{{ $r->person_in_charge }}</td>
<td>{{ $r->creator }}</td>
<td>{{ $r->profit_center }}</td>
<td>{{ $r->responsible_platform }}</td>
<td>{{ $r->external_id }}</td>
<td>{{ $r->store }}</td>
<td class="max-w-xs truncate" title="{{ $r->finance_notes }}">{{ \Illuminate\Support\Str::limit($r->finance_notes, 15) }}</td>
<td>{{ $r->alipay_order }}</td>
<td>{{ $r->bill_item }}</td>
<td class="amount-cell font-semibold">{{ number_format($r->income_amount, 2) }}</td>
<td class="max-w-xs truncate" title="{{ $r->difference_data }}">{{ \Illuminate\Support\Str::limit($r->difference_data, 15) }}</td>
<td class="text-right whitespace-nowrap">
@can('update-banking-accounts')
<a href="{{ route('zhongcai-reconciliations.edit', $r) }}"
class="text-purple hover:underline mr-3">{{ trans('general.edit') }}</a>
@endcan
@can('delete-banking-accounts')
<form action="{{ route('zhongcai-reconciliations.destroy', $r) }}"
method="POST" class="inline"
onsubmit="return confirm('{{ trans('general.delete_confirm') }}')">
@csrf @method('DELETE')
<button type="submit" class="text-red-600 hover:underline">{{ trans('general.delete') }}</button>
</form>
@endcan
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
@unless ($records->isEmpty())
<div class="mt-5">{{ $records->links() }}</div>
@endunless
</div>

View File

@ -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');