feat: 供应商应付 - 货款对账 + 5个子功能页面
新增功能页面: - 货款对账 (payment-reconciliation): 入库+代发合并对账,228K+2.8M条记录 - 退货对账 (return-reconciliation): finance_purchaseReturn, 36791行 - 发票扣款 (invoice-deductions): finance_invoiceDeduction, 195行 - 赔偿明细 (compensation-details): 纸板厂+压板厂 UNION, 98行 - 借支明细 (advance-details): finance_advanceLoans, 4行 - 样品扣款 (sample-deductions): finance_sampleDeduction, 12行 后端: - JianSunConnection: PDO_ODBC 直连 SQL Server - SupplierPayableRepository: 5个子页面的数据查询+分页 - GoodsReconciliationRepository: 货款对账数据查询 - 15个新API路由 (supplier-payables/*) - 12个JianSun Eloquent模型 前端: - 6个Blade视图 + 6个Vue组件 - webpack.mix.js 新增5个编译入口 - show.blade.php 动态视图解析
This commit is contained in:
parent
29e4b31294
commit
e5c068036b
@ -151,6 +151,25 @@ return [
|
||||
'prefix_indexes' => true,
|
||||
],
|
||||
|
||||
'jiansun' => [
|
||||
'driver' => 'odbc',
|
||||
'dsn' => sprintf(
|
||||
'Driver={SQL Server};Server=%s,%s;Database=%s',
|
||||
env('DB_JIANSUN_HOST', '218.93.191.21'),
|
||||
env('DB_JIANSUN_PORT', '1433'),
|
||||
env('DB_JIANSUN_DATABASE', 'JianSun')
|
||||
),
|
||||
'username' => env('DB_JIANSUN_USERNAME', 'financeReader'),
|
||||
'password' => env('DB_JIANSUN_PASSWORD', ''),
|
||||
'charset' => 'utf8',
|
||||
'prefix' => '',
|
||||
'prefix_indexes' => true,
|
||||
'options' => [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|
||||
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Http\Controllers\Api;
|
||||
|
||||
use App\Abstracts\Http\ApiController;
|
||||
use Modules\EcommerceFinance\Repositories\GoodsReconciliationRepository;
|
||||
|
||||
/**
|
||||
* 货款对账 API
|
||||
*
|
||||
* 提供供应商应付-货款对账页面的全部数据接口:
|
||||
* GET /api/ecommerce-finance/goods-reconciliations 列表(分页 + 筛选)
|
||||
* GET /api/ecommerce-finance/goods-reconciliations/summary 统计卡片
|
||||
* GET /api/ecommerce-finance/goods-reconciliations/options 筛选器选项
|
||||
*/
|
||||
class GoodsReconciliations extends ApiController
|
||||
{
|
||||
private GoodsReconciliationRepository $repo;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->repo = new GoodsReconciliationRepository();
|
||||
}
|
||||
|
||||
/**
|
||||
* 货款对账列表(分页 + 筛选)
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$filters = $this->buildFilters();
|
||||
$page = max((int) request('page', 1), 1);
|
||||
$limit = $this->limit();
|
||||
|
||||
$result = $this->repo->list($filters, $page, $limit);
|
||||
|
||||
return response()->json([
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
'page' => $page,
|
||||
'per_page' => $limit,
|
||||
'last_page' => (int) ceil($result['total'] / $limit),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总统计卡片
|
||||
*/
|
||||
public function summary()
|
||||
{
|
||||
$filters = $this->buildFilters();
|
||||
$summary = $this->repo->summary($filters);
|
||||
|
||||
return response()->json($summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* 筛选器选项(供应商/仓库下拉列表等)
|
||||
*/
|
||||
public function options()
|
||||
{
|
||||
$options = $this->repo->filterOptions();
|
||||
|
||||
return response()->json($options);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// Helpers
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* 从请求参数构建筛选条件
|
||||
*/
|
||||
private function buildFilters(): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
// 记录类型
|
||||
if ($type = request('record_type')) {
|
||||
$filters['record_type'] = $type; // purchase | sale | all
|
||||
}
|
||||
|
||||
// 文本筛选
|
||||
$textFields = ['supplier', 'stock_no', 'purchase_no', 'product_code', 'express_no', 'warehouse'];
|
||||
foreach ($textFields as $field) {
|
||||
if ($val = request($field)) {
|
||||
$filters[$field] = trim($val);
|
||||
}
|
||||
}
|
||||
|
||||
// 日期范围
|
||||
$dateFields = ['send_time_from', 'send_time_to', 'create_time_from', 'create_time_to'];
|
||||
foreach ($dateFields as $field) {
|
||||
if ($val = request($field)) {
|
||||
$filters[$field] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
// 发出状态
|
||||
if (($sent = request('sent_status')) && $sent !== 'all') {
|
||||
$filters['sent_status'] = $sent; // sent | unsent
|
||||
}
|
||||
|
||||
// 是否有差异
|
||||
if (request('has_diff')) {
|
||||
$filters['has_diff'] = true;
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
private function limit(): int
|
||||
{
|
||||
return min(max((int) request('limit', 25), 1), 100);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Http\Controllers\Api;
|
||||
|
||||
use App\Abstracts\Http\ApiController;
|
||||
use Modules\EcommerceFinance\Repositories\SupplierPayableRepository;
|
||||
|
||||
/**
|
||||
* 供应商应付 - 子功能页面 API
|
||||
*
|
||||
* 为 5 个子页面提供数据接口:
|
||||
* 退货对账 : /return-reconciliations + /return-reconciliations/summary + /return-reconciliations/options
|
||||
* 发票扣款 : /invoice-deductions + /invoice-deductions/summary + /invoice-deductions/options
|
||||
* 赔偿明细 : /compensation-details + /compensation-details/summary + /compensation-details/options
|
||||
* 借支明细 : /advance-details + /advance-details/summary + /advance-details/options
|
||||
* 样品扣款 : /sample-deductions + /sample-deductions/summary + /sample-deductions/options
|
||||
*/
|
||||
class SupplierPayables extends ApiController
|
||||
{
|
||||
private SupplierPayableRepository $repo;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->repo = new SupplierPayableRepository();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 退货对账
|
||||
// ============================================================
|
||||
public function returnReconciliationsIndex()
|
||||
{
|
||||
$filters = $this->filters('return');
|
||||
$result = $this->repo->returnReconciliations($filters, $this->page(), $this->limit());
|
||||
|
||||
return response()->json([
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
'page' => $this->page(),
|
||||
'per_page' => $this->limit(),
|
||||
'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function returnReconciliationsSummary()
|
||||
{
|
||||
return response()->json($this->repo->returnReconciliationSummary($this->filters('return')));
|
||||
}
|
||||
|
||||
public function returnReconciliationsOptions()
|
||||
{
|
||||
return response()->json($this->repo->filterOptionsForReturn());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 发票扣款
|
||||
// ============================================================
|
||||
public function invoiceDeductionsIndex()
|
||||
{
|
||||
$filters = $this->filters('invoice');
|
||||
$result = $this->repo->invoiceDeductions($filters, $this->page(), $this->limit());
|
||||
|
||||
return response()->json([
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
'page' => $this->page(),
|
||||
'per_page' => $this->limit(),
|
||||
'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function invoiceDeductionsSummary()
|
||||
{
|
||||
return response()->json($this->repo->invoiceDeductionSummary($this->filters('invoice')));
|
||||
}
|
||||
|
||||
public function invoiceDeductionsOptions()
|
||||
{
|
||||
return response()->json($this->repo->filterOptionsForInvoice());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 赔偿明细
|
||||
// ============================================================
|
||||
public function compensationDetailsIndex()
|
||||
{
|
||||
$filters = $this->filters('compensation');
|
||||
$result = $this->repo->compensationDetails($filters, $this->page(), $this->limit());
|
||||
|
||||
return response()->json([
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
'page' => $this->page(),
|
||||
'per_page' => $this->limit(),
|
||||
'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function compensationDetailsSummary()
|
||||
{
|
||||
return response()->json($this->repo->compensationDetailSummary($this->filters('compensation')));
|
||||
}
|
||||
|
||||
public function compensationDetailsOptions()
|
||||
{
|
||||
return response()->json($this->repo->filterOptionsForCompensation());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 借支明细
|
||||
// ============================================================
|
||||
public function advanceDetailsIndex()
|
||||
{
|
||||
$filters = $this->filters('advance');
|
||||
$result = $this->repo->advanceDetails($filters, $this->page(), $this->limit());
|
||||
|
||||
return response()->json([
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
'page' => $this->page(),
|
||||
'per_page' => $this->limit(),
|
||||
'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function advanceDetailsSummary()
|
||||
{
|
||||
return response()->json($this->repo->advanceDetailSummary($this->filters('advance')));
|
||||
}
|
||||
|
||||
public function advanceDetailsOptions()
|
||||
{
|
||||
return response()->json($this->repo->filterOptionsForAdvance());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 样品扣款
|
||||
// ============================================================
|
||||
public function sampleDeductionsIndex()
|
||||
{
|
||||
$filters = $this->filters('sample');
|
||||
$result = $this->repo->sampleDeductions($filters, $this->page(), $this->limit());
|
||||
|
||||
return response()->json([
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
'page' => $this->page(),
|
||||
'per_page' => $this->limit(),
|
||||
'last_page' => (int) ceil(max($result['total'], 0) / $this->limit()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function sampleDeductionsSummary()
|
||||
{
|
||||
return response()->json($this->repo->sampleDeductionSummary($this->filters('sample')));
|
||||
}
|
||||
|
||||
public function sampleDeductionsOptions()
|
||||
{
|
||||
return response()->json($this->repo->filterOptionsForSample());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Helpers
|
||||
// ============================================================
|
||||
private function filters(string $type): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
// 通用文本字段
|
||||
$text = ['supplier', 'warehouse', 'sku_id', 'express_no', 'creator', 'department'];
|
||||
foreach ($text as $field) {
|
||||
if ($val = request($field)) {
|
||||
$filters[$field] = trim($val);
|
||||
}
|
||||
}
|
||||
|
||||
// 日期
|
||||
foreach (['date_from', 'date_to', 'month_from', 'month_to'] as $field) {
|
||||
if ($val = request($field)) {
|
||||
$filters[$field] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
private function page(): int
|
||||
{
|
||||
return max((int) request('page', 1), 1);
|
||||
}
|
||||
|
||||
private function limit(): int
|
||||
{
|
||||
return min(max((int) request('limit', 25), 1), 100);
|
||||
}
|
||||
}
|
||||
27
modules/EcommerceFinance/Models/JianSun/AverageUnitPrice.php
Normal file
27
modules/EcommerceFinance/Models/JianSun/AverageUnitPrice.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 加权平均单价表
|
||||
* pm_averageUnitPrice
|
||||
*
|
||||
* 按货号+供应商存储的加权平均单价(262条)。
|
||||
* 用于计算"预估金额"。
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $number 货号
|
||||
* @property string $supplier 供应商名称
|
||||
* @property float $price 加权平均单价(页面表头第11列来源)
|
||||
* @property string $name 商品名称
|
||||
*/
|
||||
class AverageUnitPrice extends JianSunModel
|
||||
{
|
||||
protected $table = 'pm_averageUnitPrice';
|
||||
|
||||
protected $fillable = ['number', 'supplier', 'price', 'name'];
|
||||
|
||||
protected $casts = [
|
||||
'price' => 'decimal:4',
|
||||
];
|
||||
}
|
||||
27
modules/EcommerceFinance/Models/JianSun/JianSunModel.php
Normal file
27
modules/EcommerceFinance/Models/JianSun/JianSunModel.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Base Model for JianSun SQL Server database tables.
|
||||
* All models extending this will automatically use the 'jiansun' connection.
|
||||
*/
|
||||
abstract class JianSunModel extends Model
|
||||
{
|
||||
/** @var string */
|
||||
protected $connection = 'jiansun';
|
||||
|
||||
/** @var bool JianSun tables don't use Laravel timestamps */
|
||||
public $timestamps = false;
|
||||
|
||||
/** @var string No prefix on JianSun tables */
|
||||
protected $prefix = '';
|
||||
|
||||
/** @var string */
|
||||
protected $primaryKey = 'id';
|
||||
|
||||
/** @var bool */
|
||||
public $incrementing = true;
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 订单快递详情表(代发记录)
|
||||
* jst_order_express_details
|
||||
*
|
||||
* 存储所有代发订单的快递信息(2,812,915条)。
|
||||
* 页面中"代发记录"的快递名称和快递单号来源于此表。
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $so_id 订单编号
|
||||
* @property string $logistic_id 快递单号(页面表头第18列)
|
||||
* @property string $logistic_company_name 快递名称(页面表头第17列)
|
||||
* @property string $logistic_status_name 物流状态
|
||||
* @property string $warehouse_name 仓库名称
|
||||
* @property string $send_time 发货时间(页面表头第3列)
|
||||
* @property string $sign_time 签收时间
|
||||
* @property float $weight 重量
|
||||
*/
|
||||
class JstOrderExpressDetails extends JianSunModel
|
||||
{
|
||||
protected $table = 'jst_order_express_details';
|
||||
|
||||
protected $fillable = [
|
||||
'createdAt', 'updatedAt', 'inner_order_id', 'so_id', 'logistic_id',
|
||||
'logistic_company_name', 'logistic_status_name', 'logistic_return_flag',
|
||||
'order_status_name', 'first_question_type', 'first_question_time',
|
||||
'aftersale_status_name', 'order_labels', 'remark', 'buyer_message',
|
||||
'shop_name', 'platform_name', 'dis_co_name', 'warehouse_name',
|
||||
'receiver_address', 'pay_time', 'first_oa_confirm_time',
|
||||
'last_oa_confirm_time', 'send_time', 'fetch_time', 'sign_time',
|
||||
'logistic_time', 'weight', 'f_weight', 'ship_time_by_pdh',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'send_time' => 'datetime',
|
||||
'sign_time' => 'datetime',
|
||||
'pay_time' => 'datetime',
|
||||
'fetch_time' => 'datetime',
|
||||
'weight' => 'decimal:4',
|
||||
'f_weight' => 'decimal:4',
|
||||
];
|
||||
}
|
||||
31
modules/EcommerceFinance/Models/JianSun/JstSupplier.php
Normal file
31
modules/EcommerceFinance/Models/JianSun/JstSupplier.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 聚水潭供应商表
|
||||
* jst_supplier
|
||||
*
|
||||
* 聚水潭ERP系统的供应商数据(2,075条)。
|
||||
*
|
||||
* @property int $Id
|
||||
* @property int $supplier_id 供应商ID
|
||||
* @property string $supplier_code 供应商编码
|
||||
* @property string $name 供应商名称
|
||||
* @property string $modified 修改时间
|
||||
* @property string $remark 备注
|
||||
* @property string $group 分组
|
||||
* @property int $enabled 是否启用
|
||||
*/
|
||||
class JstSupplier extends JianSunModel
|
||||
{
|
||||
protected $table = 'jst_supplier';
|
||||
|
||||
protected $primaryKey = 'Id';
|
||||
|
||||
protected $fillable = [
|
||||
'supplier_id', 'supplier_code', 'name', 'modified', 'remark',
|
||||
'remark2', 'remark3', 'group', 'enabled', 'depositbank',
|
||||
'bankacount', 'acountnumber',
|
||||
];
|
||||
}
|
||||
45
modules/EcommerceFinance/Models/JianSun/PurchaseIn.php
Normal file
45
modules/EcommerceFinance/Models/JianSun/PurchaseIn.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 采购入库单主表
|
||||
* pm_purchaseIn
|
||||
*
|
||||
* 每次采购入库的汇总记录(52,219条)。
|
||||
* 通过 id 关联 pm_purchaseInItem 获取明细。
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $hno 入库单号(页面表头第1列)
|
||||
* @property string $whcode 仓库编码
|
||||
* @property string $sourceno 来源单号
|
||||
* @property string $title 标题
|
||||
* @property string $scmname 供应商名称
|
||||
* @property string $scmcode 供应商编码
|
||||
* @property int $scmid 供应商ID
|
||||
* @property int $incount 入库总数
|
||||
* @property string $created 创建时间(页面表头第16列)
|
||||
* @property string $memo 备注(页面表头第14列)
|
||||
* @property string $expcode 快递编码
|
||||
* @property string $expname 快递名称
|
||||
*/
|
||||
class PurchaseIn extends JianSunModel
|
||||
{
|
||||
protected $table = 'pm_purchaseIn';
|
||||
|
||||
protected $fillable = [
|
||||
'whcode', 'hno', 'sourceno', 'title', 'scmname', 'scmcode', 'scmid',
|
||||
'incount', 'created', 'memo', 'expcode', 'expname', 'postcode', 'postfee',
|
||||
'creater', 'createrid', 'inwhdate', 'finishdate', 'procode', 'proname',
|
||||
'skucode', 'skuname',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'incount' => 'integer',
|
||||
'scmid' => 'integer',
|
||||
'postfee' => 'decimal:2',
|
||||
'created' => 'datetime',
|
||||
'inwhdate' => 'datetime',
|
||||
'finishdate' => 'datetime',
|
||||
];
|
||||
}
|
||||
39
modules/EcommerceFinance/Models/JianSun/PurchaseInItem.php
Normal file
39
modules/EcommerceFinance/Models/JianSun/PurchaseInItem.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 采购入库单明细表
|
||||
* pm_purchaseInItem
|
||||
*
|
||||
* 每次采购入库的商品明细(223,382条)。
|
||||
* 通过 hid 关联 pm_purchaseIn.id。
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $hid 关联 PurchaseIn.id
|
||||
* @property string $skucode 商品编码(页面表头第6列)
|
||||
* @property string $skuname 规格名称(页面表头第7列)
|
||||
* @property string $skuoid 外部商品编码
|
||||
* @property int $incount 入库数量(页面表头第8列)
|
||||
* @property int $skucount SKU计数
|
||||
* @property float $payment 单价
|
||||
* @property string $created 创建时间
|
||||
*/
|
||||
class PurchaseInItem extends JianSunModel
|
||||
{
|
||||
protected $table = 'pm_purchaseInItem';
|
||||
|
||||
protected $fillable = [
|
||||
'hid', 'skucode', 'skuname', 'skuoid', 'incount', 'skucount',
|
||||
'payment', 'created', 'procode', 'proname', 'batchCode',
|
||||
'inventoryType', 'productDate', 'expireDate', 'produceCode',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'hid' => 'integer',
|
||||
'incount' => 'integer',
|
||||
'skucount' => 'integer',
|
||||
'payment' => 'decimal:2',
|
||||
'created' => 'datetime',
|
||||
];
|
||||
}
|
||||
29
modules/EcommerceFinance/Models/JianSun/SkuInfoManage.php
Normal file
29
modules/EcommerceFinance/Models/JianSun/SkuInfoManage.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* SKU信息管理表
|
||||
* js_sku_info_manage
|
||||
*
|
||||
* 商品SKU管理信息(8,394条),包含货号、商品编码等。
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $sku_id 商品编码(页面表头第6列)
|
||||
* @property string $sku 货号(页面表头第5列)
|
||||
* @property string $spu SPU
|
||||
* @property string $sku_manager SKU负责人
|
||||
* @property string $operation_dept_manager 运营部门负责人
|
||||
* @property string $designer 设计师
|
||||
* @property string $designer_dept 设计部门
|
||||
*/
|
||||
class SkuInfoManage extends JianSunModel
|
||||
{
|
||||
protected $table = 'js_sku_info_manage';
|
||||
|
||||
protected $fillable = [
|
||||
'sku_id', 'sku', 'spu', 'sku_manager',
|
||||
'operation_dept_manager', 'sku_business_unit_manager',
|
||||
'designer', 'designer_dept', 'create_time', 'update_time',
|
||||
];
|
||||
}
|
||||
40
modules/EcommerceFinance/Models/JianSun/Supplier.php
Normal file
40
modules/EcommerceFinance/Models/JianSun/Supplier.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 供应商基础信息表
|
||||
* js_supplier
|
||||
*
|
||||
* 供应商主数据(1,479条),包含供应商编码、名称、公司等。
|
||||
*
|
||||
* @property int $Id
|
||||
* @property string $scmcode 供应商编码
|
||||
* @property string $scmname 供应商名称
|
||||
* @property string $scmtype 供应商类型
|
||||
* @property string $company 公司全称
|
||||
* @property string $paycyclesgn 结算周期标记
|
||||
* @property string $reconciliter 对账人
|
||||
* @property string $buyer 采购员
|
||||
* @property string $coopersgn 合作状态标记
|
||||
* @property string $levelname 等级名称
|
||||
* @property string $memo 备注
|
||||
* @property string $enterpriseType 企业类型
|
||||
* @property string $creditcode 统一社会信用代码
|
||||
*/
|
||||
class Supplier extends JianSunModel
|
||||
{
|
||||
protected $table = 'js_supplier';
|
||||
|
||||
protected $primaryKey = 'Id';
|
||||
|
||||
protected $fillable = [
|
||||
'scmcode', 'scmname', 'scmtype', 'tradebody', 'company',
|
||||
'paycyclesgn', 'accountbankno', 'corpmobile', 'tradebtime', 'tradeetime',
|
||||
'coopersgn', 'cooperbtime', 'cooperetime', 'dsperid', 'dsper',
|
||||
'reconciliter', 'levelname', 'buyerid', 'buyer', 'memo',
|
||||
'guid', 'craftType', 'outline', 'capacity', 'freereason', 'area',
|
||||
'stocker', 'region', 'sindou', 'follower', 'presc', 'strategy',
|
||||
'enterpriseType', 'creditcode', 'handlePerson', 'legalPerson',
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 供应商采购结算明细表
|
||||
* fys_supplierGoods_sttlement_purchase
|
||||
*
|
||||
* 采购入库结算明细,包含每个SKU的结算数量和价格。
|
||||
* 是货款对账页面"入库记录"的核心数据源(228,039条)。
|
||||
*
|
||||
* @property int $Id
|
||||
* @property string $created 创建时间
|
||||
* @property int $stock 入库数量
|
||||
* @property int $settlementStock 结算数量
|
||||
* @property float $singlePrice 单价
|
||||
* @property float $settlementPrice 结算价格
|
||||
* @property string $priceType 价格类型
|
||||
* @property string $ownername 货主
|
||||
* @property string $scmname 供应商名称
|
||||
* @property string $procode 货号
|
||||
* @property string $whname 仓库名称
|
||||
* @property string $skuoid 商品编码
|
||||
* @property string $skuname 规格名称
|
||||
* @property string $bizpo 业务采购单号
|
||||
* @property string $spo 采购单号
|
||||
* @property string $remark 备注
|
||||
*/
|
||||
class SupplierGoodsSttlementPurchase extends JianSunModel
|
||||
{
|
||||
protected $table = 'fys_supplierGoods_sttlement_purchase';
|
||||
|
||||
protected $primaryKey = 'Id';
|
||||
|
||||
protected $fillable = [
|
||||
'created', 'stock', 'settlementStock', 'singlePrice', 'settlementPrice',
|
||||
'priceType', 'ownername', 'scmname', 'procode', 'whname',
|
||||
'skuoid', 'skuname', 'bizpo', 'spo', 'remark', 'createTime',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'stock' => 'integer',
|
||||
'settlementStock' => 'integer',
|
||||
'singlePrice' => 'decimal:2',
|
||||
'settlementPrice' => 'decimal:2',
|
||||
'created' => 'datetime',
|
||||
'createTime' => 'datetime',
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 供应商付款明细表
|
||||
* fys_supplierPaymentDetail
|
||||
*
|
||||
* @property int $Id
|
||||
* @property string $created 创建时间
|
||||
* @property string $month 月份
|
||||
* @property string $date 日期
|
||||
* @property string $scmname 供应商名称
|
||||
* @property string $scmcode 供应商编码
|
||||
* @property float $payment 付款金额
|
||||
* @property string $reason 原因
|
||||
* @property string $remark 备注
|
||||
* @property int $billstatus 单据状态
|
||||
*/
|
||||
class SupplierPaymentDetail extends JianSunModel
|
||||
{
|
||||
protected $table = 'fys_supplierPaymentDetail';
|
||||
|
||||
protected $primaryKey = 'Id';
|
||||
|
||||
protected $fillable = [
|
||||
'created', 'month', 'date', 'scmname', 'scmcode',
|
||||
'payment', 'reason', 'remark', 'billstatus',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'payment' => 'decimal:2',
|
||||
'created' => 'datetime',
|
||||
'date' => 'date',
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 供应商付款汇总表
|
||||
* fys_supplierPaymentSummary
|
||||
*
|
||||
* 核心对账数据源,每条记录代表一个供应商在某月份的应付汇总
|
||||
*
|
||||
* @property int $id
|
||||
* @property string $company 公司
|
||||
* @property string $supplier 供应商名称
|
||||
* @property string $checker 对账人
|
||||
* @property string $settleway 结算方式
|
||||
* @property string $Month 对账月份
|
||||
* @property float $ExpFieght 快递费
|
||||
* @property float $bginMonthPay 月初应付
|
||||
* @property float $unpriced 未定价
|
||||
* @property float $advloan 预付
|
||||
* @property float $loan 借款
|
||||
* @property float $refund 退款
|
||||
* @property float $procurement 采购
|
||||
* @property float $invoiceDct 发票扣款
|
||||
* @property float $boardDct 板材扣款
|
||||
* @property float $specimenDct 样品扣款
|
||||
* @property float $Totalindemnity 总赔偿
|
||||
* @property float $other 其他
|
||||
* @property float $supplySup 供应商补贴
|
||||
* @property float $ShouldPay 应付金额
|
||||
* @property float $factPay 实付金额
|
||||
* @property float $guessPrice 预估金额
|
||||
* @property float $toCorporate 到公账
|
||||
* @property float $toPrivate 到私账
|
||||
* @property string $remarks 备注
|
||||
* @property string $reason 原因
|
||||
* @property string $guid GUID
|
||||
* @property int $isExamine 是否审核
|
||||
* @property int $isPayment 是否付款
|
||||
* @property string $scmcode 供应商编码
|
||||
* @property int $supid 供应商ID
|
||||
* @property string $createTime 创建时间
|
||||
*/
|
||||
class SupplierPaymentSummary extends JianSunModel
|
||||
{
|
||||
protected $table = 'fys_supplierPaymentSummary';
|
||||
|
||||
protected $fillable = [
|
||||
'company', 'supplier', 'checker', 'settleway', 'Month',
|
||||
'ExpFieght', 'bginMonthPay', 'unpriced', 'advloan', 'loan',
|
||||
'refund', 'procurement', 'invoiceDct', 'boardDct', 'specimenDct',
|
||||
'Totalindemnity', 'other', 'supplySup', 'ShouldPay', 'factPay',
|
||||
'guessPrice', 'toCorporate', 'toPrivate', 'remarks', 'reason',
|
||||
'mark', 'guid', 'isExamine', 'isPayment', 'Month',
|
||||
'billstatus', 'opstatus', 'elstatu', 'taskid', 'supid',
|
||||
'depositMoney', 'depositType', 'cashierbypaid', 'push', 'scmcode',
|
||||
'billRemaining',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'ExpFieght' => 'decimal:2',
|
||||
'bginMonthPay' => 'decimal:2',
|
||||
'unpriced' => 'decimal:2',
|
||||
'advloan' => 'decimal:2',
|
||||
'loan' => 'decimal:2',
|
||||
'refund' => 'decimal:2',
|
||||
'procurement' => 'decimal:2',
|
||||
'invoiceDct' => 'decimal:2',
|
||||
'boardDct' => 'decimal:2',
|
||||
'specimenDct' => 'decimal:2',
|
||||
'Totalindemnity' => 'decimal:2',
|
||||
'other' => 'decimal:2',
|
||||
'supplySup' => 'decimal:2',
|
||||
'ShouldPay' => 'decimal:2',
|
||||
'factPay' => 'decimal:2',
|
||||
'guessPrice' => 'decimal:2',
|
||||
'toCorporate' => 'decimal:2',
|
||||
'toPrivate' => 'decimal:2',
|
||||
'depositMoney' => 'decimal:2',
|
||||
'cashierbypaid' => 'decimal:2',
|
||||
'billRemaining' => 'decimal:2',
|
||||
'createTime' => 'datetime',
|
||||
'modifiedTime' => 'datetime',
|
||||
];
|
||||
}
|
||||
43
modules/EcommerceFinance/Models/JianSun/UnOrderDetails.php
Normal file
43
modules/EcommerceFinance/Models/JianSun/UnOrderDetails.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Models\JianSun;
|
||||
|
||||
/**
|
||||
* 未发出订单明细表
|
||||
* pm_unOrderDetails
|
||||
*
|
||||
* 记录已采购但尚未发出的商品明细。
|
||||
*
|
||||
* @property int $Id
|
||||
* @property string $date 日期
|
||||
* @property string $purchase 采购单号
|
||||
* @property string $warehouse 仓库
|
||||
* @property string $supplier 供应商(页面表头第4列)
|
||||
* @property string $transactionNo 交易号
|
||||
* @property string $distributionNo 配送号
|
||||
* @property string $expressNo 快递单号(页面表头第18列)
|
||||
* @property string $sku 货号
|
||||
* @property string $barCode 条码
|
||||
* @property string $skuName 规格名称
|
||||
* @property string $distributionDate 配送日期
|
||||
* @property string $shipDate 发货日期
|
||||
* @property string $expressName 快递名称(页面表头第17列)
|
||||
*/
|
||||
class UnOrderDetails extends JianSunModel
|
||||
{
|
||||
protected $table = 'pm_unOrderDetails';
|
||||
|
||||
protected $primaryKey = 'Id';
|
||||
|
||||
protected $fillable = [
|
||||
'date', 'purchase', 'warehouse', 'supplier', 'transactionNo',
|
||||
'distributionNo', 'expressNo', 'sku', 'barCode', 'skuName',
|
||||
'distributionDate', 'shipDate', 'expressName',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'date' => 'datetime',
|
||||
'distributionDate' => 'datetime',
|
||||
'shipDate' => 'datetime',
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,370 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Repositories;
|
||||
|
||||
use Modules\EcommerceFinance\Services\JianSunConnection;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* 货款对账数据仓库
|
||||
*
|
||||
* 数据来源:
|
||||
* - 入库记录:fys_supplierGoods_sttlement_purchase(~228K)+ pm_purchaseIn + pm_averageUnitPrice
|
||||
* - 代发记录:fys_supplierGoods_sttlement_sale(~2.8M)+ pm_averageUnitPrice
|
||||
*
|
||||
* 性能策略:入库/代发分开查询,各自执行分页,避免全量 UNION ALL。
|
||||
*/
|
||||
class GoodsReconciliationRepository
|
||||
{
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pdo = JianSunConnection::get();
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 汇总统计(轻量:只用 COUNT + SUM,不做复杂 JOIN)
|
||||
// ===================================================================
|
||||
|
||||
public function summary(array $filters = []): array
|
||||
{
|
||||
$purchase = $this->purchaseSummary($filters);
|
||||
$sale = $this->saleSummary($filters);
|
||||
|
||||
return [
|
||||
'totalAmount' => round($purchase['totalAmount'] + $sale['totalAmount'], 2),
|
||||
'totalGuessAmount' => round($purchase['totalGuessAmount'] + $sale['totalGuessAmount'], 2),
|
||||
'sentCount' => $purchase['sentCount'] + $sale['sentCount'],
|
||||
'unsentCount' => $purchase['unsentCount'] + $sale['unsentCount'],
|
||||
'totalCount' => $purchase['totalCount'] + $sale['totalCount'],
|
||||
];
|
||||
}
|
||||
|
||||
private function purchaseSummary(array $filters): array
|
||||
{
|
||||
$where = $this->buildPurchaseWhere($filters);
|
||||
$params = $this->buildPurchaseParams($filters);
|
||||
|
||||
// 按需 JOIN,减少不必要的全表扫描
|
||||
$joinA = $joinU = '';
|
||||
if (strpos($where, 'a.') !== false) {
|
||||
$joinA = ' LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.scmname = a.supplier';
|
||||
}
|
||||
if (strpos($where, 'u.') !== false) {
|
||||
$joinU = ' LEFT JOIN pm_unOrderDetails u ON s.spo = u.purchase AND s.skuoid = u.sku';
|
||||
}
|
||||
|
||||
$sql = "SELECT
|
||||
COUNT(*) as totalCount,
|
||||
ISNULL(SUM(s.settlementPrice), 0) as totalAmount,
|
||||
0 as totalGuessAmount,
|
||||
COUNT(*) as sentCount,
|
||||
0 as unsentCount
|
||||
FROM fys_supplierGoods_sttlement_purchase s
|
||||
{$joinA}{$joinU}
|
||||
WHERE 1=1 {$where}";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
return $row ?: ['totalCount' => 0, 'totalAmount' => 0, 'totalGuessAmount' => 0, 'sentCount' => 0, 'unsentCount' => 0];
|
||||
}
|
||||
|
||||
private function saleSummary(array $filters): array
|
||||
{
|
||||
$where = $this->buildSaleWhere($filters);
|
||||
$params = $this->buildSaleParams($filters);
|
||||
|
||||
$joinA = '';
|
||||
if (strpos($where, 'a.') !== false) {
|
||||
$joinA = ' LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.supplier = a.supplier';
|
||||
}
|
||||
|
||||
$sql = "SELECT
|
||||
COUNT(*) as totalCount,
|
||||
ISNULL(SUM(s.settlementPrice), 0) as totalAmount,
|
||||
0 as totalGuessAmount,
|
||||
ISNULL(SUM(CASE WHEN s.expressNo IS NOT NULL AND s.expressNo != '' THEN 1 ELSE 0 END), 0) as sentCount,
|
||||
ISNULL(SUM(CASE WHEN s.expressNo IS NULL OR s.expressNo = '' THEN 1 ELSE 0 END), 0) as unsentCount
|
||||
FROM fys_supplierGoods_sttlement_sale s
|
||||
{$joinA}
|
||||
WHERE 1=1 {$where}";
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
return $row ?: ['totalCount' => 0, 'totalAmount' => 0, 'totalGuessAmount' => 0, 'sentCount' => 0, 'unsentCount' => 0];
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 列表查询(核心 — 分别查入库/代发再合并)
|
||||
// ===================================================================
|
||||
|
||||
public function list(array $filters = [], int $page = 1, int $perPage = 25): array
|
||||
{
|
||||
$type = $filters['record_type'] ?? 'all';
|
||||
|
||||
// 分别查询(同一分页参数,各自内部分页)
|
||||
$purchaseResult = null;
|
||||
$saleResult = null;
|
||||
$totalPurchase = 0;
|
||||
$totalSale = 0;
|
||||
|
||||
if ($type === 'all' || $type === 'purchase') {
|
||||
$where = $this->buildPurchaseWhere($filters);
|
||||
$params = $this->buildPurchaseParams($filters);
|
||||
$totalPurchase = $this->countPurchase($where, $params);
|
||||
$purchaseResult = $this->fetchPurchase($where, $params, $page, $perPage);
|
||||
}
|
||||
|
||||
if ($type === 'all' || $type === 'sale') {
|
||||
$where = $this->buildSaleWhere($filters);
|
||||
$params = $this->buildSaleParams($filters);
|
||||
$totalSale = $this->countSale($where, $params);
|
||||
$saleResult = $this->fetchSale($where, $params, $page, $perPage);
|
||||
}
|
||||
|
||||
// 合并并按 createTime 排序,取前 perPage 条
|
||||
$merged = array_merge($purchaseResult ?? [], $saleResult ?? []);
|
||||
|
||||
// 按 createTime DESC 排序
|
||||
usort($merged, function ($a, $b) {
|
||||
return strcmp($b['createTime'] ?? '', $a['createTime'] ?? '');
|
||||
});
|
||||
|
||||
// 截取当前页
|
||||
$merged = array_slice($merged, 0, $perPage);
|
||||
|
||||
// 格式化金额
|
||||
foreach ($merged as &$row) {
|
||||
$row['amount'] = round((float) $row['amount'], 2);
|
||||
$row['guessAmount'] = round((float) $row['guessAmount'], 2);
|
||||
$row['unitPrice'] = round((float) $row['unitPrice'], 2);
|
||||
$row['guessUnitPrice'] = round((float) ($row['guessUnitPrice'] ?? 0), 2);
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => $merged,
|
||||
'total' => $totalPurchase + $totalSale,
|
||||
];
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 筛选器选项
|
||||
// ===================================================================
|
||||
|
||||
public function filterOptions(): array
|
||||
{
|
||||
$sql = "SELECT DISTINCT scmname FROM fys_supplierGoods_sttlement_purchase WHERE scmname IS NOT NULL AND scmname != ''
|
||||
UNION
|
||||
SELECT DISTINCT supplier FROM fys_supplierGoods_sttlement_sale WHERE supplier IS NOT NULL AND supplier != ''
|
||||
ORDER BY scmname";
|
||||
$stmt = $this->pdo->query($sql);
|
||||
$suppliers = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$sql = "SELECT DISTINCT whname FROM fys_supplierGoods_sttlement_purchase WHERE whname IS NOT NULL AND whname != ''
|
||||
UNION
|
||||
SELECT DISTINCT whName FROM fys_supplierGoods_sttlement_sale WHERE whName IS NOT NULL AND whName != ''
|
||||
ORDER BY whname";
|
||||
$stmt = $this->pdo->query($sql);
|
||||
$warehouses = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
return [
|
||||
'suppliers' => $suppliers,
|
||||
'warehouses' => $warehouses,
|
||||
];
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 入库查询
|
||||
// ===================================================================
|
||||
|
||||
private function countPurchase(string $where, array $params): int
|
||||
{
|
||||
// 仅当筛选条件引用关联表字段时才 JOIN,否则纯主表 COUNT
|
||||
$joins = '';
|
||||
if (strpos($where, 'p.') !== false) {
|
||||
$joins .= ' LEFT JOIN pm_purchaseIn p ON s.spo = p.sourceno';
|
||||
}
|
||||
if (strpos($where, 'a.') !== false) {
|
||||
$joins .= ' LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.scmname = a.supplier';
|
||||
}
|
||||
if (strpos($where, 'u.') !== false) {
|
||||
$joins .= ' LEFT JOIN pm_unOrderDetails u ON s.spo = u.purchase AND s.skuoid = u.sku';
|
||||
}
|
||||
$sql = "SELECT COUNT(*) as cnt FROM fys_supplierGoods_sttlement_purchase s{$joins} WHERE 1=1 {$where}";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return (int) $stmt->fetch()['cnt'];
|
||||
}
|
||||
|
||||
private function fetchPurchase(string $where, array $params, int $page, int $perPage): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$sql = "SELECT
|
||||
'purchase' AS recordType,
|
||||
ISNULL(p.hno, '') AS stockNo,
|
||||
ISNULL(s.spo, '') AS purchaseNo,
|
||||
FORMAT(ISNULL(s.created, s.createTime), 'yyyy-MM-dd HH:mm:ss') AS sendTime,
|
||||
ISNULL(s.scmname, '') AS supplier,
|
||||
ISNULL(s.skuoid, '') AS goodsNo,
|
||||
ISNULL(s.procode, '') AS productCode,
|
||||
ISNULL(s.skuname, '') AS specName,
|
||||
ISNULL(s.settlementStock, 0) AS qty,
|
||||
ISNULL(s.singlePrice, 0) AS unitPrice,
|
||||
ISNULL(s.settlementPrice, 0) AS amount,
|
||||
ISNULL(a.price, 0) AS guessUnitPrice,
|
||||
ISNULL(s.settlementStock * ISNULL(a.price, 0), 0) AS guessAmount,
|
||||
'' AS supplierGoodsCode,
|
||||
ISNULL(s.remark, '') AS remark,
|
||||
ISNULL(s.whname, '') AS warehouseName,
|
||||
FORMAT(s.createTime, 'yyyy-MM-dd HH:mm:ss') AS createTime,
|
||||
'' AS expressName,
|
||||
'' AS expressNo,
|
||||
CASE WHEN u.Id IS NOT NULL THEN 1 ELSE 0 END AS isUnsent,
|
||||
s.id AS sourceId
|
||||
FROM fys_supplierGoods_sttlement_purchase s
|
||||
LEFT JOIN pm_purchaseIn p ON s.spo = p.sourceno
|
||||
LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.scmname = a.supplier
|
||||
LEFT JOIN pm_unOrderDetails u ON s.spo = u.purchase AND s.skuoid = u.sku
|
||||
WHERE 1=1 {$where}
|
||||
ORDER BY s.createTime DESC
|
||||
OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// 代发查询
|
||||
// ===================================================================
|
||||
|
||||
private function countSale(string $where, array $params): int
|
||||
{
|
||||
$sql = "SELECT COUNT(*) as cnt FROM fys_supplierGoods_sttlement_sale s
|
||||
LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.supplier = a.supplier
|
||||
WHERE 1=1 {$where}";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return (int) $stmt->fetch()['cnt'];
|
||||
}
|
||||
|
||||
private function fetchSale(string $where, array $params, int $page, int $perPage): array
|
||||
{
|
||||
$offset = ($page - 1) * $perPage;
|
||||
$sql = "SELECT
|
||||
'sale' AS recordType,
|
||||
'' AS stockNo,
|
||||
'' AS purchaseNo,
|
||||
FORMAT(s.sendTime, 'yyyy-MM-dd HH:mm:ss') AS sendTime,
|
||||
ISNULL(s.supplier, '') AS supplier,
|
||||
ISNULL(s.skucode, '') AS goodsNo,
|
||||
ISNULL(s.procode, '') AS productCode,
|
||||
ISNULL(s.skuname, '') AS specName,
|
||||
ISNULL(s.settlementStock, 0) AS qty,
|
||||
ISNULL(s.singlePrice, 0) AS unitPrice,
|
||||
ISNULL(s.settlementPrice, 0) AS amount,
|
||||
ISNULL(a.price, 0) AS guessUnitPrice,
|
||||
ISNULL(s.settlementStock * ISNULL(a.price, 0), 0) AS guessAmount,
|
||||
'' AS supplierGoodsCode,
|
||||
ISNULL(s.remark, '') AS remark,
|
||||
ISNULL(s.whName, '') AS warehouseName,
|
||||
FORMAT(s.createTime, 'yyyy-MM-dd HH:mm:ss') AS createTime,
|
||||
ISNULL(s.expressName, '') AS expressName,
|
||||
ISNULL(s.expressNo, '') AS expressNo,
|
||||
CASE WHEN s.expressNo IS NULL OR s.expressNo = '' THEN 1 ELSE 0 END AS isUnsent,
|
||||
s.Id AS sourceId
|
||||
FROM fys_supplierGoods_sttlement_sale s
|
||||
LEFT JOIN pm_averageUnitPrice a ON s.procode = a.number AND s.supplier = a.supplier
|
||||
WHERE 1=1 {$where}
|
||||
ORDER BY s.createTime DESC
|
||||
OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY";
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// WHERE 与参数构建
|
||||
// ===================================================================
|
||||
|
||||
private function like(string $v): string
|
||||
{
|
||||
return '%' . $v . '%';
|
||||
}
|
||||
|
||||
// --- 入库 WHERE ---
|
||||
private function buildPurchaseWhere(array $filters): string
|
||||
{
|
||||
$c = [];
|
||||
if (!empty($filters['supplier'])) $c[] = 'AND s.scmname LIKE :supplier';
|
||||
if (!empty($filters['stock_no'])) $c[] = 'AND p.hno LIKE :stock_no';
|
||||
if (!empty($filters['purchase_no'])) $c[] = 'AND s.spo LIKE :purchase_no';
|
||||
if (!empty($filters['product_code'])) $c[] = 'AND s.procode LIKE :product_code';
|
||||
if (!empty($filters['warehouse'])) $c[] = 'AND s.whname LIKE :warehouse';
|
||||
if (!empty($filters['send_time_from'])) $c[] = 'AND s.created >= :send_time_from';
|
||||
if (!empty($filters['send_time_to'])) $c[] = 'AND s.created <= :send_time_to';
|
||||
if (!empty($filters['create_time_from'])) $c[] = 'AND s.createTime >= :create_time_from';
|
||||
if (!empty($filters['create_time_to'])) $c[] = 'AND s.createTime <= :create_time_to';
|
||||
if (!empty($filters['sent_status'])) {
|
||||
if ($filters['sent_status'] === 'sent') $c[] = 'AND u.Id IS NULL';
|
||||
if ($filters['sent_status'] === 'unsent') $c[] = 'AND u.Id IS NOT NULL';
|
||||
}
|
||||
if (!empty($filters['has_diff'])) {
|
||||
$c[] = 'AND ABS(ISNULL(s.settlementPrice, 0) - ISNULL(s.settlementStock * ISNULL(a.price, 0), 0)) > 0.01';
|
||||
}
|
||||
return $c ? ' ' . implode(' ', $c) : '';
|
||||
}
|
||||
|
||||
private function buildPurchaseParams(array $filters): array
|
||||
{
|
||||
$p = [];
|
||||
if (!empty($filters['supplier'])) $p[':supplier'] = $this->like($filters['supplier']);
|
||||
if (!empty($filters['stock_no'])) $p[':stock_no'] = $this->like($filters['stock_no']);
|
||||
if (!empty($filters['purchase_no'])) $p[':purchase_no'] = $this->like($filters['purchase_no']);
|
||||
if (!empty($filters['product_code'])) $p[':product_code'] = $this->like($filters['product_code']);
|
||||
if (!empty($filters['warehouse'])) $p[':warehouse'] = $this->like($filters['warehouse']);
|
||||
if (!empty($filters['send_time_from'])) $p[':send_time_from'] = $filters['send_time_from'];
|
||||
if (!empty($filters['send_time_to'])) $p[':send_time_to'] = $filters['send_time_to'];
|
||||
if (!empty($filters['create_time_from'])) $p[':create_time_from'] = $filters['create_time_from'];
|
||||
if (!empty($filters['create_time_to'])) $p[':create_time_to'] = $filters['create_time_to'];
|
||||
return $p;
|
||||
}
|
||||
|
||||
// --- 代发 WHERE ---
|
||||
private function buildSaleWhere(array $filters): string
|
||||
{
|
||||
$c = [];
|
||||
if (!empty($filters['supplier'])) $c[] = 'AND s.supplier LIKE :supplier_s';
|
||||
if (!empty($filters['product_code'])) $c[] = 'AND s.procode LIKE :product_code_s';
|
||||
if (!empty($filters['express_no'])) $c[] = 'AND s.expressNo LIKE :express_no';
|
||||
if (!empty($filters['warehouse'])) $c[] = 'AND s.whName LIKE :warehouse_s';
|
||||
if (!empty($filters['send_time_from'])) $c[] = 'AND s.sendTime >= :send_time_from_s';
|
||||
if (!empty($filters['send_time_to'])) $c[] = 'AND s.sendTime <= :send_time_to_s';
|
||||
if (!empty($filters['create_time_from'])) $c[] = 'AND s.createTime >= :create_time_from_s';
|
||||
if (!empty($filters['create_time_to'])) $c[] = 'AND s.createTime <= :create_time_to_s';
|
||||
if (!empty($filters['sent_status'])) {
|
||||
if ($filters['sent_status'] === 'sent') $c[] = "AND s.expressNo IS NOT NULL AND s.expressNo != ''";
|
||||
if ($filters['sent_status'] === 'unsent') $c[] = "AND (s.expressNo IS NULL OR s.expressNo = '')";
|
||||
}
|
||||
if (!empty($filters['has_diff'])) {
|
||||
$c[] = 'AND ABS(ISNULL(s.settlementPrice, 0) - ISNULL(s.settlementStock * ISNULL(a.price, 0), 0)) > 0.01';
|
||||
}
|
||||
return $c ? ' ' . implode(' ', $c) : '';
|
||||
}
|
||||
|
||||
private function buildSaleParams(array $filters): array
|
||||
{
|
||||
$p = [];
|
||||
if (!empty($filters['supplier'])) $p[':supplier_s'] = $this->like($filters['supplier']);
|
||||
if (!empty($filters['product_code'])) $p[':product_code_s'] = $this->like($filters['product_code']);
|
||||
if (!empty($filters['express_no'])) $p[':express_no'] = $this->like($filters['express_no']);
|
||||
if (!empty($filters['warehouse'])) $p[':warehouse_s'] = $this->like($filters['warehouse']);
|
||||
if (!empty($filters['send_time_from'])) $p[':send_time_from_s'] = $filters['send_time_from'];
|
||||
if (!empty($filters['send_time_to'])) $p[':send_time_to_s'] = $filters['send_time_to'];
|
||||
if (!empty($filters['create_time_from'])) $p[':create_time_from_s'] = $filters['create_time_from'];
|
||||
if (!empty($filters['create_time_to'])) $p[':create_time_to_s'] = $filters['create_time_to'];
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,540 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Repositories;
|
||||
|
||||
use Modules\EcommerceFinance\Services\JianSunConnection;
|
||||
use PDO;
|
||||
|
||||
/**
|
||||
* 供应商应付 - 子功能页面数据仓库
|
||||
*
|
||||
* 封装 5 个子页面的数据访问逻辑:
|
||||
* - returnReconciliations() 退货对账
|
||||
* - invoiceDeductions() 发票扣款
|
||||
* - compensationDetails() 赔偿明细(UNION 纸板 + 压板)
|
||||
* - advanceDetails() 借支明细
|
||||
* - sampleDeductions() 样品扣款
|
||||
*
|
||||
* 所有方法返回统一结构: ['data' => array<int,array>, 'total' => int]
|
||||
*/
|
||||
class SupplierPayableRepository
|
||||
{
|
||||
private PDO $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->db = JianSunConnection::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用工具:带筛选 + 分页的查询
|
||||
*/
|
||||
private function runQuery(string $sql, array $params, int $page, int $pageSize, int $countThreshold = 5000): array
|
||||
{
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
|
||||
// 1. 总数(先查总数,超阈值再用近似)
|
||||
$countSql = 'SELECT COUNT(*) AS cnt FROM (' . $sql . ') AS sub';
|
||||
$total = 0;
|
||||
try {
|
||||
$stmt = $this->db->prepare($countSql);
|
||||
$stmt->execute($params);
|
||||
$total = (int) $stmt->fetchColumn();
|
||||
} catch (\Throwable $e) {
|
||||
// 极端情况:表太大查不到总数时退回到 -1
|
||||
$total = -1;
|
||||
}
|
||||
|
||||
// 2. 分页数据(用 TOP + OFFSET/FETCH 不可行,统一使用 ROW_NUMBER() OVER 包装)
|
||||
$pagedSql = 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rn FROM (' . $sql . ') AS src) AS t WHERE rn BETWEEN ? AND ?';
|
||||
$paramsWithRange = array_merge($params, [$offset + 1, $offset + $pageSize]);
|
||||
|
||||
$stmt = $this->db->prepare($pagedSql);
|
||||
$stmt->execute($paramsWithRange);
|
||||
|
||||
return [
|
||||
'data' => $stmt->fetchAll(),
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算分页的 SELECT TOP N(无 COUNT),加速大表查询
|
||||
*/
|
||||
private function runQueryFast(string $sql, array $params, int $page, int $pageSize, int $total = -1): array
|
||||
{
|
||||
$offset = ($page - 1) * $pageSize;
|
||||
$pagedSql = 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS rn FROM (' . $sql . ') AS src) AS t WHERE rn BETWEEN ? AND ?';
|
||||
$paramsWithRange = array_merge($params, [$offset + 1, $offset + $pageSize]);
|
||||
$stmt = $this->db->prepare($pagedSql);
|
||||
$stmt->execute($paramsWithRange);
|
||||
|
||||
return [
|
||||
'data' => $stmt->fetchAll(),
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 1. 退货对账 — finance_purchaseReturn (36791行)
|
||||
// ============================================================
|
||||
public function returnReconciliations(array $filters, int $page, int $pageSize): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplierName LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
if (!empty($filters['warehouse'])) {
|
||||
$where[] = 'warehouseName LIKE ?';
|
||||
$params[] = '%' . $filters['warehouse'] . '%';
|
||||
}
|
||||
if (!empty($filters['sku_id'])) {
|
||||
$where[] = 'sku_id LIKE ?';
|
||||
$params[] = '%' . $filters['sku_id'] . '%';
|
||||
}
|
||||
if (!empty($filters['express_no'])) {
|
||||
$where[] = 'expressNo LIKE ?';
|
||||
$params[] = '%' . $filters['express_no'] . '%';
|
||||
}
|
||||
if (!empty($filters['date_from'])) {
|
||||
$where[] = 'date >= ?';
|
||||
$params[] = $filters['date_from'];
|
||||
}
|
||||
if (!empty($filters['date_to'])) {
|
||||
$where[] = 'date <= ?';
|
||||
$params[] = $filters['date_to'];
|
||||
}
|
||||
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
CONVERT(varchar(10), date, 120) AS send_time,
|
||||
supplierName AS supplier,
|
||||
sku_id AS barcode,
|
||||
name AS sku_name,
|
||||
CAST(qty AS decimal(18,2)) AS qty,
|
||||
CAST(price AS decimal(18,2)) AS unit_price,
|
||||
CAST(price * qty AS decimal(18,2)) AS amount,
|
||||
CAST(priceB AS decimal(18,2)) AS guess_unit_price,
|
||||
CAST(priceB * qty AS decimal(18,2)) AS guess_amount,
|
||||
remark,
|
||||
io_id AS wms_pick_no,
|
||||
warehouseName AS warehouse,
|
||||
expressNo AS express_no,
|
||||
expressName AS express_name,
|
||||
CONVERT(varchar(10), io_date, 120) AS io_date,
|
||||
labels,
|
||||
creator_name AS creator,
|
||||
'' AS title,
|
||||
'' AS supplier_barcode,
|
||||
'' AS original_barcode,
|
||||
'' AS reconciler
|
||||
FROM finance_purchaseReturn
|
||||
$whereSql";
|
||||
|
||||
$result = $this->runQuery($sql, $params, $page, $pageSize);
|
||||
|
||||
return [
|
||||
'data' => $result['data'],
|
||||
'total' => $result['total'],
|
||||
];
|
||||
}
|
||||
|
||||
public function returnReconciliationSummary(array $filters): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplierName LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
if (!empty($filters['date_from'])) {
|
||||
$where[] = 'date >= ?';
|
||||
$params[] = $filters['date_from'];
|
||||
}
|
||||
if (!empty($filters['date_to'])) {
|
||||
$where[] = 'date <= ?';
|
||||
$params[] = $filters['date_to'];
|
||||
}
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
ISNULL(SUM(price * qty), 0) AS total_amount,
|
||||
ISNULL(SUM(priceB * qty), 0) AS total_guess_amount,
|
||||
COUNT(*) AS total_count
|
||||
FROM finance_purchaseReturn $whereSql";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return [
|
||||
'totalAmount' => (float) $row['total_amount'],
|
||||
'totalGuessAmount' => (float) $row['total_guess_amount'],
|
||||
'totalCount' => (int) $row['total_count'],
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. 发票扣款 — finance_invoiceDeduction (195行)
|
||||
// ============================================================
|
||||
public function invoiceDeductions(array $filters, int $page, int $pageSize): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
if (!empty($filters['month_from'])) {
|
||||
$where[] = 'month >= ?';
|
||||
$params[] = $filters['month_from'];
|
||||
}
|
||||
if (!empty($filters['month_to'])) {
|
||||
$where[] = 'month <= ?';
|
||||
$params[] = $filters['month_to'];
|
||||
}
|
||||
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
CONVERT(varchar(7), month, 120) AS month_str,
|
||||
'' AS category,
|
||||
supplier AS payee,
|
||||
supplier AS receiver,
|
||||
'' AS board_factory,
|
||||
CAST(0 AS decimal(18,2)) AS subsidy_fee,
|
||||
remark,
|
||||
CAST(price AS decimal(18,2)) AS price,
|
||||
remark AS detail,
|
||||
'' AS note,
|
||||
personCharge AS person_charge,
|
||||
CONVERT(varchar(19), createTime, 120) AS create_time,
|
||||
creator
|
||||
FROM finance_invoiceDeduction
|
||||
$whereSql";
|
||||
|
||||
return $this->runQuery($sql, $params, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function invoiceDeductionSummary(array $filters): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT ISNULL(SUM(price), 0) AS total_price, COUNT(*) AS total_count FROM finance_invoiceDeduction $whereSql";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return [
|
||||
'totalPrice' => (float) $row['total_price'],
|
||||
'totalCount' => (int) $row['total_count'],
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. 赔偿明细 — fys_paper_factory + fys_pressing_factory UNION
|
||||
// ============================================================
|
||||
public function compensationDetails(array $filters, int $page, int $pageSize): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
if (!empty($filters['month_from'])) {
|
||||
$where[] = 'month >= ?';
|
||||
$params[] = $filters['month_from'];
|
||||
}
|
||||
if (!empty($filters['month_to'])) {
|
||||
$where[] = 'month <= ?';
|
||||
$params[] = $filters['month_to'];
|
||||
}
|
||||
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
month,
|
||||
supplier,
|
||||
CAST(compensation_amount AS decimal(18,2)) AS amount,
|
||||
reason,
|
||||
'' AS remark,
|
||||
creator,
|
||||
CONVERT(varchar(19), create_time, 120) AS create_time,
|
||||
'纸板厂' AS factory_type
|
||||
FROM (
|
||||
SELECT
|
||||
month,
|
||||
factory_subject_name AS supplier,
|
||||
compensation_amount,
|
||||
reason,
|
||||
creator,
|
||||
create_time
|
||||
FROM fys_paper_factory_compensation_detail
|
||||
UNION ALL
|
||||
SELECT
|
||||
month,
|
||||
factory_subject_name AS supplier,
|
||||
compensation_amount,
|
||||
reason,
|
||||
creator,
|
||||
create_time
|
||||
FROM fys_pressing_factory_compensation_detail
|
||||
) AS t
|
||||
$whereSql
|
||||
ORDER BY month DESC, create_time DESC";
|
||||
|
||||
return $this->runQuery($sql, $params, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function compensationDetailSummary(array $filters): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
ISNULL(SUM(compensation_amount), 0) AS total_amount,
|
||||
COUNT(*) AS total_count
|
||||
FROM (
|
||||
SELECT compensation_amount, factory_subject_name AS supplier FROM fys_paper_factory_compensation_detail
|
||||
UNION ALL
|
||||
SELECT compensation_amount, factory_subject_name AS supplier FROM fys_pressing_factory_compensation_detail
|
||||
) AS t
|
||||
$whereSql";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return [
|
||||
'totalAmount' => (float) $row['total_amount'],
|
||||
'totalCount' => (int) $row['total_count'],
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 4. 借支明细 — finance_advanceLoans (4行, 字段不足,部分列显示-)
|
||||
// ============================================================
|
||||
public function advanceDetails(array $filters, int $page, int $pageSize): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
if (!empty($filters['month_from'])) {
|
||||
$where[] = 'month >= ?';
|
||||
$params[] = $filters['month_from'];
|
||||
}
|
||||
if (!empty($filters['month_to'])) {
|
||||
$where[] = 'month <= ?';
|
||||
$params[] = $filters['month_to'];
|
||||
}
|
||||
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
supplier,
|
||||
'' AS contract_name,
|
||||
CAST(price AS decimal(18,2)) AS advance_amount,
|
||||
CAST(0 AS decimal(18,2)) AS actual_pay,
|
||||
CAST(0 AS decimal(18,2)) AS month_deduct,
|
||||
CAST(0 AS decimal(18,2)) AS should_deduct,
|
||||
'' AS deduct_start_month,
|
||||
'' AS deduct_end_month,
|
||||
0 AS remain_periods,
|
||||
CONVERT(varchar(7), month, 120) AS month_str,
|
||||
'' AS loan_term,
|
||||
remark,
|
||||
'' AS is_paid,
|
||||
CONVERT(varchar(7), month, 120) AS pay_month,
|
||||
'' AS serial_no,
|
||||
'' AS loan_agreement,
|
||||
personCharge AS person_charge,
|
||||
'' AS receipt_0,
|
||||
'' AS receipt_1,
|
||||
'' AS receipt_2,
|
||||
CONVERT(varchar(19), createTime, 120) AS create_time,
|
||||
creator
|
||||
FROM finance_advanceLoans
|
||||
$whereSql
|
||||
ORDER BY month DESC, createTime DESC";
|
||||
|
||||
return $this->runQuery($sql, $params, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function advanceDetailSummary(array $filters): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT ISNULL(SUM(price), 0) AS total_amount, COUNT(*) AS total_count FROM finance_advanceLoans $whereSql";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return [
|
||||
'totalAmount' => (float) $row['total_amount'],
|
||||
'totalCount' => (int) $row['total_count'],
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. 样品扣款 — finance_sampleDeduction (12行)
|
||||
// ============================================================
|
||||
public function sampleDeductions(array $filters, int $page, int $pageSize): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
if (!empty($filters['department'])) {
|
||||
$where[] = 'department LIKE ?';
|
||||
$params[] = '%' . $filters['department'] . '%';
|
||||
}
|
||||
if (!empty($filters['creator'])) {
|
||||
$where[] = 'creator LIKE ?';
|
||||
$params[] = '%' . $filters['creator'] . '%';
|
||||
}
|
||||
if (!empty($filters['sku_id'])) {
|
||||
$where[] = 'sku_id LIKE ?';
|
||||
$params[] = '%' . $filters['sku_id'] . '%';
|
||||
}
|
||||
if (!empty($filters['date_from'])) {
|
||||
$where[] = 'finishTime >= ?';
|
||||
$params[] = $filters['date_from'];
|
||||
}
|
||||
if (!empty($filters['date_to'])) {
|
||||
$where[] = 'finishTime <= ?';
|
||||
$params[] = $filters['date_to'];
|
||||
}
|
||||
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
CONVERT(varchar(10), finishTime, 120) AS finish_time,
|
||||
department,
|
||||
creator,
|
||||
sku_id,
|
||||
skuName,
|
||||
CAST(num AS decimal(18,2)) AS num,
|
||||
supplier,
|
||||
purchase,
|
||||
productCode,
|
||||
CAST(shippingFee AS decimal(18,2)) AS shipping_fee,
|
||||
CAST(costPrice AS decimal(18,2)) AS cost_price,
|
||||
CAST(totalAmount AS decimal(18,2)) AS total_amount,
|
||||
CAST(estimatedPrice AS decimal(18,2)) AS estimated_price,
|
||||
CAST(estimatedPriceAll AS decimal(18,2)) AS estimated_price_all,
|
||||
'' AS remark,
|
||||
businessId,
|
||||
CONVERT(varchar(19), updateAt, 120) AS update_time
|
||||
FROM finance_sampleDeduction
|
||||
$whereSql
|
||||
ORDER BY finishTime DESC";
|
||||
|
||||
return $this->runQuery($sql, $params, $page, $pageSize);
|
||||
}
|
||||
|
||||
public function sampleDeductionSummary(array $filters): array
|
||||
{
|
||||
$where = [];
|
||||
$params = [];
|
||||
if (!empty($filters['supplier'])) {
|
||||
$where[] = 'supplier LIKE ?';
|
||||
$params[] = '%' . $filters['supplier'] . '%';
|
||||
}
|
||||
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
|
||||
|
||||
$sql = "SELECT
|
||||
ISNULL(SUM(totalAmount), 0) AS total_amount,
|
||||
ISNULL(SUM(estimatedPriceAll), 0) AS total_estimate,
|
||||
COUNT(*) AS total_count
|
||||
FROM finance_sampleDeduction $whereSql";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
return [
|
||||
'totalAmount' => (float) $row['total_amount'],
|
||||
'totalEstimate' => (float) $row['total_estimate'],
|
||||
'totalCount' => (int) $row['total_count'],
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 筛选器选项
|
||||
// ============================================================
|
||||
public function filterOptionsForReturn(): array
|
||||
{
|
||||
$suppliers = $this->fetchDistinct('SELECT DISTINCT supplierName FROM finance_purchaseReturn WHERE supplierName IS NOT NULL ORDER BY supplierName');
|
||||
$warehouses = $this->fetchDistinct('SELECT DISTINCT warehouseName FROM finance_purchaseReturn WHERE warehouseName IS NOT NULL ORDER BY warehouseName');
|
||||
|
||||
return compact('suppliers', 'warehouses');
|
||||
}
|
||||
|
||||
public function filterOptionsForInvoice(): array
|
||||
{
|
||||
$suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_invoiceDeduction WHERE supplier IS NOT NULL ORDER BY supplier');
|
||||
|
||||
return ['suppliers' => $suppliers];
|
||||
}
|
||||
|
||||
public function filterOptionsForCompensation(): array
|
||||
{
|
||||
$suppliers = $this->fetchDistinct("SELECT DISTINCT supplier FROM (
|
||||
SELECT factory_subject_name AS supplier FROM fys_paper_factory_compensation_detail WHERE factory_subject_name IS NOT NULL
|
||||
UNION
|
||||
SELECT factory_subject_name AS supplier FROM fys_pressing_factory_compensation_detail WHERE factory_subject_name IS NOT NULL
|
||||
) AS t ORDER BY supplier");
|
||||
|
||||
return ['suppliers' => $suppliers];
|
||||
}
|
||||
|
||||
public function filterOptionsForAdvance(): array
|
||||
{
|
||||
$suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_advanceLoans WHERE supplier IS NOT NULL ORDER BY supplier');
|
||||
|
||||
return ['suppliers' => $suppliers];
|
||||
}
|
||||
|
||||
public function filterOptionsForSample(): array
|
||||
{
|
||||
$suppliers = $this->fetchDistinct('SELECT DISTINCT supplier FROM finance_sampleDeduction WHERE supplier IS NOT NULL ORDER BY supplier');
|
||||
$departments = $this->fetchDistinct('SELECT DISTINCT department FROM finance_sampleDeduction WHERE department IS NOT NULL ORDER BY department');
|
||||
$creators = $this->fetchDistinct('SELECT DISTINCT creator FROM finance_sampleDeduction WHERE creator IS NOT NULL ORDER BY creator');
|
||||
|
||||
return compact('suppliers', 'departments', 'creators');
|
||||
}
|
||||
|
||||
private function fetchDistinct(string $sql): array
|
||||
{
|
||||
$rows = $this->db->query($sql)->fetchAll(PDO::FETCH_COLUMN);
|
||||
return array_values(array_filter($rows, fn ($v) => $v !== null && $v !== ''));
|
||||
}
|
||||
}
|
||||
@ -8,4 +8,37 @@ Route::api('ecommerce-finance', function () {
|
||||
Route::apiResource('stores', 'Stores')->except(['destroy']);
|
||||
Route::apiResource('import-batches', 'ImportBatches')->only(['index', 'show', 'store']);
|
||||
Route::get('reconciliation-exceptions', 'ReconciliationExceptions@index')->name('reconciliation-exceptions.index');
|
||||
|
||||
// 货款对账
|
||||
Route::get('goods-reconciliations', 'GoodsReconciliations@index')->name('goods-reconciliations.index');
|
||||
Route::get('goods-reconciliations/summary', 'GoodsReconciliations@summary')->name('goods-reconciliations.summary');
|
||||
Route::get('goods-reconciliations/options', 'GoodsReconciliations@options')->name('goods-reconciliations.options');
|
||||
|
||||
// 供应商应付 - 5 个子功能页面
|
||||
Route::prefix('supplier-payables')->group(function () {
|
||||
// 退货对账
|
||||
Route::get('return-reconciliations', 'SupplierPayables@returnReconciliationsIndex');
|
||||
Route::get('return-reconciliations/summary', 'SupplierPayables@returnReconciliationsSummary');
|
||||
Route::get('return-reconciliations/options', 'SupplierPayables@returnReconciliationsOptions');
|
||||
|
||||
// 发票扣款
|
||||
Route::get('invoice-deductions', 'SupplierPayables@invoiceDeductionsIndex');
|
||||
Route::get('invoice-deductions/summary', 'SupplierPayables@invoiceDeductionsSummary');
|
||||
Route::get('invoice-deductions/options', 'SupplierPayables@invoiceDeductionsOptions');
|
||||
|
||||
// 赔偿明细
|
||||
Route::get('compensation-details', 'SupplierPayables@compensationDetailsIndex');
|
||||
Route::get('compensation-details/summary', 'SupplierPayables@compensationDetailsSummary');
|
||||
Route::get('compensation-details/options', 'SupplierPayables@compensationDetailsOptions');
|
||||
|
||||
// 借支明细
|
||||
Route::get('advance-details', 'SupplierPayables@advanceDetailsIndex');
|
||||
Route::get('advance-details/summary', 'SupplierPayables@advanceDetailsSummary');
|
||||
Route::get('advance-details/options', 'SupplierPayables@advanceDetailsOptions');
|
||||
|
||||
// 样品扣款
|
||||
Route::get('sample-deductions', 'SupplierPayables@sampleDeductionsIndex');
|
||||
Route::get('sample-deductions/summary', 'SupplierPayables@sampleDeductionsSummary');
|
||||
Route::get('sample-deductions/options', 'SupplierPayables@sampleDeductionsOptions');
|
||||
});
|
||||
});
|
||||
|
||||
70
modules/EcommerceFinance/Services/JianSunConnection.php
Normal file
70
modules/EcommerceFinance/Services/JianSunConnection.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\EcommerceFinance\Services;
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* JianSun SQL Server 数据库连接管理
|
||||
* 使用 PDO_ODBC 直连 SQL Server(无需 sqlsrv 扩展)
|
||||
*/
|
||||
class JianSunConnection
|
||||
{
|
||||
private static ?PDO $instance = null;
|
||||
|
||||
/**
|
||||
* 获取 PDO 连接实例(单例模式)
|
||||
*/
|
||||
public static function get(): PDO
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
self::$instance = self::create();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的 PDO 连接
|
||||
*/
|
||||
public static function create(): PDO
|
||||
{
|
||||
$host = env('DB_JIANSUN_HOST', '218.93.191.21');
|
||||
$port = env('DB_JIANSUN_PORT', '1433');
|
||||
$db = env('DB_JIANSUN_DATABASE', 'JianSun');
|
||||
$user = env('DB_JIANSUN_USERNAME', 'financeReader');
|
||||
$pass = env('DB_JIANSUN_PASSWORD', '');
|
||||
|
||||
$dsn = sprintf(
|
||||
'odbc:Driver={SQL Server};Server=%s,%s;Database=%s',
|
||||
$host,
|
||||
$port,
|
||||
$db
|
||||
);
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
return $pdo;
|
||||
} catch (PDOException $e) {
|
||||
throw new \RuntimeException(
|
||||
'JianSun 数据库连接失败: ' . $e->getMessage(),
|
||||
500,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置连接(用于长时间运行或测试场景)
|
||||
*/
|
||||
public static function reset(): void
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
71378
public/css/app.css
vendored
71378
public/css/app.css
vendored
File diff suppressed because one or more lines are too long
84
resources/assets/js/views/ecommerce-finance/advance-details.js
vendored
Normal file
84
resources/assets/js/views/ecommerce-finance/advance-details.js
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
require('./../../bootstrap');
|
||||
|
||||
import Vue from 'vue';
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
import {
|
||||
Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Message
|
||||
} from 'element-ui';
|
||||
|
||||
Vue.use(DashboardPlugin);
|
||||
Vue.component(Table.name, Table);
|
||||
Vue.component(TableColumn.name, TableColumn);
|
||||
Vue.component(Pagination.name, Pagination);
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
Vue.component(Tag.name, Tag);
|
||||
Vue.component(Input.name, Input);
|
||||
Vue.component(Button.name, Button);
|
||||
Vue.prototype.$message = Message;
|
||||
|
||||
const app = new Vue({
|
||||
el: '#advance-detail-app',
|
||||
|
||||
mixins: [Global],
|
||||
|
||||
data() {
|
||||
return {
|
||||
baseUrl: url + '/api/ecommerce-finance/supplier-payables/advance-details',
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
summary: { totalAmount: 0, totalCount: 0 },
|
||||
filters: { supplier: '', month_range: null }
|
||||
};
|
||||
},
|
||||
|
||||
mounted() { this.fetchData(); },
|
||||
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.loading = true;
|
||||
const params = this.buildParams();
|
||||
Promise.all([
|
||||
axios.get(this.baseUrl, { params }),
|
||||
axios.get(this.baseUrl + '/summary', { params })
|
||||
]).then(([listRes, sumRes]) => {
|
||||
this.tableData = listRes.data.data || [];
|
||||
this.total = listRes.data.total || 0;
|
||||
this.currentPage = listRes.data.page || 1;
|
||||
this.pageSize = listRes.data.per_page || 25;
|
||||
this.summary = sumRes.data;
|
||||
}).catch(() => this.$message.error('获取数据失败'))
|
||||
.finally(() => { this.loading = false; });
|
||||
},
|
||||
|
||||
buildParams() {
|
||||
const params = { page: this.currentPage, limit: this.pageSize };
|
||||
if (this.filters.supplier) params.supplier = this.filters.supplier;
|
||||
if (this.filters.month_range && this.filters.month_range.length === 2) {
|
||||
params.month_from = this.filters.month_range[0];
|
||||
params.month_to = this.filters.month_range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
onFilterChange() { this.currentPage = 1; this.fetchData(); },
|
||||
onPageChange(p) { this.currentPage = p; this.fetchData(); },
|
||||
onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); },
|
||||
onReset() {
|
||||
this.filters = { supplier: '', month_range: null };
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
formatMoney(v) {
|
||||
if (v == null || isNaN(v)) return '0.00';
|
||||
return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
84
resources/assets/js/views/ecommerce-finance/compensation-details.js
vendored
Normal file
84
resources/assets/js/views/ecommerce-finance/compensation-details.js
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
require('./../../bootstrap');
|
||||
|
||||
import Vue from 'vue';
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
import {
|
||||
Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Message
|
||||
} from 'element-ui';
|
||||
|
||||
Vue.use(DashboardPlugin);
|
||||
Vue.component(Table.name, Table);
|
||||
Vue.component(TableColumn.name, TableColumn);
|
||||
Vue.component(Pagination.name, Pagination);
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
Vue.component(Tag.name, Tag);
|
||||
Vue.component(Input.name, Input);
|
||||
Vue.component(Button.name, Button);
|
||||
Vue.prototype.$message = Message;
|
||||
|
||||
const app = new Vue({
|
||||
el: '#compensation-detail-app',
|
||||
|
||||
mixins: [Global],
|
||||
|
||||
data() {
|
||||
return {
|
||||
baseUrl: url + '/api/ecommerce-finance/supplier-payables/compensation-details',
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
summary: { totalAmount: 0, totalCount: 0 },
|
||||
filters: { supplier: '', month_range: null }
|
||||
};
|
||||
},
|
||||
|
||||
mounted() { this.fetchData(); },
|
||||
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.loading = true;
|
||||
const params = this.buildParams();
|
||||
Promise.all([
|
||||
axios.get(this.baseUrl, { params }),
|
||||
axios.get(this.baseUrl + '/summary', { params })
|
||||
]).then(([listRes, sumRes]) => {
|
||||
this.tableData = listRes.data.data || [];
|
||||
this.total = listRes.data.total || 0;
|
||||
this.currentPage = listRes.data.page || 1;
|
||||
this.pageSize = listRes.data.per_page || 25;
|
||||
this.summary = sumRes.data;
|
||||
}).catch(() => this.$message.error('获取数据失败'))
|
||||
.finally(() => { this.loading = false; });
|
||||
},
|
||||
|
||||
buildParams() {
|
||||
const params = { page: this.currentPage, limit: this.pageSize };
|
||||
if (this.filters.supplier) params.supplier = this.filters.supplier;
|
||||
if (this.filters.month_range && this.filters.month_range.length === 2) {
|
||||
params.month_from = this.filters.month_range[0];
|
||||
params.month_to = this.filters.month_range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
onFilterChange() { this.currentPage = 1; this.fetchData(); },
|
||||
onPageChange(p) { this.currentPage = p; this.fetchData(); },
|
||||
onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); },
|
||||
onReset() {
|
||||
this.filters = { supplier: '', month_range: null };
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
formatMoney(v) {
|
||||
if (v == null || isNaN(v)) return '0.00';
|
||||
return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
277
resources/assets/js/views/ecommerce-finance/goods-reconciliations.js
vendored
Normal file
277
resources/assets/js/views/ecommerce-finance/goods-reconciliations.js
vendored
Normal file
@ -0,0 +1,277 @@
|
||||
require('./../../bootstrap');
|
||||
|
||||
import Vue from 'vue';
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableColumn,
|
||||
Pagination,
|
||||
DatePicker,
|
||||
Tag,
|
||||
Message
|
||||
} from 'element-ui';
|
||||
|
||||
Vue.use(DashboardPlugin);
|
||||
Vue.component(Table.name, Table);
|
||||
Vue.component(TableColumn.name, TableColumn);
|
||||
Vue.component(Pagination.name, Pagination);
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
Vue.component(Tag.name, Tag);
|
||||
Vue.prototype.$message = Message;
|
||||
|
||||
const app = new Vue({
|
||||
el: '#goods-reconciliation-app',
|
||||
|
||||
mixins: [Global],
|
||||
|
||||
data() {
|
||||
return {
|
||||
baseUrl: url + '/api/ecommerce-finance/goods-reconciliations',
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
activeTab: 'all',
|
||||
|
||||
summary: {
|
||||
totalAmount: 0,
|
||||
totalGuessAmount: 0,
|
||||
totalCount: 0,
|
||||
sentCount: 0,
|
||||
unsentCount: 0
|
||||
},
|
||||
|
||||
options: {
|
||||
suppliers: [],
|
||||
warehouses: []
|
||||
},
|
||||
|
||||
filters: {
|
||||
record_type: 'all',
|
||||
supplier: '',
|
||||
warehouse: '',
|
||||
stock_no: '',
|
||||
purchase_no: '',
|
||||
product_code: '',
|
||||
express_no: '',
|
||||
send_time_range: null,
|
||||
create_time_range: null,
|
||||
sent_status: 'all',
|
||||
has_diff: false
|
||||
},
|
||||
|
||||
tabs: [
|
||||
{ key: 'all', label: '全部记录' },
|
||||
{ key: 'purchase', label: '入库对账' },
|
||||
{ key: 'sale', label: '代发对账' },
|
||||
{ key: 'unsent', label: '未发出' },
|
||||
{ key: 'diff', label: '有差异' }
|
||||
],
|
||||
|
||||
sortProp: 'createTime',
|
||||
sortOrder: 'descending'
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchOptions();
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Fetch filter options (suppliers, warehouses)
|
||||
*/
|
||||
fetchOptions() {
|
||||
axios.get(this.baseUrl + '/options')
|
||||
.then(response => {
|
||||
this.options = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('获取筛选选项失败');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch data list and summary
|
||||
*/
|
||||
fetchData() {
|
||||
this.loading = true;
|
||||
|
||||
const params = this.buildParams();
|
||||
const summaryParams = this.buildSummaryParams();
|
||||
|
||||
Promise.all([
|
||||
axios.get(this.baseUrl, { params }),
|
||||
axios.get(this.baseUrl + '/summary', { params: summaryParams })
|
||||
])
|
||||
.then(([listRes, summaryRes]) => {
|
||||
this.tableData = listRes.data.data || [];
|
||||
this.total = listRes.data.total || 0;
|
||||
this.currentPage = listRes.data.page || 1;
|
||||
this.pageSize = listRes.data.per_page || 25;
|
||||
this.summary = summaryRes.data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('获取数据失败,请稍后重试');
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Build list query params
|
||||
*/
|
||||
buildParams() {
|
||||
const params = {
|
||||
page: this.currentPage,
|
||||
limit: this.pageSize,
|
||||
record_type: this.filters.record_type
|
||||
};
|
||||
|
||||
// text filters
|
||||
const textFields = ['supplier', 'warehouse', 'stock_no', 'purchase_no', 'product_code', 'express_no'];
|
||||
textFields.forEach(f => {
|
||||
if (this.filters[f]) {
|
||||
params[f] = this.filters[f];
|
||||
}
|
||||
});
|
||||
|
||||
// date range filters
|
||||
if (this.filters.send_time_range && this.filters.send_time_range.length === 2) {
|
||||
params.send_time_from = this.filters.send_time_range[0];
|
||||
params.send_time_to = this.filters.send_time_range[1];
|
||||
}
|
||||
if (this.filters.create_time_range && this.filters.create_time_range.length === 2) {
|
||||
params.create_time_from = this.filters.create_time_range[0];
|
||||
params.create_time_to = this.filters.create_time_range[1];
|
||||
}
|
||||
|
||||
// sent status
|
||||
if (this.filters.sent_status && this.filters.sent_status !== 'all') {
|
||||
params.sent_status = this.filters.sent_status;
|
||||
}
|
||||
|
||||
// has diff
|
||||
if (this.filters.has_diff) {
|
||||
params.has_diff = 1;
|
||||
}
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Summary params always use 'all' for overall counts
|
||||
*/
|
||||
buildSummaryParams() {
|
||||
const params = {};
|
||||
const textFields = ['supplier', 'warehouse', 'stock_no', 'purchase_no', 'product_code', 'express_no'];
|
||||
textFields.forEach(f => {
|
||||
if (this.filters[f]) {
|
||||
params[f] = this.filters[f];
|
||||
}
|
||||
});
|
||||
if (this.filters.send_time_range && this.filters.send_time_range.length === 2) {
|
||||
params.send_time_from = this.filters.send_time_range[0];
|
||||
params.send_time_to = this.filters.send_time_range[1];
|
||||
}
|
||||
if (this.filters.create_time_range && this.filters.create_time_range.length === 2) {
|
||||
params.create_time_from = this.filters.create_time_range[0];
|
||||
params.create_time_to = this.filters.create_time_range[1];
|
||||
}
|
||||
params.record_type = this.filters.record_type;
|
||||
if (this.filters.sent_status && this.filters.sent_status !== 'all') {
|
||||
params.sent_status = this.filters.sent_status;
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch tab
|
||||
*/
|
||||
switchTab(key) {
|
||||
this.activeTab = key;
|
||||
this.currentPage = 1;
|
||||
|
||||
switch (key) {
|
||||
case 'all':
|
||||
this.filters.record_type = 'all';
|
||||
this.filters.sent_status = 'all';
|
||||
this.filters.has_diff = false;
|
||||
break;
|
||||
case 'purchase':
|
||||
this.filters.record_type = 'purchase';
|
||||
this.filters.sent_status = 'all';
|
||||
this.filters.has_diff = false;
|
||||
break;
|
||||
case 'sale':
|
||||
this.filters.record_type = 'sale';
|
||||
this.filters.sent_status = 'all';
|
||||
this.filters.has_diff = false;
|
||||
break;
|
||||
case 'unsent':
|
||||
this.filters.sent_status = 'unsent';
|
||||
this.filters.has_diff = false;
|
||||
break;
|
||||
case 'diff':
|
||||
this.filters.has_diff = true;
|
||||
this.filters.sent_status = 'all';
|
||||
break;
|
||||
}
|
||||
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* On filter change, reset to page 1
|
||||
*/
|
||||
onFilterChange() {
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Pagination page change
|
||||
*/
|
||||
onPageChange(page) {
|
||||
this.currentPage = page;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Pagination page size change
|
||||
*/
|
||||
onPageSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sort change
|
||||
*/
|
||||
onSortChange({ prop, order }) {
|
||||
this.sortProp = prop;
|
||||
this.sortOrder = order;
|
||||
// Sorting is handled server-side for now; re-fetch
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* Format money (CNY)
|
||||
*/
|
||||
formatMoney(value) {
|
||||
if (value == null || isNaN(value)) return '0.00';
|
||||
return '¥' + Number(value).toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
84
resources/assets/js/views/ecommerce-finance/invoice-deductions.js
vendored
Normal file
84
resources/assets/js/views/ecommerce-finance/invoice-deductions.js
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
require('./../../bootstrap');
|
||||
|
||||
import Vue from 'vue';
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
import {
|
||||
Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Message
|
||||
} from 'element-ui';
|
||||
|
||||
Vue.use(DashboardPlugin);
|
||||
Vue.component(Table.name, Table);
|
||||
Vue.component(TableColumn.name, TableColumn);
|
||||
Vue.component(Pagination.name, Pagination);
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
Vue.component(Tag.name, Tag);
|
||||
Vue.component(Input.name, Input);
|
||||
Vue.component(Button.name, Button);
|
||||
Vue.prototype.$message = Message;
|
||||
|
||||
const app = new Vue({
|
||||
el: '#invoice-deduction-app',
|
||||
|
||||
mixins: [Global],
|
||||
|
||||
data() {
|
||||
return {
|
||||
baseUrl: url + '/api/ecommerce-finance/supplier-payables/invoice-deductions',
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
summary: { totalPrice: 0, totalCount: 0 },
|
||||
filters: { supplier: '', month_range: null }
|
||||
};
|
||||
},
|
||||
|
||||
mounted() { this.fetchData(); },
|
||||
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.loading = true;
|
||||
const params = this.buildParams();
|
||||
Promise.all([
|
||||
axios.get(this.baseUrl, { params }),
|
||||
axios.get(this.baseUrl + '/summary', { params })
|
||||
]).then(([listRes, sumRes]) => {
|
||||
this.tableData = listRes.data.data || [];
|
||||
this.total = listRes.data.total || 0;
|
||||
this.currentPage = listRes.data.page || 1;
|
||||
this.pageSize = listRes.data.per_page || 25;
|
||||
this.summary = sumRes.data;
|
||||
}).catch(() => this.$message.error('获取数据失败'))
|
||||
.finally(() => { this.loading = false; });
|
||||
},
|
||||
|
||||
buildParams() {
|
||||
const params = { page: this.currentPage, limit: this.pageSize };
|
||||
if (this.filters.supplier) params.supplier = this.filters.supplier;
|
||||
if (this.filters.month_range && this.filters.month_range.length === 2) {
|
||||
params.month_from = this.filters.month_range[0];
|
||||
params.month_to = this.filters.month_range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
onFilterChange() { this.currentPage = 1; this.fetchData(); },
|
||||
onPageChange(p) { this.currentPage = p; this.fetchData(); },
|
||||
onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); },
|
||||
onReset() {
|
||||
this.filters = { supplier: '', month_range: null };
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
formatMoney(v) {
|
||||
if (v == null || isNaN(v)) return '0.00';
|
||||
return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
113
resources/assets/js/views/ecommerce-finance/return-reconciliations.js
vendored
Normal file
113
resources/assets/js/views/ecommerce-finance/return-reconciliations.js
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
require('./../../bootstrap');
|
||||
|
||||
import Vue from 'vue';
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableColumn,
|
||||
Pagination,
|
||||
DatePicker,
|
||||
Tag,
|
||||
Button,
|
||||
Input,
|
||||
Select,
|
||||
Option,
|
||||
Message
|
||||
} from 'element-ui';
|
||||
|
||||
Vue.use(DashboardPlugin);
|
||||
Vue.component(Table.name, Table);
|
||||
Vue.component(TableColumn.name, TableColumn);
|
||||
Vue.component(Pagination.name, Pagination);
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
Vue.component(Tag.name, Tag);
|
||||
Vue.component(Input.name, Input);
|
||||
Vue.component(Button.name, Button);
|
||||
Vue.component(Select.name, Select);
|
||||
Vue.component(Option.name, Option);
|
||||
Vue.prototype.$message = Message;
|
||||
|
||||
const app = new Vue({
|
||||
el: '#return-reconciliation-app',
|
||||
|
||||
mixins: [Global],
|
||||
|
||||
data() {
|
||||
return {
|
||||
baseUrl: url + '/api/ecommerce-finance/supplier-payables/return-reconciliations',
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
summary: { totalAmount: 0, totalGuessAmount: 0, totalCount: 0 },
|
||||
options: { suppliers: [], warehouses: [] },
|
||||
filters: {
|
||||
supplier: '',
|
||||
warehouse: '',
|
||||
sku_id: '',
|
||||
express_no: '',
|
||||
date_range: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchOptions();
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
methods: {
|
||||
fetchOptions() {
|
||||
axios.get(this.baseUrl + '/options')
|
||||
.then(r => { this.options = r.data; })
|
||||
.catch(() => this.$message.error('获取筛选选项失败'));
|
||||
},
|
||||
|
||||
fetchData() {
|
||||
this.loading = true;
|
||||
const params = this.buildParams();
|
||||
Promise.all([
|
||||
axios.get(this.baseUrl, { params }),
|
||||
axios.get(this.baseUrl + '/summary', { params })
|
||||
]).then(([listRes, sumRes]) => {
|
||||
this.tableData = listRes.data.data || [];
|
||||
this.total = listRes.data.total || 0;
|
||||
this.currentPage = listRes.data.page || 1;
|
||||
this.pageSize = listRes.data.per_page || 25;
|
||||
this.summary = sumRes.data;
|
||||
}).catch(() => this.$message.error('获取数据失败'))
|
||||
.finally(() => { this.loading = false; });
|
||||
},
|
||||
|
||||
buildParams() {
|
||||
const params = { page: this.currentPage, limit: this.pageSize };
|
||||
['supplier', 'warehouse', 'sku_id', 'express_no'].forEach(f => {
|
||||
if (this.filters[f]) params[f] = this.filters[f];
|
||||
});
|
||||
if (this.filters.date_range && this.filters.date_range.length === 2) {
|
||||
params.date_from = this.filters.date_range[0];
|
||||
params.date_to = this.filters.date_range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
onFilterChange() { this.currentPage = 1; this.fetchData(); },
|
||||
onPageChange(p) { this.currentPage = p; this.fetchData(); },
|
||||
onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); },
|
||||
onReset() {
|
||||
this.filters = { supplier: '', warehouse: '', sku_id: '', express_no: '', date_range: null };
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
formatMoney(v) {
|
||||
if (v == null || isNaN(v)) return '0.00';
|
||||
return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
92
resources/assets/js/views/ecommerce-finance/sample-deductions.js
vendored
Normal file
92
resources/assets/js/views/ecommerce-finance/sample-deductions.js
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
require('./../../bootstrap');
|
||||
|
||||
import Vue from 'vue';
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
import {
|
||||
Table, TableColumn, Pagination, DatePicker, Tag, Button, Input, Message
|
||||
} from 'element-ui';
|
||||
|
||||
Vue.use(DashboardPlugin);
|
||||
Vue.component(Table.name, Table);
|
||||
Vue.component(TableColumn.name, TableColumn);
|
||||
Vue.component(Pagination.name, Pagination);
|
||||
Vue.component(DatePicker.name, DatePicker);
|
||||
Vue.component(Tag.name, Tag);
|
||||
Vue.component(Input.name, Input);
|
||||
Vue.component(Button.name, Button);
|
||||
Vue.prototype.$message = Message;
|
||||
|
||||
const app = new Vue({
|
||||
el: '#sample-deduction-app',
|
||||
|
||||
mixins: [Global],
|
||||
|
||||
data() {
|
||||
return {
|
||||
baseUrl: url + '/api/ecommerce-finance/supplier-payables/sample-deductions',
|
||||
loading: false,
|
||||
tableData: [],
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 25,
|
||||
summary: { totalAmount: 0, totalEstimate: 0, totalCount: 0 },
|
||||
filters: {
|
||||
supplier: '',
|
||||
department: '',
|
||||
creator: '',
|
||||
sku_id: '',
|
||||
date_range: null
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
mounted() { this.fetchData(); },
|
||||
|
||||
methods: {
|
||||
fetchData() {
|
||||
this.loading = true;
|
||||
const params = this.buildParams();
|
||||
Promise.all([
|
||||
axios.get(this.baseUrl, { params }),
|
||||
axios.get(this.baseUrl + '/summary', { params })
|
||||
]).then(([listRes, sumRes]) => {
|
||||
this.tableData = listRes.data.data || [];
|
||||
this.total = listRes.data.total || 0;
|
||||
this.currentPage = listRes.data.page || 1;
|
||||
this.pageSize = listRes.data.per_page || 25;
|
||||
this.summary = sumRes.data;
|
||||
}).catch(() => this.$message.error('获取数据失败'))
|
||||
.finally(() => { this.loading = false; });
|
||||
},
|
||||
|
||||
buildParams() {
|
||||
const params = { page: this.currentPage, limit: this.pageSize };
|
||||
['supplier', 'department', 'creator', 'sku_id'].forEach(f => {
|
||||
if (this.filters[f]) params[f] = this.filters[f];
|
||||
});
|
||||
if (this.filters.date_range && this.filters.date_range.length === 2) {
|
||||
params.date_from = this.filters.date_range[0];
|
||||
params.date_to = this.filters.date_range[1];
|
||||
}
|
||||
return params;
|
||||
},
|
||||
|
||||
onFilterChange() { this.currentPage = 1; this.fetchData(); },
|
||||
onPageChange(p) { this.currentPage = p; this.fetchData(); },
|
||||
onPageSizeChange(s) { this.pageSize = s; this.currentPage = 1; this.fetchData(); },
|
||||
onReset() {
|
||||
this.filters = { supplier: '', department: '', creator: '', sku_id: '', date_range: null };
|
||||
this.currentPage = 1;
|
||||
this.fetchData();
|
||||
},
|
||||
|
||||
formatMoney(v) {
|
||||
if (v == null || isNaN(v)) return '0.00';
|
||||
return '¥' + Number(v).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
@ -69,6 +69,8 @@
|
||||
margin-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
[v-cloak] { display: none; }
|
||||
</style>
|
||||
|
||||
<div class="my-6">
|
||||
@ -96,7 +98,21 @@
|
||||
</nav>
|
||||
@endif
|
||||
|
||||
@php
|
||||
$viewName = 'business.pages.' . $section['slug'];
|
||||
if ($page) {
|
||||
$viewName .= '.' . $page['slug'];
|
||||
}
|
||||
if ($page && $tabs->isNotEmpty()) {
|
||||
$viewName .= '.' . ($activeTab['slug'] ?? '');
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if (view()->exists($viewName))
|
||||
@include($viewName)
|
||||
@else
|
||||
<section class="business-blank-page" aria-label="{{ $activeTab['title'] ?? ($page['title'] ?? $section['title']) }}"></section>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
</x-layouts.admin>
|
||||
|
||||
@ -0,0 +1,111 @@
|
||||
<div id="advance-detail-app" class="advance-detail-page">
|
||||
<div v-cloak>
|
||||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4 text-sm text-yellow-800">
|
||||
<i class="el-icon-warning mr-1"></i>
|
||||
借支明细数据源 finance_advanceLoans 当前仅 4 行数据;表头 21 列中部分字段(如实际打款金额、分期扣款、票据等)在数据库中无对应列,已显示为占位符。
|
||||
</div>
|
||||
|
||||
{{-- 统计卡片 --}}
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">借支总金额</div>
|
||||
<div class="text-2xl font-bold text-gray-800">@{{ formatMoney(summary.totalAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">总记录数</div>
|
||||
<div class="text-2xl font-bold text-indigo-600">@{{ (summary.totalCount || 0).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 筛选栏 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<el-input v-model="filters.supplier" placeholder="供应商" size="small" style="width: 200px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-date-picker v-model="filters.month_range" type="monthrange"
|
||||
start-placeholder="月份开始" end-placeholder="月份结束" size="small"
|
||||
style="width: 240px" value-format="yyyy-MM" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-button size="small" type="primary" @click="onFilterChange">查询</el-button>
|
||||
<el-button size="small" @click="onReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 数据表格 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div v-if="loading" class="flex items-center justify-center py-20">
|
||||
<span class="text-gray-400">加载中...</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData" stripe border style="width: 100%" size="small" height="600">
|
||||
<el-table-column type="index" label="#" width="50" fixed="left"></el-table-column>
|
||||
<el-table-column prop="supplier" label="供应商" width="140" fixed="left" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="协议签订名称" width="140">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.contract_name || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="advance_amount" label="借支金额-借款协议" width="140" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="scope.row.advance_amount < 0 ? 'text-red-600' : 'text-gray-800'">@{{ formatMoney(scope.row.advance_amount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="实际打款金额" width="120" align="right">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ formatMoney(scope.row.actual_pay) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="本月扣款金额" width="120" align="right">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ formatMoney(scope.row.month_deduct) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应扣金额" width="120" align="right">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ formatMoney(scope.row.should_deduct) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分期起始扣款月份" width="130">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.deduct_start_month || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分期最后次扣款月份" width="130">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.deduct_end_month || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remain_periods" label="分期剩余期数" width="110" align="center"></el-table-column>
|
||||
<el-table-column prop="month_str" label="借支日期" width="110"></el-table-column>
|
||||
<el-table-column label="借款期限" width="100">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.loan_term || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="180" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="是否打款" width="100">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.is_paid || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pay_month" label="打款月份" width="110"></el-table-column>
|
||||
<el-table-column label="流水号" width="120">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.serial_no || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="借支协议" width="120">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.loan_agreement || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="person_charge" label="负责人" width="100"></el-table-column>
|
||||
<el-table-column label="票据" width="80">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.receipt_0 || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="票据1" width="80">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.receipt_1 || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="票据2" width="80">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.receipt_2 || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex items-center justify-between px-4 py-3 border-t border-gray-100" v-if="!loading">
|
||||
<div class="text-sm text-gray-500">共 @{{ total }} 条记录</div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[25, 50, 100]"
|
||||
@size-change="onPageSizeChange"
|
||||
@current-change="onPageChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('body_end')
|
||||
<script src="{{ asset('public/js/ecommerce-finance/advance-details.min.js') }}"></script>
|
||||
@endpush
|
||||
@ -0,0 +1,73 @@
|
||||
<div id="compensation-detail-app" class="compensation-detail-page">
|
||||
<div v-cloak>
|
||||
{{-- 统计卡片 --}}
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">赔付总金额</div>
|
||||
<div class="text-2xl font-bold text-red-600">@{{ formatMoney(summary.totalAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">总记录数</div>
|
||||
<div class="text-2xl font-bold text-indigo-600">@{{ (summary.totalCount || 0).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 筛选栏 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<el-input v-model="filters.supplier" placeholder="供应商" size="small" style="width: 220px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-date-picker v-model="filters.month_range" type="monthrange"
|
||||
start-placeholder="月份开始" end-placeholder="月份结束" size="small"
|
||||
style="width: 240px" value-format="yyyy-MM" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-button size="small" type="primary" @click="onFilterChange">查询</el-button>
|
||||
<el-button size="small" @click="onReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 数据表格 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div v-if="loading" class="flex items-center justify-center py-20">
|
||||
<span class="text-gray-400">加载中...</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData" stripe border style="width: 100%" size="small" height="600">
|
||||
<el-table-column type="index" label="#" width="50" fixed="left"></el-table-column>
|
||||
<el-table-column prop="month" label="日期" width="100" fixed="left"></el-table-column>
|
||||
<el-table-column prop="supplier" label="供应商" width="260" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="amount" label="赔付金额" width="140" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="scope.row.amount < 0 ? 'text-red-600' : 'text-gray-800'">@{{ formatMoney(scope.row.amount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reason" label="赔付原因" width="240" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="180" show-overflow-tooltip>
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.remark || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="factory_type" label="类型" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.factory_type === '纸板厂' ? 'primary' : 'success'">@{{ scope.row.factory_type }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="创建人" width="100"></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex items-center justify-between px-4 py-3 border-t border-gray-100" v-if="!loading">
|
||||
<div class="text-sm text-gray-500">共 @{{ total }} 条记录</div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[25, 50, 100]"
|
||||
@size-change="onPageSizeChange"
|
||||
@current-change="onPageChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('body_end')
|
||||
<script src="{{ asset('public/js/ecommerce-finance/compensation-details.min.js') }}"></script>
|
||||
@endpush
|
||||
@ -0,0 +1,79 @@
|
||||
<div id="invoice-deduction-app" class="invoice-deduction-page">
|
||||
<div v-cloak>
|
||||
{{-- 统计卡片 --}}
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">扣款总金额</div>
|
||||
<div class="text-2xl font-bold text-red-600">@{{ formatMoney(summary.totalPrice) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">总记录数</div>
|
||||
<div class="text-2xl font-bold text-indigo-600">@{{ (summary.totalCount || 0).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 筛选栏 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<el-input v-model="filters.supplier" placeholder="供应商/打款抬头/收款方" size="small" style="width: 220px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-date-picker v-model="filters.month_range" type="monthrange"
|
||||
start-placeholder="月份开始" end-placeholder="月份结束" size="small"
|
||||
style="width: 240px" value-format="yyyy-MM" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-button size="small" type="primary" @click="onFilterChange">查询</el-button>
|
||||
<el-button size="small" @click="onReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 数据表格 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div v-if="loading" class="flex items-center justify-center py-20">
|
||||
<span class="text-gray-400">加载中...</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData" stripe border style="width: 100%" size="small" height="600">
|
||||
<el-table-column type="index" label="#" width="50" fixed="left"></el-table-column>
|
||||
<el-table-column prop="month_str" label="日期" width="100" fixed="left"></el-table-column>
|
||||
<el-table-column label="类别" width="100">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.category || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payee" label="打款抬头" width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="receiver" label="收款方" width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="供板材厂家/代扣厂家" width="180">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.board_factory || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="补贴费用" width="120" align="right">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ formatMoney(scope.row.subsidy_fee) }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="200" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="price" label="对私打款金额" width="140" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="scope.row.price < 0 ? 'text-red-600' : 'text-gray-800'">@{{ formatMoney(scope.row.price) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="detail" label="明细" width="200" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="注明" width="100">
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.note || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="person_charge" label="负责人" width="100" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex items-center justify-between px-4 py-3 border-t border-gray-100" v-if="!loading">
|
||||
<div class="text-sm text-gray-500">共 @{{ total }} 条记录</div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[25, 50, 100]"
|
||||
@size-change="onPageSizeChange"
|
||||
@current-change="onPageChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('body_end')
|
||||
<script src="{{ asset('public/js/ecommerce-finance/invoice-deductions.min.js') }}"></script>
|
||||
@endpush
|
||||
@ -0,0 +1,139 @@
|
||||
<div id="goods-reconciliation-app" class="goods-reconciliation-page">
|
||||
<div v-cloak>
|
||||
{{-- 统计卡片 --}}
|
||||
<div class="grid grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">对账总金额</div>
|
||||
<div class="text-2xl font-bold text-gray-800">@{{ formatMoney(summary.totalAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">预估总金额</div>
|
||||
<div class="text-2xl font-bold text-gray-800">@{{ formatMoney(summary.totalGuessAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">已发出</div>
|
||||
<div class="text-2xl font-bold text-green-600">@{{ summary.sentCount.toLocaleString() }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">未发出</div>
|
||||
<div class="text-2xl font-bold text-orange-500">@{{ summary.unsentCount.toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 筛选栏 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3 mb-3">
|
||||
<el-select v-model="filters.record_type" placeholder="记录类型" size="small" style="width: 140px" @change="onFilterChange">
|
||||
<el-option label="全部记录" value="all"></el-option>
|
||||
<el-option label="入库对账" value="purchase"></el-option>
|
||||
<el-option label="代发对账" value="sale"></el-option>
|
||||
</el-select>
|
||||
<el-select v-model="filters.supplier" placeholder="供应商" size="small" style="width: 160px" filterable clearable @change="onFilterChange">
|
||||
<el-option v-for="s in options.suppliers" :key="s" :label="s" :value="s"></el-option>
|
||||
</el-select>
|
||||
<el-select v-model="filters.warehouse" placeholder="仓库" size="small" style="width: 140px" filterable clearable @change="onFilterChange">
|
||||
<el-option v-for="w in options.warehouses" :key="w" :label="w" :value="w"></el-option>
|
||||
</el-select>
|
||||
<el-input v-model="filters.stock_no" placeholder="入库单号" size="small" style="width: 140px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-input v-model="filters.purchase_no" placeholder="采购单号" size="small" style="width: 140px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<el-input v-model="filters.product_code" placeholder="商品编码" size="small" style="width: 140px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-input v-model="filters.express_no" placeholder="快递单号" size="small" style="width: 140px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-date-picker v-model="filters.send_time_range" type="daterange"
|
||||
start-placeholder="发货开始" end-placeholder="发货结束" size="small"
|
||||
style="width: 260px" value-format="yyyy-MM-dd" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-date-picker v-model="filters.create_time_range" type="daterange"
|
||||
start-placeholder="创建开始" end-placeholder="创建结束" size="small"
|
||||
style="width: 260px" value-format="yyyy-MM-dd" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-select v-model="filters.sent_status" placeholder="发出状态" size="small" style="width: 120px" clearable @change="onFilterChange">
|
||||
<el-option label="全部" value="all"></el-option>
|
||||
<el-option label="已发出" value="sent"></el-option>
|
||||
<el-option label="未发出" value="unsent"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tab 切换 --}}
|
||||
<div class="flex gap-1 mb-4">
|
||||
<button v-for="tab in tabs" :key="tab.key"
|
||||
@click="switchTab(tab.key)"
|
||||
:class="['px-4 py-2 rounded-lg text-sm font-medium transition-colors',
|
||||
activeTab === tab.key ? 'bg-indigo-600 text-white shadow-sm' : 'bg-white text-gray-600 border border-gray-200 hover:bg-gray-50']">
|
||||
@{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- 数据表格 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div v-if="loading" class="flex items-center justify-center py-20">
|
||||
<span class="text-gray-400">加载中...</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData" stripe border style="width: 100%" size="small"
|
||||
:default-sort="{prop: 'createTime', order: 'descending'}"
|
||||
@sort-change="onSortChange">
|
||||
<el-table-column type="index" label="#" width="55" fixed="left"></el-table-column>
|
||||
<el-table-column prop="recordType" label="类型" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.recordType === 'purchase' ? 'primary' : 'success'">
|
||||
@{{ scope.row.recordType === 'purchase' ? '入库' : '代发' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stockNo" label="入库单号" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="purchaseNo" label="采购单号" width="110" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="sendTime" label="发货时间" width="145" sortable="custom"></el-table-column>
|
||||
<el-table-column prop="supplier" label="供应商" width="110" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="skuCode" label="货号" width="130" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="productCode" label="商品编码" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="skuName" label="规格名称" width="180" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="qty" label="数量" width="80" align="right"></el-table-column>
|
||||
<el-table-column label="单价" width="100" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.unitPrice) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="120" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预估单价" width="100" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.guessUnitPrice) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预估金额" width="120" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.guessAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="supplierGoodsCode" label="供应商商品编码" width="140" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="warehouse" label="仓库名称" width="100" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="145" sortable="custom"></el-table-column>
|
||||
<el-table-column prop="expressName" label="快递名称" width="100" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="expressNo" label="快递单号" width="130" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="未发出" width="90" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.isUnsent" size="mini" type="warning">未发出</el-tag>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
{{-- 分页 --}}
|
||||
<div class="flex items-center justify-between px-4 py-3 border-t border-gray-100" v-if="!loading">
|
||||
<div class="text-sm text-gray-500">共 @{{ total }} 条记录</div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[25, 50, 100]"
|
||||
@size-change="onPageSizeChange"
|
||||
@current-change="onPageChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('body_end')
|
||||
<script src="{{ asset('public/js/ecommerce-finance/goods-reconciliations.min.js') }}"></script>
|
||||
@endpush
|
||||
@ -0,0 +1,107 @@
|
||||
<div id="return-reconciliation-app" class="return-reconciliation-page">
|
||||
<div v-cloak>
|
||||
{{-- 统计卡片 --}}
|
||||
<div class="grid grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">对账总金额</div>
|
||||
<div class="text-2xl font-bold text-gray-800">@{{ formatMoney(summary.totalAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">预估总金额</div>
|
||||
<div class="text-2xl font-bold text-gray-800">@{{ formatMoney(summary.totalGuessAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">总记录数</div>
|
||||
<div class="text-2xl font-bold text-indigo-600">@{{ (summary.totalCount || 0).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 筛选栏 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<el-input v-model="filters.supplier" placeholder="明细供应商" size="small" style="width: 160px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-select v-model="filters.warehouse" placeholder="仓库" size="small" style="width: 140px" filterable clearable @change="onFilterChange">
|
||||
<el-option v-for="w in options.warehouses" :key="w" :label="w" :value="w"></el-option>
|
||||
</el-select>
|
||||
<el-input v-model="filters.sku_id" placeholder="条形码" size="small" style="width: 160px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-input v-model="filters.express_no" placeholder="快递单号" size="small" style="width: 160px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-date-picker v-model="filters.date_range" type="daterange"
|
||||
start-placeholder="发货开始" end-placeholder="发货结束" size="small"
|
||||
style="width: 260px" value-format="yyyy-MM-dd" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-button size="small" type="primary" @click="onFilterChange">查询</el-button>
|
||||
<el-button size="small" @click="onReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 数据表格 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div v-if="loading" class="flex items-center justify-center py-20">
|
||||
<span class="text-gray-400">加载中...</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData" stripe border style="width: 100%" size="small" height="600">
|
||||
<el-table-column type="index" label="#" width="50" fixed="left"></el-table-column>
|
||||
<el-table-column prop="send_time" label="发货时间" width="110" fixed="left" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="supplier" label="明细供应商" width="110" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="barcode" label="条形码" width="140" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="sku_name" label="商品名称" width="220" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="qty" label="发货量" width="80" align="right"></el-table-column>
|
||||
<el-table-column label="单价" width="100" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.unit_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" width="110" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预估单价" width="100" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.guess_unit_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预估金额" width="110" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.guess_amount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="supplier_barcode" label="供应商条码" width="120" show-overflow-tooltip>
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.supplier_barcode || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="140" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="title" label="标题" width="100" show-overflow-tooltip>
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.title || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="wms_pick_no" label="wms配货单号" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="warehouse" label="仓库名称" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="express_no" label="快递单号" width="130" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="express_name" label="快递名称" width="100" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="io_date" label="发货时间" width="110" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="original_barcode" label="原条码" width="130" show-overflow-tooltip>
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.original_barcode || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="labels" label="标记多标签" width="120" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.labels" size="mini" type="warning">@{{ scope.row.labels }}</el-tag>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="creator" label="创建人" width="100" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="reconciler" label="对账人" width="100" show-overflow-tooltip>
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.reconciler || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex items-center justify-between px-4 py-3 border-t border-gray-100" v-if="!loading">
|
||||
<div class="text-sm text-gray-500">共 @{{ total }} 条记录</div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[25, 50, 100]"
|
||||
@size-change="onPageSizeChange"
|
||||
@current-change="onPageChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('body_end')
|
||||
<script src="{{ asset('public/js/ecommerce-finance/return-reconciliations.min.js') }}"></script>
|
||||
@endpush
|
||||
@ -0,0 +1,93 @@
|
||||
<div id="sample-deduction-app" class="sample-deduction-page">
|
||||
<div v-cloak>
|
||||
{{-- 统计卡片 --}}
|
||||
<div class="grid grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">合计扣款金额</div>
|
||||
<div class="text-2xl font-bold text-red-600">@{{ formatMoney(summary.totalAmount) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">预估总金额</div>
|
||||
<div class="text-2xl font-bold text-gray-800">@{{ formatMoney(summary.totalEstimate) }}</div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
||||
<div class="text-sm text-gray-500 mb-1">总记录数</div>
|
||||
<div class="text-2xl font-bold text-indigo-600">@{{ (summary.totalCount || 0).toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 筛选栏 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 p-4 mb-4 shadow-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<el-input v-model="filters.supplier" placeholder="厂家" size="small" style="width: 160px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-input v-model="filters.department" placeholder="部门" size="small" style="width: 160px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-input v-model="filters.creator" placeholder="申请人" size="small" style="width: 120px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-input v-model="filters.sku_id" placeholder="商品代码" size="small" style="width: 160px" clearable @clear="onFilterChange" @keyup.enter="onFilterChange"></el-input>
|
||||
<el-date-picker v-model="filters.date_range" type="daterange"
|
||||
start-placeholder="开始" end-placeholder="结束" size="small"
|
||||
style="width: 240px" value-format="yyyy-MM-dd" @change="onFilterChange">
|
||||
</el-date-picker>
|
||||
<el-button size="small" type="primary" @click="onFilterChange">查询</el-button>
|
||||
<el-button size="small" @click="onReset">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 数据表格 --}}
|
||||
<div class="bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden">
|
||||
<div v-if="loading" class="flex items-center justify-center py-20">
|
||||
<span class="text-gray-400">加载中...</span>
|
||||
</div>
|
||||
<el-table v-else :data="tableData" stripe border style="width: 100%" size="small" height="600">
|
||||
<el-table-column type="index" label="#" width="50" fixed="left"></el-table-column>
|
||||
<el-table-column prop="finish_time" label="日期" width="110" fixed="left"></el-table-column>
|
||||
<el-table-column prop="department" label="部门" width="200" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="creator" label="申请人" width="100"></el-table-column>
|
||||
<el-table-column prop="sku_id" label="商品代码" width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="skuName" label="商品名称" width="220" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="num" label="数量" width="70" align="right"></el-table-column>
|
||||
<el-table-column prop="supplier" label="厂家" width="120" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="purchase" label="对应采购" width="200" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="productCode" label="扣款厂家商品代码" width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="shipping_fee" label="运费" width="100" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.shipping_fee) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="cost_price" label="成本价" width="110" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.cost_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="total_amount" label="合计扣款金额" width="130" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span :class="scope.row.total_amount < 0 ? 'text-red-600' : 'text-gray-800'">@{{ formatMoney(scope.row.total_amount) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="estimated_price" label="预估价" width="100" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.estimated_price) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="estimated_price_all" label="预估金额" width="110" align="right">
|
||||
<template slot-scope="scope">@{{ formatMoney(scope.row.estimated_price_all) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" width="150" show-overflow-tooltip>
|
||||
<template slot-scope="scope"><span class="text-gray-400">@{{ scope.row.remark || '-' }}</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="businessId" label="业务单号" width="200" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex items-center justify-between px-4 py-3 border-t border-gray-100" v-if="!loading">
|
||||
<div class="text-sm text-gray-500">共 @{{ total }} 条记录</div>
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next, sizes, jumper"
|
||||
:total="total"
|
||||
:page-size="pageSize"
|
||||
:current-page.sync="currentPage"
|
||||
:page-sizes="[25, 50, 100]"
|
||||
@size-change="onPageSizeChange"
|
||||
@current-change="onPageChange">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('body_end')
|
||||
<script src="{{ asset('public/js/ecommerce-finance/sample-deductions.min.js') }}"></script>
|
||||
@endpush
|
||||
8
webpack.mix.js
vendored
8
webpack.mix.js
vendored
@ -61,6 +61,14 @@ mix
|
||||
// Modules
|
||||
.js('resources/assets/js/views/modules/apps.js', 'public/js/modules/apps.min.js')
|
||||
|
||||
// Ecommerce Finance
|
||||
.js('resources/assets/js/views/ecommerce-finance/goods-reconciliations.js', 'public/js/ecommerce-finance/goods-reconciliations.min.js')
|
||||
.js('resources/assets/js/views/ecommerce-finance/return-reconciliations.js', 'public/js/ecommerce-finance/return-reconciliations.min.js')
|
||||
.js('resources/assets/js/views/ecommerce-finance/invoice-deductions.js', 'public/js/ecommerce-finance/invoice-deductions.min.js')
|
||||
.js('resources/assets/js/views/ecommerce-finance/compensation-details.js', 'public/js/ecommerce-finance/compensation-details.min.js')
|
||||
.js('resources/assets/js/views/ecommerce-finance/advance-details.js', 'public/js/ecommerce-finance/advance-details.min.js')
|
||||
.js('resources/assets/js/views/ecommerce-finance/sample-deductions.js', 'public/js/ecommerce-finance/sample-deductions.min.js')
|
||||
|
||||
// Portal
|
||||
.js('resources/assets/js/views/portal/apps.js', 'public/js/portal/apps.min.js')
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user