diff --git a/app/Business/TableRegistry.php b/app/Business/TableRegistry.php index 13b61a68..6b7e0a75 100644 --- a/app/Business/TableRegistry.php +++ b/app/Business/TableRegistry.php @@ -12,6 +12,7 @@ use App\Models\Business\CompanyStoreData; use App\Models\Business\ExpressReceiving; use App\Models\Business\FundForecast; use App\Models\Business\InvoiceBasic; +use App\Models\Business\InstallationDetail; use App\Models\Business\StoreChange; use App\Models\Business\StoreChangeLog; use App\Models\Business\StoreClosing; @@ -166,6 +167,7 @@ class TableRegistry 'fund-management/bank-accounts' => BankAccount::class, 'fund-management/shared-company-accounts' => BankAccount::class, // 重用 BankAccount,仅类型过滤 'billing-transactions/store-receiving-balance' => StoreReceivingAccount::class, + 'financial-operations/installation-details' => InstallationDetail::class, ]; } diff --git a/app/Models/Business/InstallationDetail.php b/app/Models/Business/InstallationDetail.php new file mode 100644 index 00000000..b90f2c3f --- /dev/null +++ b/app/Models/Business/InstallationDetail.php @@ -0,0 +1,78 @@ + 'integer', + 'item_quantity' => 'integer', + 'actual_install_quantity' => 'integer', + 'detail_requested_quantity' => 'integer', + 'requested_amount' => 'decimal:2', + 'settled_amount' => 'decimal:2', + 'actual_install_fee' => 'decimal:2', + 'payment_amount' => 'decimal:2', + 'detail_requested_amount' => 'decimal:2', + 'detail_settled_amount' => 'decimal:2', + 'detail_received_amount' => 'decimal:2', + ]; + + public static function columnLabels(): array + { + return [ + 'installation_no' => '安装单号', 'delete_status' => '删除状态', 'quote_status' => '报价状态', + 'quote_deadline' => '报价截至时间', 'installation_company' => '安装公司', 'submitted_at' => '提交时间', + 'shipped_at' => '发货时间', 'buyer_nickname' => '买家昵称', 'store_name' => '店铺名称', + 'installation_type' => '类型', 'pre_push_verification' => '推送前验证', 'push_status' => '推送状态', + 'push_result' => '推送结果', 'arranged_status' => '是否安排', 'order_no' => '订单编号', + 'item_quantity' => '商品数量', 'buyer_name' => '买家姓名', 'buyer_phone' => '买家号码', 'notes' => '备注', + 'province' => '省', 'city' => '市', 'district' => '区', 'address' => '地址', + 'requested_amount' => '申请总金额', 'settled_amount' => '结算总金额', 'image_urls' => '图片地址', + 'completed_status' => '是否完工', 'customer_service_review' => '客服审核', + 'customer_service_reviewed_at' => '客服审核时间', 'finance_review' => '财务审核', + 'finance_reviewed_at' => '财务审核时间', 'actual_install_quantity' => '实际安装数量', + 'actual_install_fee' => '实际安装费用', 'actual_install_product_name' => '实际安装商品名称', + 'installer_phone' => '安装师傅电话', 'installation_company_notes' => '安装公司备注', + 'installation_fee_payer' => '安装费承担方', 'payment_channel' => '收款渠道', + 'payment_transaction_no' => '收款交易单号', 'payment_voucher' => '收款凭证', 'payment_amount' => '收款金额', + 'creator' => '创建人', 'related_guid' => '关联guid', 'product_id' => '商品id', 'id_owner' => 'ID负责人', + 'department_owner' => '部门负责人', 'auxiliary_spu' => '辅助码Spu', 'product_store_code' => '商品店铺编码', + 'item_no' => '货号', 'barcode' => '条形码', 'product_name' => '商品名称', 'supplier_name' => '供应商名称', + 'detail_requested_amount' => '申请明细金额', 'detail_requested_quantity' => '申请明细数量', + 'detail_settled_amount' => '结算明细金额', 'detail_received_amount' => '收款明细金额', + 'change_reason' => '变更原因', 'source_created_at' => '创建时间', 'source_modified_at' => '修改时间', + 'product_name_1' => '商品名称(1)', 'installation_company_1' => '安装公司', + ]; + } + + public static function fieldTypes(): array + { + return [ + 'item_quantity' => 'number', 'actual_install_quantity' => 'number', 'detail_requested_quantity' => 'number', + 'requested_amount' => 'currency', 'settled_amount' => 'currency', 'actual_install_fee' => 'currency', + 'payment_amount' => 'currency', 'detail_requested_amount' => 'currency', + 'detail_settled_amount' => 'currency', 'detail_received_amount' => 'currency', + ]; + } +} diff --git a/database/migrations/2026_08_31_000000_create_business_installation_details_table.php b/database/migrations/2026_08_31_000000_create_business_installation_details_table.php new file mode 100644 index 00000000..a0e6327d --- /dev/null +++ b/database/migrations/2026_08_31_000000_create_business_installation_details_table.php @@ -0,0 +1,87 @@ +id(); + $table->unsignedInteger('company_id')->index(); + $table->text('installation_no')->nullable(); + $table->text('delete_status')->nullable(); + $table->text('quote_status')->nullable(); + $table->text('quote_deadline')->nullable(); + $table->text('installation_company')->nullable(); + $table->text('submitted_at')->nullable(); + $table->text('shipped_at')->nullable(); + $table->text('buyer_nickname')->nullable(); + $table->text('store_name')->nullable(); + $table->text('installation_type')->nullable(); + $table->text('pre_push_verification')->nullable(); + $table->text('push_status')->nullable(); + $table->text('push_result')->nullable(); + $table->text('arranged_status')->nullable(); + $table->text('order_no')->nullable(); + $table->integer('item_quantity')->nullable(); + $table->text('buyer_name')->nullable(); + $table->text('buyer_phone')->nullable(); + $table->text('notes')->nullable(); + $table->text('province')->nullable(); + $table->text('city')->nullable(); + $table->text('district')->nullable(); + $table->text('address')->nullable(); + $table->decimal('requested_amount', 18, 2)->nullable(); + $table->decimal('settled_amount', 18, 2)->nullable(); + $table->text('image_urls')->nullable(); + $table->text('completed_status')->nullable(); + $table->text('customer_service_review')->nullable(); + $table->text('customer_service_reviewed_at')->nullable(); + $table->text('finance_review')->nullable(); + $table->text('finance_reviewed_at')->nullable(); + $table->integer('actual_install_quantity')->nullable(); + $table->decimal('actual_install_fee', 18, 2)->nullable(); + $table->text('actual_install_product_name')->nullable(); + $table->text('installer_phone')->nullable(); + $table->text('installation_company_notes')->nullable(); + $table->text('installation_fee_payer')->nullable(); + $table->text('payment_channel')->nullable(); + $table->text('payment_transaction_no')->nullable(); + $table->text('payment_voucher')->nullable(); + $table->decimal('payment_amount', 18, 2)->nullable(); + $table->text('creator')->nullable(); + $table->text('related_guid')->nullable(); + $table->text('product_id')->nullable(); + $table->text('id_owner')->nullable(); + $table->text('department_owner')->nullable(); + $table->text('auxiliary_spu')->nullable(); + $table->text('product_store_code')->nullable(); + $table->text('item_no')->nullable(); + $table->text('barcode')->nullable(); + $table->text('product_name')->nullable(); + $table->text('supplier_name')->nullable(); + $table->decimal('detail_requested_amount', 18, 2)->nullable(); + $table->integer('detail_requested_quantity')->nullable(); + $table->decimal('detail_settled_amount', 18, 2)->nullable(); + $table->decimal('detail_received_amount', 18, 2)->nullable(); + $table->text('change_reason')->nullable(); + $table->text('source_created_at')->nullable(); + $table->text('source_modified_at')->nullable(); + $table->text('product_name_1')->nullable(); + $table->text('installation_company_1')->nullable(); + $table->timestamps(); + $table->softDeletes(); + // installation_no is a TEXT field and cannot be included in a full + // composite index under MySQL's 3072-byte key limit; company_id + // already provides the tenant filter index above. + }); + } + + public function down(): void + { + Schema::dropIfExists('business_installation_details'); + } +}; diff --git a/tests/Feature/Business/ManagementCrudTest.php b/tests/Feature/Business/ManagementCrudTest.php index 320525e8..86b362b7 100644 --- a/tests/Feature/Business/ManagementCrudTest.php +++ b/tests/Feature/Business/ManagementCrudTest.php @@ -4,6 +4,7 @@ namespace Tests\Feature\Business; use App\Business\UpstreamBusinessInformation; use App\Models\Auth\Permission; +use App\Models\Business\InstallationDetail; use App\Utilities\BusinessNavigation; use Illuminate\Pagination\LengthAwarePaginator; use Mockery; @@ -20,6 +21,34 @@ class ManagementCrudTest extends FeatureTestCase ); } + public function testInstallationDetailsRendersWorkbookHeadersAndImportedRows(): void + { + InstallationDetail::create([ + 'company_id' => $this->company->id, + 'installation_no' => 'INSTALL-TEST-001', + 'installation_company' => '测试安装公司', + 'store_name' => '测试店铺', + 'requested_amount' => 120.50, + 'settled_amount' => 100.00, + 'item_quantity' => 2, + ]); + + $this->loginAs() + ->get(route('business.pages.show', [ + 'section' => 'financial-operations', + 'page' => 'installation-details', + ])) + ->assertOk() + ->assertSeeText('安装明细 · 数据列表') + ->assertSeeText('安装单号') + ->assertSeeText('申请总金额') + ->assertSeeText('商品名称(1)') + ->assertSeeText('INSTALL-TEST-001') + ->assertSeeText('测试安装公司') + ->assertSeeText('120.50') + ->assertSeeText('共 1 条'); + } + public function testCompanyInformationUsesWorkbookHeadersAndReadOnlyUpstreamRows(): void { $this->mockUpstreamPage((object) [