m0_70156489 2 weeks ago
parent
commit
39b144fae5

+ 156 - 64
application/admin/controller/Procuremen.php

@@ -1042,33 +1042,52 @@ class Procuremen extends Backend
     }
 
     /**
-     * 已下发/审批/确认列表左侧月份:直接查 purchase_order,避免读整包 Redis
+     * 已下发/审批/确认列表左侧月份:SQL DISTINCT,禁止把该阶段全部行拉进 PHP
      */
     protected function GetIssuedOrderYearMonths(string $stage): array
     {
+        if ($stage !== 'audit' && $stage !== 'confirm') {
+            return $this->GetIndexYearMonths();
+        }
         $ymSet = [];
+        $collectYm = function ($query, string $expr) use (&$ymSet): void {
+            try {
+                $rows = $query->fieldRaw($expr . ' AS ym')->group($expr)->select();
+            } catch (\Throwable $e) {
+                $rows = [];
+            }
+            if (!is_array($rows)) {
+                return;
+            }
+            foreach ($rows as $r) {
+                $ym = is_array($r) ? trim((string)($r['ym'] ?? '')) : '';
+                if (preg_match('/^\d{4}-\d{2}$/', $ym)) {
+                    $ymSet[$ym] = true;
+                }
+            }
+        };
         try {
-            $query = Db::table('purchase_order');
-            $this->applyPurchaseOrderNotDeletedWhere($query);
+            $q1 = Db::table('purchase_order');
+            $this->applyPurchaseOrderNotDeletedWhere($q1);
             if ($stage === 'audit') {
-                $query->whereIn('wflow_status', ProcuremenStatus::wflowPendingConfirmValues());
-            } elseif ($stage === 'confirm') {
-                $this->applyPurchaseOrderConfirmStageWhere($query);
+                $q1->whereIn('wflow_status', ProcuremenStatus::wflowPendingConfirmValues());
             } else {
-                return $this->GetIndexYearMonths();
+                $this->applyPurchaseOrderConfirmStageWhere($q1);
             }
-            $rows = $query->field('pick_time,createtime,dputrecord,dStamp')->select();
-            if (is_array($rows)) {
-                foreach ($rows as $r) {
-                    if (!is_array($r)) {
-                        continue;
-                    }
-                    $ym = $this->procuremenRowYearMonth($this->normalizePurchaseOrderListTimeRow($r), 'pick_time');
-                    if ($ym !== null) {
-                        $ymSet[$ym] = true;
-                    }
-                }
+            $q1->whereNotNull('pick_time')->where('pick_time', '<>', '')->where('pick_time', 'not like', '0000-00-00%');
+            $collectYm($q1, 'LEFT(`pick_time`, 7)');
+
+            $q2 = Db::table('purchase_order');
+            $this->applyPurchaseOrderNotDeletedWhere($q2);
+            if ($stage === 'audit') {
+                $q2->whereIn('wflow_status', ProcuremenStatus::wflowPendingConfirmValues());
+            } else {
+                $this->applyPurchaseOrderConfirmStageWhere($q2);
             }
+            $q2->where(function ($q) {
+                $q->whereNull('pick_time')->whereOr('pick_time', '')->whereOr('pick_time', 'like', '0000-00-00%');
+            });
+            $collectYm($q2, 'LEFT(IF(`createtime` > 946684800, FROM_UNIXTIME(`createtime`), `createtime`), 7)');
         } catch (\Throwable $e) {
             $ymSet = [];
         }
@@ -1123,13 +1142,12 @@ class Procuremen extends Backend
 
     protected function applyPurchaseOrderNotDeletedWhere($query): void
     {
-        $query->whereRaw(
-            '(mod_rq IS NULL OR TRIM(CAST(mod_rq AS CHAR(32))) = \'\' OR TRIM(CAST(mod_rq AS CHAR(32))) LIKE \'0000-00-00%\')'
-        );
+        $query->whereRaw("(mod_rq IS NULL OR mod_rq = '' OR mod_rq LIKE '0000-00-00%')");
     }
 
     /**
      * 审批列表:wflow_status = 待审批(兼容旧值 2)
+     * 完结判断必须走 CAST 文案比较:status 为 0/'0' 时,NOT IN ('已完结','1') 会被 MySQL 把「已完结」转成 0 而整表滤空
      */
     protected function applyPurchaseOrderConfirmStageWhere($query): void
     {
@@ -1306,8 +1324,13 @@ class Procuremen extends Backend
 
     protected function applyProcuremenMonthRangeWhere($query, string $monthStart, string $monthEnd, string $primary = 'pick_time', string $alias = ''): void
     {
+        if (!preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $monthStart)
+            || !preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $monthEnd)) {
+            return;
+        }
         $expr = $this->procuremenListTimeSqlExpr($primary, $alias);
-        $query->whereRaw("({$expr}) >= ? AND ({$expr}) <= ?", [$monthStart, $monthEnd]);
+        // 禁止 whereRaw 用 ? :与 whereIn 命名绑定混用时 PDO 会失败,列表被 catch 成空
+        $query->whereRaw("({$expr}) >= '{$monthStart}' AND ({$expr}) <= '{$monthEnd}'");
     }
 
     /**
@@ -1329,7 +1352,7 @@ class Procuremen extends Backend
                 return [];
             }
             $query->order('pick_time', 'desc')->order('id', 'desc');
-            if ($applyMonthRange && preg_match('/^\d{4}-\d{2}$/', $ym) && $stage === 'pick') {
+            if ($applyMonthRange && preg_match('/^\d{4}-\d{2}$/', $ym)) {
                 $monthStart = $ym . '-01 00:00:00';
                 $monthEnd = date('Y-m-t 23:59:59', strtotime($monthStart));
                 $this->applyProcuremenMonthRangeWhere($query, $monthStart, $monthEnd, 'pick_time');
@@ -1369,8 +1392,13 @@ class Procuremen extends Backend
             $indexPhpRoot = $rootTrue . '/index.php';
         }
         $this->view->assign('procuremenRedisApi', $indexPhpRoot . '/api/procuremen/getprocuremen');
-        $defaultYm = $this->resolveProcuremenDefaultYm($stage);
-        $sidebar = $stage === 'pick' ? $this->GetIndexYearMonths() : $this->GetIssuedOrderYearMonths($stage);
+        if ($stage === 'pick') {
+            $defaultYm = $this->resolveProcuremenDefaultYm($stage);
+            $sidebar = $this->GetIndexYearMonths();
+        } else {
+            $sidebar = $this->GetIssuedOrderYearMonths($stage);
+            $defaultYm = $this->latestYmFromSidebar($sidebar);
+        }
         $sidebar = $this->ensureProcuremenSidebarHasYm($sidebar, $defaultYm);
         $this->view->assign('defaultYm', $defaultYm);
         $this->view->assign('sidebarYearMonths', $sidebar);
@@ -1380,7 +1408,34 @@ class Procuremen extends Backend
         $this->view->assign('procuremenBtnPickDelete', $this->procuremenCanPickDelete());
         $this->view->assign('procuremenBtnComplete', $this->procuremenCanComplete());
         $this->view->assign('procuremenBtnAuditAbandon', $this->hasProcuremenPerm(['auditabandon']));
-        $this->assignconfig('procuremenFilterOptions', $this->buildProcuremenFilterOptions($stage));
+        // 筛选项由列表 AJAX 带回,避免进页再扫 4 次 DISTINCT 白屏
+        $this->assignconfig('procuremenFilterOptions', [
+            'czlyq'   => [],
+            'CCLBMMC' => [],
+            'cywyxm'  => [],
+            'CGYMC'   => [],
+        ]);
+    }
+
+    /**
+     * @param array<int, array{year?:string, months?:array}> $sidebar
+     */
+    protected function latestYmFromSidebar(array $sidebar): string
+    {
+        $best = '';
+        foreach ($sidebar as $block) {
+            if (!is_array($block) || empty($block['months']) || !is_array($block['months'])) {
+                continue;
+            }
+            foreach ($block['months'] as $item) {
+                $ym = is_array($item) ? trim((string)($item['ym'] ?? '')) : '';
+                if (preg_match('/^\d{4}-\d{2}$/', $ym) && ($best === '' || strcmp($ym, $best) > 0)) {
+                    $best = $ym;
+                }
+            }
+        }
+
+        return $best !== '' ? $best : date('Y-m');
     }
 
     /**
@@ -1653,28 +1708,19 @@ class Procuremen extends Backend
 
         try {
             $listTimePrimary = in_array($wffTab, ['audit', 'confirm'], true) ? 'pick_time' : 'dputrecord';
-            if ($wffTab === 'audit') {
-                $pool = $this->procuremenPoolFromPurchaseOrderDbRows(
-                    $this->loadPurchaseOrderRowsForListStage('audit', $ym, $applyMonthRange)
-                );
-            } elseif ($wffTab === 'confirm') {
-                $dbRows = $this->loadPurchaseOrderRowsForListStage('confirm', $ym, $applyMonthRange);
-                if ($dbRows === []) {
-                    try {
-                        $dbRows = Db::table('purchase_order')
-                            ->field('id,scydgy_id,CCYDH,CYJMC,CCLBMMC,CDXMC,CGYBH,CGYMC,CDW,NGZL,CDF,cGzzxMc,MBZ,cywyxm,czlyq,'
-                                . 'This_quantity,ceilingPrice,pick_time,createtime,dputrecord,dStamp,wflow_status,status,pick_company_name')
-                            ->whereRaw('(mod_rq IS NULL OR TRIM(CAST(mod_rq AS CHAR(32))) = \'\' OR TRIM(CAST(mod_rq AS CHAR(32))) LIKE \'0000-00-00%\')')
-                            ->whereIn('wflow_status', ProcuremenStatus::wflowPendingApprovalValues())
-                            ->order('pick_time', 'desc')->order('id', 'desc')
-                            ->select();
-                    } catch (\Throwable $e) {
-                        Log::write('协助审批列表 fallback 查询异常:' . $e->getMessage(), 'error');
-                        $dbRows = [];
+            if (in_array($wffTab, ['audit', 'confirm'], true)) {
+                $dbRows = $this->loadPurchaseOrderRowsForListStage($wffTab, $ym, $applyMonthRange);
+                if ($applyMonthRange && $dbRows === []) {
+                    $fallbackYm = $this->resolveProcuremenDefaultYm($wffTab);
+                    if ($fallbackYm !== $ym && preg_match('/^\d{4}-\d{2}$/', $fallbackYm)) {
+                        $ym = $fallbackYm;
+                        $monthStart = $ym . '-01 00:00:00';
+                        $monthEnd = date('Y-m-t 23:59:59', strtotime($monthStart));
+                        $dbRows = $this->loadPurchaseOrderRowsForListStage($wffTab, $ym, true);
                     }
                 }
-                $pool = $this->procuremenPoolFromPurchaseOrderDbRows($dbRows);
-                if ($pool === []) {
+                $pool = $this->procuremenPoolFromPurchaseOrderDbRows(is_array($dbRows) ? $dbRows : []);
+                if ($wffTab === 'confirm' && $pool === []) {
                     Log::write('协助审批列表 pool 为空 ym=' . $ym . ' wffTab=' . $wffTab, 'warning');
                 }
             } else {
@@ -1717,12 +1763,6 @@ class Procuremen extends Backend
                 }
             }
 
-            if (in_array($wffTab, ['audit', 'confirm'], true) && $applyMonthRange) {
-                $ym = $this->resolveProcuremenActiveYm($wffTab, $ym, $pool, $listTimePrimary);
-                $monthStart = $ym . '-01 00:00:00';
-                $monthEnd = date('Y-m-t 23:59:59', strtotime($monthStart));
-            }
-
             $filtered = $this->filterProcuremenIndexPool(
                 $pool,
                 $monthStart,
@@ -3175,6 +3215,61 @@ class Procuremen extends Backend
         $group['remark_quote_pending'] = 0;
     }
 
+    /**
+     * 下发订单备注 MBZ(印件规格),不是供应商填写
+     *
+     * @param array{pos?:array, merge_rows?:array} $bundle
+     */
+    protected function resolveBundleOrderMbz(array $bundle): string
+    {
+        foreach (['pos', 'merge_rows'] as $key) {
+            foreach ($bundle[$key] ?? [] as $row) {
+                if (!is_array($row)) {
+                    continue;
+                }
+                $mbz = trim((string)($row['MBZ'] ?? $row['mbz'] ?? ''));
+                if ($mbz !== '') {
+                    return $mbz;
+                }
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * 供应商自己填的备注;与订单 MBZ 相同视为未填(历史误把规格写入明细)
+     */
+    protected function resolveSupplierOwnRemark($detailRemark, $orderMbz = ''): string
+    {
+        $own = trim((string)$detailRemark);
+        $mbz = trim((string)$orderMbz);
+        if ($own === '' || ($mbz !== '' && $own === $mbz)) {
+            return '';
+        }
+
+        return $own;
+    }
+
+    /**
+     * 供应商备注展示:仅自己填写的;空则不占备注行
+     *
+     * @param array<string, mixed> $group
+     */
+    protected function finalizeSupplierRemarkDisplay(array &$group, string $orderMbz, bool $quoteVisible): void
+    {
+        $group['remark'] = $this->resolveSupplierOwnRemark($group['remark'] ?? '', $orderMbz);
+        $this->maskSupplierRemarkBeforeBidOpen($group, $quoteVisible);
+        $group['has_remark'] = trim((string)($group['remark'] ?? '')) !== '';
+        $lineCount = (int)($group['line_count'] ?? 0);
+        if ($lineCount <= 0) {
+            $lines = $group['lines'] ?? $group['pick_lines'] ?? [];
+            $lineCount = is_array($lines) ? count($lines) : 0;
+        }
+        $extra = (!empty($group['has_remark']) ? 1 : 0) + (!empty($group['has_total']) ? 1 : 0);
+        $group['display_rowspan'] = $lineCount > 0 ? ($lineCount + $extra) : 0;
+    }
+
     /**
      * @param array{ccydh?:string} $bundle
      */
@@ -3691,6 +3786,7 @@ class Procuremen extends Backend
             return [];
         }
         $quoteVisible = $this->canViewProcuremenSupplierQuotesForBundle($bundle);
+        $orderMbz = $this->resolveBundleOrderMbz($bundle);
         $gymcMap = [];
         $qtyBySid = [];
         $orderedSids = [];
@@ -3868,11 +3964,8 @@ class Procuremen extends Backend
             $byCompany[$cn]['has_quote'] = $quoteVisible && $total > 0 && $quoted === $total;
             $byCompany[$cn]['line_count'] = $total;
             $byCompany[$cn]['has_total'] = $total > 0;
-            $byCompany[$cn]['has_remark'] = $total > 0;
-            $this->maskSupplierRemarkBeforeBidOpen($byCompany[$cn], $quoteVisible);
             $byCompany[$cn]['total_text'] = ($quoteVisible && $groupHasTotal) ? $this->formatProcuremenMoneyDisplay($groupTotal) : '';
-            // 工序行 + 备注行 + 总计行
-            $byCompany[$cn]['display_rowspan'] = $total > 0 ? ($total + 2) : 0;
+            $this->finalizeSupplierRemarkDisplay($byCompany[$cn], $orderMbz, $quoteVisible);
         }
 
         $groups = array_values($byCompany);
@@ -3905,6 +3998,7 @@ class Procuremen extends Backend
             return [];
         }
         $quoteVisible = $this->canViewProcuremenSupplierQuotesForBundle($bundle);
+        $orderMbz = $this->resolveBundleOrderMbz($bundle);
         $gymcMap = [];
         $qtyBySid = [];
         $orderedSids = [];
@@ -4083,11 +4177,8 @@ class Procuremen extends Backend
             $g['pick_lines'] = $pickLines;
             $g['line_count'] = $lineCount;
             $g['has_total'] = $lineCount > 0;
-            $g['has_remark'] = $lineCount > 0;
-            $this->maskSupplierRemarkBeforeBidOpen($g, $quoteVisible);
             $g['total_text'] = ($quoteVisible && $groupHasTotal) ? $this->formatProcuremenMoneyDisplay($groupTotal) : '';
-            // 工序行 + 备注行 + 总计行
-            $g['display_rowspan'] = $lineCount > 0 ? ($lineCount + 2) : 0;
+            $this->finalizeSupplierRemarkDisplay($g, $orderMbz, $quoteVisible);
             $detailPicksMap = is_array($g['detail_picks'] ?? null) ? $g['detail_picks'] : [];
             $detailPicks = [];
             $usedPickSids = [];
@@ -7405,6 +7496,10 @@ class Procuremen extends Backend
         $ccydh = trim((string)($main['CCYDH'] ?? ''));
         $quoteVisible = $this->isProcuremenBidOpenVerified($ccydh)
             || ((int)($main['manual_pick'] ?? 0) === 1);
+        $orderMbz = trim((string)($main['MBZ'] ?? $main['mbz'] ?? ''));
+        if ($orderMbz === '') {
+            $orderMbz = $this->resolveBundleOrderMbz(['pos' => [$main], 'merge_rows' => $mergeRows]);
+        }
         $qtyBySid = [];
         $gymcBySid = [];
         $orderedSids = [];
@@ -7546,23 +7641,20 @@ class Procuremen extends Backend
                 }
             }
             $lineCount = count($lines);
-            $hasRemark = $lineCount > 0;
             $supplierGroup = [
                 'company_name'   => $cn,
                 'username'       => $username,
                 'email'          => is_array($firstDetail) ? trim((string)($firstDetail['email'] ?? '')) : '',
                 'phone'          => $ph,
                 'remark'         => $remark,
-                'has_remark'     => $hasRemark,
                 'is_selected'    => ($selectedCompany !== '' && $cn === $selectedCompany),
                 'is_void'        => $groupVoid,
                 'lines'          => $lines,
                 'line_count'     => $lineCount,
-                'display_rowspan'=> $lineCount > 0 ? ($lineCount + 2) : 0,
                 'total_text'     => ($quoteVisible && $groupHas) ? $this->formatProcuremenMoneyDisplay($groupTotal) : '',
                 'has_total'      => $lineCount > 0,
             ];
-            $this->maskSupplierRemarkBeforeBidOpen($supplierGroup, $quoteVisible);
+            $this->finalizeSupplierRemarkDisplay($supplierGroup, $orderMbz, $quoteVisible);
             $supplierGroups[] = $supplierGroup;
         }
         usort($supplierGroups, function ($a, $b) {

+ 147 - 63
application/admin/controller/Procuremenarchive.php

@@ -27,6 +27,9 @@ class Procuremenarchive extends Backend
             'auditabandon'   => $canArchiveAbandon,
             'archiveabandon' => $canArchiveAbandon,
         ]);
+        if (session_status() === PHP_SESSION_ACTIVE) {
+            @session_write_close();
+        }
     }
 
     /**
@@ -93,12 +96,18 @@ class Procuremenarchive extends Backend
      */
     protected function applyPurchaseOrderArchiveWhere($query): void
     {
-        $quoted = [];
-        foreach (ProcuremenStatus::wflowApprovedValues() as $v) {
-            $quoted[] = "'" . str_replace("'", "''", (string)$v) . "'";
-        }
-        $wflowSql = 'TRIM(CAST(`wflow_status` AS CHAR)) IN (' . implode(',', $quoted) . ')';
-        $query->whereRaw('(' . $wflowSql . ' OR ' . ProcuremenStatus::sqlPoCompleted('status') . ')');
+        $query->where(function ($q) {
+            $q->whereIn('wflow_status', ProcuremenStatus::wflowApprovedValues())
+                ->whereOr('status', 'in', ProcuremenStatus::poCompletedValues());
+        });
+    }
+
+    /**
+     * 与 collapseArchiveRowsByOrder 一致:有订单号按 CCYDH 合并,空单号按 id
+     */
+    protected function archiveOrderGroupKeySql(): string
+    {
+        return "IF(TRIM(IFNULL(`CCYDH`,''))='', CONCAT('_id_', `id`), TRIM(`CCYDH`))";
     }
 
     public function index()
@@ -119,15 +128,97 @@ class Procuremenarchive extends Backend
 
             $sortField = preg_match('/^[a-zA-Z0-9_]+$/', (string)$sort) ? $sort : 'complete_time';
             $orderDir = strtoupper((string)$order) === 'ASC' ? 'ASC' : 'DESC';
-            // 完结/审批时间为计算字段,库表排序仅用真实列
-            $dbSortAllow = ['id', 'CCYDH', 'CYJMC', 'CGYMC', 'pick_company_name', 'createtime', 'pick_time', 'dStamp'];
-            $dbSortField = in_array($sortField, $dbSortAllow, true) ? $sortField : 'id';
-            $listQuery = Db::table('purchase_order');
-            $applyFilters($listQuery);
-            $rows = $listQuery
-                ->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time')
-                ->order($dbSortField, $orderDir)
-                ->select();
+            $groupKeySql = $this->archiveOrderGroupKeySql();
+
+            $total = 0;
+            try {
+                $countQuery = Db::table('purchase_order');
+                $applyFilters($countQuery);
+                $subSql = $countQuery->fieldRaw($groupKeySql . ' AS gk')->group($groupKeySql)->buildSql();
+                $cntRows = Db::query('SELECT COUNT(*) AS c FROM ' . $subSql . ' _archive_g');
+                $total = (int)($cntRows[0]['c'] ?? 0);
+            } catch (\Throwable $e) {
+                try {
+                    $countQuery = Db::table('purchase_order');
+                    $applyFilters($countQuery);
+                    $total = (int)$countQuery->count('DISTINCT CCYDH');
+                } catch (\Throwable $e2) {
+                    $total = 0;
+                }
+            }
+
+            if ($total <= 0) {
+                return json(['total' => 0, 'rows' => []]);
+            }
+
+            $orderSql = 'MAX(`id`) ' . $orderDir;
+            if ($sortField === 'CCYDH') {
+                $orderSql = $groupKeySql . ' ' . $orderDir;
+            } elseif (in_array($sortField, ['CYJMC', 'CGYMC', 'pick_company_name'], true)) {
+                $orderSql = 'MAX(`' . $sortField . '`) ' . $orderDir;
+            }
+
+            $pageQuery = Db::table('purchase_order');
+            $applyFilters($pageQuery);
+            try {
+                $groups = $pageQuery
+                    ->fieldRaw($groupKeySql . ' AS gk, MAX(`id`) AS max_id')
+                    ->group($groupKeySql)
+                    ->orderRaw($orderSql)
+                    ->limit($offset, $limit)
+                    ->select();
+            } catch (\Throwable $e) {
+                $groups = [];
+            }
+            if (!is_array($groups) || $groups === []) {
+                return json(['total' => $total, 'rows' => []]);
+            }
+
+            $ccydhs = [];
+            $orphanIds = [];
+            foreach ($groups as $g) {
+                if (!is_array($g)) {
+                    continue;
+                }
+                $gk = trim((string)($g['gk'] ?? ''));
+                if ($gk === '') {
+                    continue;
+                }
+                if (strpos($gk, '_id_') === 0) {
+                    $oid = (int)substr($gk, 4);
+                    if ($oid > 0) {
+                        $orphanIds[$oid] = true;
+                    }
+                } else {
+                    $ccydhs[$gk] = true;
+                }
+            }
+            $ccydhList = array_keys($ccydhs);
+            $orphanIdList = array_keys($orphanIds);
+            if ($ccydhList === [] && $orphanIdList === []) {
+                return json(['total' => $total, 'rows' => []]);
+            }
+
+            $detailQuery = Db::table('purchase_order');
+            $applyFilters($detailQuery);
+            $detailQuery->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,pick_company_name,createtime,dStamp,pick_time');
+            $detailQuery->where(function ($q) use ($ccydhList, $orphanIdList) {
+                if ($ccydhList !== []) {
+                    $quoted = [];
+                    foreach ($ccydhList as $dh) {
+                        $quoted[] = "'" . str_replace("'", "''", (string)$dh) . "'";
+                    }
+                    $q->whereRaw('TRIM(`CCYDH`) IN (' . implode(',', $quoted) . ')');
+                }
+                if ($orphanIdList !== []) {
+                    if ($ccydhList !== []) {
+                        $q->whereOr('id', 'in', $orphanIdList);
+                    } else {
+                        $q->where('id', 'in', $orphanIdList);
+                    }
+                }
+            });
+            $rows = $detailQuery->select();
             if (!is_array($rows)) {
                 $rows = [];
             }
@@ -187,13 +278,11 @@ class Procuremenarchive extends Backend
                 }
                 $sid = (int)($r['scydgy_id'] ?? 0);
                 $approve = ProcuremenTime::resolveCompletedDone($r, $approveTsMap);
-                // 完结:操作日志优先;无日志时用入库评分表时间兜底
                 $completeMap = $completeTsMap;
                 if ($sid > 0 && empty($completeMap[$sid]) && isset($inboundTimeMap[$sid])) {
                     $completeMap[$sid] = $inboundTimeMap[$sid];
                 }
                 $complete = ProcuremenTime::resolveCompletedDone($r, $completeMap);
-                // 未评分完结时勿用 pick_time 冒充完结:无完结日志则留空(审批时间仍单独展示)
                 if ($sid > 0 && empty($completeTsMap[$sid]) && empty($inboundTimeMap[$sid])) {
                     $complete = ['ts' => 0, 'text' => ''];
                 }
@@ -213,58 +302,53 @@ class Procuremenarchive extends Backend
             }
 
             $merged = $this->collapseArchiveRowsByOrder($out);
-            $nMerged = count($merged);
-            if ($nMerged > 1) {
-                if ($sortField === 'id') {
-                    usort($merged, function ($a, $b) use ($orderDir) {
-                        $cmp = ((int)($a['id'] ?? 0)) <=> ((int)($b['id'] ?? 0));
-                        if ($cmp === 0) {
-                            return strcmp((string)($a['CCYDH'] ?? ''), (string)($b['CCYDH'] ?? ''));
-                        }
+            $pageRows = $this->reorderArchiveRowsByGroupKeys($merged, $groups);
 
-                        return $orderDir === 'ASC' ? $cmp : -$cmp;
-                    });
-                } elseif ($sortField === 'createtime') {
-                    usort($merged, function ($a, $b) use ($orderDir) {
-                        $ta = (int)($a['createtime'] ?? 0);
-                        $tb = (int)($b['createtime'] ?? 0);
-                        if ($ta === $tb) {
-                            return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
-                        }
+            return json(['total' => $total, 'rows' => $pageRows]);
+        }
 
-                        return $orderDir === 'ASC' ? ($ta <=> $tb) : ($tb <=> $ta);
-                    });
-                } else {
-                    // 默认 / complete_time:有完结时间按完结时间;无完结则按审批时间
-                    usort($merged, function ($a, $b) use ($orderDir) {
-                        $ca = (int)($a['complete_time'] ?? 0);
-                        $cb = (int)($b['complete_time'] ?? 0);
-                        $ta = $ca > 0 ? $ca : (int)($a['createtime'] ?? 0);
-                        $tb = $cb > 0 ? $cb : (int)($b['createtime'] ?? 0);
-                        if ($ta <= 0 && $tb <= 0) {
-                            return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
-                        }
-                        if ($ta <= 0) {
-                            return 1;
-                        }
-                        if ($tb <= 0) {
-                            return -1;
-                        }
-                        if ($ta === $tb) {
-                            return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
-                        }
+        return $this->view->fetch();
+    }
 
-                        return $orderDir === 'ASC' ? ($ta <=> $tb) : ($tb <=> $ta);
-                    });
-                }
+    /**
+     * @param array<int, array<string, mixed>> $merged
+     * @param array<int, array<string, mixed>> $groups
+     * @return array<int, array<string, mixed>>
+     */
+    protected function reorderArchiveRowsByGroupKeys(array $merged, array $groups): array
+    {
+        $pos = [];
+        foreach ($groups as $i => $g) {
+            if (!is_array($g)) {
+                continue;
             }
-
-            $pageRows = array_slice($merged, $offset, $limit);
-
-            return json(['total' => $nMerged, 'rows' => $pageRows]);
+            $gk = trim((string)($g['gk'] ?? ''));
+            if ($gk !== '') {
+                $pos[$gk] = $i;
+            }
+        }
+        if ($pos === []) {
+            return $merged;
         }
+        usort($merged, function ($a, $b) use ($pos) {
+            $ka = trim((string)($a['CCYDH'] ?? ''));
+            if ($ka === '') {
+                $ka = '_id_' . (int)($a['id'] ?? 0);
+            }
+            $kb = trim((string)($b['CCYDH'] ?? ''));
+            if ($kb === '') {
+                $kb = '_id_' . (int)($b['id'] ?? 0);
+            }
+            $ia = $pos[$ka] ?? 999999;
+            $ib = $pos[$kb] ?? 999999;
+            if ($ia === $ib) {
+                return ((int)($b['id'] ?? 0)) <=> ((int)($a['id'] ?? 0));
+            }
 
-        return $this->view->fetch();
+            return $ia <=> $ib;
+        });
+
+        return array_values($merged);
     }
 
     /**

+ 13 - 5
application/admin/controller/Supplierservicescore.php

@@ -44,10 +44,11 @@ class Supplierservicescore extends Backend
                 $limit = 50;
             }
 
-            // 有搜索词:跨月查该供应商;无搜索:按查询月份展示当月
+            // 默认只读月度表,避免进页全量重算卡住;点「刷新」带 resync=1 再按规则同步
+            $forceSync = (string)$this->request->param('resync', '0') === '1';
             $list = $keyword !== ''
                 ? $this->loadSupplierScoreSearchList($keyword)
-                : $this->loadMonthlySupplierScoreList($ym, '');
+                : $this->loadMonthlySupplierScoreList($ym, '', $forceSync);
             $total = count($list);
             $pageRows = array_slice($list, $offset, $limit);
             $seqBase = $offset;
@@ -78,6 +79,13 @@ class Supplierservicescore extends Backend
             return json(['total' => $total, 'rows' => $rows]);
         }
 
+        $defaultYm = ProcuremenSupplierScore::latestReviewYm();
+        if (!preg_match('/^\d{4}-\d{2}$/', $defaultYm)) {
+            $defaultYm = date('Y-m');
+        }
+        $this->assign('defaultYm', $defaultYm);
+        $this->assignconfig('supplierScoreDefaultYm', $defaultYm);
+
         return $this->view->fetch();
     }
 
@@ -280,10 +288,10 @@ class Supplierservicescore extends Backend
     /**
      * 月度评审记录列表(仅展示,最终得分可人工填写)
      *
-     * @param bool $resync 是否先按规则重算(导出时 false,避免卡顿
+     * @param bool $resync 是否先按规则重算(列表/导出默认 false;点刷新传 true
      * @return array<int, array<string, mixed>>
      */
-    protected function loadMonthlySupplierScoreList(string $ym, string $keyword = '', bool $resync = true): array
+    protected function loadMonthlySupplierScoreList(string $ym, string $keyword = '', bool $resync = false): array
     {
         $ym = trim($ym);
         if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
@@ -462,7 +470,7 @@ class Supplierservicescore extends Backend
             return sprintf('%04d-%02d', (int)$m[1], (int)$m[2]);
         }
 
-        return date('Y-m');
+        return ProcuremenSupplierScore::latestReviewYm();
     }
 
     protected function resolveSearchKeyword(): string

+ 1 - 1
application/admin/view/procuremen/index.html

@@ -976,7 +976,7 @@
                             <div id="procuremen-toolbar-host" class="procuremen-toolbar-host clearfix"></div>
                             <div class="procuremen-table-area">
                                 <div id="toolbar" class="toolbar">
-                                    <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" >刷新 <i class="fa fa-refresh"></i> </a>
+                                    <a href="javascript:;" class="btn btn-primary btn-refresh" data-force-refresh="false" title="{:__('Refresh')}" >刷新 <i class="fa fa-refresh"></i> </a>
                                     {if isset($procuremenBtnAuditAbandon) && $procuremenBtnAuditAbandon}
                                     <a href="javascript:;" class="btn btn-danger procuremen-stage-hide hide" id="btn-procuremen-audit-abandon" title="勾选一条或多条订单退回协助初选重新下发(历史记录保留)"><i class="fa fa-repeat"></i> 重新下发</a>
                                     {/if}

+ 2 - 2
application/admin/view/supplierservicescore/index.html

@@ -70,13 +70,13 @@
     <div class="panel-body">
         <div class="widget-body no-padding">
             <div id="toolbar" class="toolbar">
-                <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}"><i class="fa fa-refresh"></i> 刷新</a>
+                <a href="javascript:;" class="btn btn-primary btn-refresh" title="刷新并按规则重算本月得分"><i class="fa fa-refresh"></i> 刷新</a>
                 <span style="display:inline-block;margin-left:10px;vertical-align:middle;">
                     <a href="javascript:;" class="btn btn-success btn-export-review" id="btn-export-review" title="按月份导出供应商评审表">
                         <i class="fa fa-download"></i> 导出供应商评审表
                     </a>
                     <label style="margin:0 6px 0 10px;font-weight:normal;" for="export-review-ym">查询月份</label>
-                    <input type="month" id="export-review-ym" class="form-control input-sm" style="display:inline-block;width:150px;height:30px;vertical-align:middle;" title="选择月份后自动查询该月评分"/>
+                    <input type="month" id="export-review-ym" class="form-control input-sm" value="{$defaultYm|default=''}" style="display:inline-block;width:150px;height:30px;vertical-align:middle;" title="选择月份后自动查询该月评分"/>
                 </span>
             </div>
             <table id="table" class="table table-striped table-bordered table-hover table-nowrap text-center"></table>

+ 126 - 34
application/common/library/ProcuremenSupplierScore.php

@@ -34,6 +34,9 @@ class ProcuremenSupplierScore
     /** @var bool|null */
     protected static $schemaReady = null;
 
+    /** 请求内缓存:供应商+月份 → 入库订单聚合,避免质量/价格/交货各查一遍 */
+    protected static $inboundAggCache = [];
+
     /** 结构探测缓存键(升版本可强制再跑一遍迁移) */
     const SCHEMA_CACHE_KEY = 'procuremen_supplier_score_schema_ok_v6';
 
@@ -488,14 +491,33 @@ class ProcuremenSupplierScore
             return 0;
         }
         self::ensureFinalScoreTable();
+        $existMap = [];
+        try {
+            $existRows = Db::table(self::TABLE_FINAL)
+                ->where('ym', $ym)
+                ->where('company_name', 'in', array_keys($uniq))
+                ->field('company_name')
+                ->select();
+            if (is_array($existRows)) {
+                foreach ($existRows as $er) {
+                    if (!is_array($er)) {
+                        continue;
+                    }
+                    $ecn = trim((string)($er['company_name'] ?? ''));
+                    if ($ecn !== '') {
+                        $existMap[$ecn] = true;
+                    }
+                }
+            }
+        } catch (\Throwable $e) {
+        }
         $now = date('Y-m-d H:i:s');
         $inserted = 0;
         foreach (array_keys($uniq) as $cn) {
+            if (!empty($existMap[$cn])) {
+                continue;
+            }
             try {
-                $exists = Db::table(self::TABLE_FINAL)->where('ym', $ym)->where('company_name', $cn)->find();
-                if (is_array($exists) && $exists !== []) {
-                    continue;
-                }
                 Db::table(self::TABLE_FINAL)->insert([
                     'ym'                => $ym,
                     'company_name'      => $cn,
@@ -718,6 +740,10 @@ class ProcuremenSupplierScore
         if ($companyName === '' || $ym === '') {
             return [];
         }
+        $cacheKey = $companyName . "\0" . $ym;
+        if (isset(self::$inboundAggCache[$cacheKey])) {
+            return self::$inboundAggCache[$cacheKey];
+        }
         /** @var array<string, array{result:string,delivery_status:string,customer_complaint:int,order_interrupt:int}> $byOrder */
         $byOrder = [];
         try {
@@ -747,7 +773,7 @@ class ProcuremenSupplierScore
                 ];
             }
 
-            return $byOrder;
+            return self::$inboundAggCache[$cacheKey] = $byOrder;
         }
         $sidSet = [];
         $inboundTimeBySid = [];
@@ -816,7 +842,7 @@ class ProcuremenSupplierScore
             }
         }
         if ($byOrder !== []) {
-            return $byOrder;
+            return self::$inboundAggCache[$cacheKey] = $byOrder;
         }
         // 回退:操作日志仅能还原合格/不合格(同样按完结月过滤)
         $legacy = self::loadInboundOrderResultsFromOperLogs($companyName, $ym);
@@ -829,7 +855,7 @@ class ProcuremenSupplierScore
             ];
         }
 
-        return $byOrder;
+        return self::$inboundAggCache[$cacheKey] = $byOrder;
     }
 
     /**
@@ -1240,11 +1266,25 @@ class ProcuremenSupplierScore
                 }
             }
         }
-        // 2) 全部有入库评分的供应商:再按完结月精确过滤
+        // 2) 当月完结工序 + 当月入库评分时间,再按完结月精确过滤(勿全表拉取入库评分)
+        $scoreRows = [];
         try {
-            $scoreRows = Db::table('purchase_order_inbound_score')
-                ->field('scydgy_id,company_name,createtime,updatetime')
-                ->select();
+            $scoreQuery = Db::table('purchase_order_inbound_score')
+                ->field('scydgy_id,company_name,createtime,updatetime');
+            $completeSidList = array_keys($completeSids);
+            if ($completeSidList !== []) {
+                $scoreQuery->where(function ($q) use ($completeSidList, $ym) {
+                    $q->where('scydgy_id', 'in', $completeSidList)
+                        ->whereOr('createtime', 'like', $ym . '%')
+                        ->whereOr('updatetime', 'like', $ym . '%');
+                });
+            } else {
+                $scoreQuery->where(function ($q) use ($ym) {
+                    $q->where('createtime', 'like', $ym . '%')
+                        ->whereOr('updatetime', 'like', $ym . '%');
+                });
+            }
+            $scoreRows = $scoreQuery->select();
         } catch (\Throwable $e) {
             $scoreRows = [];
             Log::write('collectMonthlyReviewCompanyNames inbound: ' . $e->getMessage(), 'error');
@@ -1387,7 +1427,10 @@ class ProcuremenSupplierScore
             return $result;
         }
         self::ensureFinalScoreTable();
-        self::repairInboundScoreFromOperLogs($ym);
+        // 单家同步(入库评分后)不必全月扫操作日志回填
+        if ($onlyCompany === null || trim($onlyCompany) === '') {
+            self::repairInboundScoreFromOperLogs($ym);
+        }
         $companies = self::collectMonthlyReviewCompanyNames($ym, $onlyCompany);
         if ($companies === []) {
             return $result;
@@ -1583,13 +1626,46 @@ class ProcuremenSupplierScore
         }
     }
 
+    /**
+     * 默认查询月份:优先月度评审表已有记录的最近一月,其次询价评分表,再次入库评分时间
+     */
+    public static function latestReviewYm(): string
+    {
+        self::ensureFinalScoreTable();
+        foreach ([self::TABLE_FINAL, self::TABLE_SCORE] as $table) {
+            try {
+                $raw = Db::table($table)->max('ym');
+                $ym = self::formatScoreYm($raw);
+                if ($ym !== '') {
+                    return $ym;
+                }
+            } catch (\Throwable $e) {
+            }
+        }
+        try {
+            $row = Db::query(
+                "SELECT DATE_FORMAT(MAX(`createtime`), '%Y-%m') AS `ym` FROM `purchase_order_inbound_score`"
+                . " WHERE `createtime` IS NOT NULL AND CAST(`createtime` AS CHAR(32)) NOT LIKE '0000-00-00%'"
+            );
+            if (is_array($row) && isset($row[0])) {
+                $ym = self::formatScoreYm($row[0]['ym'] ?? '');
+                if ($ym !== '') {
+                    return $ym;
+                }
+            }
+        } catch (\Throwable $e) {
+        }
+
+        return date('Y-m');
+    }
+
     /**
      * 读取某月评审记录(仅读月度表)
      *
-     * @param bool $resync true=先补齐供应商并按规则重算;false=只读库(导出用,避免卡顿)
+     * @param bool $resync true=先补齐供应商并按规则重算;false=只读库(列表/导出默认,避免卡顿)
      * @return array<string, array{score:float,quality_score:?float,price_score:?float,delivery_score:?float,final_score:?float,final_saved:int,score_grade:string,score_date:string}>
      */
-    public static function loadMonthlyMapByYm(string $ym, bool $resync = true): array
+    public static function loadMonthlyMapByYm(string $ym, bool $resync = false): array
     {
         $ym = trim($ym);
         if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
@@ -1602,16 +1678,23 @@ class ProcuremenSupplierScore
             // 按月度规则重算质量/价格/交货/增值(满分105;qp_manual=1 跳过)
             self::syncMonthlyReviewScoresFromRules($ym);
         }
-        try {
-            $rows = Db::table(self::TABLE_FINAL)
-                ->where('ym', $ym)
-                ->field('company_name,score,quality_score,price_score,delivery_score,value_added_score,final_score,score_grade,ym')
-                ->select();
-        } catch (\Throwable $e) {
-            return [];
-        }
-        if (!is_array($rows)) {
-            return [];
+        $fetchRows = function () use ($ym) {
+            try {
+                $rows = Db::table(self::TABLE_FINAL)
+                    ->where('ym', $ym)
+                    ->field('company_name,score,quality_score,price_score,delivery_score,value_added_score,final_score,score_grade,ym')
+                    ->select();
+            } catch (\Throwable $e) {
+                return [];
+            }
+
+            return is_array($rows) ? $rows : [];
+        };
+        $rows = $fetchRows();
+        // 只读进页时若月度表还没行,先按询价明细补名称,避免选错月份外看起来像「没数据」
+        if ($rows === [] && !$resync) {
+            self::ensureMonthlySupplierNamesFromOrderYm($ym);
+            $rows = $fetchRows();
         }
         $out = [];
         foreach ($rows as $r) {
@@ -2700,7 +2783,6 @@ class ProcuremenSupplierScore
      */
     public static function detectShowLeadScore(array $quoteGroups = [], ?array $rule = null): bool
     {
-        $hasSavedScore = false;
         foreach ($quoteGroups as $g) {
             if (!is_array($g)) {
                 continue;
@@ -2708,13 +2790,6 @@ class ProcuremenSupplierScore
             if (!empty($g['show_service_lead_score'])) {
                 return true;
             }
-            if (!empty($g['show_service_score'])) {
-                $hasSavedScore = true;
-            }
-        }
-        // 历史单已按旧规则落库且交货期权重为 0:不显示这两列
-        if ($hasSavedScore) {
-            return false;
         }
         $rule = $rule ?: self::getDefaultRule();
 
@@ -3019,6 +3094,10 @@ class ProcuremenSupplierScore
             return $quoteGroups;
         }
 
+        $rule = self::resolveRuleForCcydh($ccydh);
+        $ruleQw = (int)($rule['quality_weight'] ?? 0);
+        $rulePw = (int)($rule['price_weight'] ?? 0);
+        $ruleLw = (int)($rule['lead_weight'] ?? 0);
         $saved = self::loadSavedByCcydh($ccydh);
         $needCalc = ($saved === []);
         if (!$needCalc) {
@@ -3035,9 +3114,22 @@ class ProcuremenSupplierScore
             }
             $needCalc = !$anyHit;
         }
-        // 无落库记录时仅内存计算用于展示;落库由开标验证 saveForOrder 负责
+        if (!$needCalc) {
+            foreach ($saved as $it) {
+                if (!is_array($it)) {
+                    continue;
+                }
+                if ((int)($it['quality_weight'] ?? 0) !== $ruleQw
+                    || (int)($it['price_weight'] ?? 0) !== $rulePw
+                    || (int)($it['lead_weight'] ?? 0) !== $ruleLw) {
+                    $needCalc = true;
+                    break;
+                }
+            }
+        }
+        // 无落库或权重与本单规则不一致:按本单规则内存重算展示;落库仍由开标验证 saveForOrder 负责
         if ($needCalc) {
-            $saved = self::calculateForQuoteGroups($quoteGroups, null, date('Y-m'));
+            $saved = self::calculateForQuoteGroups($quoteGroups, $rule, date('Y-m'));
         }
 
         foreach ($quoteGroups as &$g) {

+ 15 - 15
application/database.php

@@ -4,23 +4,23 @@ use think\Env;
 return [
     // 数据库类型
 
-//    // 服务器地址
-//    'hostname'        => Env::get('database.hostname', '121.40.91.170'),
-//    // 数据库名
-//    'database'        => Env::get('database.database', 'xinhua_erp'),
-//    // 用户名
-//    'username'        => Env::get('database.username', 'xinhua_erp'),
-//    // 密码
-//    'password'        => Env::get('database.password', 'YiDxNKHBiAkTnRtb'),
+    // // 服务器地址
+    // 'hostname'        => Env::get('database.hostname', '121.40.91.170'),
+    // // 数据库名
+    // 'database'        => Env::get('database.database', 'xinhua_erp'),
+    // // 用户名
+    // 'username'        => Env::get('database.username', 'xinhua_erp'),
+    // // 密码
+    // 'password'        => Env::get('database.password', 'YiDxNKHBiAkTnRtb'),
 
     // 服务器地址
-    'hostname'        => Env::get('database.hostname', 'rm-bp17w41t6c09dl10ayo.mysql.rds.aliyuncs.com'),
-    // 数据库名
-    'database'        => Env::get('database.database', 'xinhua_erp'),
-    // 用户名
-    'username'        => Env::get('database.username', 'xinhua_erp'),
-    // 密码
-    'password'        => Env::get('database.password', 'D3#xJ9!sQ4%vU8'),
+   'hostname'        => Env::get('database.hostname', 'rm-bp17w41t6c09dl10ayo.mysql.rds.aliyuncs.com'),
+   // 数据库名
+   'database'        => Env::get('database.database', 'xinhua_erp'),
+   // 用户名
+   'username'        => Env::get('database.username', 'xinhua_erp'),
+   // 密码
+   'password'        => Env::get('database.password', 'D3#xJ9!sQ4%vU8'),
     // 端口
     'hostport'        => Env::get('database.hostport', ''),
     // 连接dsn

+ 5 - 0
application/extra/mproc.php

@@ -40,6 +40,11 @@ return [
     // 手机端交货情况:所属角色组(默认「生产员」,展示名「生产部」)
     'mobile_delivery_group_id'  => 15,
     'mobile_delivery_dept_name' => '生产部',
+    // 送货单二维码根地址。仅本地扫码用;线上会自动改用 mobile_base_url(https://xh.7in6.com)
+    'delivery_scan_base'        => 'http://20.0.6.57',
+    // 送货单抬头(客户=本公司)
+    'delivery_customer_name'    => '浙江新华数码印务有限公司',
+    'delivery_address'          => '杭州市经济技术开发区文海北路369号',
     // 手机端新增订单询价:所属角色组(默认「业务员」,与 rfq_notify_salesman_group_id 一致)
     'mobile_rfq_group_id'       => 12,
 

+ 446 - 8
application/index/controller/Index.php

@@ -495,7 +495,8 @@ class Index extends Frontend
         $okInbound = stripos($s, 'index/index/inboundscore') !== false;
         $okDelivery = stripos($s, 'index/index/deliveryscore') !== false;
         $okRfqAdd = stripos($s, 'index/index/rfqadd') !== false;
-        if (!$okHome && !$okInbound && !$okDelivery && !$okRfqAdd) {
+        $okNote = stripos($s, 'index/index/deliverynote') !== false;
+        if (!$okHome && !$okInbound && !$okDelivery && !$okRfqAdd && !$okNote) {
             return '';
         }
         if (stripos($s, 'index/index/login') !== false) {
@@ -719,6 +720,11 @@ class Index extends Frontend
             return '';
         }
 
+        $raw = $this->mprocSanitizeRedirectUrl($redirectPathOrUrl);
+        if ($raw !== '' && stripos($raw, 'deliverynote') !== false) {
+            return $this->mprocAbsoluteFromSanitizedPath($raw, 'index/index/deliverynote');
+        }
+
         return $this->mprocBuildAfterLoginIndexUrl($redirectPathOrUrl);
     }
 
@@ -829,6 +835,20 @@ class Index extends Frontend
         return '';
     }
 
+    /**
+     * 供应商自己填的备注;与订单 MBZ 相同视为未填
+     */
+    protected function mprocSupplierOwnRemark($detailRemark, $orderMbz = ''): string
+    {
+        $own = trim((string)$detailRemark);
+        $mbz = trim((string)$orderMbz);
+        if ($own === '' || ($mbz !== '' && $own === $mbz)) {
+            return '';
+        }
+
+        return $own;
+    }
+
     /**
      * 列表:非管理员按 company_name 与登录单位一致,或 phone 与登录手机号一致(兼容手工下发单位名细微差异)
      *
@@ -2161,6 +2181,7 @@ class Index extends Frontend
             $g['can_edit'] = $canEdit ? 1 : 0;
             $g['mproc_bid_open_verified'] = $bidOpen ? 1 : 0;
             $remark = '';
+            $orderRemark = '';
             $doneLabel = '';
             $pickResult = '';
             $hasWin = false;
@@ -2170,13 +2191,17 @@ class Index extends Frontend
                 if (!is_array($ln)) {
                     continue;
                 }
-                $rm = trim((string)($ln['mproc_remark'] ?? ''));
-                if ($rm === '') {
-                    $rm = trim((string)($ln['mproc_order_remark'] ?? $ln['MBZ'] ?? ''));
-                }
+                $rm = $this->mprocSupplierOwnRemark(
+                    (string)($ln['mproc_remark'] ?? ''),
+                    (string)($ln['mproc_order_remark'] ?? $ln['MBZ'] ?? '')
+                );
                 if ($rm !== '' && $remark === '') {
                     $remark = $rm;
                 }
+                $ombz = trim((string)($ln['mproc_order_remark'] ?? $ln['MBZ'] ?? ''));
+                if ($ombz !== '' && $orderRemark === '') {
+                    $orderRemark = $ombz;
+                }
                 $pr = trim((string)($ln['mproc_pick_result'] ?? ''));
                 $dl = trim((string)($ln['mproc_done_label'] ?? ''));
                 if ($pr === '中标' || $dl === '中标') {
@@ -2198,6 +2223,7 @@ class Index extends Frontend
                 $pickResult = '';
             }
             $g['remark'] = $remark;
+            $g['mproc_order_remark'] = $orderRemark;
             $g['mproc_done_label'] = $doneLabel;
             $g['mproc_pick_result'] = $pickResult;
             $out[] = $g;
@@ -2371,10 +2397,13 @@ class Index extends Frontend
             }
             $row['mproc_fill_hint'] = '';
             $row['mproc_this_quantity_display'] = $this->mprocResolveDisplayThisQuantity($row);
-            // 列表备注:优先下发备注 MBZ,其次供应商明细备注
+            // 列表备注:仅供应商自己填写的明细备注,不用订单 MBZ 顶替
             $orderRemark = trim((string)($row['mproc_order_remark'] ?? $row['MBZ'] ?? ''));
             $detailRemark = $this->mprocResolveDetailRemark($row);
-            $row['mproc_remark'] = $orderRemark !== '' ? $orderRemark : $detailRemark;
+            if ($orderRemark === '') {
+                $row['mproc_order_remark'] = '';
+            }
+            $row['mproc_remark'] = $this->mprocSupplierOwnRemark($detailRemark, $orderRemark);
         }
         unset($row);
         $rows = array_values(array_filter($rows, function ($r) {
@@ -2972,6 +3001,7 @@ class Index extends Frontend
         $this->view->assign('mprocFocusTab', $mprocFocusTab);
         $this->view->assign('mprocBootstrapToken', trim((string)($user['token'] ?? '')));
         $this->view->assign('mprocBootstrapKeepHours', $this->mprocKeepHours());
+        $this->view->assign('mprocDeliveryNoteUrl', url('index/index/deliverynote'));
 
         if ($mainTab === 'me') {
             $this->view->assign('rows', []);
@@ -3737,6 +3767,11 @@ class Index extends Frontend
             }
             throw new \InvalidArgumentException('无权修改备注');
         }
+        $orderMbz = '';
+        if (is_array($po)) {
+            $orderMbz = trim((string)($po['MBZ'] ?? $po['mbz'] ?? ''));
+        }
+        $remarkRaw = $this->mprocSupplierOwnRemark($remarkRaw, $orderMbz);
 
         $ccydhCol = $this->mprocResolveProcuremenColumn(['ccydh']);
         $companyCol = $this->mprocResolveProcuremenColumn(['company_name']);
@@ -4118,18 +4153,421 @@ class Index extends Frontend
             return;
         }
         $this->mprocEnsureInboundScoreTable();
+        $ccydh = trim((string)$this->request->get('ccydh', ''));
         $q = trim((string)$this->request->get('q', ''));
+        if ($q === '' && $ccydh !== '') {
+            $q = $ccydh;
+        }
+        if ($ccydh === '' && $q !== '') {
+            $ccydh = $q;
+        }
+        $adminId = (int)($user['admin_id'] ?? 0);
+        $scoreTab = 'pending';
+        $rows = $this->mprocLoadInboundScoreRows('pending', $q, $adminId, 'delivery');
+        if ($ccydh !== '' && $rows === []) {
+            $scored = $this->mprocLoadInboundScoreRows('scored', $q, $adminId, 'delivery');
+            if ($scored !== []) {
+                $scoreTab = 'scored';
+                $rows = $scored;
+            }
+        }
         $profile = $this->mprocProfileForUser($user);
         $this->view->assign('mprocSearchQ', $q);
+        $this->view->assign('mprocFocusCcydh', $ccydh);
+        $this->view->assign('mprocScoreTab', $scoreTab);
         $this->view->assign('mprocProfile', $profile);
         $this->view->assign('mprocBootstrapToken', trim((string)($user['token'] ?? '')));
         $this->view->assign('mprocBootstrapKeepHours', $this->mprocKeepHours());
         $this->view->assign('mprocScoreScope', 'delivery');
-        $this->view->assign('rows', $this->mprocLoadInboundScoreRows('pending', $q, (int)($user['admin_id'] ?? 0), 'delivery'));
+        $this->view->assign('rows', $rows);
 
         return $this->view->fetch();
     }
 
+    /**
+     * 供应商送货单(中标后出示二维码,生产部扫码进入交货评估)
+     */
+    public function deliverynote()
+    {
+        $user = $this->mprocGetUser();
+        if (!$user) {
+            $uri = isset($_SERVER['REQUEST_URI']) ? (string)$_SERVER['REQUEST_URI'] : '';
+            $safe = $this->mprocSanitizeRedirectUrl($uri);
+            if ($safe !== '') {
+                Session::set('mproc_intended_url', $safe);
+            }
+            $this->redirect($this->mprocBuildLoginUrl($safe));
+
+            return;
+        }
+        if (!empty($user['is_admin'])) {
+            $jump = $this->mprocBuildAfterLoginHomeUrl($user);
+            if ($jump === '') {
+                $this->mprocDropCurrentLogin();
+                $this->redirect(url('index/index/login'));
+
+                return;
+            }
+            $this->redirect($jump);
+
+            return;
+        }
+        $user = $this->mprocSyncSessionCustomerUser($user);
+        $ccydh = trim((string)$this->request->param('ccydh', ''));
+        $note = $this->mprocLoadWonDeliveryNote($user, $ccydh);
+        if ($note === null) {
+            $this->error('未找到该中标订单的送货单', url('index/index/index', ['tab' => 'done']));
+
+            return;
+        }
+        $this->view->assign('note', $note);
+
+        return $this->view->fetch();
+    }
+
+    /**
+     * 当前供应商的中标送货单数据;无权或不存在返回 null
+     *
+     * @param array<string, mixed> $user
+     * @return array<string, mixed>|null
+     */
+    protected function mprocLoadWonDeliveryNote(array $user, string $ccydh): ?array
+    {
+        $ccydh = trim($ccydh);
+        if ($ccydh === '') {
+            return null;
+        }
+        $company = trim((string)($user['company_name'] ?? ''));
+        $phone = trim((string)($user['phone'] ?? ''));
+        if ($company === '' && $phone !== '') {
+            $company = $this->mprocResolveCompanyForLoginPhone($phone);
+        }
+        try {
+            $poRows = Db::table('purchase_order')
+                ->where('CCYDH', $ccydh)
+                ->whereRaw('(mod_rq IS NULL OR TRIM(CAST(mod_rq AS CHAR(32))) = \'\' OR TRIM(CAST(mod_rq AS CHAR(32))) LIKE \'0000-00-00%\')')
+                ->field('id,scydgy_id,CCYDH,CYJMC,CGYMC,CDW,CDF,MBZ,cGzzxMc,CCLBMMC,CDXMC,pick_company_name,pick_time,wflow_status,status')
+                ->order('id', 'asc')
+                ->select();
+        } catch (\Throwable $e) {
+            $poRows = [];
+        }
+        if (!is_array($poRows) || $poRows === []) {
+            return null;
+        }
+        $won = false;
+        $gymcList = [];
+        foreach ($poRows as $po) {
+            if (!is_array($po)) {
+                continue;
+            }
+            $pick = trim((string)($po['pick_company_name'] ?? ''));
+            if ($pick !== '' && $company !== '' && $pick === $company) {
+                $won = true;
+            }
+            $gx = trim((string)($po['CGYMC'] ?? ''));
+            if ($gx !== '' && !in_array($gx, $gymcList, true)) {
+                $gymcList[] = $gx;
+            }
+        }
+        $detailPhone = '';
+        try {
+            $dq = Db::table('purchase_order_detail')->where('CCYDH', $ccydh);
+            $userWhere = $this->mprocListWhereForLoginUser($user);
+            if ($userWhere !== []) {
+                $dq->where($userWhere);
+            }
+            $details = $dq->field('id,scydgy_id,company_name,phone,status,status_name,remark')->select();
+        } catch (\Throwable $e) {
+            $details = [];
+        }
+        if (is_array($details)) {
+            foreach ($details as $d) {
+                if (!is_array($d)) {
+                    continue;
+                }
+                if ($detailPhone === '') {
+                    $detailPhone = trim((string)($d['phone'] ?? ''));
+                }
+                $dCn = trim((string)($d['company_name'] ?? ''));
+                if ($dCn !== '' && $company !== '' && $dCn === $company) {
+                    if (ProcuremenStatus::isPodPicked($d['status'] ?? '')) {
+                        $won = true;
+                    }
+                }
+            }
+        }
+        if (!$won) {
+            return null;
+        }
+        $pickFirst = static function (array $rows, array $keys): string {
+            foreach ($rows as $row) {
+                if (!is_array($row)) {
+                    continue;
+                }
+                foreach ($keys as $k) {
+                    $v = trim((string)($row[$k] ?? ''));
+                    if ($v !== '') {
+                        return $v;
+                    }
+                }
+            }
+
+            return '';
+        };
+        $profile = $this->mprocProfileForUser($user);
+        $supplierName = trim((string)($profile['company_name'] ?? ''));
+        if ($supplierName === '') {
+            $supplierName = $company;
+        }
+        if ($supplierName === '') {
+            $supplierName = $pickFirst($poRows, ['pick_company_name']);
+        }
+        $mobile = trim((string)($profile['phone'] ?? ''));
+        if ($mobile === '') {
+            $mobile = $phone !== '' ? $phone : $detailPhone;
+        }
+        $contact = trim((string)($profile['contact_name'] ?? ''));
+        $customerName = trim((string)Config::get('mproc.delivery_customer_name'));
+        if ($customerName === '') {
+            $customerName = '浙江新华数码印务有限公司';
+        }
+        $address = trim((string)Config::get('mproc.delivery_address'));
+        if ($address === '') {
+            $address = '杭州市经济技术开发区文海北路369号';
+        }
+        $base = $this->mprocDeliveryScanBaseUrl();
+        $scanUrl = $base . $this->mprocDeliveryScanPath() . '?' . http_build_query(
+            ['ccydh' => $ccydh, 'q' => $ccydh],
+            '',
+            '&',
+            PHP_QUERY_RFC3986
+        );
+
+        return [
+            'generated_at'   => $this->mprocDeliveryNoteGeneratedAt($ccydh, $supplierName),
+            'ccydh'          => $ccydh,
+            'cyjmc'          => $pickFirst($poRows, ['CYJMC']),
+            'cgymc'          => $gymcList !== [] ? implode('、', $gymcList) : '',
+            'cdw'            => $pickFirst($poRows, ['CDW']),
+            'cdf'            => $pickFirst($poRows, ['CDF']),
+            'remark'         => $pickFirst($poRows, ['MBZ']),
+            'customer_name'  => $customerName,
+            'address'        => $address,
+            'supplier_name'  => $supplierName,
+            'contact_name'   => $contact,
+            'mobile'         => $mobile,
+            'scan_url'       => $scanUrl,
+            'qr_data_uri'    => $this->mprocDeliveryQrDataUri($scanUrl),
+        ];
+    }
+
+    /**
+     * 送货单首次生成时间:点过送货码后保持不变
+     */
+    protected function mprocDeliveryNoteGeneratedAt(string $ccydh, string $company): string
+    {
+        $now = date('Y-m-d H:i:s');
+        $this->mprocEnsureDeliveryNoteTable();
+        try {
+            $q = Db::table('purchase_order_delivery_note')->where('ccydh', $ccydh);
+            if ($company !== '') {
+                $q->where('company_name', $company);
+            }
+            $row = $q->order('id', 'asc')->find();
+            if (is_array($row) && trim((string)($row['generated_at'] ?? '')) !== '') {
+                $t = strtotime((string)$row['generated_at']);
+
+                return $t ? date('Y-m-d H:i', $t) : date('Y-m-d H:i');
+            }
+            Db::table('purchase_order_delivery_note')->insert([
+                'ccydh'        => $ccydh,
+                'company_name' => $company,
+                'generated_at' => $now,
+                'createtime'   => $now,
+                'updatetime'   => $now,
+            ]);
+
+            return date('Y-m-d H:i');
+        } catch (\Throwable $e) {
+            return date('Y-m-d H:i');
+        }
+    }
+
+    protected function mprocEnsureDeliveryNoteTable(): void
+    {
+        static $ok = false;
+        if ($ok) {
+            return;
+        }
+        try {
+            Db::query('SELECT 1 FROM `purchase_order_delivery_note` LIMIT 1');
+            $ok = true;
+        } catch (\Throwable $e) {
+            try {
+                Db::execute("CREATE TABLE IF NOT EXISTS `purchase_order_delivery_note` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT,
+  `ccydh` varchar(64) NOT NULL DEFAULT '' COMMENT '订单号',
+  `company_name` varchar(255) NOT NULL DEFAULT '' COMMENT '供应商',
+  `generated_at` datetime NOT NULL COMMENT '首次生成时间',
+  `createtime` datetime DEFAULT NULL,
+  `updatetime` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_ccydh_company` (`ccydh`,`company_name`(64))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='送货单首次生成记录'");
+                $ok = true;
+            } catch (\Throwable $e2) {
+            }
+        }
+    }
+
+    /**
+     * QR Code 容错 H(约 30%),输出 data URI,避免手机端 canvas 空白
+     */
+    protected function mprocDeliveryQrDataUri(string $text): string
+    {
+        $text = trim($text);
+        if ($text === '' || !function_exists('imagepng')) {
+            return '';
+        }
+        $lib = ROOT_PATH . 'extend' . DIRECTORY_SEPARATOR . 'phpqrcode' . DIRECTORY_SEPARATOR . 'phpqrcode.php';
+        if (!is_file($lib)) {
+            return '';
+        }
+        if (!class_exists('QRcode', false)) {
+            require_once $lib;
+        }
+        if (!class_exists('QRcode')) {
+            return '';
+        }
+        $tmp = tempnam(sys_get_temp_dir(), 'dnqr');
+        if ($tmp === false) {
+            return '';
+        }
+        $bin = '';
+        try {
+            \QRcode::png($text, $tmp, 'H', 6, 2);
+            $bin = is_file($tmp) ? (string)file_get_contents($tmp) : '';
+        } catch (\Throwable $e) {
+            $bin = '';
+        }
+        if (is_file($tmp)) {
+            @unlink($tmp);
+        }
+        if ($bin === '' || strncmp($bin, "\x89PNG", 4) !== 0) {
+            return '';
+        }
+
+        return 'data:image/png;base64,' . base64_encode($bin);
+    }
+
+    /**
+     * 送货单二维码根地址。
+     * 本地测扫码用 delivery_scan_base(局域网 IP);线上访问正式域名时自动用线上地址,不受本机 IP 配置影响。
+     */
+    protected function mprocDeliveryScanBaseUrl(): string
+    {
+        $reqBase = rtrim((string)$this->request->root(true), '/');
+        $host = strtolower((string)$this->request->host());
+        if (strpos($host, ':') !== false) {
+            $host = explode(':', $host, 2)[0];
+        }
+        $isLocalHost = $this->mprocIsLanIpv4($host)
+            || in_array($host, ['xh', 'xh.cn', 'www.xh.cn', 'localhost', '127.0.0.1'], true)
+            || strpos($host, '127.') === 0;
+        if (!$isLocalHost) {
+            $prod = rtrim(trim((string)Config::get('mproc.mobile_base_url')), '/');
+            if ($prod !== '' && preg_match('#^https?://#i', $prod)) {
+                return $prod;
+            }
+
+            return $reqBase;
+        }
+        $cfg = rtrim(trim((string)Config::get('mproc.delivery_scan_base')), '/');
+        if ($cfg !== '' && preg_match('#^https?://#i', $cfg)) {
+            return $cfg;
+        }
+        if ($this->mprocIsLanIpv4($host)) {
+            return $reqBase;
+        }
+        $lanIp = $this->mprocDetectLanIpv4();
+        if ($lanIp === '') {
+            return $reqBase;
+        }
+        $parts = parse_url($reqBase);
+        $path = (is_array($parts) && isset($parts['path'])) ? (string)$parts['path'] : '';
+        $scheme = (is_array($parts) && !empty($parts['scheme'])) ? (string)$parts['scheme'] : ($this->request->scheme() ?: 'http');
+        $port = '';
+        if (is_array($parts) && !empty($parts['port']) && (int)$parts['port'] !== 80 && (int)$parts['port'] !== 443) {
+            $port = ':' . (int)$parts['port'];
+        }
+
+        return rtrim($scheme . '://' . $lanIp . $port . $path, '/');
+    }
+
+    /**
+     * 送货单扫码路径。局域网 IP 通常没有伪静态,必须带 /index.php
+     */
+    protected function mprocDeliveryScanPath(): string
+    {
+        $indexPath = trim((string)Config::get('mproc.mobile_index_path'));
+        if ($indexPath === '') {
+            $indexPath = '/index.php/index/index/index';
+        } else {
+            $indexPath = '/' . ltrim($indexPath, '/');
+        }
+        $path = preg_replace('#/index$#', '/deliveryscore', rtrim($indexPath, '/'));
+        if (!is_string($path) || $path === '' || substr($path, -6) === '/index') {
+            return '/index.php/index/index/deliveryscore';
+        }
+
+        return $path;
+    }
+
+    protected function mprocIsLanIpv4(string $ip): bool
+    {
+        if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+            return false;
+        }
+        if ($ip === '127.0.0.1' || strpos($ip, '127.') === 0 || strpos($ip, '169.254.') === 0) {
+            return false;
+        }
+
+        return (bool)preg_match('/^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|20\.0\.)/', $ip);
+    }
+
+    protected function mprocDetectLanIpv4(): string
+    {
+        if (function_exists('socket_create')) {
+            $sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
+            if ($sock) {
+                @socket_connect($sock, '8.8.8.8', 53);
+                $addr = '';
+                @socket_getsockname($sock, $addr);
+                @socket_close($sock);
+                if (is_string($addr) && $this->mprocIsLanIpv4($addr)) {
+                    return $addr;
+                }
+            }
+        }
+        if (strncasecmp(PHP_OS, 'WIN', 3) === 0) {
+            $out = @shell_exec('ipconfig');
+            if (is_string($out) && preg_match_all('/IPv4[^0-9]+(\d+\.\d+\.\d+\.\d+)/', $out, $m)) {
+                foreach ($m[1] as $ip) {
+                    if ($this->mprocIsLanIpv4((string)$ip)) {
+                        return (string)$ip;
+                    }
+                }
+            }
+        }
+        $guess = gethostbyname(gethostname());
+        if (is_string($guess) && $this->mprocIsLanIpv4($guess)) {
+            return $guess;
+        }
+
+        return '';
+    }
+
     /**
      * 质量评分列表 JSON(质检;tab=pending|scored)
      */

+ 621 - 0
application/index/view/index/deliverynote.html

@@ -0,0 +1,621 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+    <title>送货单</title>
+    <style>
+        * { box-sizing: border-box; }
+        html, body {
+            margin: 0; min-height: 100%;
+            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
+            background: #eef3f7; color: #222; font-size: 14px;
+        }
+        .bar {
+            position: sticky; top: 0; z-index: 20;
+            height: 48px; padding: 0 12px;
+            background: #3c8dbc; color: #fff;
+            display: flex; align-items: center; justify-content: space-between;
+        }
+        .bar a, .bar button {
+            color: #fff; text-decoration: none; font-size: 14px; padding: 6px 4px;
+            background: none; border: 0; font-family: inherit; cursor: pointer;
+        }
+        .bar h1 { margin: 0; font-size: 16px; font-weight: 600; }
+        .bar button:disabled { opacity: .7; }
+        #note-toast {
+            position: fixed; left: 50%; bottom: 72px; z-index: 40;
+            transform: translateX(-50%);
+            padding: 8px 16px; border-radius: 6px;
+            background: rgba(0,0,0,.78); color: #fff; font-size: 13px;
+            pointer-events: none;
+        }
+        #save-mask {
+            display: none; position: fixed; inset: 0; z-index: 30;
+            background: rgba(0,0,0,.78); flex-direction: column;
+            align-items: center; justify-content: center; padding: 16px 12px 24px;
+        }
+        #save-mask.show { display: flex; }
+        #save-mask .save-tip { color: #fff; font-size: 14px; margin-bottom: 12px; }
+        #save-mask img {
+            max-width: 88%; max-height: 68vh; background: #fff;
+            -webkit-touch-callout: default;
+        }
+        #save-mask .save-actions { margin-top: 14px; }
+        #save-mask .save-actions button {
+            min-width: 108px; height: 36px; border: 0; border-radius: 6px;
+            font-size: 14px; color: #fff; background: #666;
+            -webkit-tap-highlight-color: transparent;
+        }
+        .sheet {
+            position: relative;
+            max-width: 420px; margin: 12px auto 24px;
+            background: #fff; padding: 16px 16px 22px;
+            box-shadow: 0 1px 6px rgba(0,0,0,.06);
+        }
+        .meta-top { font-size: 12px; color: #666; }
+        .brand { text-align: center; margin-top: 6px; }
+        .brand .co { font-size: 17px; font-weight: 700; color: #111; line-height: 1.35; }
+        .brand .doc { margin-top: 4px; font-size: 20px; font-weight: 700; letter-spacing: .3em; color: #222; }
+        .kv { margin-top: 12px; font-size: 13px; line-height: 1.7; color: #333; }
+        .kv b { font-weight: 600; color: #555; }
+        .qr-wrap { text-align: center; margin: 14px 0 8px; }
+        #qrcode {
+            display: inline-block; width: 132px; height: 132px;
+            padding: 6px; border: 1px solid #ddd; background: #fff;
+        }
+        #qrcode img, #qrcode canvas { display: block; margin: 0 auto; }
+        #qrcode table {
+            width: 120px !important; height: 120px !important;
+            border-collapse: collapse; table-layout: fixed; margin: 0 auto;
+        }
+        #qrcode table td { padding: 0 !important; border: 0 !important; }
+        .ord-no { text-align: center; font-size: 13px; font-weight: 600; margin: 6px 0 0; letter-spacing: .04em; }
+        .block { margin-top: 16px; padding-top: 12px; border-top: 1px dashed #ddd; }
+        .block h2 { margin: 0 0 8px; font-size: 13px; color: #888; font-weight: 600; }
+        .line { font-size: 13px; line-height: 1.75; color: #333; }
+        .line span { color: #888; }
+        @media print {
+            body { background: #fff; }
+            .bar, #save-mask, #note-toast { display: none !important; }
+            .sheet { margin: 0; box-shadow: none; max-width: none; }
+        }
+    </style>
+</head>
+<body>
+<div class="bar">
+    <a href="{:url('index/index/index')}?tab=done" id="btn-note-back">返回</a>
+    <h1>送货码</h1>
+    <button type="button" id="btn-note-save">保存</button>
+</div>
+<div id="save-mask" aria-hidden="true">
+    <div class="save-tip">长按图片保存到相册或转发</div>
+    <img id="save-preview" alt="送货码">
+    <div class="save-actions">
+        <button type="button" id="btn-save-close">关闭</button>
+    </div>
+</div>
+<div id="note-toast" hidden></div>
+<div class="sheet">
+    <div class="meta-top">生成时间:{$note.generated_at|default=''|htmlentities}</div>
+    <div class="brand">
+        <div class="co">{$note.supplier_name|default=''|htmlentities}</div>
+        <div class="doc">送货码</div>
+    </div>
+    <div class="kv"><b>客户名称:</b>{notempty name="note.customer_name"}{$note.customer_name|htmlentities}{else /}—{/notempty}</div>
+    <div class="kv"><b>送货地址:</b>{notempty name="note.address"}{$note.address|htmlentities}{else /}—{/notempty}</div>
+    <div class="qr-wrap">
+        <div id="qrcode">{notempty name="note.qr_data_uri"}<img src="{$note.qr_data_uri}" width="120" height="120" alt="送货码">{/notempty}</div>
+    </div>
+    <div class="block">
+        <h2>订单信息</h2>
+        <div class="line"><span>订单号:</span>{$note.ccydh|default=''|htmlentities}</div>
+        <div class="line"><span>印件名称:</span>{$note.cyjmc|default=''|htmlentities}</div>
+        <div class="line"><span>工序名称:</span>{$note.cgymc|default=''|htmlentities}</div>
+        <div class="line"><span>单位:</span>{$note.cdw|default=''|htmlentities}</div>
+        <div class="line"><span>订法:</span>{$note.cdf|default=''|htmlentities}</div>
+        <div class="line"><span>备注:</span>{notempty name="note.remark"}{$note.remark|htmlentities}{else /}—{/notempty}</div>
+    </div>
+    <div class="block">
+        <h2>供应商信息</h2>
+        <div class="line"><span>供应商名称:</span>{$note.supplier_name|default=''|htmlentities}</div>
+        <div class="line"><span>姓名:</span>{$note.contact_name|default=''|htmlentities}</div>
+        <div class="line"><span>手机号:</span>{$note.mobile|default=''|htmlentities}</div>
+    </div>
+</div>
+<script src="__CDN__/assets/js/jquery.js"></script>
+<script src="__CDN__/assets/js/jquery.qrcode.min.js"></script>
+<script>
+(function () {
+    var ccydh = {:json_encode((string)($note['ccydh'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
+    var saveBlob = null;
+    var saveUrl = '';
+    var toastTimer = 0;
+
+    var back = document.getElementById('btn-note-back');
+    if (back) {
+        back.addEventListener('click', function (e) {
+            if (window.history.length > 1) {
+                e.preventDefault();
+                history.back();
+            }
+        });
+    }
+
+    function toast(msg) {
+        var el = document.getElementById('note-toast');
+        if (!el) {
+            return;
+        }
+        el.textContent = msg;
+        el.hidden = false;
+        clearTimeout(toastTimer);
+        toastTimer = setTimeout(function () {
+            el.hidden = true;
+        }, 1800);
+    }
+
+    function isMobile() {
+        return /Android|iPhone|iPad|iPod|Mobile|MicroMessenger/i.test(navigator.userAgent || '');
+    }
+
+    function filename() {
+        return '送货单' + (ccydh ? '-' + ccydh : '') + '.png';
+    }
+
+    function revokeSaveUrl() {
+        if (saveUrl) {
+            URL.revokeObjectURL(saveUrl);
+            saveUrl = '';
+        }
+    }
+
+    function closeMask() {
+        var mask = document.getElementById('save-mask');
+        if (mask) {
+            mask.classList.remove('show');
+            mask.setAttribute('aria-hidden', 'true');
+        }
+        document.body.style.overflow = '';
+    }
+
+    function showMask(blob) {
+        var mask = document.getElementById('save-mask');
+        var img = document.getElementById('save-preview');
+        if (!mask || !img) {
+            return;
+        }
+        revokeSaveUrl();
+        saveBlob = blob;
+        saveUrl = URL.createObjectURL(blob);
+        img.src = saveUrl;
+        mask.classList.add('show');
+        mask.setAttribute('aria-hidden', 'false');
+        document.body.style.overflow = 'hidden';
+    }
+
+    function tryShare(blob) {
+        if (!blob || typeof navigator.share !== 'function' || typeof File === 'undefined') {
+            return Promise.resolve(false);
+        }
+        var file = new File([blob], filename(), {type: 'image/png'});
+        var payload = {files: [file], title: '送货单'};
+        if (typeof navigator.canShare === 'function' && !navigator.canShare(payload)) {
+            return Promise.resolve(false);
+        }
+        return navigator.share(payload).then(function () {
+            return true;
+        }).catch(function (err) {
+            if (err && err.name === 'AbortError') {
+                return true;
+            }
+            return false;
+        });
+    }
+
+    function tryDownload(blob) {
+        if (isMobile()) {
+            return false;
+        }
+        var a = document.createElement('a');
+        a.href = URL.createObjectURL(blob);
+        a.download = filename();
+        a.style.display = 'none';
+        document.body.appendChild(a);
+        a.click();
+        setTimeout(function () {
+            URL.revokeObjectURL(a.href);
+            a.remove();
+        }, 800);
+        return true;
+    }
+
+    function isDarkColor(color) {
+        var c = String(color || '').replace(/\s+/g, '').toLowerCase();
+        if (!c || c === 'transparent' || c === 'rgba(0,0,0,0)') {
+            return false;
+        }
+        if (c === '#000' || c === '#000000' || c === 'black' || c === 'rgb(0,0,0)') {
+            return true;
+        }
+        var m = c.match(/^rgba?\((\d+),(\d+),(\d+)/);
+        if (m) {
+            return (parseInt(m[1], 10) + parseInt(m[2], 10) + parseInt(m[3], 10)) < 380;
+        }
+        return false;
+    }
+
+    function qrTableToDataUrl(table) {
+        var n = table.rows.length;
+        if (!n) {
+            return '';
+        }
+        var c = document.createElement('canvas');
+        c.width = n;
+        c.height = n;
+        var ctx = c.getContext('2d');
+        if (!ctx) {
+            return '';
+        }
+        ctx.fillStyle = '#ffffff';
+        ctx.fillRect(0, 0, n, n);
+        ctx.fillStyle = '#000000';
+        for (var r = 0; r < n; r++) {
+            var cells = table.rows[r].cells;
+            for (var i = 0; i < cells.length; i++) {
+                var bg = cells[i].style.backgroundColor || '';
+                if (!bg && window.getComputedStyle) {
+                    bg = getComputedStyle(cells[i]).backgroundColor;
+                }
+                if (isDarkColor(bg)) {
+                    ctx.fillRect(i, r, 1, 1);
+                }
+            }
+        }
+        return c.toDataURL('image/png');
+    }
+
+    function getQrDataUrl() {
+        var box = document.getElementById('qrcode');
+        if (!box) {
+            return '';
+        }
+        var img = box.querySelector('img');
+        if (img && img.src) {
+            return img.src;
+        }
+        var canvas = box.querySelector('canvas');
+        if (canvas) {
+            try {
+                return canvas.toDataURL('image/png');
+            } catch (e) {}
+        }
+        var table = box.querySelector('table');
+        if (table) {
+            return qrTableToDataUrl(table);
+        }
+        return '';
+    }
+
+    function loadQrImage(src) {
+        return new Promise(function (resolve) {
+            if (!src) {
+                resolve(null);
+                return;
+            }
+            var exist = document.querySelector('#qrcode img');
+            if (exist && exist.src === src && exist.complete && exist.naturalWidth) {
+                resolve(exist);
+                return;
+            }
+            var im = new Image();
+            im.onload = function () {
+                resolve(im);
+            };
+            im.onerror = function () {
+                resolve(null);
+            };
+            im.src = src;
+        });
+    }
+
+    function wrapText(ctx, text, maxWidth) {
+        var s = String(text || '');
+        var lines = [];
+        var line = '';
+        for (var i = 0; i < s.length; i++) {
+            var next = line + s.charAt(i);
+            if (line && ctx.measureText(next).width > maxWidth) {
+                lines.push(line);
+                line = s.charAt(i);
+            } else {
+                line = next;
+            }
+        }
+        if (line !== '') {
+            lines.push(line);
+        }
+        return lines.length ? lines : [''];
+    }
+
+    function drawSpacedCenter(ctx, text, x, y, tracking) {
+        var s = String(text || '');
+        var extra = tracking * Math.max(0, s.length - 1);
+        var w = ctx.measureText(s).width + extra;
+        var left = x - w / 2;
+        for (var i = 0; i < s.length; i++) {
+            ctx.fillText(s.charAt(i), left, y);
+            left += ctx.measureText(s.charAt(i)).width + tracking;
+        }
+    }
+
+    function drawLabeledLine(ctx, text, x, y, maxWidth, fontFamily) {
+        var sp = String(text || '').indexOf(':');
+        var label = sp >= 0 ? text.slice(0, sp + 1) : '';
+        var rest = sp >= 0 ? text.slice(sp + 1) : text;
+        ctx.font = '13px ' + fontFamily;
+        if (!label) {
+            ctx.fillStyle = '#333333';
+            wrapText(ctx, text, maxWidth).forEach(function (ln) {
+                ctx.fillText(ln, x, y);
+                y += 22;
+            });
+            return y;
+        }
+        ctx.fillStyle = '#888888';
+        ctx.fillText(label, x, y);
+        var lw = ctx.measureText(label).width;
+        ctx.fillStyle = '#333333';
+        var firstMax = Math.max(40, maxWidth - lw);
+        var restLines = wrapText(ctx, rest, firstMax);
+        if (restLines.length) {
+            ctx.fillText(restLines[0], x + lw, y);
+            y += 22;
+            for (var i = 1; i < restLines.length; i++) {
+                ctx.fillText(restLines[i], x, y);
+                y += 22;
+            }
+        } else {
+            y += 22;
+        }
+        return y;
+    }
+
+    function drawNoteCanvas(qrImage) {
+        var sheet = document.querySelector('.sheet');
+        var cssW = sheet && sheet.offsetWidth ? sheet.offsetWidth : 360;
+        var pad = 16;
+        var inner = cssW - pad * 2;
+        var scale = 2;
+        var fontFamily = '-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif';
+        var probe = document.createElement('canvas').getContext('2d');
+        if (!probe) {
+            throw new Error('无法生成图片');
+        }
+
+        var meta = ((document.querySelector('.meta-top') || {}).textContent || '').replace(/\s+/g, ' ').trim();
+        var co = ((document.querySelector('.brand .co') || {}).textContent || '').replace(/\s+/g, ' ').trim();
+        var docTitle = ((document.querySelector('.brand .doc') || {}).textContent || '送货单').replace(/\s+/g, ' ').trim();
+        var kvs = [];
+        document.querySelectorAll('.sheet > .kv').forEach(function (el) {
+            kvs.push((el.textContent || '').replace(/\s+/g, ' ').trim());
+        });
+        var ordNo = ((document.querySelector('.ord-no') || {}).textContent || '').replace(/\s+/g, ' ').trim();
+        var blocks = [];
+        document.querySelectorAll('.sheet > .block').forEach(function (block) {
+            blocks.push({
+                title: ((block.querySelector('h2') || {}).textContent || '').replace(/\s+/g, ' ').trim(),
+                lines: Array.prototype.map.call(block.querySelectorAll('.line'), function (line) {
+                    return (line.textContent || '').replace(/\s+/g, ' ').trim();
+                })
+            });
+        });
+
+        var canvas = document.createElement('canvas');
+        canvas.width = Math.round(cssW * scale);
+        canvas.height = Math.round(1200 * scale);
+        var ctx = canvas.getContext('2d');
+        ctx.scale(scale, scale);
+        ctx.fillStyle = '#ffffff';
+        ctx.fillRect(0, 0, cssW, 1200);
+        ctx.textBaseline = 'top';
+
+        var cy = pad;
+        ctx.font = '12px ' + fontFamily;
+        ctx.fillStyle = '#666666';
+        ctx.fillText(meta, pad, cy);
+        cy += 24;
+        ctx.font = 'bold 17px ' + fontFamily;
+        ctx.fillStyle = '#111111';
+        wrapText(ctx, co, inner).forEach(function (ln) {
+            var tw = ctx.measureText(ln).width;
+            ctx.fillText(ln, (cssW - tw) / 2, cy);
+            cy += 24;
+        });
+        cy += 4;
+        ctx.font = 'bold 20px ' + fontFamily;
+        ctx.fillStyle = '#222222';
+        drawSpacedCenter(ctx, docTitle, cssW / 2, cy, 6);
+        cy += 34;
+        kvs.forEach(function (kv) {
+            var sp = kv.indexOf(':');
+            var label = sp >= 0 ? kv.slice(0, sp + 1) : '';
+            var val = sp >= 0 ? kv.slice(sp + 1) : kv;
+            ctx.font = '600 13px ' + fontFamily;
+            ctx.fillStyle = '#555555';
+            ctx.fillText(label, pad, cy);
+            var lw = ctx.measureText(label).width;
+            ctx.font = '13px ' + fontFamily;
+            ctx.fillStyle = '#333333';
+            var valLines = wrapText(ctx, val, Math.max(40, inner - lw));
+            valLines.forEach(function (ln, i) {
+                ctx.fillText(ln, pad + (i === 0 ? lw : 0), cy);
+                cy += 22;
+            });
+            cy += 4;
+        });
+        cy += 8;
+        var qrBox = 132;
+        var qx = (cssW - qrBox) / 2;
+        ctx.strokeStyle = '#dddddd';
+        ctx.lineWidth = 1;
+        ctx.fillStyle = '#ffffff';
+        ctx.fillRect(qx, cy, qrBox, qrBox);
+        ctx.strokeRect(qx + 0.5, cy + 0.5, qrBox - 1, qrBox - 1);
+        if (qrImage) {
+            ctx.imageSmoothingEnabled = false;
+            ctx.drawImage(qrImage, qx + 6, cy + 6, 120, 120);
+        }
+        cy += qrBox + 8;
+        ctx.font = 'bold 13px ' + fontFamily;
+        ctx.fillStyle = '#222222';
+        var onw = ctx.measureText(ordNo).width;
+        ctx.fillText(ordNo, (cssW - onw) / 2, cy);
+        cy += 20;
+        blocks.forEach(function (block) {
+            cy += 10;
+            ctx.strokeStyle = '#dddddd';
+            ctx.setLineDash([4, 3]);
+            ctx.beginPath();
+            ctx.moveTo(pad, cy);
+            ctx.lineTo(cssW - pad, cy);
+            ctx.stroke();
+            ctx.setLineDash([]);
+            cy += 12;
+            ctx.font = '600 13px ' + fontFamily;
+            ctx.fillStyle = '#888888';
+            ctx.fillText(block.title, pad, cy);
+            cy += 22;
+            block.lines.forEach(function (line) {
+                cy = drawLabeledLine(ctx, line, pad, cy, inner, fontFamily);
+            });
+        });
+        cy += pad;
+        var out = document.createElement('canvas');
+        out.width = canvas.width;
+        out.height = Math.max(1, Math.round(cy * scale));
+        var octx = out.getContext('2d');
+        octx.fillStyle = '#ffffff';
+        octx.fillRect(0, 0, out.width, out.height);
+        octx.drawImage(canvas, 0, 0);
+        return out;
+    }
+
+    function canvasToBlob(canvas) {
+        return new Promise(function (resolve, reject) {
+            if (canvas.toBlob) {
+                canvas.toBlob(function (blob) {
+                    if (blob) {
+                        resolve(blob);
+                    } else {
+                        reject(new Error('生成图片失败'));
+                    }
+                }, 'image/png');
+                return;
+            }
+            try {
+                var data = canvas.toDataURL('image/png');
+                var arr = data.split(',');
+                var bin = atob(arr[1] || '');
+                var u8 = new Uint8Array(bin.length);
+                for (var i = 0; i < bin.length; i++) {
+                    u8[i] = bin.charCodeAt(i);
+                }
+                resolve(new Blob([u8], {type: 'image/png'}));
+            } catch (e) {
+                reject(e);
+            }
+        });
+    }
+
+    function captureSheet() {
+        return loadQrImage(getQrDataUrl()).then(function (qrImage) {
+            return canvasToBlob(drawNoteCanvas(qrImage));
+        });
+    }
+
+    var saveBtn = document.getElementById('btn-note-save');
+    if (saveBtn) {
+        saveBtn.addEventListener('click', function (e) {
+            e.preventDefault();
+            if (saveBtn.disabled) {
+                return;
+            }
+            saveBtn.disabled = true;
+            saveBtn.textContent = '保存中';
+            captureSheet().then(function (blob) {
+                if (!isMobile()) {
+                    return tryShare(blob).then(function (ok) {
+                        if (ok) {
+                            toast('已保存');
+                            return;
+                        }
+                        if (tryDownload(blob)) {
+                            toast('图片已下载');
+                            return;
+                        }
+                        showMask(blob);
+                    });
+                }
+                showMask(blob);
+            }).catch(function () {
+                toast('保存失败,请稍后重试');
+            }).then(function () {
+                saveBtn.disabled = false;
+                saveBtn.textContent = '保存';
+            });
+        });
+    }
+
+    var closeBtn = document.getElementById('btn-save-close');
+    if (closeBtn) {
+        closeBtn.addEventListener('click', function (e) {
+            e.preventDefault();
+            closeMask();
+        });
+    }
+
+    function renderQr(text) {
+        var box = document.getElementById('qrcode');
+        if (!box) {
+            return;
+        }
+        if (box.querySelector('img')) {
+            return;
+        }
+        if (!text || typeof jQuery === 'undefined' || !jQuery.fn.qrcode) {
+            return;
+        }
+        try {
+            jQuery(box).empty().qrcode({
+                width: 120,
+                height: 120,
+                text: text,
+                render: 'table',
+                correctLevel: 2,
+                background: '#ffffff',
+                foreground: '#000000'
+            });
+        } catch (err) {
+            return;
+        }
+        var table = box.querySelector('table');
+        var dataUrl = table ? qrTableToDataUrl(table) : '';
+        if (!dataUrl) {
+            return;
+        }
+        var img = document.createElement('img');
+        img.src = dataUrl;
+        img.width = 120;
+        img.height = 120;
+        img.alt = '送货码';
+        box.innerHTML = '';
+        box.appendChild(img);
+    }
+
+    var url = {:json_encode((string)($note['scan_url'] ?? ''), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
+    renderQr(url);
+})();
+</script>
+</body>
+</html>

+ 59 - 10
application/index/view/index/deliveryscore.html

@@ -100,9 +100,8 @@
             flex: 1; min-width: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
             padding: 10px; background: #f5f5f5;
         }
-        .card {
-            background: #fff; border-radius: 10px; padding: 12px;
-            margin-bottom: 10px; box-shadow: 0 1px 4px rgba(0,0,0,.06);
+        .card.is-focus {
+            box-shadow: 0 0 0 2px #3c8dbc, 0 1px 6px rgba(60,141,188,.22);
         }
         .card .ord {
             font-size: 15px; font-weight: 700; color: #222; margin-bottom: 8px;
@@ -305,18 +304,24 @@
     </div>
     <div class="body-wrap">
         <div class="side" id="listCats">
-            <button type="button" class="cat active" data-list-tab="pending">待确认</button>
-            <button type="button" class="cat" data-list-tab="scored">已确认</button>
+            <button type="button" class="cat {eq name='mprocScoreTab' value='pending'}active{/eq}{empty name='mprocScoreTab'}active{/empty}" data-list-tab="pending">待确认</button>
+            <button type="button" class="cat {eq name='mprocScoreTab' value='scored'}active{/eq}" data-list-tab="scored">已确认</button>
         </div>
         <div class="main" id="listMain">
             {if $rows}
             {volist name="rows" id="row"}
-            <div class="card" data-ccydh="{$row.CCYDH|htmlentities}">
+            <div class="card{if $mprocFocusCcydh == $row.CCYDH} is-focus{/if}" data-ccydh="{$row.CCYDH|htmlentities}">
                 <div class="ord">{$row.CCYDH|default=''|htmlentities}{if $row.CYJMC} {$row.CYJMC|htmlentities}{/if}</div>
                 <div class="meta">
                     <div><b>供应商:</b>{$row.pick_company_name|default='—'|htmlentities}</div>
                     <div><b>工序:</b>{$row.CGYMC|default='—'|htmlentities}</div>
                 </div>
+                {eq name="mprocScoreTab" value="scored"}
+                <div class="score-summary">
+                    <span>交货情况:<span class="res{eq name='row.delivery_status' value='滞后'} is-fail{/eq}">{$row.delivery_status|default=''|htmlentities}</span></span>
+                    <span>交货日期:{$row.delivery_date|default=''|htmlentities}</span>
+                </div>
+                {/eq}
                 <div class="actions">
                     <button type="button" class="btn-score">确认交货</button>
                 </div>
@@ -566,7 +571,9 @@
         return no || name || '—';
     }
 
-    var currentTab = 'pending';
+    var currentTab = {:json_encode(isset($mprocScoreTab) && $mprocScoreTab === 'scored' ? 'scored' : 'pending')};
+    var focusCcydh = {:json_encode(isset($mprocFocusCcydh) ? (string)$mprocFocusCcydh : '', JSON_UNESCAPED_UNICODE)};
+    var focusOpened = false;
 
     function emptyText(tab) {
         if (tab === 'scored') return '暂无已确认订单';
@@ -583,7 +590,9 @@
         var html = '';
         var pending = currentTab === 'pending';
         rows.forEach(function (row) {
-            html += '<div class="card" data-ccydh="' + esc(row.CCYDH || '') + '">';
+            var no = String(row.CCYDH || '').trim();
+            var hl = (focusCcydh && no === focusCcydh) ? ' is-focus' : '';
+            html += '<div class="card' + hl + '" data-ccydh="' + esc(row.CCYDH || '') + '">';
             html += '<div class="ord">' + esc(titleText(row)) + '</div>';
             html += '<div class="meta">';
             html += '<div><b>供应商:</b>' + esc(row.pick_company_name || '—') + '</div>';
@@ -626,10 +635,35 @@
             .then(function (r) { return r.json(); });
     }
 
+    function locateFocusOrder() {
+        var no = String(focusCcydh || '').trim();
+        if (!no) return false;
+        var card = null;
+        document.querySelectorAll('#listMain .card[data-ccydh]').forEach(function (el) {
+            if (String(el.getAttribute('data-ccydh') || '').trim() === no) {
+                card = el;
+            }
+        });
+        if (!card) return false;
+        card.classList.add('is-focus');
+        try {
+            card.scrollIntoView({ behavior: 'smooth', block: 'center' });
+        } catch (e) {
+            card.scrollIntoView();
+        }
+        if (!focusOpened && currentTab === 'pending') {
+            focusOpened = true;
+            if (card.querySelector('.btn-score')) {
+                openScoreForm(card);
+            }
+        }
+        return true;
+    }
+
     var searchTimer = null;
     function loadList() {
         var q = (document.getElementById('qInput') || {}).value || '';
-        postForm(listUrl, { tab: currentTab, q: q }).then(function (ret) {
+        return postForm(listUrl, { tab: currentTab, q: q }).then(function (ret) {
             if (!ret || (ret.code !== 1 && ret.code !== '1')) {
                 if (ret && /登录/.test(String(ret.msg || ''))) {
                     location.href = loginUrl;
@@ -640,6 +674,7 @@
             }
             var d = ret.data || {};
             renderRows(d.rows || []);
+            locateFocusOrder();
         }).catch(function () {
             showToast('网络错误', 'err');
         });
@@ -705,6 +740,16 @@
         return el ? String(el.value || '') : '';
     }
 
+    function todayYmd() {
+        var d = new Date();
+        var y = d.getFullYear();
+        var m = String(d.getMonth() + 1);
+        var day = String(d.getDate());
+        if (m.length < 2) m = '0' + m;
+        if (day.length < 2) day = '0' + day;
+        return y + '-' + m + '-' + day;
+    }
+
     function fillScoreForm(status, date) {
         var deliveryRadios = document.querySelectorAll('input[name="delivery_status"]');
         deliveryRadios.forEach(function (r) {
@@ -712,7 +757,7 @@
         });
         var dateEl = document.getElementById('deliveryDate');
         if (dateEl) {
-            dateEl.value = /^\d{4}-\d{2}-\d{2}$/.test(String(date || '')) ? String(date) : '';
+            dateEl.value = /^\d{4}-\d{2}-\d{2}$/.test(String(date || '')) ? String(date) : todayYmd();
         }
     }
 
@@ -848,6 +893,10 @@
         if (!card) return;
         openScoreForm(card);
     });
+
+    if (focusCcydh) {
+        locateFocusOrder();
+    }
 })();
 </script>
 </body>

+ 194 - 16
application/index/view/index/index.html

@@ -158,6 +158,28 @@
             top: 0;
             right: 0;
         }
+        .order-group-head.has-delivery-code .title,
+        .order-group-head.has-delivery-code .order-group-deadline-row {
+            padding-right: 72px;
+        }
+        .btn-delivery-code {
+            position: absolute;
+            top: 26px;
+            right: 0;
+            z-index: 2;
+            display: inline-block;
+            font-size: 12px;
+            line-height: 1.2;
+            padding: 4px 8px;
+            border-radius: 6px;
+            font-weight: 600;
+            white-space: nowrap;
+            text-decoration: none;
+            color: #3c8dbc;
+            background: #fff;
+            border: 1px solid #3c8dbc;
+        }
+        .btn-delivery-code:active { opacity: .85; }
         .order-line {
             padding: 6px 0 4px;
             border-top: none;
@@ -752,15 +774,23 @@
     {empty name="groups"}
     {notempty name="rows"}
     {volist name="rows" id="r"}
-    <div class="card order-group-card js-order-group{if $mprocFocusEid && $mprocFocusEid == $r.eid && ($mprocFocusTab == '' || $mprocFocusTab == $mprocTab)} mproc-card-highlight{/if}" data-remark="{$r.mproc_remark|default=''|htmlentities}" data-delivery-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$r.mproc_delivery_deadline|default=''|htmlentities}{/eq}" data-bid-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$r.mproc_bid_deadline|default=''|htmlentities}{/eq}" data-bid-open="{eq name='mprocIsRfqTab' value='1'}0{else /}{$r.mproc_bid_open_verified|default=0}{/eq}" data-is-rfq="{$mprocIsRfqTab|default=0}">
-        <div class="order-group-head">
+    <div class="card order-group-card js-order-group{if $mprocFocusEid && $mprocFocusEid == $r.eid && ($mprocFocusTab == '' || $mprocFocusTab == $mprocTab)} mproc-card-highlight{/if}" data-ccydh="{$r.CCYDH|default=''|htmlentities}" data-remark="{$r.mproc_remark|default=''|htmlentities}" data-delivery-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$r.mproc_delivery_deadline|default=''|htmlentities}{/eq}" data-bid-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$r.mproc_bid_deadline|default=''|htmlentities}{/eq}" data-bid-open="{eq name='mprocIsRfqTab' value='1'}0{else /}{$r.mproc_bid_open_verified|default=0}{/eq}" data-is-rfq="{$mprocIsRfqTab|default=0}">
+        <div class="order-group-head{eq name='mprocStatusTab' value='done'}{eq name='r.mproc_pick_result' value='中标'} has-delivery-code{/eq}{/eq}">
             <p class="title">{notempty name="r.CCYDH"}<span class="mproc-ord-no">{$r.CCYDH}</span>{/notempty}{$r.CYJMC|default=''}{eq name="mprocStatusTab" value="draft"}{eq name="mprocIsRfqTab" value="1"}<em class="mproc-pick-badge mproc-pick-unquoted">待报价</em>{else /}<em class="mproc-pick-badge mproc-pick-unquoted">未报价</em>{/eq}{/eq}{eq name="mprocStatusTab" value="submitted"}<em class="mproc-pick-badge mproc-pick-quoted">已报价</em>{/eq}{notempty name="r.mproc_done_label"}{eq name="mprocStatusTab" value="done"}<em class="mproc-pick-badge {eq name='r.mproc_pick_result' value='中标'}mproc-pick-win{else /}{eq name='r.mproc_pick_result' value='未中标'}mproc-pick-lose{else /}mproc-pick-expired{/eq}{/eq}">{$r.mproc_done_label|htmlentities}</em>{/eq}{/notempty}</p>
+            {eq name="mprocStatusTab" value="done"}{eq name="r.mproc_pick_result" value="中标"}{notempty name="r.CCYDH"}
+            <a class="btn-delivery-code" href="{$mprocDeliveryNoteUrl|default=''}?ccydh={$r.CCYDH|urlencode}" data-stop="1">送货码</a>
+            {/notempty}{/eq}{/eq}
             {eq name="mprocIsRfqTab" value="0"}
             <div class="kv-row order-group-deadline-row">
                 <div class="kv"><span>招标截止日期</span>{notempty name="r.mproc_bid_deadline_display"}{$r.mproc_bid_deadline_display|htmlentities}{else /}—{/notempty}</div>
                 <div class="kv"><span>交货截止日期</span>{notempty name="r.mproc_delivery_deadline_display"}{$r.mproc_delivery_deadline_display|htmlentities}{else /}—{/notempty}</div>
             </div>
-            <div class="kv order-group-remark"><span>备注</span>{$r.mproc_remark|default=''|htmlentities}</div>
+            {notempty name="r.mproc_order_remark"}
+            <div class="kv order-group-remark"><span>订单备注</span>{$r.mproc_order_remark|htmlentities}</div>
+            {/notempty}
+            {notempty name="r.mproc_remark"}
+            <div class="kv order-group-remark"><span>备注</span>{$r.mproc_remark|htmlentities}</div>
+            {/notempty}
             {/eq}
         </div>
         <div class="order-line js-line"
@@ -799,15 +829,23 @@
     {/notempty}
     {else /}
     {volist name="groups" id="g"}
-    <div class="card order-group-card js-order-group{volist name='g.lines' id='_fe'}{if $mprocFocusEid && $mprocFocusEid == $_fe.eid && ($mprocFocusTab == '' || $mprocFocusTab == $mprocTab)} mproc-card-highlight{/if}{/volist}" data-remark="{$g.remark|default=''|htmlentities}" data-delivery-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$g.mproc_delivery_deadline|default=''|htmlentities}{/eq}" data-bid-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$g.mproc_bid_deadline|default=''|htmlentities}{/eq}" data-bid-open="{eq name='mprocIsRfqTab' value='1'}0{else /}{$g.mproc_bid_open_verified|default=0}{/eq}" data-is-rfq="{$mprocIsRfqTab|default=0}">
-        <div class="order-group-head">
+    <div class="card order-group-card js-order-group{volist name='g.lines' id='_fe'}{if $mprocFocusEid && $mprocFocusEid == $_fe.eid && ($mprocFocusTab == '' || $mprocFocusTab == $mprocTab)} mproc-card-highlight{/if}{/volist}" data-ccydh="{$g.CCYDH|default=''|htmlentities}" data-remark="{$g.remark|default=''|htmlentities}" data-delivery-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$g.mproc_delivery_deadline|default=''|htmlentities}{/eq}" data-bid-deadline="{eq name='mprocIsRfqTab' value='1'}{else /}{$g.mproc_bid_deadline|default=''|htmlentities}{/eq}" data-bid-open="{eq name='mprocIsRfqTab' value='1'}0{else /}{$g.mproc_bid_open_verified|default=0}{/eq}" data-is-rfq="{$mprocIsRfqTab|default=0}">
+        <div class="order-group-head{eq name='mprocStatusTab' value='done'}{eq name='g.mproc_pick_result' value='中标'} has-delivery-code{/eq}{/eq}">
             <p class="title">{notempty name="g.CCYDH"}<span class="mproc-ord-no">{$g.CCYDH}</span>{/notempty}{$g.CYJMC|default=''}{eq name="mprocStatusTab" value="draft"}{eq name="mprocIsRfqTab" value="1"}<em class="mproc-pick-badge mproc-pick-unquoted">待报价</em>{else /}<em class="mproc-pick-badge mproc-pick-unquoted">未报价</em>{/eq}{/eq}{eq name="mprocStatusTab" value="submitted"}<em class="mproc-pick-badge mproc-pick-quoted">已报价</em>{/eq}{notempty name="g.mproc_done_label"}{eq name="mprocStatusTab" value="done"}<em class="mproc-pick-badge {eq name='g.mproc_pick_result' value='中标'}mproc-pick-win{else /}{eq name='g.mproc_pick_result' value='未中标'}mproc-pick-lose{else /}mproc-pick-expired{/eq}{/eq}">{$g.mproc_done_label|htmlentities}</em>{/eq}{/notempty}</p>
+            {eq name="mprocStatusTab" value="done"}{eq name="g.mproc_pick_result" value="中标"}{notempty name="g.CCYDH"}
+            <a class="btn-delivery-code" href="{$mprocDeliveryNoteUrl|default=''}?ccydh={$g.CCYDH|urlencode}" data-stop="1">送货码</a>
+            {/notempty}{/eq}{/eq}
             {eq name="mprocIsRfqTab" value="0"}
             <div class="kv-row order-group-deadline-row">
                 <div class="kv"><span>招标截止日期</span>{notempty name="g.mproc_bid_deadline_display"}{$g.mproc_bid_deadline_display|htmlentities}{else /}—{/notempty}</div>
                 <div class="kv"><span>交货截止日期</span>{notempty name="g.mproc_delivery_deadline_display"}{$g.mproc_delivery_deadline_display|htmlentities}{else /}—{/notempty}</div>
             </div>
-            <div class="kv order-group-remark"><span>备注</span>{$g.remark|default=''|htmlentities}</div>
+            {notempty name="g.mproc_order_remark"}
+            <div class="kv order-group-remark"><span>订单备注</span>{$g.mproc_order_remark|htmlentities}</div>
+            {/notempty}
+            {notempty name="g.remark"}
+            <div class="kv order-group-remark"><span>备注</span>{$g.remark|htmlentities}</div>
+            {/notempty}
             {/eq}
         </div>
         {volist name="g.lines" id="r"}
@@ -1047,7 +1085,7 @@
         });
     })();
 
-    var boot = {:json_encode(['main_tab' => $mprocMainTab, 'tab' => $mprocTab, 'q' => $mprocSearchQ, 'is_admin' => $mprocIsAdmin, 'can_change_pwd' => isset($mprocCanChangePwd) ? (int)$mprocCanChangePwd : 0, 'focus_eid' => isset($mprocFocusEid) ? (int)$mprocFocusEid : 0, 'focus_tab' => isset($mprocFocusTab) ? (string)$mprocFocusTab : ''], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
+    var boot = {:json_encode(['main_tab' => $mprocMainTab, 'tab' => $mprocTab, 'q' => $mprocSearchQ, 'is_admin' => $mprocIsAdmin, 'can_change_pwd' => isset($mprocCanChangePwd) ? (int)$mprocCanChangePwd : 0, 'focus_eid' => isset($mprocFocusEid) ? (int)$mprocFocusEid : 0, 'focus_tab' => isset($mprocFocusTab) ? (string)$mprocFocusTab : '', 'delivery_note_url' => isset($mprocDeliveryNoteUrl) ? (string)$mprocDeliveryNoteUrl : ''], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)};
     var changePwdUrl = mprocEndpointUrl('mprocChangePwd.html');
     var currentMain = boot.main_tab === 'me' ? 'me' : 'orders';
     var MPROC_LIST_TABS = ['rfq', 'rfq_submitted', 'draft', 'submitted', 'done'];
@@ -1491,6 +1529,43 @@
         return '<em class="mproc-pick-badge ' + cls + '">' + mprocEsc(label) + '</em>';
     }
 
+    function mprocGroupIsWin(g, lines) {
+        if (mprocStatusFromListTab(currentListTab) !== 'done') {
+            return false;
+        }
+        var pr = pick(g, ['mproc_pick_result']);
+        var dl = pick(g, ['mproc_done_label']);
+        if (pr === '中标' || dl === '中标') {
+            return true;
+        }
+        var win = false;
+        (lines || []).forEach(function (r) {
+            var rpr = String(r.mproc_pick_result || '').trim();
+            var rdl = String(r.mproc_done_label || '').trim();
+            if (rpr === '中标' || rdl === '中标') {
+                win = true;
+            }
+        });
+        return win;
+    }
+
+    function mprocDeliveryNoteHref(ccydh) {
+        var base = String((boot && boot.delivery_note_url) || '').trim();
+        ccydh = String(ccydh || '').trim();
+        if (!base || !ccydh) {
+            return '';
+        }
+        return base + (base.indexOf('?') >= 0 ? '&' : '?') + 'ccydh=' + encodeURIComponent(ccydh);
+    }
+
+    function mprocDeliveryNoteBtnHtml(ccydh) {
+        var href = mprocDeliveryNoteHref(ccydh);
+        if (!href) {
+            return '';
+        }
+        return '<a class="btn-delivery-code" href="' + mprocEscAttr(href) + '" data-stop="1">送货码</a>';
+    }
+
     function mprocResolveGroupDoneBadge(g, lines) {
         var label = pick(g, ['mproc_done_label']);
         var pickResult = pick(g, ['mproc_pick_result']);
@@ -1639,24 +1714,34 @@
                 return parseInt(r.mproc_bid_open_verified, 10) === 1;
             });
         }
-        var remark = pick(g, ['remark', 'Remark', 'mproc_remark', 'MBZ', 'mproc_order_remark']);
-        if (!remark && lines.length) {
+        var orderRemark = pick(g, ['mproc_order_remark']);
+        var remark = pick(g, ['remark', 'mproc_remark']);
+        if (lines.length) {
             for (var ri = 0; ri < lines.length; ri++) {
-                var rmk = pick(lines[ri], ['mproc_remark', 'mproc_order_remark', 'MBZ', 'remark']);
-                if (rmk) {
-                    remark = rmk;
-                    break;
+                if (!orderRemark) {
+                    orderRemark = pick(lines[ri], ['mproc_order_remark', 'MBZ']);
+                }
+                if (!remark) {
+                    remark = pick(lines[ri], ['mproc_remark']);
                 }
             }
         }
+        if (orderRemark && remark && orderRemark === remark) {
+            remark = '';
+        }
         var remarkAttr = ' data-remark="' + mprocEscAttr(remark || '') + '"';
         var isRfq = mprocIsRfqListTab(currentListTab);
-        var remarkHtml = isRfq ? '' : ('<div class="kv order-group-remark"><span>备注</span>' + mprocEsc(remark || '') + '</div>');
+        var remarkHtml = isRfq ? '' : (
+            (orderRemark ? '<div class="kv order-group-remark"><span>订单备注</span>' + mprocEsc(orderRemark) + '</div>' : '')
+            + (remark ? '<div class="kv order-group-remark"><span>备注</span>' + mprocEsc(remark) + '</div>' : '')
+        );
         var deadlineHtml = isRfq ? '' : ('<div class="kv-row order-group-deadline-row">'
             + '<div class="kv"><span>招标截止日期</span>' + mprocEsc(bidDl || '—') + '</div>'
             + '<div class="kv"><span>交货截止日期</span>' + mprocEsc(delDlDisp || '—') + '</div>'
             + '</div>');
         var badgeHtml = mprocResolveGroupStatusBadge(g, lines);
+        var isWin = mprocGroupIsWin(g, lines);
+        var noteBtn = isWin ? mprocDeliveryNoteBtnHtml(ccydh) : '';
         var shouldFocus = focusEid > 0 && (focusTab === '' || focusTab === currentListTab);
         var groupHl = shouldFocus && lines.some(function (r) {
             return (parseInt(r.eid, 10) || 0) === focusEid;
@@ -1665,12 +1750,14 @@
             return renderOrderLineHtml(r);
         }).join('');
         return '<div class="card order-group-card js-order-group' + groupHl + '"' + remarkAttr
+            + ' data-ccydh="' + mprocEscAttr(ccydh) + '"'
             + ' data-delivery-deadline="' + mprocEscAttr(isRfq ? '' : (delDl || '')) + '"'
             + ' data-bid-deadline="' + mprocEscAttr(isRfq ? '' : (bidDlRaw || '')) + '"'
             + ' data-bid-open="' + (isRfq ? '0' : (bidOpen ? '1' : '0')) + '"'
             + ' data-is-rfq="' + (isRfq ? '1' : '0') + '">'
-            + '<div class="order-group-head">'
+            + '<div class="order-group-head' + (noteBtn ? ' has-delivery-code' : '') + '">'
             + '<p class="title">' + (ccydh ? ('<span class="mproc-ord-no">' + mprocEsc(ccydh) + '</span>') : '') + mprocEsc(tit) + badgeHtml + '</p>'
+            + noteBtn
             + deadlineHtml
             + remarkHtml
             + '</div>'
@@ -1705,7 +1792,8 @@
                 mproc_bid_deadline_display: pick(r, ['mproc_bid_deadline_display']),
                 mproc_delivery_deadline_display: pick(r, ['mproc_delivery_deadline_display']),
                 mproc_delivery_deadline: pick(r, ['mproc_delivery_deadline', 'delivery_deadline']),
-                remark: pick(r, ['mproc_remark', 'mproc_order_remark', 'MBZ', 'remark']),
+                mproc_order_remark: pick(r, ['mproc_order_remark', 'MBZ']),
+                remark: pick(r, ['mproc_remark', 'remark']),
                 can_edit: r.mproc_can_edit,
                 lines: [r]
             }, focusEid, focusTab);
@@ -1717,6 +1805,85 @@
         return inp ? String(inp.value || '').trim() : '';
     }
 
+    var MPROC_LIST_SCROLL_KEY = 'mproc_order_list_scroll';
+
+    function mprocListScrollPane() {
+        return document.querySelector('.mproc-order-main');
+    }
+
+    function mprocSaveListScroll(href) {
+        var pane = mprocListScrollPane();
+        if (!pane) {
+            return;
+        }
+        var ccydh = '';
+        try {
+            ccydh = new URL(href, window.location.href).searchParams.get('ccydh') || '';
+        } catch (e) {
+        }
+        try {
+            sessionStorage.setItem(MPROC_LIST_SCROLL_KEY, JSON.stringify({
+                tab: currentListTab,
+                q: getSearchQ(),
+                top: pane.scrollTop || 0,
+                ccydh: String(ccydh || '')
+            }));
+        } catch (e2) {
+        }
+    }
+
+    function mprocRestoreListScroll() {
+        if (currentMain !== 'orders') {
+            return;
+        }
+        var raw = '';
+        try {
+            raw = sessionStorage.getItem(MPROC_LIST_SCROLL_KEY) || '';
+        } catch (e) {
+        }
+        if (!raw) {
+            return;
+        }
+        var st = null;
+        try {
+            st = JSON.parse(raw);
+        } catch (e2) {
+            return;
+        }
+        if (!st || st.tab !== currentListTab) {
+            return;
+        }
+        if (String(st.q || '') !== getSearchQ()) {
+            return;
+        }
+        var pane = mprocListScrollPane();
+        if (!pane) {
+            return;
+        }
+        var apply = function () {
+            var ccydh = String(st.ccydh || '').trim();
+            if (ccydh && listInner) {
+                var card = null;
+                var groups = listInner.querySelectorAll('.js-order-group');
+                for (var i = 0; i < groups.length; i++) {
+                    if (String(groups[i].getAttribute('data-ccydh') || '').trim() === ccydh) {
+                        card = groups[i];
+                        break;
+                    }
+                }
+                if (card) {
+                    var top = card.getBoundingClientRect().top - pane.getBoundingClientRect().top + pane.scrollTop - 12;
+                    pane.scrollTop = Math.max(0, top);
+                    return;
+                }
+            }
+            pane.scrollTop = parseInt(st.top, 10) || 0;
+        };
+        apply();
+        setTimeout(apply, 50);
+        setTimeout(apply, 220);
+    }
+
     /** 拉取订单列表:左侧分类 rfq=询价 / draft=报价 / submitted / done */
     function fetchOrderList(listTab, q, opts) {
         opts = opts || {};
@@ -1809,6 +1976,7 @@
             }
             mprocApplyFocusToList();
             mprocSyncPageUrl();
+            mprocRestoreListScroll();
         }).catch(function (e) {
             if (myAbort && mprocListAbort !== myAbort) {
                 return;
@@ -2682,6 +2850,12 @@
 
     if (listInner) {
         listInner.addEventListener('click', function (e) {
+            var noteBtn = e.target.closest('.btn-delivery-code');
+            if (noteBtn) {
+                e.stopPropagation();
+                mprocSaveListScroll(noteBtn.getAttribute('href') || '');
+                return;
+            }
             var btn = e.target.closest('.js-open-edit');
             if (!btn) return;
             e.stopPropagation();
@@ -2864,6 +3038,10 @@
         }
         mprocSyncPageUrl();
     })();
+    mprocRestoreListScroll();
+    window.addEventListener('pageshow', function () {
+        mprocRestoreListScroll();
+    });
     mprocScheduleFocusScroll();
     window.addEventListener('load', mprocScheduleFocusScroll);
     window.addEventListener('orientationchange', function () {

+ 3625 - 0
extend/phpqrcode/phpqrcode.php

@@ -0,0 +1,3625 @@
+<?php
+
+/*
+ * PHP QR Code encoder
+ *
+ * This file contains MERGED version of PHP QR Code library.
+ * It was auto-generated from full version for your convenience.
+ *
+ * This merged version was configured to not require any external files,
+ * with disabled cache, error logging and weaker but faster mask matching.
+ * If you need tune it up please use non-merged version.
+ *
+ * For full version, documentation, examples of use please visit:
+ *
+ *    http://phpqrcode.sourceforge.net/
+ *    https://sourceforge.net/projects/phpqrcode/
+ *    https://github.com/t0k4rt/phpqrcode
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+ 
+
+/*
+ * Version: 1.1.4
+ * Build: 2010100721
+ */
+
+
+
+//---- qrconst.php -----------------------------
+
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Common constants
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+	// Encoding modes
+	 
+	define('QR_MODE_NUL', -1);
+	define('QR_MODE_NUM', 0);
+	define('QR_MODE_AN', 1);
+	define('QR_MODE_8', 2);
+	define('QR_MODE_KANJI', 3);
+	define('QR_MODE_STRUCTURE', 4);
+
+	// Levels of error correction.
+
+	define('QR_ECLEVEL_L', 0);
+	define('QR_ECLEVEL_M', 1);
+	define('QR_ECLEVEL_Q', 2);
+	define('QR_ECLEVEL_H', 3);
+	
+	// Supported output formats
+	
+	define('QR_FORMAT_TEXT', 0);
+	define('QR_FORMAT_PNG',  1);
+	
+	class qrstr {
+		public static function set(&$srctab, $x, $y, $repl, $replLen = false) {
+			$srctab[$y] = substr_replace($srctab[$y], ($replLen !== false)?substr($repl,0,$replLen):$repl, $x, ($replLen !== false)?$replLen:strlen($repl));
+		}
+	}	
+
+
+
+//---- merged_config.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Config file, tuned-up for merged verion
+ */
+     
+    define('QR_CACHEABLE', false);       // use cache - more disk reads but less CPU power, masks and format templates are stored there
+    define('QR_CACHE_DIR', false);       // used when QR_CACHEABLE === true
+    define('QR_LOG_DIR', false);         // default error logs dir   
+    
+    define('QR_FIND_BEST_MASK', true);                                                          // if true, estimates best mask (spec. default, but extremally slow; set to false to significant performance boost but (propably) worst quality code
+    define('QR_FIND_FROM_RANDOM', 2);                                                       // if false, checks all masks available, otherwise value tells count of masks need to be checked, mask id are got randomly
+    define('QR_DEFAULT_MASK', 2);                                                               // when QR_FIND_BEST_MASK === false
+                                                  
+    define('QR_PNG_MAXIMUM_SIZE',  1024);                                                       // maximum allowed png image width (in pixels), tune to make sure GD and PHP can handle such big images
+                                                  
+
+
+
+//---- qrtools.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Toolset, handy and debug utilites.
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+    class QRtools {
+    
+        //----------------------------------------------------------------------
+        public static function binarize($frame)
+        {
+            $len = count($frame);
+            foreach ($frame as &$frameLine) {
+                
+                for($i=0; $i<$len; $i++) {
+                    $frameLine[$i] = (ord($frameLine[$i])&1)?'1':'0';
+                }
+            }
+            
+            return $frame;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function tcpdfBarcodeArray($code, $mode = 'QR,L', $tcPdfVersion = '4.5.037')
+        {
+            $barcode_array = array();
+            
+            if (!is_array($mode))
+                $mode = explode(',', $mode);
+                
+            $eccLevel = 'L';
+                
+            if (count($mode) > 1) {
+                $eccLevel = $mode[1];
+            }
+                
+            $qrTab = QRcode::text($code, false, $eccLevel);
+            $size = count($qrTab);
+                
+            $barcode_array['num_rows'] = $size;
+            $barcode_array['num_cols'] = $size;
+            $barcode_array['bcode'] = array();
+                
+            foreach ($qrTab as $line) {
+                $arrAdd = array();
+                foreach(str_split($line) as $char)
+                    $arrAdd[] = ($char=='1')?1:0;
+                $barcode_array['bcode'][] = $arrAdd;
+            }
+                    
+            return $barcode_array;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function clearCache()
+        {
+            self::$frames = array();
+        }
+        
+        //----------------------------------------------------------------------
+        public static function buildCache()
+        {
+			QRtools::markTime('before_build_cache');
+			
+			$mask = new QRmask();
+            for ($a=1; $a <= QRSPEC_VERSION_MAX; $a++) {
+                $frame = QRspec::newFrame($a);
+                if (QR_IMAGE) {
+                    $fileName = QR_CACHE_DIR.'frame_'.$a.'.png';
+                    QRimage::png(self::binarize($frame), $fileName, 1, 0);
+                }
+				
+				$width = count($frame);
+				$bitMask = array_fill(0, $width, array_fill(0, $width, 0));
+				for ($maskNo=0; $maskNo<8; $maskNo++)
+					$mask->makeMaskNo($maskNo, $width, $frame, $bitMask, true);
+            }
+			
+			QRtools::markTime('after_build_cache');
+        }
+
+        //----------------------------------------------------------------------
+        public static function log($outfile, $err)
+        {
+            if (QR_LOG_DIR !== false) {
+                if ($err != '') {
+                    if ($outfile !== false) {
+                        file_put_contents(QR_LOG_DIR.basename($outfile).'-errors.txt', date('Y-m-d H:i:s').': '.$err, FILE_APPEND);
+                    } else {
+                        file_put_contents(QR_LOG_DIR.'errors.txt', date('Y-m-d H:i:s').': '.$err, FILE_APPEND);
+                    }
+                }    
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public static function dumpMask($frame) 
+        {
+            $width = count($frame);
+            for($y=0;$y<$width;$y++) {
+                for($x=0;$x<$width;$x++) {
+                    echo ord($frame[$y][$x]).',';
+                }
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public static function markTime($markerId)
+        {
+            list($usec, $sec) = explode(" ", microtime());
+            $time = ((float)$usec + (float)$sec);
+            
+            if (!isset($GLOBALS['qr_time_bench']))
+                $GLOBALS['qr_time_bench'] = array();
+            
+            $GLOBALS['qr_time_bench'][$markerId] = $time;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function timeBenchmark()
+        {
+            self::markTime('finish');
+        
+            $lastTime = 0;
+            $startTime = 0;
+            $p = 0;
+
+            echo '<table cellpadding="3" cellspacing="1">
+                    <thead><tr style="border-bottom:1px solid silver"><td colspan="2" style="text-align:center">BENCHMARK</td></tr></thead>
+                    <tbody>';
+
+            foreach($GLOBALS['qr_time_bench'] as $markerId=>$thisTime) {
+                if ($p > 0) {
+                    echo '<tr><th style="text-align:right">till '.$markerId.': </th><td>'.number_format($thisTime-$lastTime, 6).'s</td></tr>';
+                } else {
+                    $startTime = $thisTime;
+                }
+                
+                $p++;
+                $lastTime = $thisTime;
+            }
+            
+            echo '</tbody><tfoot>
+                <tr style="border-top:2px solid black"><th style="text-align:right">TOTAL: </th><td>'.number_format($lastTime-$startTime, 6).'s</td></tr>
+            </tfoot>
+            </table>';
+        }
+        
+        public static function save($content, $filename_path)
+        {           
+            try {
+                $handle = fopen($filename_path, "w");
+                fwrite($handle, $content);
+                fclose($handle);
+                return true;
+            } catch (Exception $e) {
+                echo 'Exception reçue : ',  $e->getMessage(), "\n";
+            }      
+            
+        }
+        
+    }
+    
+    //##########################################################################
+    
+    QRtools::markTime('start');
+    
+
+
+
+//---- qrspec.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * QR Code specifications
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * The following data / specifications are taken from
+ * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004)
+ *  or
+ * "Automatic identification and data capture techniques -- 
+ *  QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+    define('QRSPEC_VERSION_MAX', 40);
+    define('QRSPEC_WIDTH_MAX',   177);
+
+    define('QRCAP_WIDTH',        0);
+    define('QRCAP_WORDS',        1);
+    define('QRCAP_REMINDER',     2);
+    define('QRCAP_EC',           3);
+
+    class QRspec {
+    
+        public static $capacity = array(
+            array(  0,    0, 0, array(   0,    0,    0,    0)),
+            array( 21,   26, 0, array(   7,   10,   13,   17)), // 1
+            array( 25,   44, 7, array(  10,   16,   22,   28)),
+            array( 29,   70, 7, array(  15,   26,   36,   44)),
+            array( 33,  100, 7, array(  20,   36,   52,   64)),
+            array( 37,  134, 7, array(  26,   48,   72,   88)), // 5
+            array( 41,  172, 7, array(  36,   64,   96,  112)),
+            array( 45,  196, 0, array(  40,   72,  108,  130)),
+            array( 49,  242, 0, array(  48,   88,  132,  156)),
+            array( 53,  292, 0, array(  60,  110,  160,  192)),
+            array( 57,  346, 0, array(  72,  130,  192,  224)), //10
+            array( 61,  404, 0, array(  80,  150,  224,  264)),
+            array( 65,  466, 0, array(  96,  176,  260,  308)),
+            array( 69,  532, 0, array( 104,  198,  288,  352)),
+            array( 73,  581, 3, array( 120,  216,  320,  384)),
+            array( 77,  655, 3, array( 132,  240,  360,  432)), //15
+            array( 81,  733, 3, array( 144,  280,  408,  480)),
+            array( 85,  815, 3, array( 168,  308,  448,  532)),
+            array( 89,  901, 3, array( 180,  338,  504,  588)),
+            array( 93,  991, 3, array( 196,  364,  546,  650)),
+            array( 97, 1085, 3, array( 224,  416,  600,  700)), //20
+            array(101, 1156, 4, array( 224,  442,  644,  750)),
+            array(105, 1258, 4, array( 252,  476,  690,  816)),
+            array(109, 1364, 4, array( 270,  504,  750,  900)),
+            array(113, 1474, 4, array( 300,  560,  810,  960)),
+            array(117, 1588, 4, array( 312,  588,  870, 1050)), //25
+            array(121, 1706, 4, array( 336,  644,  952, 1110)),
+            array(125, 1828, 4, array( 360,  700, 1020, 1200)),
+            array(129, 1921, 3, array( 390,  728, 1050, 1260)),
+            array(133, 2051, 3, array( 420,  784, 1140, 1350)),
+            array(137, 2185, 3, array( 450,  812, 1200, 1440)), //30
+            array(141, 2323, 3, array( 480,  868, 1290, 1530)),
+            array(145, 2465, 3, array( 510,  924, 1350, 1620)),
+            array(149, 2611, 3, array( 540,  980, 1440, 1710)),
+            array(153, 2761, 3, array( 570, 1036, 1530, 1800)),
+            array(157, 2876, 0, array( 570, 1064, 1590, 1890)), //35
+            array(161, 3034, 0, array( 600, 1120, 1680, 1980)),
+            array(165, 3196, 0, array( 630, 1204, 1770, 2100)),
+            array(169, 3362, 0, array( 660, 1260, 1860, 2220)),
+            array(173, 3532, 0, array( 720, 1316, 1950, 2310)),
+            array(177, 3706, 0, array( 750, 1372, 2040, 2430)) //40
+        );
+        
+        //----------------------------------------------------------------------
+        public static function getDataLength($version, $level)
+        {
+            return self::$capacity[$version][QRCAP_WORDS] - self::$capacity[$version][QRCAP_EC][$level];
+        }
+        
+        //----------------------------------------------------------------------
+        public static function getECCLength($version, $level)
+        {
+            return self::$capacity[$version][QRCAP_EC][$level];
+        }
+        
+        //----------------------------------------------------------------------
+        public static function getWidth($version)
+        {
+            return self::$capacity[$version][QRCAP_WIDTH];
+        }
+        
+        //----------------------------------------------------------------------
+        public static function getRemainder($version)
+        {
+            return self::$capacity[$version][QRCAP_REMINDER];
+        }
+        
+        //----------------------------------------------------------------------
+        public static function getMinimumVersion($size, $level)
+        {
+
+            for($i=1; $i<= QRSPEC_VERSION_MAX; $i++) {
+                $words  = self::$capacity[$i][QRCAP_WORDS] - self::$capacity[$i][QRCAP_EC][$level];
+                if($words >= $size) 
+                    return $i;
+            }
+
+            return -1;
+        }
+    
+        //######################################################################
+        
+        public static $lengthTableBits = array(
+            array(10, 12, 14),
+            array( 9, 11, 13),
+            array( 8, 16, 16),
+            array( 8, 10, 12)
+        );
+        
+        //----------------------------------------------------------------------
+        public static function lengthIndicator($mode, $version)
+        {
+            if ($mode == QR_MODE_STRUCTURE)
+                return 0;
+                
+            if ($version <= 9) {
+                $l = 0;
+            } else if ($version <= 26) {
+                $l = 1;
+            } else {
+                $l = 2;
+            }
+
+            return self::$lengthTableBits[$mode][$l];
+        }
+        
+        //----------------------------------------------------------------------
+        public static function maximumWords($mode, $version)
+        {
+            if($mode == QR_MODE_STRUCTURE) 
+                return 3;
+                
+            if($version <= 9) {
+                $l = 0;
+            } else if($version <= 26) {
+                $l = 1;
+            } else {
+                $l = 2;
+            }
+
+            $bits = self::$lengthTableBits[$mode][$l];
+            $words = (1 << $bits) - 1;
+            
+            if($mode == QR_MODE_KANJI) {
+                $words *= 2; // the number of bytes is required
+            }
+
+            return $words;
+        }
+
+        // Error correction code -----------------------------------------------
+        // Table of the error correction code (Reed-Solomon block)
+        // See Table 12-16 (pp.30-36), JIS X0510:2004.
+
+        public static $eccTable = array(
+            array(array( 0,  0), array( 0,  0), array( 0,  0), array( 0,  0)),
+            array(array( 1,  0), array( 1,  0), array( 1,  0), array( 1,  0)), // 1
+            array(array( 1,  0), array( 1,  0), array( 1,  0), array( 1,  0)),
+            array(array( 1,  0), array( 1,  0), array( 2,  0), array( 2,  0)),
+            array(array( 1,  0), array( 2,  0), array( 2,  0), array( 4,  0)),
+            array(array( 1,  0), array( 2,  0), array( 2,  2), array( 2,  2)), // 5
+            array(array( 2,  0), array( 4,  0), array( 4,  0), array( 4,  0)),
+            array(array( 2,  0), array( 4,  0), array( 2,  4), array( 4,  1)),
+            array(array( 2,  0), array( 2,  2), array( 4,  2), array( 4,  2)),
+            array(array( 2,  0), array( 3,  2), array( 4,  4), array( 4,  4)),
+            array(array( 2,  2), array( 4,  1), array( 6,  2), array( 6,  2)), //10
+            array(array( 4,  0), array( 1,  4), array( 4,  4), array( 3,  8)),
+            array(array( 2,  2), array( 6,  2), array( 4,  6), array( 7,  4)),
+            array(array( 4,  0), array( 8,  1), array( 8,  4), array(12,  4)),
+            array(array( 3,  1), array( 4,  5), array(11,  5), array(11,  5)),
+            array(array( 5,  1), array( 5,  5), array( 5,  7), array(11,  7)), //15
+            array(array( 5,  1), array( 7,  3), array(15,  2), array( 3, 13)),
+            array(array( 1,  5), array(10,  1), array( 1, 15), array( 2, 17)),
+            array(array( 5,  1), array( 9,  4), array(17,  1), array( 2, 19)),
+            array(array( 3,  4), array( 3, 11), array(17,  4), array( 9, 16)),
+            array(array( 3,  5), array( 3, 13), array(15,  5), array(15, 10)), //20
+            array(array( 4,  4), array(17,  0), array(17,  6), array(19,  6)),
+            array(array( 2,  7), array(17,  0), array( 7, 16), array(34,  0)),
+            array(array( 4,  5), array( 4, 14), array(11, 14), array(16, 14)),
+            array(array( 6,  4), array( 6, 14), array(11, 16), array(30,  2)),
+            array(array( 8,  4), array( 8, 13), array( 7, 22), array(22, 13)), //25
+            array(array(10,  2), array(19,  4), array(28,  6), array(33,  4)),
+            array(array( 8,  4), array(22,  3), array( 8, 26), array(12, 28)),
+            array(array( 3, 10), array( 3, 23), array( 4, 31), array(11, 31)),
+            array(array( 7,  7), array(21,  7), array( 1, 37), array(19, 26)),
+            array(array( 5, 10), array(19, 10), array(15, 25), array(23, 25)), //30
+            array(array(13,  3), array( 2, 29), array(42,  1), array(23, 28)),
+            array(array(17,  0), array(10, 23), array(10, 35), array(19, 35)),
+            array(array(17,  1), array(14, 21), array(29, 19), array(11, 46)),
+            array(array(13,  6), array(14, 23), array(44,  7), array(59,  1)),
+            array(array(12,  7), array(12, 26), array(39, 14), array(22, 41)), //35
+            array(array( 6, 14), array( 6, 34), array(46, 10), array( 2, 64)),
+            array(array(17,  4), array(29, 14), array(49, 10), array(24, 46)),
+            array(array( 4, 18), array(13, 32), array(48, 14), array(42, 32)),
+            array(array(20,  4), array(40,  7), array(43, 22), array(10, 67)),
+            array(array(19,  6), array(18, 31), array(34, 34), array(20, 61)),//40
+        );                                                                       
+
+        //----------------------------------------------------------------------
+        // CACHEABLE!!!
+        
+        public static function getEccSpec($version, $level, array &$spec)
+        {
+            if (count($spec) < 5) {
+                $spec = array(0,0,0,0,0);
+            }
+
+            $b1   = self::$eccTable[$version][$level][0];
+            $b2   = self::$eccTable[$version][$level][1];
+            $data = self::getDataLength($version, $level);
+            $ecc  = self::getECCLength($version, $level);
+
+            if($b2 == 0) {
+                $spec[0] = $b1;
+                $spec[1] = (int)($data / $b1);
+                $spec[2] = (int)($ecc / $b1);
+                $spec[3] = 0; 
+                $spec[4] = 0;
+            } else {
+                $spec[0] = $b1;
+                $spec[1] = (int)($data / ($b1 + $b2));
+                $spec[2] = (int)($ecc  / ($b1 + $b2));
+                $spec[3] = $b2;
+                $spec[4] = $spec[1] + 1;
+            }
+        }
+
+        // Alignment pattern ---------------------------------------------------
+
+        // Positions of alignment patterns.
+        // This array includes only the second and the third position of the 
+        // alignment patterns. Rest of them can be calculated from the distance 
+        // between them.
+         
+        // See Table 1 in Appendix E (pp.71) of JIS X0510:2004.
+         
+        public static $alignmentPattern = array(      
+            array( 0,  0),
+            array( 0,  0), array(18,  0), array(22,  0), array(26,  0), array(30,  0), // 1- 5
+            array(34,  0), array(22, 38), array(24, 42), array(26, 46), array(28, 50), // 6-10
+            array(30, 54), array(32, 58), array(34, 62), array(26, 46), array(26, 48), //11-15
+            array(26, 50), array(30, 54), array(30, 56), array(30, 58), array(34, 62), //16-20
+            array(28, 50), array(26, 50), array(30, 54), array(28, 54), array(32, 58), //21-25
+            array(30, 58), array(34, 62), array(26, 50), array(30, 54), array(26, 52), //26-30
+            array(30, 56), array(34, 60), array(30, 58), array(34, 62), array(30, 54), //31-35
+            array(24, 50), array(28, 54), array(32, 58), array(26, 54), array(30, 58), //35-40
+        );                                                                                  
+
+        
+        /** --------------------------------------------------------------------
+         * Put an alignment marker.
+         * @param frame
+         * @param width
+         * @param ox,oy center coordinate of the pattern
+         */
+        public static function putAlignmentMarker(array &$frame, $ox, $oy)
+        {
+            $finder = array(
+                "\xa1\xa1\xa1\xa1\xa1",
+                "\xa1\xa0\xa0\xa0\xa1",
+                "\xa1\xa0\xa1\xa0\xa1",
+                "\xa1\xa0\xa0\xa0\xa1",
+                "\xa1\xa1\xa1\xa1\xa1"
+            );                        
+            
+            $yStart = $oy-2;         
+            $xStart = $ox-2;
+            
+            for($y=0; $y<5; $y++) {
+                QRstr::set($frame, $xStart, $yStart+$y, $finder[$y]);
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public static function putAlignmentPattern($version, &$frame, $width)
+        {
+            if($version < 2)
+                return;
+
+            $d = self::$alignmentPattern[$version][1] - self::$alignmentPattern[$version][0];
+            if($d < 0) {
+                $w = 2;
+            } else {
+                $w = (int)(($width - self::$alignmentPattern[$version][0]) / $d + 2);
+            }
+
+            if($w * $w - 3 == 1) {
+                $x = self::$alignmentPattern[$version][0];
+                $y = self::$alignmentPattern[$version][0];
+                self::putAlignmentMarker($frame, $x, $y);
+                return;
+            }
+
+            $cx = self::$alignmentPattern[$version][0];
+            for($x=1; $x<$w - 1; $x++) {
+                self::putAlignmentMarker($frame, 6, $cx);
+                self::putAlignmentMarker($frame, $cx,  6);
+                $cx += $d;
+            }
+
+            $cy = self::$alignmentPattern[$version][0];
+            for($y=0; $y<$w-1; $y++) {
+                $cx = self::$alignmentPattern[$version][0];
+                for($x=0; $x<$w-1; $x++) {
+                    self::putAlignmentMarker($frame, $cx, $cy);
+                    $cx += $d;
+                }
+                $cy += $d;
+            }
+        }
+
+        // Version information pattern -----------------------------------------
+
+		// Version information pattern (BCH coded).
+        // See Table 1 in Appendix D (pp.68) of JIS X0510:2004.
+        
+		// size: [QRSPEC_VERSION_MAX - 6]
+		
+        public static $versionPattern = array(
+            0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d,
+            0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9,
+            0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75,
+            0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64,
+            0x27541, 0x28c69
+        );
+
+        //----------------------------------------------------------------------
+        public static function getVersionPattern($version)
+        {
+            if($version < 7 || $version > QRSPEC_VERSION_MAX)
+                return 0;
+
+            return self::$versionPattern[$version -7];
+        }
+
+        // Format information --------------------------------------------------
+        // See calcFormatInfo in tests/test_qrspec.c (orginal qrencode c lib)
+        
+        public static $formatInfo = array(
+            array(0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976),
+            array(0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0),
+            array(0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed),
+            array(0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b)
+        );
+
+        public static function getFormatInfo($mask, $level)
+        {
+            if($mask < 0 || $mask > 7)
+                return 0;
+                
+            if($level < 0 || $level > 3)
+                return 0;                
+
+            return self::$formatInfo[$level][$mask];
+        }
+
+        // Frame ---------------------------------------------------------------
+        // Cache of initial frames.
+         
+        public static $frames = array();
+
+        /** --------------------------------------------------------------------
+         * Put a finder pattern.
+         * @param frame
+         * @param width
+         * @param ox,oy upper-left coordinate of the pattern
+         */
+        public static function putFinderPattern(&$frame, $ox, $oy)
+        {
+            $finder = array(
+                "\xc1\xc1\xc1\xc1\xc1\xc1\xc1",
+                "\xc1\xc0\xc0\xc0\xc0\xc0\xc1",
+                "\xc1\xc0\xc1\xc1\xc1\xc0\xc1",
+                "\xc1\xc0\xc1\xc1\xc1\xc0\xc1",
+                "\xc1\xc0\xc1\xc1\xc1\xc0\xc1",
+                "\xc1\xc0\xc0\xc0\xc0\xc0\xc1",
+                "\xc1\xc1\xc1\xc1\xc1\xc1\xc1"
+            );                            
+            
+            for($y=0; $y<7; $y++) {
+                QRstr::set($frame, $ox, $oy+$y, $finder[$y]);
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public static function createFrame($version)
+        {
+            $width = self::$capacity[$version][QRCAP_WIDTH];
+            $frameLine = str_repeat ("\0", $width);
+            $frame = array_fill(0, $width, $frameLine);
+
+            // Finder pattern
+            self::putFinderPattern($frame, 0, 0);
+            self::putFinderPattern($frame, $width - 7, 0);
+            self::putFinderPattern($frame, 0, $width - 7);
+            
+            // Separator
+            $yOffset = $width - 7;
+            
+            for($y=0; $y<7; $y++) {
+                $frame[$y][7] = "\xc0";
+                $frame[$y][$width - 8] = "\xc0";
+                $frame[$yOffset][7] = "\xc0";
+                $yOffset++;
+            }
+            
+            $setPattern = str_repeat("\xc0", 8);
+            
+            QRstr::set($frame, 0, 7, $setPattern);
+            QRstr::set($frame, $width-8, 7, $setPattern);
+            QRstr::set($frame, 0, $width - 8, $setPattern);
+        
+            // Format info
+            $setPattern = str_repeat("\x84", 9);
+            QRstr::set($frame, 0, 8, $setPattern);
+            QRstr::set($frame, $width - 8, 8, $setPattern, 8);
+            
+            $yOffset = $width - 8;
+
+            for($y=0; $y<8; $y++,$yOffset++) {
+                $frame[$y][8] = "\x84";
+                $frame[$yOffset][8] = "\x84";
+            }
+
+            // Timing pattern  
+            
+            for($i=1; $i<$width-15; $i++) {
+                $frame[6][7+$i] = chr(0x90 | ($i & 1));
+                $frame[7+$i][6] = chr(0x90 | ($i & 1));
+            }
+            
+            // Alignment pattern  
+            self::putAlignmentPattern($version, $frame, $width);
+            
+            // Version information 
+            if($version >= 7) {
+                $vinf = self::getVersionPattern($version);
+
+                $v = $vinf;
+                
+                for($x=0; $x<6; $x++) {
+                    for($y=0; $y<3; $y++) {
+                        $frame[($width - 11)+$y][$x] = chr(0x88 | ($v & 1));
+                        $v = $v >> 1;
+                    }
+                }
+
+                $v = $vinf;
+                for($y=0; $y<6; $y++) {
+                    for($x=0; $x<3; $x++) {
+                        $frame[$y][$x+($width - 11)] = chr(0x88 | ($v & 1));
+                        $v = $v >> 1;
+                    }
+                }
+            }
+    
+            // and a little bit...  
+            $frame[$width - 8][8] = "\x81";
+            
+            return $frame;
+        }
+
+        //----------------------------------------------------------------------
+        public static function debug($frame, $binary_mode = false)
+        {
+            if ($binary_mode) {
+            
+                    foreach ($frame as &$frameLine) {
+                        $frameLine = join('<span class="m">&nbsp;&nbsp;</span>', explode('0', $frameLine));
+                        $frameLine = join('&#9608;&#9608;', explode('1', $frameLine));
+                    }
+                    
+                    ?>
+                <style>
+                    .m { background-color: white; }
+                </style>
+                <?php
+                    echo '<pre><tt><br/ ><br/ ><br/ >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+                    echo join("<br/ >&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", $frame);
+                    echo '</tt></pre><br/ ><br/ ><br/ ><br/ ><br/ ><br/ >';
+            
+            } else {
+            
+                foreach ($frame as &$frameLine) {
+                    $frameLine = join('<span class="m">&nbsp;</span>',  explode("\xc0", $frameLine));
+                    $frameLine = join('<span class="m">&#9618;</span>', explode("\xc1", $frameLine));
+                    $frameLine = join('<span class="p">&nbsp;</span>',  explode("\xa0", $frameLine));
+                    $frameLine = join('<span class="p">&#9618;</span>', explode("\xa1", $frameLine));
+                    $frameLine = join('<span class="s">&#9671;</span>', explode("\x84", $frameLine)); //format 0
+                    $frameLine = join('<span class="s">&#9670;</span>', explode("\x85", $frameLine)); //format 1
+                    $frameLine = join('<span class="x">&#9762;</span>', explode("\x81", $frameLine)); //special bit
+                    $frameLine = join('<span class="c">&nbsp;</span>',  explode("\x90", $frameLine)); //clock 0
+                    $frameLine = join('<span class="c">&#9719;</span>', explode("\x91", $frameLine)); //clock 1
+                    $frameLine = join('<span class="f">&nbsp;</span>',  explode("\x88", $frameLine)); //version
+                    $frameLine = join('<span class="f">&#9618;</span>', explode("\x89", $frameLine)); //version
+                    $frameLine = join('&#9830;', explode("\x01", $frameLine));
+                    $frameLine = join('&#8901;', explode("\0", $frameLine));
+                }
+                
+                ?>
+                <style>
+                    .p { background-color: yellow; }
+                    .m { background-color: #00FF00; }
+                    .s { background-color: #FF0000; }
+                    .c { background-color: aqua; }
+                    .x { background-color: pink; }
+                    .f { background-color: gold; }
+                </style>
+                <?php
+                echo "<pre><tt>";
+                echo join("<br/ >", $frame);
+                echo "</tt></pre>";
+            
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public static function serial($frame)
+        {
+            return gzcompress(join("\n", $frame), 9);
+        }
+        
+        //----------------------------------------------------------------------
+        public static function unserial($code)
+        {
+            return explode("\n", gzuncompress($code));
+        }
+        
+        //----------------------------------------------------------------------
+        public static function newFrame($version)
+        {
+            if($version < 1 || $version > QRSPEC_VERSION_MAX) 
+                return null;
+
+            if(!isset(self::$frames[$version])) {
+                
+                $fileName = QR_CACHE_DIR.'frame_'.$version.'.dat';
+                
+                if (QR_CACHEABLE) {
+                    if (file_exists($fileName)) {
+                        self::$frames[$version] = self::unserial(file_get_contents($fileName));
+                    } else {
+                        self::$frames[$version] = self::createFrame($version);
+                        file_put_contents($fileName, self::serial(self::$frames[$version]));
+                    }
+                } else {
+                    self::$frames[$version] = self::createFrame($version);
+                }
+            }
+            
+            if(is_null(self::$frames[$version]))
+                return null;
+
+            return self::$frames[$version];
+        }
+
+        //----------------------------------------------------------------------
+        public static function rsBlockNum($spec)     { return $spec[0] + $spec[3]; }
+        public static function rsBlockNum1($spec)    { return $spec[0]; }
+        public static function rsDataCodes1($spec)   { return $spec[1]; }
+        public static function rsEccCodes1($spec)    { return $spec[2]; }
+        public static function rsBlockNum2($spec)    { return $spec[3]; }
+        public static function rsDataCodes2($spec)   { return $spec[4]; }
+        public static function rsEccCodes2($spec)    { return $spec[2]; }
+        public static function rsDataLength($spec)   { return ($spec[0] * $spec[1]) + ($spec[3] * $spec[4]);    }
+        public static function rsEccLength($spec)    { return ($spec[0] + $spec[3]) * $spec[2]; }
+        
+    }
+
+
+
+//---- qrimage.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Image output of code using GD2
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+    define('QR_IMAGE', true);
+
+    class QRimage {
+
+        //----------------------------------------------------------------------
+        public static function png($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE, $back_color, $fore_color)
+        {
+            $image = self::image($frame, $pixelPerPoint, $outerFrame, $back_color, $fore_color);
+
+            if ($filename === false) {
+                Header("Content-type: image/png");
+                ImagePng($image);
+            } else {
+                if($saveandprint===TRUE){
+                    ImagePng($image, $filename);
+                    header("Content-type: image/png");
+                    ImagePng($image);
+                }else{
+                    ImagePng($image, $filename);
+                }
+            }
+
+            ImageDestroy($image);
+        }
+
+        //----------------------------------------------------------------------
+        public static function jpg($frame, $filename = false, $pixelPerPoint = 8, $outerFrame = 4, $q = 85)
+        {
+            $image = self::image($frame, $pixelPerPoint, $outerFrame);
+
+            if ($filename === false) {
+                Header("Content-type: image/jpeg");
+                ImageJpeg($image, null, $q);
+            } else {
+                ImageJpeg($image, $filename, $q);
+            }
+
+            ImageDestroy($image);
+        }
+
+        //----------------------------------------------------------------------
+        private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4, $back_color = 0xFFFFFF, $fore_color = 0x000000)
+        {
+            $h = count($frame);
+            $w = strlen($frame[0]);
+
+            $imgW = $w + 2*$outerFrame;
+            $imgH = $h + 2*$outerFrame;
+
+            $base_image =ImageCreate($imgW, $imgH);
+
+            // convert a hexadecimal color code into decimal format (red = 255 0 0, green = 0 255 0, blue = 0 0 255)
+            $r1 = round((($fore_color & 0xFF0000) >> 16), 5);
+            $g1 = round((($fore_color & 0x00FF00) >> 8), 5);
+            $b1 = round(($fore_color & 0x0000FF), 5);
+
+            // convert a hexadecimal color code into decimal format (red = 255 0 0, green = 0 255 0, blue = 0 0 255)
+            $r2 = round((($back_color & 0xFF0000) >> 16), 5);
+            $g2 = round((($back_color & 0x00FF00) >> 8), 5);
+            $b2 = round(($back_color & 0x0000FF), 5);
+
+
+
+            $col[0] = ImageColorAllocate($base_image, $r2, $g2, $b2);
+            $col[1] = ImageColorAllocate($base_image, $r1, $g1, $b1);
+
+            imagefill($base_image, 0, 0, $col[0]);
+
+            for($y=0; $y<$h; $y++) {
+                for($x=0; $x<$w; $x++) {
+                    if ($frame[$y][$x] == '1') {
+                        ImageSetPixel($base_image,$x+$outerFrame,$y+$outerFrame,$col[1]);
+                    }
+                }
+            }
+
+            $target_image =ImageCreate($imgW * $pixelPerPoint, $imgH * $pixelPerPoint);
+            ImageCopyResized($target_image, $base_image, 0, 0, 0, 0, $imgW * $pixelPerPoint, $imgH * $pixelPerPoint, $imgW, $imgH);
+            ImageDestroy($base_image);
+
+            return $target_image;
+        }
+    }
+
+
+
+
+//---- qrinput.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Input encoding class
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+    define('STRUCTURE_HEADER_BITS',  20);
+    define('MAX_STRUCTURED_SYMBOLS', 16);
+
+    class QRinputItem {
+    
+        public $mode;
+        public $size;
+        public $data;
+        public $bstream;
+
+        public function __construct($mode, $size, $data, $bstream = null) 
+        {
+            $setData = array_slice($data, 0, $size);
+            
+            if (count($setData) < $size) {
+                $setData = array_merge($setData, array_fill(0,$size-count($setData),0));
+            }
+        
+            if(!QRinput::check($mode, $size, $setData)) {
+                throw new Exception('Error m:'.$mode.',s:'.$size.',d:'.join(',',$setData));
+            }
+            
+            $this->mode = $mode;
+            $this->size = $size;
+            $this->data = $setData;
+            $this->bstream = $bstream;
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeModeNum($version)
+        {
+            try {
+            
+                $words = (int)($this->size / 3);
+                $bs = new QRbitstream();
+                
+                $val = 0x1;
+                $bs->appendNum(4, $val);
+                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_NUM, $version), $this->size);
+
+                for($i=0; $i<$words; $i++) {
+                    $val  = (ord($this->data[$i*3  ]) - ord('0')) * 100;
+                    $val += (ord($this->data[$i*3+1]) - ord('0')) * 10;
+                    $val += (ord($this->data[$i*3+2]) - ord('0'));
+                    $bs->appendNum(10, $val);
+                }
+
+                if($this->size - $words * 3 == 1) {
+                    $val = ord($this->data[$words*3]) - ord('0');
+                    $bs->appendNum(4, $val);
+                } else if($this->size - $words * 3 == 2) {
+                    $val  = (ord($this->data[$words*3  ]) - ord('0')) * 10;
+                    $val += (ord($this->data[$words*3+1]) - ord('0'));
+                    $bs->appendNum(7, $val);
+                }
+
+                $this->bstream = $bs;
+                return 0;
+                
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeModeAn($version)
+        {
+            try {
+                $words = (int)($this->size / 2);
+                $bs = new QRbitstream();
+                
+                $bs->appendNum(4, 0x02);
+                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_AN, $version), $this->size);
+
+                for($i=0; $i<$words; $i++) {
+                    $val  = (int)QRinput::lookAnTable(ord($this->data[$i*2  ])) * 45;
+                    $val += (int)QRinput::lookAnTable(ord($this->data[$i*2+1]));
+
+                    $bs->appendNum(11, $val);
+                }
+
+                if($this->size & 1) {
+                    $val = QRinput::lookAnTable(ord($this->data[$words * 2]));
+                    $bs->appendNum(6, $val);
+                }
+        
+                $this->bstream = $bs;
+                return 0;
+            
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeMode8($version)
+        {
+            try {
+                $bs = new QRbitstream();
+
+                $bs->appendNum(4, 0x4);
+                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_8, $version), $this->size);
+
+                for($i=0; $i<$this->size; $i++) {
+                    $bs->appendNum(8, ord($this->data[$i]));
+                }
+
+                $this->bstream = $bs;
+                return 0;
+            
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeModeKanji($version)
+        {
+            try {
+
+                $bs = new QRbitrtream();
+                
+                $bs->appendNum(4, 0x8);
+                $bs->appendNum(QRspec::lengthIndicator(QR_MODE_KANJI, $version), (int)($this->size / 2));
+
+                for($i=0; $i<$this->size; $i+=2) {
+                    $val = (ord($this->data[$i]) << 8) | ord($this->data[$i+1]);
+                    if($val <= 0x9ffc) {
+                        $val -= 0x8140;
+                    } else {
+                        $val -= 0xc140;
+                    }
+                    
+                    $h = ($val >> 8) * 0xc0;
+                    $val = ($val & 0xff) + $h;
+
+                    $bs->appendNum(13, $val);
+                }
+
+                $this->bstream = $bs;
+                return 0;
+            
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public function encodeModeStructure()
+        {
+            try {
+                $bs =  new QRbitstream();
+                
+                $bs->appendNum(4, 0x03);
+                $bs->appendNum(4, ord($this->data[1]) - 1);
+                $bs->appendNum(4, ord($this->data[0]) - 1);
+                $bs->appendNum(8, ord($this->data[2]));
+
+                $this->bstream = $bs;
+                return 0;
+            
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public function estimateBitStreamSizeOfEntry($version)
+        {
+            $bits = 0;
+
+            if($version == 0) 
+                $version = 1;
+
+            switch($this->mode) {
+                case QR_MODE_NUM:        $bits = QRinput::estimateBitsModeNum($this->size);    break;
+                case QR_MODE_AN:        $bits = QRinput::estimateBitsModeAn($this->size);    break;
+                case QR_MODE_8:            $bits = QRinput::estimateBitsMode8($this->size);    break;
+                case QR_MODE_KANJI:        $bits = QRinput::estimateBitsModeKanji($this->size);break;
+                case QR_MODE_STRUCTURE:    return STRUCTURE_HEADER_BITS;            
+                default:
+                    return 0;
+            }
+
+            $l = QRspec::lengthIndicator($this->mode, $version);
+            $m = 1 << $l;
+            $num = (int)(($this->size + $m - 1) / $m);
+
+            $bits += $num * (4 + $l);
+
+            return $bits;
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeBitStream($version)
+        {
+            try {
+            
+                unset($this->bstream);
+                $words = QRspec::maximumWords($this->mode, $version);
+                
+                if($this->size > $words) {
+                
+                    $st1 = new QRinputItem($this->mode, $words, $this->data);
+                    $st2 = new QRinputItem($this->mode, $this->size - $words, array_slice($this->data, $words));
+
+                    $st1->encodeBitStream($version);
+                    $st2->encodeBitStream($version);
+                    
+                    $this->bstream = new QRbitstream();
+                    $this->bstream->append($st1->bstream);
+                    $this->bstream->append($st2->bstream);
+                    
+                    unset($st1);
+                    unset($st2);
+                    
+                } else {
+                    
+                    $ret = 0;
+                    
+                    switch($this->mode) {
+                        case QR_MODE_NUM:        $ret = $this->encodeModeNum($version);    break;
+                        case QR_MODE_AN:        $ret = $this->encodeModeAn($version);    break;
+                        case QR_MODE_8:            $ret = $this->encodeMode8($version);    break;
+                        case QR_MODE_KANJI:        $ret = $this->encodeModeKanji($version);break;
+                        case QR_MODE_STRUCTURE:    $ret = $this->encodeModeStructure();    break;
+                        
+                        default:
+                            break;
+                    }
+                    
+                    if($ret < 0)
+                        return -1;
+                }
+
+                return $this->bstream->size();
+            
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+    };
+    
+    //##########################################################################
+
+    class QRinput {
+
+        public $items;
+        
+        private $version;
+        private $level;
+        
+        //----------------------------------------------------------------------
+        public function __construct($version = 0, $level = QR_ECLEVEL_L)
+        {
+            if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) {
+                throw new Exception('Invalid version no');
+            }
+            
+            $this->version = $version;
+            $this->level = $level;
+        }
+        
+        //----------------------------------------------------------------------
+        public function getVersion()
+        {
+            return $this->version;
+        }
+        
+        //----------------------------------------------------------------------
+        public function setVersion($version)
+        {
+            if($version < 0 || $version > QRSPEC_VERSION_MAX) {
+                throw new Exception('Invalid version no');
+                return -1;
+            }
+
+            $this->version = $version;
+
+            return 0;
+        }
+        
+        //----------------------------------------------------------------------
+        public function getErrorCorrectionLevel()
+        {
+            return $this->level;
+        }
+
+        //----------------------------------------------------------------------
+        public function setErrorCorrectionLevel($level)
+        {
+            if($level > QR_ECLEVEL_H) {
+                throw new Exception('Invalid ECLEVEL');
+                return -1;
+            }
+
+            $this->level = $level;
+
+            return 0;
+        }
+        
+        //----------------------------------------------------------------------
+        public function appendEntry(QRinputItem $entry)
+        {
+            $this->items[] = $entry;
+        }
+        
+        //----------------------------------------------------------------------
+        public function append($mode, $size, $data)
+        {
+            try {
+                $entry = new QRinputItem($mode, $size, $data);
+                $this->items[] = $entry;
+                return 0;
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        
+        public function insertStructuredAppendHeader($size, $index, $parity)
+        {
+            if( $size > MAX_STRUCTURED_SYMBOLS ) {
+                throw new Exception('insertStructuredAppendHeader wrong size');
+            }
+            
+            if( $index <= 0 || $index > MAX_STRUCTURED_SYMBOLS ) {
+                throw new Exception('insertStructuredAppendHeader wrong index');
+            }
+
+            $buf = array($size, $index, $parity);
+            
+            try {
+                $entry = new QRinputItem(QR_MODE_STRUCTURE, 3, buf);
+                array_unshift($this->items, $entry);
+                return 0;
+            } catch (Exception $e) {
+                return -1;
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public function calcParity()
+        {
+            $parity = 0;
+            
+            foreach($this->items as $item) {
+                if($item->mode != QR_MODE_STRUCTURE) {
+                    for($i=$item->size-1; $i>=0; $i--) {
+                        $parity ^= $item->data[$i];
+                    }
+                }
+            }
+
+            return $parity;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function checkModeNum($size, $data)
+        {
+            for($i=0; $i<$size; $i++) {
+                if((ord($data[$i]) < ord('0')) || (ord($data[$i]) > ord('9'))){
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        //----------------------------------------------------------------------
+        public static function estimateBitsModeNum($size)
+        {
+            $w = (int)$size / 3;
+            $bits = $w * 10;
+            
+            switch($size - $w * 3) {
+                case 1:
+                    $bits += 4;
+                    break;
+                case 2:
+                    $bits += 7;
+                    break;
+                default:
+                    break;
+            }
+
+            return $bits;
+        }
+        
+        //----------------------------------------------------------------------
+        public static $anTable = array(
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,
+             0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1,
+            -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+            25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+        );
+        
+        //----------------------------------------------------------------------
+        public static function lookAnTable($c)
+        {
+            return (($c > 127)?-1:self::$anTable[$c]);
+        }
+        
+        //----------------------------------------------------------------------
+        public static function checkModeAn($size, $data)
+        {
+            for($i=0; $i<$size; $i++) {
+                if (self::lookAnTable(ord($data[$i])) == -1) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function estimateBitsModeAn($size)
+        {
+            $w = (int)($size / 2);
+            $bits = $w * 11;
+            
+            if($size & 1) {
+                $bits += 6;
+            }
+
+            return $bits;
+        }
+    
+        //----------------------------------------------------------------------
+        public static function estimateBitsMode8($size)
+        {
+            return $size * 8;
+        }
+        
+        //----------------------------------------------------------------------
+        public function estimateBitsModeKanji($size)
+        {
+            return (int)(($size / 2) * 13);
+        }
+        
+        //----------------------------------------------------------------------
+        public static function checkModeKanji($size, $data)
+        {
+            if($size & 1)
+                return false;
+
+            for($i=0; $i<$size; $i+=2) {
+                $val = (ord($data[$i]) << 8) | ord($data[$i+1]);
+                if( $val < 0x8140 
+                || ($val > 0x9ffc && $val < 0xe040) 
+                || $val > 0xebbf) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /***********************************************************************
+         * Validation
+         **********************************************************************/
+
+        public static function check($mode, $size, $data)
+        {
+            if($size <= 0) 
+                return false;
+
+            switch($mode) {
+                case QR_MODE_NUM:       return self::checkModeNum($size, $data);   break;
+                case QR_MODE_AN:        return self::checkModeAn($size, $data);    break;
+                case QR_MODE_KANJI:     return self::checkModeKanji($size, $data); break;
+                case QR_MODE_8:         return true; break;
+                case QR_MODE_STRUCTURE: return true; break;
+                
+                default:
+                    break;
+            }
+
+            return false;
+        }
+        
+        
+        //----------------------------------------------------------------------
+        public function estimateBitStreamSize($version)
+        {
+            $bits = 0;
+
+            foreach($this->items as $item) {
+                $bits += $item->estimateBitStreamSizeOfEntry($version);
+            }
+
+            return $bits;
+        }
+        
+        //----------------------------------------------------------------------
+        public function estimateVersion()
+        {
+            $version = 0;
+            $prev = 0;
+            do {
+                $prev = $version;
+                $bits = $this->estimateBitStreamSize($prev);
+                $version = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);
+                if ($version < 0) {
+                    return -1;
+                }
+            } while ($version > $prev);
+
+            return $version;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function lengthOfCode($mode, $version, $bits)
+        {
+            $payload = $bits - 4 - QRspec::lengthIndicator($mode, $version);
+            switch($mode) {
+                case QR_MODE_NUM:
+                    $chunks = (int)($payload / 10);
+                    $remain = $payload - $chunks * 10;
+                    $size = $chunks * 3;
+                    if($remain >= 7) {
+                        $size += 2;
+                    } else if($remain >= 4) {
+                        $size += 1;
+                    }
+                    break;
+                case QR_MODE_AN:
+                    $chunks = (int)($payload / 11);
+                    $remain = $payload - $chunks * 11;
+                    $size = $chunks * 2;
+                    if($remain >= 6) 
+                        $size++;
+                    break;
+                case QR_MODE_8:
+                    $size = (int)($payload / 8);
+                    break;
+                case QR_MODE_KANJI:
+                    $size = (int)(($payload / 13) * 2);
+                    break;
+                case QR_MODE_STRUCTURE:
+                    $size = (int)($payload / 8);
+                    break;
+                default:
+                    $size = 0;
+                    break;
+            }
+            
+            $maxsize = QRspec::maximumWords($mode, $version);
+            if($size < 0) $size = 0;
+            if($size > $maxsize) $size = $maxsize;
+
+            return $size;
+        }
+        
+        //----------------------------------------------------------------------
+        public function createBitStream()
+        {
+            $total = 0;
+
+            foreach($this->items as $item) {
+                $bits = $item->encodeBitStream($this->version);
+                
+                if($bits < 0) 
+                    return -1;
+                    
+                $total += $bits;
+            }
+
+            return $total;
+        }
+        
+        //----------------------------------------------------------------------
+        public function convertData()
+        {
+            $ver = $this->estimateVersion();
+            if($ver > $this->getVersion()) {
+                $this->setVersion($ver);
+            }
+
+            for(;;) {
+                $bits = $this->createBitStream();
+                
+                if($bits < 0) 
+                    return -1;
+                    
+                $ver = QRspec::getMinimumVersion((int)(($bits + 7) / 8), $this->level);
+                if($ver < 0) {
+                    throw new Exception('WRONG VERSION');
+                } else if($ver > $this->getVersion()) {
+                    $this->setVersion($ver);
+                } else {
+                    break;
+                }
+            }
+
+            return 0;
+        }
+        
+        //----------------------------------------------------------------------
+        public function appendPaddingBit(&$bstream)
+        {
+            $bits = $bstream->size();
+            $maxwords = QRspec::getDataLength($this->version, $this->level);
+            $maxbits = $maxwords * 8;
+
+            if ($maxbits == $bits) {
+                return 0;
+            }
+
+            if ($maxbits - $bits < 5) {
+                return $bstream->appendNum($maxbits - $bits, 0);
+            }
+
+            $bits += 4;
+            $words = (int)(($bits + 7) / 8);
+
+            $padding = new QRbitstream();
+            $ret = $padding->appendNum($words * 8 - $bits + 4, 0);
+            
+            if($ret < 0) 
+                return $ret;
+
+            $padlen = $maxwords - $words;
+            
+            if($padlen > 0) {
+                
+                $padbuf = array();
+                for($i=0; $i<$padlen; $i++) {
+                    $padbuf[$i] = ($i&1)?0x11:0xec;
+                }
+                
+                $ret = $padding->appendBytes($padlen, $padbuf);
+                
+                if($ret < 0)
+                    return $ret;
+                
+            }
+
+            $ret = $bstream->append($padding);
+            
+            return $ret;
+        }
+
+        //----------------------------------------------------------------------
+        public function mergeBitStream()
+        {
+            if($this->convertData() < 0) {
+                return null;
+            }
+
+            $bstream = new QRbitstream();
+            
+            foreach($this->items as $item) {
+                $ret = $bstream->append($item->bstream);
+                if($ret < 0) {
+                    return null;
+                }
+            }
+
+            return $bstream;
+        }
+
+        //----------------------------------------------------------------------
+        public function getBitStream()
+        {
+
+            $bstream = $this->mergeBitStream();
+            
+            if($bstream == null) {
+                return null;
+            }
+            
+            $ret = $this->appendPaddingBit($bstream);
+            if($ret < 0) {
+                return null;
+            }
+
+            return $bstream;
+        }
+        
+        //----------------------------------------------------------------------
+        public function getByteStream()
+        {
+            $bstream = $this->getBitStream();
+            if($bstream == null) {
+                return null;
+            }
+            
+            return $bstream->toByte();
+        }
+    }
+        
+        
+    
+
+
+
+//---- qrbitstream.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Bitstream class
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+     
+    class QRbitstream {
+    
+        public $data = array();
+        
+        //----------------------------------------------------------------------
+        public function size()
+        {
+            return count($this->data);
+        }
+        
+        //----------------------------------------------------------------------
+        public function allocate($setLength)
+        {
+            $this->data = array_fill(0, $setLength, 0);
+            return 0;
+        }
+    
+        //----------------------------------------------------------------------
+        public static function newFromNum($bits, $num)
+        {
+            $bstream = new QRbitstream();
+            $bstream->allocate($bits);
+            
+            $mask = 1 << ($bits - 1);
+            for($i=0; $i<$bits; $i++) {
+                if($num & $mask) {
+                    $bstream->data[$i] = 1;
+                } else {
+                    $bstream->data[$i] = 0;
+                }
+                $mask = $mask >> 1;
+            }
+
+            return $bstream;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function newFromBytes($size, $data)
+        {
+            $bstream = new QRbitstream();
+            $bstream->allocate($size * 8);
+            $p=0;
+
+            for($i=0; $i<$size; $i++) {
+                $mask = 0x80;
+                for($j=0; $j<8; $j++) {
+                    if($data[$i] & $mask) {
+                        $bstream->data[$p] = 1;
+                    } else {
+                        $bstream->data[$p] = 0;
+                    }
+                    $p++;
+                    $mask = $mask >> 1;
+                }
+            }
+
+            return $bstream;
+        }
+        
+        //----------------------------------------------------------------------
+        public function append(QRbitstream $arg)
+        {
+            if (is_null($arg)) {
+                return -1;
+            }
+            
+            if($arg->size() == 0) {
+                return 0;
+            }
+            
+            if($this->size() == 0) {
+                $this->data = $arg->data;
+                return 0;
+            }
+            
+            $this->data = array_values(array_merge($this->data, $arg->data));
+
+            return 0;
+        }
+        
+        //----------------------------------------------------------------------
+        public function appendNum($bits, $num)
+        {
+            if ($bits == 0) 
+                return 0;
+
+            $b = QRbitstream::newFromNum($bits, $num);
+            
+            if(is_null($b))
+                return -1;
+
+            $ret = $this->append($b);
+            unset($b);
+
+            return $ret;
+        }
+
+        //----------------------------------------------------------------------
+        public function appendBytes($size, $data)
+        {
+            if ($size == 0) 
+                return 0;
+
+            $b = QRbitstream::newFromBytes($size, $data);
+            
+            if(is_null($b))
+                return -1;
+
+            $ret = $this->append($b);
+            unset($b);
+
+            return $ret;
+        }
+        
+        //----------------------------------------------------------------------
+        public function toByte()
+        {
+        
+            $size = $this->size();
+
+            if($size == 0) {
+                return array();
+            }
+            
+            $data = array_fill(0, (int)(($size + 7) / 8), 0);
+            $bytes = (int)($size / 8);
+
+            $p = 0;
+            
+            for($i=0; $i<$bytes; $i++) {
+                $v = 0;
+                for($j=0; $j<8; $j++) {
+                    $v = $v << 1;
+                    $v |= $this->data[$p];
+                    $p++;
+                }
+                $data[$i] = $v;
+            }
+            
+            if($size & 7) {
+                $v = 0;
+                for($j=0; $j<($size & 7); $j++) {
+                    $v = $v << 1;
+                    $v |= $this->data[$p];
+                    $p++;
+                }
+                $data[$bytes] = $v;
+            }
+
+            return $data;
+        }
+
+    }
+
+
+
+
+//---- qrsplit.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Input splitting classes
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * The following data / specifications are taken from
+ * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004)
+ *  or
+ * "Automatic identification and data capture techniques --
+ *  QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+    class QRsplit {
+
+        public $dataStr = '';
+        public $input;
+        public $modeHint;
+
+        //----------------------------------------------------------------------
+        public function __construct($dataStr, $input, $modeHint)
+        {
+            $this->dataStr  = $dataStr;
+            $this->input    = $input;
+            $this->modeHint = $modeHint;
+        }
+
+        //----------------------------------------------------------------------
+        public static function isdigitat($str, $pos)
+        {
+            if ($pos >= strlen($str))
+                return false;
+
+            return ((ord($str[$pos]) >= ord('0'))&&(ord($str[$pos]) <= ord('9')));
+        }
+
+        //----------------------------------------------------------------------
+        public static function isalnumat($str, $pos)
+        {
+            if ($pos >= strlen($str))
+                return false;
+
+            return (QRinput::lookAnTable(ord($str[$pos])) >= 0);
+        }
+
+        //----------------------------------------------------------------------
+        public function identifyMode($pos)
+        {
+            if ($pos >= strlen($this->dataStr))
+                return QR_MODE_NUL;
+
+            $c = $this->dataStr[$pos];
+
+            if(self::isdigitat($this->dataStr, $pos)) {
+                return QR_MODE_NUM;
+            } else if(self::isalnumat($this->dataStr, $pos)) {
+                return QR_MODE_AN;
+            } else if($this->modeHint == QR_MODE_KANJI) {
+
+                if ($pos+1 < strlen($this->dataStr))
+                {
+                    $d = $this->dataStr[$pos+1];
+                    $word = (ord($c) << 8) | ord($d);
+                    if(($word >= 0x8140 && $word <= 0x9ffc) || ($word >= 0xe040 && $word <= 0xebbf)) {
+                        return QR_MODE_KANJI;
+                    }
+                }
+            }
+
+            return QR_MODE_8;
+        }
+
+        //----------------------------------------------------------------------
+        public function eatNum()
+        {
+            $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion());
+
+            $p = 0;
+            while(self::isdigitat($this->dataStr, $p)) {
+                $p++;
+            }
+
+            $run = $p;
+            $mode = $this->identifyMode($p);
+
+            if($mode == QR_MODE_8) {
+                $dif = QRinput::estimateBitsModeNum($run) + 4 + $ln
+                     + QRinput::estimateBitsMode8(1)         // + 4 + l8
+                     - QRinput::estimateBitsMode8($run + 1); // - 4 - l8
+                if($dif > 0) {
+                    return $this->eat8();
+                }
+            }
+            if($mode == QR_MODE_AN) {
+                $dif = QRinput::estimateBitsModeNum($run) + 4 + $ln
+                     + QRinput::estimateBitsModeAn(1)        // + 4 + la
+                     - QRinput::estimateBitsModeAn($run + 1);// - 4 - la
+                if($dif > 0) {
+                    return $this->eatAn();
+                }
+            }
+
+            $ret = $this->input->append(QR_MODE_NUM, $run, str_split($this->dataStr));
+            if($ret < 0)
+                return -1;
+
+            return $run;
+        }
+
+        //----------------------------------------------------------------------
+        public function eatAn()
+        {
+            $la = QRspec::lengthIndicator(QR_MODE_AN,  $this->input->getVersion());
+            $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion());
+
+            $p = 0;
+
+            while(self::isalnumat($this->dataStr, $p)) {
+                if(self::isdigitat($this->dataStr, $p)) {
+                    $q = $p;
+                    while(self::isdigitat($this->dataStr, $q)) {
+                        $q++;
+                    }
+
+                    $dif = QRinput::estimateBitsModeAn($p) // + 4 + la
+                         + QRinput::estimateBitsModeNum($q - $p) + 4 + $ln
+                         - QRinput::estimateBitsModeAn($q); // - 4 - la
+
+                    if($dif < 0) {
+                        break;
+                    } else {
+                        $p = $q;
+                    }
+                } else {
+                    $p++;
+                }
+            }
+
+            $run = $p;
+
+            if(!self::isalnumat($this->dataStr, $p)) {
+                $dif = QRinput::estimateBitsModeAn($run) + 4 + $la
+                     + QRinput::estimateBitsMode8(1) // + 4 + l8
+                      - QRinput::estimateBitsMode8($run + 1); // - 4 - l8
+                if($dif > 0) {
+                    return $this->eat8();
+                }
+            }
+
+            $ret = $this->input->append(QR_MODE_AN, $run, str_split($this->dataStr));
+            if($ret < 0)
+                return -1;
+
+            return $run;
+        }
+
+        //----------------------------------------------------------------------
+        public function eatKanji()
+        {
+            $p = 0;
+
+            while($this->identifyMode($p) == QR_MODE_KANJI) {
+                $p += 2;
+            }
+
+            $ret = $this->input->append(QR_MODE_KANJI, $p, str_split($this->dataStr));
+            if($ret < 0)
+                return -1;
+
+            return $ret;
+        }
+
+        //----------------------------------------------------------------------
+        public function eat8()
+        {
+            $la = QRspec::lengthIndicator(QR_MODE_AN, $this->input->getVersion());
+            $ln = QRspec::lengthIndicator(QR_MODE_NUM, $this->input->getVersion());
+
+            $p = 1;
+            $dataStrLen = strlen($this->dataStr);
+
+            while($p < $dataStrLen) {
+
+                $mode = $this->identifyMode($p);
+                if($mode == QR_MODE_KANJI) {
+                    break;
+                }
+                if($mode == QR_MODE_NUM) {
+                    $q = $p;
+                    while(self::isdigitat($this->dataStr, $q)) {
+                        $q++;
+                    }
+                    $dif = QRinput::estimateBitsMode8($p) // + 4 + l8
+                         + QRinput::estimateBitsModeNum($q - $p) + 4 + $ln
+                         - QRinput::estimateBitsMode8($q); // - 4 - l8
+                    if($dif < 0) {
+                        break;
+                    } else {
+                        $p = $q;
+                    }
+                } else if($mode == QR_MODE_AN) {
+                    $q = $p;
+                    while(self::isalnumat($this->dataStr, $q)) {
+                        $q++;
+                    }
+                    $dif = QRinput::estimateBitsMode8($p)  // + 4 + l8
+                         + QRinput::estimateBitsModeAn($q - $p) + 4 + $la
+                         - QRinput::estimateBitsMode8($q); // - 4 - l8
+                    if($dif < 0) {
+                        break;
+                    } else {
+                        $p = $q;
+                    }
+                } else {
+                    $p++;
+                }
+            }
+
+            $run = $p;
+            $ret = $this->input->append(QR_MODE_8, $run, str_split($this->dataStr));
+
+            if($ret < 0)
+                return -1;
+
+            return $run;
+        }
+
+        //----------------------------------------------------------------------
+        public function splitString()
+        {
+            while (strlen($this->dataStr) > 0)
+            {
+                if($this->dataStr == '')
+                    return 0;
+
+                $mode = $this->identifyMode(0);
+
+                switch ($mode) {
+                    case QR_MODE_NUM: $length = $this->eatNum(); break;
+                    case QR_MODE_AN:  $length = $this->eatAn(); break;
+                    case QR_MODE_KANJI:
+                        if ($mode == QR_MODE_KANJI)
+                                $length = $this->eatKanji();
+                        else    $length = $this->eat8();
+                        break;
+                    default: $length = $this->eat8(); break;
+
+                }
+
+                if($length == 0) return 0;
+                if($length < 0)  return -1;
+
+                $this->dataStr = substr($this->dataStr, $length);
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public function toUpper()
+        {
+            $stringLen = strlen($this->dataStr);
+            $p = 0;
+
+            while ($p<$stringLen) {
+                $mode = self::identifyMode(substr($this->dataStr, $p));
+                if($mode == QR_MODE_KANJI) {
+                    $p += 2;
+                } else {
+                    if (ord($this->dataStr[$p]) >= ord('a') && ord($this->dataStr[$p]) <= ord('z')) {
+                        $this->dataStr[$p] = chr(ord($this->dataStr[$p]) - 32);
+                    }
+                    $p++;
+                }
+            }
+
+            return $this->dataStr;
+        }
+
+        //----------------------------------------------------------------------
+        public static function splitStringToQRinput($string, QRinput $input, $modeHint, $casesensitive = true)
+        {
+            if(is_null($string) || $string == '\0' || $string == '') {
+                throw new Exception('empty string!!!');
+            }
+
+            $split = new QRsplit($string, $input, $modeHint);
+
+            if(!$casesensitive)
+                $split->toUpper();
+
+            return $split->splitString();
+        }
+    }
+
+
+
+//---- qrrscode.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Reed-Solomon error correction support
+ * 
+ * Copyright (C) 2002, 2003, 2004, 2006 Phil Karn, KA9Q
+ * (libfec is released under the GNU Lesser General Public License.)
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+    class QRrsItem {
+    
+        public $mm;                  // Bits per symbol 
+        public $nn;                  // Symbols per block (= (1<<mm)-1) 
+        public $alpha_to = array();  // log lookup table 
+        public $index_of = array();  // Antilog lookup table 
+        public $genpoly = array();   // Generator polynomial 
+        public $nroots;              // Number of generator roots = number of parity symbols 
+        public $fcr;                 // First consecutive root, index form 
+        public $prim;                // Primitive element, index form 
+        public $iprim;               // prim-th root of 1, index form 
+        public $pad;                 // Padding bytes in shortened block 
+        public $gfpoly;
+    
+        //----------------------------------------------------------------------
+        public function modnn($x)
+        {
+            while ($x >= $this->nn) {
+                $x -= $this->nn;
+                $x = ($x >> $this->mm) + ($x & $this->nn);
+            }
+            
+            return $x;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad)
+        {
+            // Common code for intializing a Reed-Solomon control block (char or int symbols)
+            // Copyright 2004 Phil Karn, KA9Q
+            // May be used under the terms of the GNU Lesser General Public License (LGPL)
+
+            $rs = null;
+            
+            // Check parameter ranges
+            if($symsize < 0 || $symsize > 8)                     return $rs;
+            if($fcr < 0 || $fcr >= (1<<$symsize))                return $rs;
+            if($prim <= 0 || $prim >= (1<<$symsize))             return $rs;
+            if($nroots < 0 || $nroots >= (1<<$symsize))          return $rs; // Can't have more roots than symbol values!
+            if($pad < 0 || $pad >= ((1<<$symsize) -1 - $nroots)) return $rs; // Too much padding
+
+            $rs = new QRrsItem();
+            $rs->mm = $symsize;
+            $rs->nn = (1<<$symsize)-1;
+            $rs->pad = $pad;
+
+            $rs->alpha_to = array_fill(0, $rs->nn+1, 0);
+            $rs->index_of = array_fill(0, $rs->nn+1, 0);
+          
+            // PHP style macro replacement ;)
+            $NN =& $rs->nn;
+            $A0 =& $NN;
+            
+            // Generate Galois field lookup tables
+            $rs->index_of[0] = $A0; // log(zero) = -inf
+            $rs->alpha_to[$A0] = 0; // alpha**-inf = 0
+            $sr = 1;
+          
+            for($i=0; $i<$rs->nn; $i++) {
+                $rs->index_of[$sr] = $i;
+                $rs->alpha_to[$i] = $sr;
+                $sr <<= 1;
+                if($sr & (1<<$symsize)) {
+                    $sr ^= $gfpoly;
+                }
+                $sr &= $rs->nn;
+            }
+            
+            if($sr != 1){
+                // field generator polynomial is not primitive!
+                $rs = NULL;
+                return $rs;
+            }
+
+            /* Form RS code generator polynomial from its roots */
+            $rs->genpoly = array_fill(0, $nroots+1, 0);
+        
+            $rs->fcr = $fcr;
+            $rs->prim = $prim;
+            $rs->nroots = $nroots;
+            $rs->gfpoly = $gfpoly;
+
+            /* Find prim-th root of 1, used in decoding */
+            for($iprim=1;($iprim % $prim) != 0;$iprim += $rs->nn)
+            ; // intentional empty-body loop!
+            
+            $rs->iprim = (int)($iprim / $prim);
+            $rs->genpoly[0] = 1;
+            
+            for ($i = 0,$root=$fcr*$prim; $i < $nroots; $i++, $root += $prim) {
+                $rs->genpoly[$i+1] = 1;
+
+                // Multiply rs->genpoly[] by  @**(root + x)
+                for ($j = $i; $j > 0; $j--) {
+                    if ($rs->genpoly[$j] != 0) {
+                        $rs->genpoly[$j] = $rs->genpoly[$j-1] ^ $rs->alpha_to[$rs->modnn($rs->index_of[$rs->genpoly[$j]] + $root)];
+                    } else {
+                        $rs->genpoly[$j] = $rs->genpoly[$j-1];
+                    }
+                }
+                // rs->genpoly[0] can never be zero
+                $rs->genpoly[0] = $rs->alpha_to[$rs->modnn($rs->index_of[$rs->genpoly[0]] + $root)];
+            }
+            
+            // convert rs->genpoly[] to index form for quicker encoding
+            for ($i = 0; $i <= $nroots; $i++)
+                $rs->genpoly[$i] = $rs->index_of[$rs->genpoly[$i]];
+
+            return $rs;
+        }
+        
+        //----------------------------------------------------------------------
+        public function encode_rs_char($data, &$parity)
+        {
+            $MM       =& $this->mm;
+            $NN       =& $this->nn;
+            $ALPHA_TO =& $this->alpha_to;
+            $INDEX_OF =& $this->index_of;
+            $GENPOLY  =& $this->genpoly;
+            $NROOTS   =& $this->nroots;
+            $FCR      =& $this->fcr;
+            $PRIM     =& $this->prim;
+            $IPRIM    =& $this->iprim;
+            $PAD      =& $this->pad;
+            $A0       =& $NN;
+
+            $parity = array_fill(0, $NROOTS, 0);
+
+            for($i=0; $i< ($NN-$NROOTS-$PAD); $i++) {
+                
+                $feedback = $INDEX_OF[$data[$i] ^ $parity[0]];
+                if($feedback != $A0) {      
+                    // feedback term is non-zero
+            
+                    // This line is unnecessary when GENPOLY[NROOTS] is unity, as it must
+                    // always be for the polynomials constructed by init_rs()
+                    $feedback = $this->modnn($NN - $GENPOLY[$NROOTS] + $feedback);
+            
+                    for($j=1;$j<$NROOTS;$j++) {
+                        $parity[$j] ^= $ALPHA_TO[$this->modnn($feedback + $GENPOLY[$NROOTS-$j])];
+                    }
+                }
+                
+                // Shift 
+                array_shift($parity);
+                if($feedback != $A0) {
+                    array_push($parity, $ALPHA_TO[$this->modnn($feedback + $GENPOLY[0])]);
+                } else {
+                    array_push($parity, 0);
+                }
+            }
+        }
+    }
+    
+    //##########################################################################
+    
+    class QRrs {
+    
+        public static $items = array();
+        
+        //----------------------------------------------------------------------
+        public static function init_rs($symsize, $gfpoly, $fcr, $prim, $nroots, $pad)
+        {
+            foreach(self::$items as $rs) {
+                if($rs->pad != $pad)       continue;
+                if($rs->nroots != $nroots) continue;
+                if($rs->mm != $symsize)    continue;
+                if($rs->gfpoly != $gfpoly) continue;
+                if($rs->fcr != $fcr)       continue;
+                if($rs->prim != $prim)     continue;
+
+                return $rs;
+            }
+
+            $rs = QRrsItem::init_rs_char($symsize, $gfpoly, $fcr, $prim, $nroots, $pad);
+            array_unshift(self::$items, $rs);
+
+            return $rs;
+        }
+    }
+
+
+
+//---- qrmask.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Masking
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+	define('N1', 3);
+	define('N2', 3);
+	define('N3', 40);
+	define('N4', 10);
+
+	class QRmask {
+
+		public $runLength = array();
+
+		//----------------------------------------------------------------------
+		public function __construct()
+        {
+            $this->runLength = array_fill(0, QRSPEC_WIDTH_MAX + 1, 0);
+        }
+
+        //----------------------------------------------------------------------
+        public function writeFormatInformation($width, &$frame, $mask, $level)
+        {
+            $blacks = 0;
+            $format =  QRspec::getFormatInfo($mask, $level);
+
+            for($i=0; $i<8; $i++) {
+                if($format & 1) {
+                    $blacks += 2;
+                    $v = 0x85;
+                } else {
+                    $v = 0x84;
+                }
+
+                $frame[8][$width - 1 - $i] = chr($v);
+                if($i < 6) {
+                    $frame[$i][8] = chr($v);
+                } else {
+                    $frame[$i + 1][8] = chr($v);
+                }
+                $format = $format >> 1;
+            }
+
+            for($i=0; $i<7; $i++) {
+                if($format & 1) {
+                    $blacks += 2;
+                    $v = 0x85;
+                } else {
+                    $v = 0x84;
+                }
+
+                $frame[$width - 7 + $i][8] = chr($v);
+                if($i == 0) {
+                    $frame[8][7] = chr($v);
+                } else {
+                    $frame[8][6 - $i] = chr($v);
+                }
+
+                $format = $format >> 1;
+            }
+
+            return $blacks;
+        }
+
+        //----------------------------------------------------------------------
+        public function mask0($x, $y) { return ($x+$y)&1;                       }
+        public function mask1($x, $y) { return ($y&1);                          }
+        public function mask2($x, $y) { return ($x%3);                          }
+        public function mask3($x, $y) { return ($x+$y)%3;                       }
+        public function mask4($x, $y) { return (((int)($y/2))+((int)($x/3)))&1; }
+        public function mask5($x, $y) { return (($x*$y)&1)+($x*$y)%3;           }
+        public function mask6($x, $y) { return ((($x*$y)&1)+($x*$y)%3)&1;       }
+        public function mask7($x, $y) { return ((($x*$y)%3)+(($x+$y)&1))&1;     }
+
+        //----------------------------------------------------------------------
+        private function generateMaskNo($maskNo, $width, $frame)
+        {
+            $bitMask = array_fill(0, $width, array_fill(0, $width, 0));
+
+            for($y=0; $y<$width; $y++) {
+                for($x=0; $x<$width; $x++) {
+                    if(ord($frame[$y][$x]) & 0x80) {
+                        $bitMask[$y][$x] = 0;
+                    } else {
+                        $maskFunc = call_user_func(array($this, 'mask'.$maskNo), $x, $y);
+                        $bitMask[$y][$x] = ($maskFunc == 0)?1:0;
+                    }
+
+                }
+            }
+
+            return $bitMask;
+        }
+
+        //----------------------------------------------------------------------
+        public static function serial($bitFrame)
+        {
+            $codeArr = array();
+
+            foreach ($bitFrame as $line)
+                $codeArr[] = join('', $line);
+
+            return gzcompress(join("\n", $codeArr), 9);
+        }
+
+        //----------------------------------------------------------------------
+        public static function unserial($code)
+        {
+            $codeArr = array();
+
+            $codeLines = explode("\n", gzuncompress($code));
+            foreach ($codeLines as $line)
+                $codeArr[] = str_split($line);
+
+            return $codeArr;
+        }
+
+        //----------------------------------------------------------------------
+        public function makeMaskNo($maskNo, $width, $s, &$d, $maskGenOnly = false)
+        {
+            $b = 0;
+            $bitMask = array();
+
+            $fileName = QR_CACHE_DIR.'mask_'.$maskNo.DIRECTORY_SEPARATOR.'mask_'.$width.'_'.$maskNo.'.dat';
+
+            if (QR_CACHEABLE) {
+                if (file_exists($fileName)) {
+                    $bitMask = self::unserial(file_get_contents($fileName));
+                } else {
+                    $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d);
+                    if (!file_exists(QR_CACHE_DIR.'mask_'.$maskNo))
+                        mkdir(QR_CACHE_DIR.'mask_'.$maskNo);
+                    file_put_contents($fileName, self::serial($bitMask));
+                }
+            } else {
+                $bitMask = $this->generateMaskNo($maskNo, $width, $s, $d);
+            }
+
+            if ($maskGenOnly)
+                return;
+
+            $d = $s;
+
+            for($y=0; $y<$width; $y++) {
+                for($x=0; $x<$width; $x++) {
+                    if($bitMask[$y][$x] == 1) {
+                        $d[$y][$x] = chr(ord($s[$y][$x]) ^ (int)$bitMask[$y][$x]);
+                    }
+                    $b += (int)(ord($d[$y][$x]) & 1);
+                }
+            }
+
+            return $b;
+        }
+
+        //----------------------------------------------------------------------
+        public function makeMask($width, $frame, $maskNo, $level)
+        {
+            $masked = array_fill(0, $width, str_repeat("\0", $width));
+            $this->makeMaskNo($maskNo, $width, $frame, $masked);
+            $this->writeFormatInformation($width, $masked, $maskNo, $level);
+
+            return $masked;
+        }
+
+        //----------------------------------------------------------------------
+        public function calcN1N3($length)
+        {
+            $demerit = 0;
+
+            for($i=0; $i<$length; $i++) {
+
+                if($this->runLength[$i] >= 5) {
+                    $demerit += (N1 + ($this->runLength[$i] - 5));
+                }
+                if($i & 1) {
+                    if(($i >= 3) && ($i < ($length-2)) && ($this->runLength[$i] % 3 == 0)) {
+                        $fact = (int)($this->runLength[$i] / 3);
+                        if(($this->runLength[$i-2] == $fact) &&
+                           ($this->runLength[$i-1] == $fact) &&
+                           ($this->runLength[$i+1] == $fact) &&
+                           ($this->runLength[$i+2] == $fact)) {
+                            if(($this->runLength[$i-3] < 0) || ($this->runLength[$i-3] >= (4 * $fact))) {
+                                $demerit += N3;
+                            } else if((($i+3) >= $length) || ($this->runLength[$i+3] >= (4 * $fact))) {
+                                $demerit += N3;
+                            }
+                        }
+                    }
+                }
+            }
+            return $demerit;
+        }
+
+        //----------------------------------------------------------------------
+        public function evaluateSymbol($width, $frame)
+        {
+            $head = 0;
+            $demerit = 0;
+
+            for($y=0; $y<$width; $y++) {
+                $head = 0;
+                $this->runLength[0] = 1;
+
+                $frameY = $frame[$y];
+
+                if ($y>0)
+                    $frameYM = $frame[$y-1];
+
+                for($x=0; $x<$width; $x++) {
+                    if(($x > 0) && ($y > 0)) {
+                        $b22 = ord($frameY[$x]) & ord($frameY[$x-1]) & ord($frameYM[$x]) & ord($frameYM[$x-1]);
+                        $w22 = ord($frameY[$x]) | ord($frameY[$x-1]) | ord($frameYM[$x]) | ord($frameYM[$x-1]);
+
+                        if(($b22 | ($w22 ^ 1))&1) {
+                            $demerit += N2;
+                        }
+                    }
+                    if(($x == 0) && (ord($frameY[$x]) & 1)) {
+                        $this->runLength[0] = -1;
+                        $head = 1;
+                        $this->runLength[$head] = 1;
+                    } else if($x > 0) {
+                        if((ord($frameY[$x]) ^ ord($frameY[$x-1])) & 1) {
+                            $head++;
+                            $this->runLength[$head] = 1;
+                        } else {
+                            $this->runLength[$head]++;
+                        }
+                    }
+                }
+
+                $demerit += $this->calcN1N3($head+1);
+            }
+
+            for($x=0; $x<$width; $x++) {
+                $head = 0;
+                $this->runLength[0] = 1;
+
+                for($y=0; $y<$width; $y++) {
+                    if($y == 0 && (ord($frame[$y][$x]) & 1)) {
+                        $this->runLength[0] = -1;
+                        $head = 1;
+                        $this->runLength[$head] = 1;
+                    } else if($y > 0) {
+                        if((ord($frame[$y][$x]) ^ ord($frame[$y-1][$x])) & 1) {
+                            $head++;
+                            $this->runLength[$head] = 1;
+                        } else {
+                            $this->runLength[$head]++;
+                        }
+                    }
+                }
+
+                $demerit += $this->calcN1N3($head+1);
+            }
+
+            return $demerit;
+        }
+
+
+        //----------------------------------------------------------------------
+        public function mask($width, $frame, $level)
+        {
+            $minDemerit = PHP_INT_MAX;
+            $bestMaskNum = 0;
+            $bestMask = array();
+
+            $checked_masks = array(0,1,2,3,4,5,6,7);
+
+            if (QR_FIND_FROM_RANDOM !== false) {
+
+                $howManuOut = 8-(QR_FIND_FROM_RANDOM % 9);
+                for ($i = 0; $i <  $howManuOut; $i++) {
+                    $remPos = rand (0, count($checked_masks)-1);
+                    unset($checked_masks[$remPos]);
+                    $checked_masks = array_values($checked_masks);
+                }
+
+            }
+
+            $bestMask = $frame;
+
+            foreach($checked_masks as $i) {
+                $mask = array_fill(0, $width, str_repeat("\0", $width));
+
+                $demerit = 0;
+                $blacks = 0;
+                $blacks  = $this->makeMaskNo($i, $width, $frame, $mask);
+                $blacks += $this->writeFormatInformation($width, $mask, $i, $level);
+                $blacks  = (int)(100 * $blacks / ($width * $width));
+                $demerit = (int)((int)(abs($blacks - 50) / 5) * N4);
+                $demerit += $this->evaluateSymbol($width, $mask);
+
+                if($demerit < $minDemerit) {
+                    $minDemerit = $demerit;
+                    $bestMask = $mask;
+                    $bestMaskNum = $i;
+                }
+            }
+
+            return $bestMask;
+        }
+
+        //----------------------------------------------------------------------
+    }
+
+
+
+
+//---- qrencode.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Main encoder classes.
+ *
+ * Based on libqrencode C library distributed under LGPL 2.1
+ * Copyright (C) 2006, 2007, 2008, 2009 Kentaro Fukuchi <fukuchi@megaui.net>
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+    class QRrsblock {
+        public $dataLength;
+        public $data = array();
+        public $eccLength;
+        public $ecc = array();
+        
+        public function __construct($dl, $data, $el, &$ecc, QRrsItem $rs)
+        {
+            $rs->encode_rs_char($data, $ecc);
+        
+            $this->dataLength = $dl;
+            $this->data = $data;
+            $this->eccLength = $el;
+            $this->ecc = $ecc;
+        }
+    };
+    
+    //##########################################################################
+
+    class QRrawcode {
+        public $version;
+        public $datacode = array();
+        public $ecccode = array();
+        public $blocks;
+        public $rsblocks = array(); //of RSblock
+        public $count;
+        public $dataLength;
+        public $eccLength;
+        public $b1;
+        
+        //----------------------------------------------------------------------
+        public function __construct(QRinput $input)
+        {
+            $spec = array(0,0,0,0,0);
+            
+            $this->datacode = $input->getByteStream();
+            if(is_null($this->datacode)) {
+                throw new Exception('null imput string');
+            }
+
+            QRspec::getEccSpec($input->getVersion(), $input->getErrorCorrectionLevel(), $spec);
+
+            $this->version = $input->getVersion();
+            $this->b1 = QRspec::rsBlockNum1($spec);
+            $this->dataLength = QRspec::rsDataLength($spec);
+            $this->eccLength = QRspec::rsEccLength($spec);
+            $this->ecccode = array_fill(0, $this->eccLength, 0);
+            $this->blocks = QRspec::rsBlockNum($spec);
+            
+            $ret = $this->init($spec);
+            if($ret < 0) {
+                throw new Exception('block alloc error');
+                return null;
+            }
+
+            $this->count = 0;
+        }
+        
+        //----------------------------------------------------------------------
+        public function init(array $spec)
+        {
+            $dl = QRspec::rsDataCodes1($spec);
+            $el = QRspec::rsEccCodes1($spec);
+            $rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el);
+            
+
+            $blockNo = 0;
+            $dataPos = 0;
+            $eccPos = 0;
+            for($i=0; $i<QRspec::rsBlockNum1($spec); $i++) {
+                $ecc = array_slice($this->ecccode,$eccPos);
+                $this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el,  $ecc, $rs);
+                $this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc);
+                
+                $dataPos += $dl;
+                $eccPos += $el;
+                $blockNo++;
+            }
+
+            if(QRspec::rsBlockNum2($spec) == 0)
+                return 0;
+
+            $dl = QRspec::rsDataCodes2($spec);
+            $el = QRspec::rsEccCodes2($spec);
+            $rs = QRrs::init_rs(8, 0x11d, 0, 1, $el, 255 - $dl - $el);
+            
+            if($rs == NULL) return -1;
+            
+            for($i=0; $i<QRspec::rsBlockNum2($spec); $i++) {
+                $ecc = array_slice($this->ecccode,$eccPos);
+                $this->rsblocks[$blockNo] = new QRrsblock($dl, array_slice($this->datacode, $dataPos), $el, $ecc, $rs);
+                $this->ecccode = array_merge(array_slice($this->ecccode,0, $eccPos), $ecc);
+                
+                $dataPos += $dl;
+                $eccPos += $el;
+                $blockNo++;
+            }
+
+            return 0;
+        }
+        
+        //----------------------------------------------------------------------
+        public function getCode()
+        {
+            $ret;
+
+            if($this->count < $this->dataLength) {
+                $row = $this->count % $this->blocks;
+                $col = $this->count / $this->blocks;
+                if($col >= $this->rsblocks[0]->dataLength) {
+                    $row += $this->b1;
+                }
+                $ret = $this->rsblocks[$row]->data[$col];
+            } else if($this->count < $this->dataLength + $this->eccLength) {
+                $row = ($this->count - $this->dataLength) % $this->blocks;
+                $col = ($this->count - $this->dataLength) / $this->blocks;
+                $ret = $this->rsblocks[$row]->ecc[$col];
+            } else {
+                return 0;
+            }
+            $this->count++;
+            
+            return $ret;
+        }
+    }
+
+    //##########################################################################
+    
+    class QRcode {
+    
+        public $version;
+        public $width;
+        public $data; 
+        
+        //----------------------------------------------------------------------
+        public function encodeMask(QRinput $input, $mask)
+        {
+            if($input->getVersion() < 0 || $input->getVersion() > QRSPEC_VERSION_MAX) {
+                throw new Exception('wrong version');
+            }
+            if($input->getErrorCorrectionLevel() > QR_ECLEVEL_H) {
+                throw new Exception('wrong level');
+            }
+
+            $raw = new QRrawcode($input);
+            
+            QRtools::markTime('after_raw');
+            
+            $version = $raw->version;
+            $width = QRspec::getWidth($version);
+            $frame = QRspec::newFrame($version);
+            
+            $filler = new FrameFiller($width, $frame);
+            if(is_null($filler)) {
+                return NULL;
+            }
+
+            // inteleaved data and ecc codes
+            for($i=0; $i<$raw->dataLength + $raw->eccLength; $i++) {
+                $code = $raw->getCode();
+                $bit = 0x80;
+                for($j=0; $j<8; $j++) {
+                    $addr = $filler->next();
+                    $filler->setFrameAt($addr, 0x02 | (($bit & $code) != 0));
+                    $bit = $bit >> 1;
+                }
+            }
+            
+            QRtools::markTime('after_filler');
+            
+            unset($raw);
+            
+            // remainder bits
+            $j = QRspec::getRemainder($version);
+            for($i=0; $i<$j; $i++) {
+                $addr = $filler->next();
+                $filler->setFrameAt($addr, 0x02);
+            }
+            
+            $frame = $filler->frame;
+            unset($filler);
+            
+            
+            // masking
+            $maskObj = new QRmask();
+            if($mask < 0) {
+            
+                if (QR_FIND_BEST_MASK) {
+                    $masked = $maskObj->mask($width, $frame, $input->getErrorCorrectionLevel());
+                } else {
+                    $masked = $maskObj->makeMask($width, $frame, (intval(QR_DEFAULT_MASK) % 8), $input->getErrorCorrectionLevel());
+                }
+            } else {
+                $masked = $maskObj->makeMask($width, $frame, $mask, $input->getErrorCorrectionLevel());
+            }
+            
+            if($masked == NULL) {
+                return NULL;
+            }
+            
+            QRtools::markTime('after_mask');
+            
+            $this->version = $version;
+            $this->width = $width;
+            $this->data = $masked;
+            
+            return $this;
+        }
+    
+        //----------------------------------------------------------------------
+        public function encodeInput(QRinput $input)
+        {
+            return $this->encodeMask($input, -1);
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeString8bit($string, $version, $level)
+        {
+            if($string == NULL) {
+                throw new Exception('empty string!');
+                return NULL;
+            }
+
+            $input = new QRinput($version, $level);
+            if($input == NULL) return NULL;
+
+            $ret = $input->append($input, QR_MODE_8, strlen($string), str_split($string));
+            if($ret < 0) {
+                unset($input);
+                return NULL;
+            }
+            return $this->encodeInput($input);
+        }
+
+        //----------------------------------------------------------------------
+        public function encodeString($string, $version, $level, $hint, $casesensitive)
+        {
+
+            if($hint != QR_MODE_8 && $hint != QR_MODE_KANJI) {
+                throw new Exception('bad hint');
+                return NULL;
+            }
+
+            $input = new QRinput($version, $level);
+            if($input == NULL) return NULL;
+
+            $ret = QRsplit::splitStringToQRinput($string, $input, $hint, $casesensitive);
+            if($ret < 0) {
+                return NULL;
+            }
+
+            return $this->encodeInput($input);
+        }
+        
+        //----------------------------------------------------------------------
+        public static function png($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false, $back_color = 0xFFFFFF, $fore_color = 0x000000) 
+        {
+            $enc = QRencode::factory($level, $size, $margin, $back_color, $fore_color);
+            return $enc->encodePNG($text, $outfile, $saveandprint=false);
+        }
+
+        //----------------------------------------------------------------------
+        public static function text($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) 
+        {
+            $enc = QRencode::factory($level, $size, $margin);
+            return $enc->encode($text, $outfile);
+        }
+        
+        //----------------------------------------------------------------------
+        public static function eps($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false, $back_color = 0xFFFFFF, $fore_color = 0x000000, $cmyk = false) 
+        {
+            $enc = QRencode::factory($level, $size, $margin, $back_color, $fore_color, $cmyk);
+            return $enc->encodeEPS($text, $outfile, $saveandprint=false);
+        }
+        
+        //----------------------------------------------------------------------
+        public static function svg($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4, $saveandprint=false, $back_color = 0xFFFFFF, $fore_color = 0x000000)
+        {
+            $enc = QRencode::factory($level, $size, $margin, $back_color, $fore_color);
+            return $enc->encodeSVG($text, $outfile, $saveandprint=false);
+        }
+
+        //----------------------------------------------------------------------
+        public static function raw($text, $outfile = false, $level = QR_ECLEVEL_L, $size = 3, $margin = 4) 
+        {
+            $enc = QRencode::factory($level, $size, $margin);
+            return $enc->encodeRAW($text, $outfile);
+        }
+    }
+    
+    //##########################################################################
+    
+    class FrameFiller {
+    
+        public $width;
+        public $frame;
+        public $x;
+        public $y;
+        public $dir;
+        public $bit;
+        
+        //----------------------------------------------------------------------
+        public function __construct($width, &$frame)
+        {
+            $this->width = $width;
+            $this->frame = $frame;
+            $this->x = $width - 1;
+            $this->y = $width - 1;
+            $this->dir = -1;
+            $this->bit = -1;
+        }
+        
+        //----------------------------------------------------------------------
+        public function setFrameAt($at, $val)
+        {
+            $this->frame[$at['y']][$at['x']] = chr($val);
+        }
+        
+        //----------------------------------------------------------------------
+        public function getFrameAt($at)
+        {
+            return ord($this->frame[$at['y']][$at['x']]);
+        }
+        
+        //----------------------------------------------------------------------
+        public function next()
+        {
+            do {
+            
+                if($this->bit == -1) {
+                    $this->bit = 0;
+                    return array('x'=>$this->x, 'y'=>$this->y);
+                }
+
+                $x = $this->x;
+                $y = $this->y;
+                $w = $this->width;
+
+                if($this->bit == 0) {
+                    $x--;
+                    $this->bit++;
+                } else {
+                    $x++;
+                    $y += $this->dir;
+                    $this->bit--;
+                }
+
+                if($this->dir < 0) {
+                    if($y < 0) {
+                        $y = 0;
+                        $x -= 2;
+                        $this->dir = 1;
+                        if($x == 6) {
+                            $x--;
+                            $y = 9;
+                        }
+                    }
+                } else {
+                    if($y == $w) {
+                        $y = $w - 1;
+                        $x -= 2;
+                        $this->dir = -1;
+                        if($x == 6) {
+                            $x--;
+                            $y -= 8;
+                        }
+                    }
+                }
+                if($x < 0 || $y < 0) return null;
+
+                $this->x = $x;
+                $this->y = $y;
+
+            } while(ord($this->frame[$y][$x]) & 0x80);
+                        
+            return array('x'=>$x, 'y'=>$y);
+        }
+        
+    } ;
+    
+    //##########################################################################    
+    
+    class QRencode {
+    
+        public $casesensitive = true;
+        public $eightbit = false;
+        
+        public $version = 0;
+        public $size = 3;
+        public $margin = 4;
+        public $back_color = 0xFFFFFF;
+        public $fore_color = 0x000000;
+        
+        public $structured = 0; // not supported yet
+        
+        public $level = QR_ECLEVEL_L;
+        public $hint = QR_MODE_8;
+        
+        //----------------------------------------------------------------------
+        public static function factory($level = QR_ECLEVEL_L, $size = 3, $margin = 4, $back_color = 0xFFFFFF, $fore_color = 0x000000, $cmyk = false)
+        {
+            $enc = new QRencode();
+            $enc->size = $size;
+            $enc->margin = $margin;
+            $enc->fore_color = $fore_color;
+            $enc->back_color = $back_color;
+            $enc->cmyk = $cmyk;
+            
+            switch ($level.'') {
+                case '0':
+                case '1':
+                case '2':
+                case '3':
+                        $enc->level = $level;
+                    break;
+                case 'l':
+                case 'L':
+                        $enc->level = QR_ECLEVEL_L;
+                    break;
+                case 'm':
+                case 'M':
+                        $enc->level = QR_ECLEVEL_M;
+                    break;
+                case 'q':
+                case 'Q':
+                        $enc->level = QR_ECLEVEL_Q;
+                    break;
+                case 'h':
+                case 'H':
+                        $enc->level = QR_ECLEVEL_H;
+                    break;
+            }
+            
+            return $enc;
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeRAW($intext, $outfile = false) 
+        {
+            $code = new QRcode();
+
+            if($this->eightbit) {
+                $code->encodeString8bit($intext, $this->version, $this->level);
+            } else {
+                $code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive);
+            }
+            
+            return $code->data;
+        }
+
+        //----------------------------------------------------------------------
+        public function encode($intext, $outfile = false) 
+        {
+            $code = new QRcode();
+
+            if($this->eightbit) {
+                $code->encodeString8bit($intext, $this->version, $this->level);
+            } else {
+                $code->encodeString($intext, $this->version, $this->level, $this->hint, $this->casesensitive);
+            }
+            
+            QRtools::markTime('after_encode');
+            
+            if ($outfile!== false) {
+                file_put_contents($outfile, join("\n", QRtools::binarize($code->data)));
+            } else {
+                return QRtools::binarize($code->data);
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodePNG($intext, $outfile = false,$saveandprint=false) 
+        {
+            try {
+            
+                ob_start();
+                $tab = $this->encode($intext);
+                $err = ob_get_contents();
+                ob_end_clean();
+                
+                if ($err != '')
+                    QRtools::log($outfile, $err);
+                
+                $maxSize = (int)(QR_PNG_MAXIMUM_SIZE / (count($tab)+2*$this->margin));
+                
+                QRimage::png($tab, $outfile, min(max(1, $this->size), $maxSize), $this->margin,$saveandprint, $this->back_color, $this->fore_color);
+            
+            } catch (Exception $e) {
+            
+                QRtools::log($outfile, $e->getMessage());
+            
+            }
+        }
+        
+        //----------------------------------------------------------------------
+        public function encodeEPS($intext, $outfile = false,$saveandprint=false) 
+        {
+            try {
+            
+                ob_start();
+                $tab = $this->encode($intext);
+                $err = ob_get_contents();
+                ob_end_clean();
+                
+                if ($err != '')
+                    QRtools::log($outfile, $err);
+                
+                $maxSize = (int)(QR_PNG_MAXIMUM_SIZE / (count($tab)+2*$this->margin));
+                
+                QRvect::eps($tab, $outfile, min(max(1, $this->size), $maxSize), $this->margin,$saveandprint, $this->back_color, $this->fore_color, $this->cmyk);
+            
+            } catch (Exception $e) {
+            
+                QRtools::log($outfile, $e->getMessage());
+            
+            }
+        }
+
+        //----------------------------------------------------------------------
+        public function encodeSVG($intext, $outfile = false,$saveandprint=false) 
+        {
+            try {
+            
+                ob_start();
+                $tab = $this->encode($intext);
+                $err = ob_get_contents();
+                ob_end_clean();
+                
+                if ($err != '')
+                    QRtools::log($outfile, $err);
+                
+                $maxSize = (int)(QR_PNG_MAXIMUM_SIZE / (count($tab)+2*$this->margin));
+
+                QRvect::svg($tab, $outfile, min(max(1, $this->size), $maxSize), $this->margin,$saveandprint, $this->back_color, $this->fore_color);
+            
+            } catch (Exception $e) {
+            
+                QRtools::log($outfile, $e->getMessage());
+            
+            }
+        }
+    }
+
+
+
+
+//---- qrvect.php -----------------------------
+
+
+
+
+/*
+ * PHP QR Code encoder
+ *
+ * Image output of code using GD2
+ *
+ * PHP QR Code is distributed under LGPL 3
+ * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+ 
+    define('QR_VECT', true);
+
+    class QRvect {
+    
+        //----------------------------------------------------------------------
+        public static function eps($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE, $back_color = 0xFFFFFF, $fore_color = 0x000000, $cmyk = false) 
+        {
+            $vect = self::vectEPS($frame, $pixelPerPoint, $outerFrame, $back_color, $fore_color, $cmyk);
+            
+            if ($filename === false) {
+                header("Content-Type: application/postscript");
+                header('Content-Disposition: filename="qrcode.eps"');
+                echo $vect;
+            } else {
+                if($saveandprint===TRUE){
+                    QRtools::save($vect, $filename);
+                    header("Content-Type: application/postscript");
+                    header('Content-Disposition: filename="qrcode.eps"');
+                    echo $vect;
+                }else{
+                    QRtools::save($vect, $filename);
+                }
+            }
+        }
+        
+    
+        //----------------------------------------------------------------------
+        private static function vectEPS($frame, $pixelPerPoint = 4, $outerFrame = 4, $back_color = 0xFFFFFF, $fore_color = 0x000000, $cmyk = false) 
+        {
+            $h = count($frame);
+            $w = strlen($frame[0]);
+            
+            $imgW = $w + 2*$outerFrame;
+            $imgH = $h + 2*$outerFrame;
+            
+            if ($cmyk)
+            {
+                // convert color value into decimal eps format
+                $c = round((($fore_color & 0xFF000000) >> 16) / 255, 5);
+                $m = round((($fore_color & 0x00FF0000) >> 16) / 255, 5);
+                $y = round((($fore_color & 0x0000FF00) >> 8) / 255, 5);
+                $k = round(($fore_color & 0x000000FF) / 255, 5);
+                $fore_color_string = $c.' '.$m.' '.$y.' '.$k.' setcmykcolor'."\n";
+
+                // convert color value into decimal eps format
+                $c = round((($back_color & 0xFF000000) >> 16) / 255, 5);
+                $m = round((($back_color & 0x00FF0000) >> 16) / 255, 5);
+                $y = round((($back_color & 0x0000FF00) >> 8) / 255, 5);
+                $k = round(($back_color & 0x000000FF) / 255, 5);
+                $back_color_string = $c.' '.$m.' '.$y.' '.$k.' setcmykcolor'."\n";
+            }
+            else
+            {
+                // convert a hexadecimal color code into decimal eps format (green = 0 1 0, blue = 0 0 1, ...)
+                $r = round((($fore_color & 0xFF0000) >> 16) / 255, 5);
+                $b = round((($fore_color & 0x00FF00) >> 8) / 255, 5);
+                $g = round(($fore_color & 0x0000FF) / 255, 5);
+                $fore_color_string = $r.' '.$b.' '.$g.' setrgbcolor'."\n";
+
+                // convert a hexadecimal color code into decimal eps format (green = 0 1 0, blue = 0 0 1, ...)
+                $r = round((($back_color & 0xFF0000) >> 16) / 255, 5);
+                $b = round((($back_color & 0x00FF00) >> 8) / 255, 5);
+                $g = round(($back_color & 0x0000FF) / 255, 5);
+                $back_color_string = $r.' '.$b.' '.$g.' setrgbcolor'."\n";
+            }
+            
+            $output = 
+            '%!PS-Adobe EPSF-3.0'."\n".
+            '%%Creator: PHPQrcodeLib'."\n".
+            '%%Title: QRcode'."\n".
+            '%%CreationDate: '.date('Y-m-d')."\n".
+            '%%DocumentData: Clean7Bit'."\n".
+            '%%LanguageLevel: 2'."\n".
+            '%%Pages: 1'."\n".
+            '%%BoundingBox: 0 0 '.$imgW * $pixelPerPoint.' '.$imgH * $pixelPerPoint."\n";
+            
+            // set the scale
+            $output .= $pixelPerPoint.' '.$pixelPerPoint.' scale'."\n";
+            // position the center of the coordinate system
+            
+            $output .= $outerFrame.' '.$outerFrame.' translate'."\n";
+           
+           
+            
+            
+            // redefine the 'rectfill' operator to shorten the syntax
+            $output .= '/F { rectfill } def'."\n";
+            
+            // set the symbol color
+            $output .= $back_color_string;
+            $output .= '-'.$outerFrame.' -'.$outerFrame.' '.($w + 2*$outerFrame).' '.($h + 2*$outerFrame).' F'."\n";
+            
+            
+            // set the symbol color
+            $output .= $fore_color_string;
+
+            // Convert the matrix into pixels
+
+            for($i=0; $i<$h; $i++) {
+                for($j=0; $j<$w; $j++) {
+                    if( $frame[$i][$j] == '1') {
+                        $y = $h - 1 - $i;
+                        $x = $j;
+                        $output .= $x.' '.$y.' 1 1 F'."\n";
+                    }
+                }
+            }
+            
+            
+            $output .='%%EOF';
+            
+            return $output;
+        }
+        
+        //----------------------------------------------------------------------
+        public static function svg($frame, $filename = false, $pixelPerPoint = 4, $outerFrame = 4,$saveandprint=FALSE, $back_color, $fore_color) 
+        {
+            $vect = self::vectSVG($frame, $pixelPerPoint, $outerFrame, $back_color, $fore_color);
+            
+            if ($filename === false) {
+                header("Content-Type: image/svg+xml");
+                //header('Content-Disposition: attachment, filename="qrcode.svg"');
+                echo $vect;
+            } else {
+                if($saveandprint===TRUE){
+                    QRtools::save($vect, $filename);
+                    header("Content-Type: image/svg+xml");
+                    //header('Content-Disposition: filename="'.$filename.'"');
+                    echo $vect;
+                }else{
+                    QRtools::save($vect, $filename);
+                }
+            }
+        }
+        
+    
+        //----------------------------------------------------------------------
+        private static function vectSVG($frame, $pixelPerPoint = 4, $outerFrame = 4, $back_color = 0xFFFFFF, $fore_color = 0x000000) 
+        {
+            $h = count($frame);
+            $w = strlen($frame[0]);
+            
+            $imgW = $w + 2*$outerFrame;
+            $imgH = $h + 2*$outerFrame;
+            
+            
+            $output = 
+            '<?xml version="1.0" encoding="utf-8"?>'."\n".
+            '<svg version="1.1" baseProfile="full"  width="'.$imgW * $pixelPerPoint.'" height="'.$imgH * $pixelPerPoint.'" viewBox="0 0 '.$imgW * $pixelPerPoint.' '.$imgH * $pixelPerPoint.'"
+             xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events">'."\n".
+            '<desc></desc>'."\n";
+
+            $output =
+            '<?xml version="1.0" encoding="utf-8"?>'."\n".
+            '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">'."\n".
+            '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" width="'.$imgW * $pixelPerPoint.'" height="'.$imgH * $pixelPerPoint.'" viewBox="0 0 '.$imgW * $pixelPerPoint.' '.$imgH * $pixelPerPoint.'">'."\n".
+            '<desc></desc>'."\n";
+                
+            if(!empty($back_color)) {
+                $backgroundcolor = str_pad(dechex($back_color), 6, "0", STR_PAD_LEFT);
+                $output .= '<rect width="'.$imgW * $pixelPerPoint.'" height="'.$imgH * $pixelPerPoint.'" fill="#'.$backgroundcolor.'" cx="0" cy="0" />'."\n";
+            }
+                
+            $output .= 
+            '<defs>'."\n".
+            '<rect id="p" width="'.$pixelPerPoint.'" height="'.$pixelPerPoint.'" />'."\n".
+            '</defs>'."\n".
+            '<g fill="#'.str_pad(dechex($fore_color), 6, "0", STR_PAD_LEFT).'">'."\n";
+                
+                
+            // Convert the matrix into pixels
+
+            for($i=0; $i<$h; $i++) {
+                for($j=0; $j<$w; $j++) {
+                    if( $frame[$i][$j] == '1') {
+                        $y = ($i + $outerFrame) * $pixelPerPoint;
+                        $x = ($j + $outerFrame) * $pixelPerPoint;
+                        $output .= '<use x="'.$x.'" y="'.$y.'" xlink:href="#p" />'."\n";
+                    }
+                }
+            }
+            $output .= 
+            '</g>'."\n".
+            '</svg>';
+            
+            return $output;
+        }
+    }
+    
+    
+
+

+ 23 - 5
public/assets/js/backend/procuremen.js

@@ -923,6 +923,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 if (hostOk) {
                     if ($btFt.length) {
                         $btFt.hide().attr('data-procuremen-toolbar-dup', '1');
+                        $btFt.find('.btn-refresh').attr('data-force-refresh', 'false');
                     }
                     // 每次都校正右侧顺序,保证贴右且图标顺序与图二一致
                     procuremenNormalizeToolbarRight($hostFt);
@@ -2015,6 +2016,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
             });
 
             Table.api.bindevent(table);
+            // 避免顶栏切回本页时 addtabs 再点一次刷新(进页会连刷两次)
+            $layout.find('.btn-refresh').attr('data-force-refresh', 'false');
             // 本页不需要「跨页选择模式」提示
             table.closest('.bootstrap-table').find('.btn-selected-tips').remove();
             $('#procuremen-toolbar-host .btn-selected-tips, #toolbar .btn-selected-tips').remove();
@@ -2796,9 +2799,26 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
 
             $layout.find('.procuremen-main').off('click.procuremenTbRefresh').on('click.procuremenTbRefresh', '.bootstrap-table > .fixed-table-toolbar .btn-refresh, #procuremen-toolbar-host .btn-refresh, .procuremen-table-area #toolbar .btn-refresh', function (e) {
                 e.preventDefault();
+                e.stopImmediatePropagation();
+                if (Controller._procuremenRefreshBusy) {
+                    return;
+                }
+                Controller._procuremenRefreshBusy = true;
                 var $spinFa = $('.bootstrap-table > .fixed-table-toolbar .btn-refresh .fa, #procuremen-toolbar-host .btn-refresh .fa, .procuremen-table-area #toolbar .btn-refresh .fa');
                 $spinFa.addClass('fa-spin');
-                var apiBase = ($layout.attr('data-procuremen-redis-api') || '').toString().trim();
+                var finishRefresh = function () {
+                    try {
+                        table.bootstrapTable('refresh');
+                    } catch (ignore) {
+                    }
+                    setTimeout(function () {
+                        Controller._procuremenRefreshBusy = false;
+                    }, 400);
+                };
+                // 仅初选需要重拉 ERP Redis;确认/审核页再打一遍会连刷两次
+                var apiBase = Controller.wffTab === 'pick'
+                    ? ($layout.attr('data-procuremen-redis-api') || '').toString().trim()
+                    : '';
                 if (apiBase) {
                     var sep = apiBase.indexOf('?') > -1 ? '&' : '?';
                     var refreshUrl = apiBase + sep + 'refresh=1';
@@ -2813,11 +2833,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                         }
                     }).fail(function () {
                         console.warn('procuremen redis refresh failed');
-                    }).always(function () {
-                        table.bootstrapTable('refresh');
-                    });
+                    }).always(finishRefresh);
                 } else {
-                    table.bootstrapTable('refresh');
+                    finishRefresh();
                 }
             });
             table.on('refresh.bs.table', function () {

+ 1 - 0
public/assets/js/backend/procuremenarchive.js

@@ -62,6 +62,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 commonSearch: true,
                 search: true,
                 pagination: true,
+                sidePagination: 'server',
                 smartDisplay: false,
                 clickToSelect: false,
                 pageSize: Config.pagesize || localStorage.getItem('pagesize') || 20,

+ 21 - 0
public/assets/js/backend/supplierservicescore.js

@@ -12,6 +12,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 return (n < 10 ? '0' : '') + n;
             }
             function currentYm() {
+                var cfg = '';
+                try {
+                    if (typeof Config !== 'undefined') {
+                        cfg = normalizeYm(Config.supplierScoreDefaultYm);
+                    }
+                } catch (e) {
+                }
+                if (cfg) {
+                    return cfg;
+                }
                 var d = new Date();
                 return d.getFullYear() + '-' + pad2(d.getMonth() + 1);
             }
@@ -155,6 +165,15 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 });
             }
 
+            var forceResync = 0;
+            $(document).off('mousedown.sscResync', '.btn-refresh')
+                .on('mousedown.sscResync', '.btn-refresh', function () {
+                    forceResync = 1;
+                    if (typeof Toastr !== 'undefined') {
+                        Toastr.info('正在按规则重算本月得分,请稍候…');
+                    }
+                });
+
             var table = $('#table');
             table.bootstrapTable({
                 url: $.fn.bootstrapTable.defaults.extend.index_url,
@@ -331,6 +350,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
                 queryParams: function (params) {
                     var ym = resolveYm();
                     params.ym = ym;
+                    params.resync = forceResync ? 1 : 0;
+                    forceResync = 0;
                     var filter = {ym: ym};
                     var op = {ym: '='};
                     var keyword = String(params.search || '').trim();

File diff suppressed because it is too large
+ 19 - 0
public/assets/js/html2canvas.min.js


Some files were not shown because too many files changed in this diff