财务经营报表增加安装明细 (#83)

根据安装费工作表增加安装明细数据表、模型和通用页面映射。
This commit is contained in:
chenxi 2026-08-31 16:37:38 +08:00
parent fdc42dd84d
commit 410e8bc04a
4 changed files with 196 additions and 0 deletions

View File

@ -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,
];
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Models\Business;
use App\Abstracts\Model;
class InstallationDetail extends Model
{
protected $table = 'business_installation_details';
protected $fillable = [
'company_id', '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', 'product_id', 'id_owner',
'department_owner', 'auxiliary_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', 'installation_company_1',
];
protected $casts = [
'company_id' => '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',
];
}
}

View File

@ -0,0 +1,87 @@
<?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('business_installation_details', function (Blueprint $table): void {
$table->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');
}
};

View File

@ -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) [