Procházet zdrojové kódy

工单增加规格字段,完善列表展示与套打填充。

支持选产品带出规格、套打时输出工单规格和备注,并补全工单/产品列表字段与搜索。

Co-authored-by: Cursor <cursoragent@cursor.com>
unknown před 1 měsícem
rodič
revize
bce231d14a

+ 1 - 0
fastadmin/application/admin/lang/zh-cn/ps/workorder.php

@@ -7,6 +7,7 @@ return [
     'No'              => '工单号',
     'Product_id'      => '产品',
     'Product_name'    => '名称',
+    'Spec'            => '规格',
     'Qty'             => '数量',
     'Batch_no'        => '批次',
     'Plan_date'       => '计划日期',

+ 1 - 0
fastadmin/application/api/controller/ps/Open.php

@@ -132,6 +132,7 @@ class Open extends Api
                     'no'            => '工单号,必填,本厂内唯一,存在则更新',
                     'product_code'  => '产品编号,可选;填写则关联已有产品或自动建档',
                     'product_name'  => '产品名称。不选产品时必填;选了产品可带出',
+                    'spec'          => '规格。可选;选了产品可带出',
                     'batch_no'      => '批次',
                     'customer'      => '客户',
                     'plan_date'     => '计划日期 YYYY-MM-DD',

+ 1 - 1
fastadmin/application/api/controller/ps/Product.php

@@ -53,7 +53,7 @@ class Product extends Api
         $keyword = $this->request->request('keyword', '', 'trim');
         $query = $this->model->order('id desc');
         if ($keyword !== '') {
-            $query->where('name|code', 'like', '%' . $keyword . '%');
+            $query->where('name|code|spec|remark', 'like', '%' . $keyword . '%');
         }
         $list = $query->paginate($limit);
         $this->success('', ['total' => $list->total(), 'rows' => $list->items()]);

+ 10 - 2
fastadmin/application/api/controller/ps/Workorder.php

@@ -50,7 +50,7 @@ class Workorder extends Api
         $keyword = $this->request->request('keyword', '', 'trim');
         $query = $this->model->with(['product'])->order('id desc');
         if ($keyword !== '') {
-            $query->where('no|batch_no|customer|product_name', 'like', '%' . $keyword . '%');
+            $query->where('no|batch_no|customer|product_name|spec|remark', 'like', '%' . $keyword . '%');
         }
         $list = $query->paginate($limit);
         $this->success('', ['total' => $list->total(), 'rows' => $list->items()]);
@@ -141,16 +141,24 @@ class Workorder extends Api
     {
         $productId = (int)($params['product_id'] ?? 0);
         $name = trim((string)($params['product_name'] ?? ''));
+        $spec = trim((string)($params['spec'] ?? ''));
         unset($params['name']);
-        if ($productId > 0 && $name === '') {
+        $product = null;
+        if ($productId > 0) {
             $product = ProductModel::get($productId);
+        }
+        if ($productId > 0 && $name === '') {
             $name = $product ? (string)$product['name'] : '';
         }
         if ($name === '') {
             $this->error('请选择产品或填写名称');
         }
+        if ($spec === '' && $product) {
+            $spec = (string)$product['spec'];
+        }
         $params['product_id'] = $productId;
         $params['product_name'] = mb_substr($name, 0, 100);
+        $params['spec'] = mb_substr($spec, 0, 100);
         return $params;
     }
 }

+ 6 - 0
fastadmin/application/common/library/ps/BizIngest.php

@@ -94,11 +94,17 @@ class BizIngest
         if ($productName === '') {
             throw new Exception('请选择产品或填写名称');
         }
+        $spec = self::str($payload, ['spec'], 100);
+        if ($spec === '' && $productId > 0) {
+            $linked = isset($product) ? $product : Product::get($productId);
+            $spec = $linked ? (string)$linked['spec'] : '';
+        }
         $row = (new Workorder())->where('factory_id', $factoryId)->where('no', $no)->find();
         $data = [
             'no'              => $no,
             'product_id'      => $productId,
             'product_name'    => $productName,
+            'spec'            => $spec,
             'batch_no'        => self::str($payload, ['batch_no', 'batch'], 64),
             'customer'        => self::str($payload, ['customer'], 100),
             'plan_date'       => self::date($payload, ['plan_date', 'date']),

+ 19 - 3
fastadmin/application/common/library/ps/PrintPlanner.php

@@ -53,6 +53,10 @@ class PrintPlanner
             $processes = self::resolveProcesses($workorder['processes'], $product ? $product['processes'] : [], $options);
             $qtyDefault = $workorder['qty'];
             $sheetQtyDefault = $workorder['sheet_qty'];
+            $storedSpec = trim((string)($workorder['spec'] ?? ''));
+            if ($storedSpec === '' && $product) {
+                $storedSpec = trim((string)$product['spec']);
+            }
             $header = [
                 'workorder_id' => (int)$workorder['id'],
                 'workorder_no' => self::pickText($options, 'no', (string)$workorder['no']),
@@ -60,6 +64,7 @@ class PrintPlanner
                 'plan_date'    => (string)$workorder['plan_date'],
                 'customer'     => (string)$workorder['customer'],
                 'remark'       => (string)$workorder['remark'],
+                'spec'         => $storedSpec,
             ];
             $storedName = trim((string)($workorder['product_name'] ?? ''));
             $nameOverride = self::pickText($options, 'name', $storedName);
@@ -68,8 +73,11 @@ class PrintPlanner
             }
             if ($nameOverride !== '' && $product) {
                 $product['name'] = $nameOverride;
+                if ($storedSpec !== '') {
+                    $product['spec'] = $storedSpec;
+                }
             } elseif ($nameOverride !== '') {
-                $product = ['id' => 0, 'code' => '', 'name' => $nameOverride, 'spec' => '', 'unit' => ''];
+                $product = ['id' => 0, 'code' => '', 'name' => $nameOverride, 'spec' => $storedSpec, 'unit' => ''];
             }
         } elseif ($bizId > 0) {
             $product = Product::with('processes')->find($bizId);
@@ -131,6 +139,12 @@ class PrintPlanner
             $processNames[] = $item['name'];
         }
 
+        $specText = isset($header['spec'])
+            ? (string)$header['spec']
+            : ($product ? (string)$product['spec'] : '');
+        if ($specText === '' && $product) {
+            $specText = (string)$product['spec'];
+        }
         $fill = [
             'factory.name'    => (string)$factory['name'],
             'factory.code'    => (string)$factory['code'],
@@ -139,7 +153,7 @@ class PrintPlanner
             'product.id'      => $product ? (int)$product['id'] : 0,
             'product.code'    => $product ? (string)$product['code'] : '',
             'product.name'    => $product ? (string)$product['name'] : '',
-            'product.spec'    => $product ? (string)$product['spec'] : '',
+            'product.spec'    => $specText,
             'product.unit'    => $product ? (string)$product['unit'] : '',
             'workorder.no'    => $header['workorder_no'],
             'workorder.qty'   => self::formatQty($qty),
@@ -150,6 +164,7 @@ class PrintPlanner
             'workorder.plan_date' => $header['plan_date'],
             'workorder.customer' => $header['customer'],
             'workorder.remark' => $header['remark'],
+            'workorder.spec'  => $specText,
             'process_names'   => implode('、', $processNames),
             'sheet_qty'       => self::formatQty($sheetQty),
             'operator'        => $operator ? (string)($operator['nickname'] ?: $operator['username']) : '',
@@ -979,6 +994,7 @@ class PrintPlanner
             ['key' => 'qr', 'label' => '二维码区域'],
             ['key' => 'workorder.plan_date', 'label' => '计划日期'],
             ['key' => 'workorder.customer', 'label' => '客户'],
+            ['key' => 'workorder.spec', 'label' => '工单规格'],
             ['key' => 'workorder.remark', 'label' => '备注'],
             ['key' => 'sheet.qty', 'label' => '本张数量'],
             ['key' => 'sheet.no_text', 'label' => '页码'],
@@ -1064,7 +1080,7 @@ class PrintPlanner
             '码包号' => 'workorder.pack_no', '包号' => 'workorder.pack_no',
             '客户' => 'workorder.customer',
             '日期' => 'workorder.plan_date', '计划日' => 'workorder.plan_date',
-            '备注' => 'workorder.remark', '操作员' => 'operator', '时间' => 'print_time',
+            '备注' => 'workorder.remark', '工单规格' => 'workorder.spec', '操作员' => 'operator', '时间' => 'print_time',
             '页码' => 'sheet.no_text', '工序' => 'process_names',
         ];
         if (preg_match('/^二维码/', $name) || $name === 'QR' || $name === 'qr') {

+ 1 - 0
fastadmin/application/sql/ps_schema.sql

@@ -176,6 +176,7 @@ CREATE TABLE IF NOT EXISTS `fa_ps_workorder` (
   `no` varchar(64) NOT NULL DEFAULT '' COMMENT '工单号',
   `product_id` int(10) unsigned NOT NULL DEFAULT '0',
   `product_name` varchar(100) NOT NULL DEFAULT '' COMMENT '产品名称,可不关联产品档案',
+  `spec` varchar(100) NOT NULL DEFAULT '' COMMENT '规格,可不关联产品档案',
   `qty` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '总数量',
   `sheet_qty` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '每版数量,一版一张流程单',
   `batch_no` varchar(64) NOT NULL DEFAULT '' COMMENT '批次',

+ 7 - 0
fastadmin/application/sql/ps_workorder_spec.sql

@@ -0,0 +1,7 @@
+ALTER TABLE `fa_ps_workorder`
+  ADD COLUMN `spec` varchar(100) NOT NULL DEFAULT '' COMMENT '规格,可不关联产品档案' AFTER `product_name`;
+
+UPDATE `fa_ps_workorder` w
+LEFT JOIN `fa_ps_product` p ON p.id = w.product_id
+SET w.spec = IFNULL(p.spec, '')
+WHERE w.spec = '' AND w.product_id > 0;

+ 20 - 0
fastadmin/application/sql/ps_workorder_spec_run.php

@@ -0,0 +1,20 @@
+<?php
+$root = dirname(dirname(__DIR__));
+define('APP_PATH', $root . '/application/');
+require $root . '/thinkphp/base.php';
+\think\App::initCommon();
+
+$prefix = \think\Db::getConfig('prefix');
+$table = $prefix . 'ps_workorder';
+$rows = \think\Db::query("SHOW COLUMNS FROM `{$table}` LIKE 'spec'");
+if ($rows) {
+    echo $table . ".spec exists\n";
+} else {
+    \think\Db::execute("ALTER TABLE `{$table}` ADD COLUMN `spec` varchar(100) NOT NULL DEFAULT '' COMMENT '规格,可不关联产品档案' AFTER `product_name`");
+    echo $table . ".spec added\n";
+}
+
+$updated = \think\Db::execute(
+    "UPDATE `{$table}` w LEFT JOIN `{$prefix}ps_product` p ON p.id = w.product_id SET w.spec = IFNULL(p.spec, '') WHERE w.spec = '' AND w.product_id > 0"
+);
+echo "backfilled {$updated} rows\n";

+ 3 - 0
vue/src/components/ImportDialog.vue

@@ -44,6 +44,9 @@
         <el-table-column label="产品名称" min-width="120">
           <template #default="{ row }"><el-input v-model="row.product_name" size="small" /></template>
         </el-table-column>
+        <el-table-column label="规格" width="120">
+          <template #default="{ row }"><el-input v-model="row.spec" size="small" /></template>
+        </el-table-column>
       </template>
       <el-table-column label="工序数" width="80">
         <template #default="{ row }">{{ (row.processes && row.processes.length) || row.process_count || 0 }}</template>

+ 8 - 5
vue/src/views/product/List.vue

@@ -1,18 +1,21 @@
 <template>
   <div class="page-card">
     <div class="toolbar">
-      <el-input v-model="keyword" placeholder="搜索编码 / 名称" style="width:240px" clearable @keyup.enter="load" />
+      <el-input v-model="keyword" placeholder="搜索编码 / 名称 / 规格 / 备注" style="width:300px" clearable @keyup.enter="load" />
       <div>
         <el-button v-if="hasPerms('product.import')" @click="importVisible = true">文件导入</el-button>
         <el-button v-if="hasPerms('product.edit')" type="primary" @click="$router.push('/products/edit')">手工新建</el-button>
       </div>
     </div>
     <el-table :data="rows" v-loading="loading">
-      <el-table-column prop="code" label="编码" width="140" />
-      <el-table-column prop="name" label="名称" />
-      <el-table-column prop="spec" label="规格" />
+      <el-table-column prop="code" label="编码" min-width="130" show-overflow-tooltip />
+      <el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip />
+      <el-table-column prop="spec" label="规格" min-width="120" show-overflow-tooltip />
       <el-table-column prop="unit" label="单位" width="80" />
-      <el-table-column label="操作" width="260">
+      <el-table-column label="备注" min-width="160" show-overflow-tooltip>
+        <template #default="{ row }">{{ row.remark || '—' }}</template>
+      </el-table-column>
+      <el-table-column label="操作" width="260" fixed="right">
         <template #default="{ row }">
           <el-button v-if="hasPerms('print.do')" text type="success" @click="openPrint({ bizType: 'product', bizId: row.id, no: row.code, name: row.name })">套打</el-button>
           <el-button v-if="hasPerms('product.edit')" text type="primary" @click="$router.push('/products/edit/' + row.id)">编辑</el-button>

+ 6 - 0
vue/src/views/workorder/Edit.vue

@@ -17,6 +17,9 @@
       <el-form-item label="名称">
         <el-input v-model="form.product_name" placeholder="选择产品后自动带出,不选产品则手动填写" />
       </el-form-item>
+      <el-form-item label="规格">
+        <el-input v-model="form.spec" placeholder="选择产品后自动带出,也可手动填写" />
+      </el-form-item>
       <el-form-item label="批次"><el-input v-model="form.batch_no" /></el-form-item>
       <el-form-item label="客户"><el-input v-model="form.customer" /></el-form-item>
       <el-form-item label="计划日期"><el-date-picker v-model="form.plan_date" value-format="YYYY-MM-DD" /></el-form-item>
@@ -53,6 +56,7 @@ const form = reactive({
   no: '',
   product_id: '',
   product_name: '',
+  spec: '',
   batch_no: '',
   customer: '',
   plan_date: '',
@@ -73,6 +77,7 @@ onMounted(async () => {
     Object.assign(form, row)
     form.product_id = row.product_id || ''
     form.product_name = row.product_name || (row.product && row.product.name) || ''
+    form.spec = row.spec || (row.product && row.product.spec) || ''
     form.processes = row.processes || []
   } finally {
     loading.value = false
@@ -85,6 +90,7 @@ async function onProduct(id) {
   }
   const row = await productInfo(id)
   form.product_name = row.name || ''
+  form.spec = row.spec || ''
   if (form.processes && form.processes.length) {
     return
   }

+ 27 - 7
vue/src/views/workorder/List.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="page-card">
     <div class="toolbar">
-      <el-input v-model="keyword" placeholder="搜索工单号 / 名称 / 批次 / 客户" style="width:280px" clearable @keyup.enter="load" />
+      <el-input v-model="keyword" placeholder="搜索工单号 / 名称 / 规格 / 批次 / 客户 / 备注" style="width:360px" clearable @keyup.enter="load" />
       <div>
         <InstallLodopButton />
         <el-button v-if="hasPerms('print.view')" @click="historyVisible = true">打印记录</el-button>
@@ -10,13 +10,22 @@
       </div>
     </div>
     <el-table :data="rows" v-loading="loading">
-      <el-table-column prop="no" label="工单号" width="160" />
-      <el-table-column prop="product_name" label="名称" />
-      <el-table-column prop="batch_no" label="批次" />
-      <el-table-column prop="customer" label="客户" />
+      <el-table-column prop="no" label="工单号" min-width="140" show-overflow-tooltip />
+      <el-table-column prop="product_name" label="名称" min-width="160" show-overflow-tooltip />
+      <el-table-column label="规格" min-width="120" show-overflow-tooltip>
+        <template #default="{ row }">{{ row.spec || row.product?.spec || '—' }}</template>
+      </el-table-column>
+      <el-table-column label="产品编码" min-width="120" show-overflow-tooltip>
+        <template #default="{ row }">{{ row.product?.code || '—' }}</template>
+      </el-table-column>
+      <el-table-column prop="batch_no" label="批次" min-width="110" show-overflow-tooltip />
+      <el-table-column prop="customer" label="客户" min-width="110" show-overflow-tooltip />
       <el-table-column prop="plan_date" label="计划日期" width="120" />
-      <el-table-column prop="status" label="状态" width="100" />
-      <el-table-column label="操作" width="260">
+      <el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
+      <el-table-column label="状态" width="96">
+        <template #default="{ row }">{{ statusText(row.status) }}</template>
+      </el-table-column>
+      <el-table-column label="操作" width="260" fixed="right">
         <template #default="{ row }">
           <el-button v-if="hasPerms('print.do')" text type="success" @click="openPrint({ bizType: 'workorder', bizId: row.id, no: row.no, name: row.product_name })">套打</el-button>
           <el-button v-if="hasPerms('workorder.edit')" text type="primary" @click="$router.push('/workorders/edit/' + row.id)">编辑</el-button>
@@ -58,6 +67,17 @@ const loading = ref(false)
 const historyVisible = ref(false)
 const importVisible = ref(false)
 
+const statusMap = {
+  draft: '草稿',
+  doing: '进行中',
+  done: '已完成',
+  cancel: '已取消',
+}
+
+function statusText(value) {
+  return statusMap[String(value || '')] || value || '—'
+}
+
 async function load() {
   loading.value = true
   try {