|
|
@@ -0,0 +1,2568 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace app\admin\controller;
|
|
|
+
|
|
|
+use app\common\controller\Backend;
|
|
|
+use app\common\library\AliyunOss;
|
|
|
+use PHPMailer\PHPMailer\PHPMailer;
|
|
|
+use think\Config;
|
|
|
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
|
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
|
|
+use PhpOffice\PhpSpreadsheet\Style\Border;
|
|
|
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
|
+use think\Db;
|
|
|
+use think\Log;
|
|
|
+
|
|
|
+class Procuremen extends Backend
|
|
|
+{
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 顶部快速搜索(多列 OR LIKE);字段须为真实列名且带别名 a/b,列不宜过多以免 SQL 异常
|
|
|
+ */
|
|
|
+ protected $searchFields = 'a.ID,b.CCYDH,b.CYJMC,a.CDXMC,a.cGzzxMc';
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Procuremen模型对象
|
|
|
+ * @var \app\admin\model\Procuremen
|
|
|
+ */
|
|
|
+ protected $model = null;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @var array
|
|
|
+ */
|
|
|
+ protected $noNeedRight = ['review', 'reviewCompanies', 'outward_detail', 'export_month_outward', 'snapshotToProcure'];
|
|
|
+
|
|
|
+ public function _initialize()
|
|
|
+ {
|
|
|
+ parent::_initialize();
|
|
|
+ $this->model = new \app\admin\model\Procuremen;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手机端外发明细首页直达链接(登录后打开对应明细,免搜索)
|
|
|
+ * 配置 application/extra/mproc.php:mobile_base_url、mobile_index_path
|
|
|
+ *
|
|
|
+ * @param int $purchaseOrderDetailId purchase_order_detail 主键
|
|
|
+ */
|
|
|
+ protected function buildMprocMobileOrderUrl($purchaseOrderDetailId)
|
|
|
+ {
|
|
|
+ static $mprocCfgLoaded = false;
|
|
|
+ if (!$mprocCfgLoaded) {
|
|
|
+ $mprocCfgLoaded = true;
|
|
|
+ if (is_file(APP_PATH . 'extra/mproc.php')) {
|
|
|
+ Config::load(APP_PATH . 'extra/mproc.php', 'mproc');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $eid = (int)$purchaseOrderDetailId;
|
|
|
+ $base = $this->resolveMprocMobilePublicBaseUrl();
|
|
|
+ if ($base === '') {
|
|
|
+ throw new \Exception('请在 application/extra/mproc.php 中配置外网可访问的正式域名');
|
|
|
+ }
|
|
|
+ $path = trim((string)Config::get('mproc.mobile_index_path'));
|
|
|
+ if ($path === '') {
|
|
|
+ $path = '/index.php/index/index/index';
|
|
|
+ } else {
|
|
|
+ $path = '/' . ltrim($path, '/');
|
|
|
+ }
|
|
|
+ $query = ['main_tab' => 'orders'];
|
|
|
+ if ($eid > 0) {
|
|
|
+ $query['focus_eid'] = $eid;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $base . $path . '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 邮件/短信外链:须为带点的公网域名
|
|
|
+ */
|
|
|
+ protected function procuremenOutboundBaseUrlLooksValid($baseUrl)
|
|
|
+ {
|
|
|
+ $u = trim((string)$baseUrl);
|
|
|
+ if ($u === '' || !preg_match('#^https?://#i', $u)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $host = parse_url($u, PHP_URL_HOST);
|
|
|
+ if (!is_string($host) || $host === '') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (strcasecmp($host, 'localhost') === 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (preg_match('/^(127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/', $host)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 无点主机名(xh、erp 等)在小米/企业邮安全跳转中常报 Invalid url
|
|
|
+ if (strpos($host, '.') === false) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析用于外发邮件/短信的手机端站点根(https://域名 无末尾斜杠)
|
|
|
+ */
|
|
|
+ protected function resolveMprocMobilePublicBaseUrl()
|
|
|
+ {
|
|
|
+ $candidates = [];
|
|
|
+ $candidates[] = trim((string)Config::get('mproc.mobile_base_url'));
|
|
|
+ $candidates[] = trim((string)Config::get('site.indexurl'));
|
|
|
+ $cdn = trim((string)Config::get('site.cdnurl'));
|
|
|
+ if ($cdn !== '' && preg_match('#^https?://#i', $cdn)) {
|
|
|
+ $candidates[] = rtrim($cdn, '/');
|
|
|
+ }
|
|
|
+ $reqBase = rtrim($this->request->scheme() . '://' . $this->request->host(), '/');
|
|
|
+ $candidates[] = $reqBase;
|
|
|
+
|
|
|
+ foreach ($candidates as $c) {
|
|
|
+ if ($c === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $c = rtrim($c, '/');
|
|
|
+ if ($this->procuremenOutboundBaseUrlLooksValid($c)) {
|
|
|
+ return $c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::write('外发邮件手机链接:未配置有效 mproc.mobile_base_url,且当前访问主机不适合作为外链,请在 application/extra/mproc.php 设置 https 正式域名', 'warning');
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 排除 mcyd.dputrecord 空串、零日期等非法值,避免 MySQL 1525(DATE_FORMAT/BETWEEN 遇 '' 报错)
|
|
|
+ */
|
|
|
+ protected function whereMcydDputrecordValid($query, $alias = 'a')
|
|
|
+ {
|
|
|
+ $c = $alias . '.dStamp';
|
|
|
+ // 先转成 CHAR 再判断,避免 DATETIME 列里存 '' / 0000-00-00 时参与日期函数报错
|
|
|
+ return $query->whereRaw(
|
|
|
+ $c . ' IS NOT NULL'
|
|
|
+ . ' AND TRIM(CAST(' . $c . ' AS CHAR(32))) <> \'\''
|
|
|
+ . ' AND TRIM(CAST(' . $c . ' AS CHAR(32))) NOT LIKE \'0000-00-00%\''
|
|
|
+ . ' AND TRIM(CAST(' . $c . ' AS CHAR(32))) REGEXP \'^[12][0-9]{3}-[01][0-9]-[0-3][0-9]\''
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 左侧菜单:仅「近 12 个自然月(含当月)」
|
|
|
+ */
|
|
|
+ protected function getIndexSidebarYearMonths()
|
|
|
+ {
|
|
|
+ $ymSet = [];
|
|
|
+ $anchor = strtotime(date('Y-m-01'));
|
|
|
+ for ($i = 0; $i < 12; $i++) {
|
|
|
+ $ym = date('Y-m', strtotime("-{$i} month", $anchor));
|
|
|
+ $ymSet[$ym] = true;
|
|
|
+ }
|
|
|
+ $windowKeys = array_keys($ymSet);
|
|
|
+ $minYm = min($windowKeys);
|
|
|
+ $maxYm = max($windowKeys);
|
|
|
+
|
|
|
+ $rows = [];
|
|
|
+ try {
|
|
|
+ $query = Db::table('scydgy')
|
|
|
+ ->alias('a')
|
|
|
+ ->join('mcyd b', 'b.ICYDID = a.ICYDID AND b.iStatus >= 10', 'inner')
|
|
|
+ ->where([
|
|
|
+ 'a.bwjg' => 1,
|
|
|
+ 'a.iEndBz' => 0,
|
|
|
+ 'a.iType' => 0,
|
|
|
+ 'a.iStatus' => 10,
|
|
|
+ ]);
|
|
|
+ $this->whereMcydDputrecordValid($query, 'a');
|
|
|
+ $rows = $query->field("DATE_FORMAT(a.dputrecord, '%Y-%m') as ym")
|
|
|
+ ->group("DATE_FORMAT(a.dputrecord, '%Y-%m')")
|
|
|
+ ->orderRaw("DATE_FORMAT(a.dputrecord, '%Y-%m') DESC")
|
|
|
+ ->select();
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $rows = [];
|
|
|
+ }
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ $ym = isset($r['ym']) ? trim((string)$r['ym']) : '';
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (strcmp($ym, $minYm) >= 0 && strcmp($ym, $maxYm) <= 0) {
|
|
|
+ $ymSet[$ym] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $ymList = array_keys($ymSet);
|
|
|
+ rsort($ymList, SORT_STRING);
|
|
|
+
|
|
|
+ $byYear = [];
|
|
|
+ foreach ($ymList as $ym) {
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $y = substr($ym, 0, 4);
|
|
|
+ $m = (int)substr($ym, 5, 2);
|
|
|
+ if (!isset($byYear[$y])) {
|
|
|
+ $byYear[$y] = [];
|
|
|
+ }
|
|
|
+ $byYear[$y][] = ['ym' => $ym, 'label' => $m . '月'];
|
|
|
+ }
|
|
|
+ krsort($byYear, SORT_NUMERIC);
|
|
|
+ foreach ($byYear as $y => $items) {
|
|
|
+ usort($byYear[$y], function ($a, $b) {
|
|
|
+ return strcmp($b['ym'], $a['ym']);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ $sidebar = [];
|
|
|
+ foreach ($byYear as $y => $months) {
|
|
|
+ $sidebar[] = ['year' => $y, 'months' => $months];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $sidebar;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 列表页
|
|
|
+ */
|
|
|
+ public function index(){
|
|
|
+ $this->request->filter(['strip_tags', 'trim']);
|
|
|
+
|
|
|
+ if (!$this->request->isAjax()) {
|
|
|
+ $this->view->assign('defaultYm', date('Y-m'));
|
|
|
+ $this->view->assign('sidebarYearMonths', $this->getIndexSidebarYearMonths());
|
|
|
+ return $this->view->fetch();
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($this->request->request('keyField')) {
|
|
|
+ return $this->selectpage();
|
|
|
+ }
|
|
|
+
|
|
|
+ list(, $sort, $order, $offset, $limit) = $this->buildparams();
|
|
|
+ $limit = max(10, min(500, (int)$limit));
|
|
|
+
|
|
|
+ $ym = $this->request->request('ym', date('Y-m'));
|
|
|
+ $ym = is_string($ym) ? trim($ym) : '';
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ $ym = date('Y-m');
|
|
|
+ }
|
|
|
+ $monthStart = $ym . '-01 00:00:00';
|
|
|
+ $monthEnd = date('Y-m-t 23:59:59', strtotime($monthStart));
|
|
|
+
|
|
|
+ $search = trim((string)$this->request->get('search', ''));
|
|
|
+ $hasActiveSearch = ($search !== '');
|
|
|
+ if (!$hasActiveSearch) {
|
|
|
+ $filterStr = $this->request->get('filter', '');
|
|
|
+ if ($filterStr !== '' && $filterStr !== '[]' && $filterStr !== '{}') {
|
|
|
+ $arr = json_decode($filterStr, true);
|
|
|
+ if (is_array($arr) && count($arr) > 0) {
|
|
|
+ foreach ($arr as $v) {
|
|
|
+ if (is_array($v)) {
|
|
|
+ if (array_filter($v, function ($x) {
|
|
|
+ return $x !== '' && $x !== null;
|
|
|
+ })) {
|
|
|
+ $hasActiveSearch = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } elseif ($v !== '' && $v !== null) {
|
|
|
+ $hasActiveSearch = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $applyMonthRange = !$hasActiveSearch;
|
|
|
+
|
|
|
+ $filterArr = (array)json_decode($this->request->get('filter', ''), true);
|
|
|
+ $opArr = (array)json_decode($this->request->get('op', ''), true);
|
|
|
+
|
|
|
+ /* wff_tab:all=未发;pending=已下发;picked=已选中(主表 status=1 且存在明细 status=1);done=已完结 */
|
|
|
+ $wffTab = trim((string)$this->request->request('wff_tab', 'all'));
|
|
|
+ if (!in_array($wffTab, ['all', 'pending', 'picked', 'done'], true)) {
|
|
|
+ $wffTab = 'all';
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ $issuedSet = $this->loadIssuedScydgySet();
|
|
|
+
|
|
|
+ if (in_array($wffTab, ['pending', 'done', 'picked'], true)) {
|
|
|
+ $wantStatus = $wffTab === 'pending' ? 0 : 1;
|
|
|
+ $pool = [];
|
|
|
+ $dbRows = [];
|
|
|
+ try {
|
|
|
+ if ($wantStatus === 0) {
|
|
|
+ $dbRows = Db::table('purchase_order')
|
|
|
+ ->where('status', $wantStatus)
|
|
|
+ ->select();
|
|
|
+ } else {
|
|
|
+ $dbRows = Db::table('purchase_order')
|
|
|
+ ->where('status', $wantStatus)
|
|
|
+ ->select();
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $dbRows = [];
|
|
|
+ }
|
|
|
+ if (!is_array($dbRows)) {
|
|
|
+ $dbRows = [];
|
|
|
+ }
|
|
|
+ if ($wffTab === 'picked') {
|
|
|
+ $pickedSidSet = $this->loadScydgyIdsWithPickedSupplierDetail();
|
|
|
+ $dbRows = array_values(array_filter($dbRows, function ($dbRow) use ($pickedSidSet) {
|
|
|
+ if (!is_array($dbRow)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $sid = (int)($dbRow['scydgy_id'] ?? 0);
|
|
|
+ if ($sid <= 0 && isset($dbRow['SCYDGY_ID'])) {
|
|
|
+ $sid = (int)$dbRow['SCYDGY_ID'];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $sid > 0 && isset($pickedSidSet[$sid]);
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ $dStampMap = [];
|
|
|
+ $sidList = [];
|
|
|
+ foreach ($dbRows as $tmpRow) {
|
|
|
+ if (is_array($tmpRow) && isset($tmpRow['scydgy_id'])) {
|
|
|
+ $sid = (int)$tmpRow['scydgy_id'];
|
|
|
+ if ($sid > 0) {
|
|
|
+ $sidList[$sid] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($sidList !== []) {
|
|
|
+ try {
|
|
|
+ $dStampMap = Db::table('scydgy')->where('ID', 'in', array_keys($sidList))->column('dStamp', 'ID');
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $dStampMap = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($dbRows as $dbRow) {
|
|
|
+ $rj = null;
|
|
|
+ if (!empty($dbRow['row_json'])) {
|
|
|
+ $decoded = json_decode($dbRow['row_json'], true);
|
|
|
+ if (is_array($decoded)) {
|
|
|
+ $rj = $decoded;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($rj !== null && isset($rj['ID'])) {
|
|
|
+ $rid = (int)$rj['ID'];
|
|
|
+ $dsFix = isset($rj['dStamp']) ? trim((string)$rj['dStamp']) : '';
|
|
|
+ if (($dsFix === '' || stripos($dsFix, '0000-00-00') === 0)
|
|
|
+ && isset($dStampMap[$rid]) && trim((string)$dStampMap[$rid]) !== '') {
|
|
|
+ $rj['dStamp'] = $dStampMap[$rid];
|
|
|
+ }
|
|
|
+ if (array_key_exists('id', $dbRow)) {
|
|
|
+ $rj['purchase_order_id'] = (int)$dbRow['id'];
|
|
|
+ }
|
|
|
+ $pool[] = $rj;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $r = [];
|
|
|
+ foreach ($dbRow as $k => $v) {
|
|
|
+ $lk = strtolower((string)$k);
|
|
|
+ if ($lk === 'id' || $lk === 'row_json') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $r[$k] = $v;
|
|
|
+ }
|
|
|
+ if (isset($dbRow['scydgy_id'])) {
|
|
|
+ $r['ID'] = (int)$dbRow['scydgy_id'];
|
|
|
+ $sid = $r['ID'];
|
|
|
+ $dsOut = '';
|
|
|
+ if (isset($dStampMap[$sid])) {
|
|
|
+ $t = trim((string)$dStampMap[$sid]);
|
|
|
+ if ($t !== '' && stripos($t, '0000-00-00') !== 0) {
|
|
|
+ $dsOut = $dStampMap[$sid];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($dsOut === '' && !empty($dbRow['createtime'])) {
|
|
|
+ $ct = $dbRow['createtime'];
|
|
|
+ if (is_numeric($ct) && (int)$ct > 946684800) {
|
|
|
+ $dsOut = date('Y-m-d H:i:s', (int)$ct);
|
|
|
+ } elseif (is_string($ct) && trim($ct) !== '') {
|
|
|
+ $tc = trim($ct);
|
|
|
+ if ($tc !== '' && stripos($tc, '0000-00-00') !== 0) {
|
|
|
+ $dsOut = $tc;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($dsOut !== '') {
|
|
|
+ $r['dStamp'] = $dsOut;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (array_key_exists('id', $dbRow)) {
|
|
|
+ $r['purchase_order_id'] = (int)$dbRow['id'];
|
|
|
+ }
|
|
|
+ $pool[] = $r;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $redis = redis();
|
|
|
+ $raw = $redis->get('procuremen_redis');
|
|
|
+ if ($raw === false || $raw === '') {
|
|
|
+ return json([
|
|
|
+ 'total' => 0,
|
|
|
+ 'rows' => [],
|
|
|
+ 'msg' => '暂无缓存数据',
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ $decoded = json_decode($raw, true);
|
|
|
+ $pool = (is_array($decoded) && isset($decoded['data']) && is_array($decoded['data']))
|
|
|
+ ? $decoded['data'] : [];
|
|
|
+
|
|
|
+ // 未发:主表已有且 status 为 0/1 才从列表隐藏;status 为空(NULL)仍可在未发展示
|
|
|
+ try {
|
|
|
+ $poIds = Db::table('purchase_order')
|
|
|
+ ->whereRaw('(`status` = 0 OR `status` = 1)')
|
|
|
+ ->column('scydgy_id');
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $poIds = [];
|
|
|
+ }
|
|
|
+ if ($pool !== [] && is_array($poIds) && $poIds !== []) {
|
|
|
+ $poSet = [];
|
|
|
+ foreach ($poIds as $pid) {
|
|
|
+ $k = (int)$pid;
|
|
|
+ if ($k > 0) {
|
|
|
+ $poSet[$k] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($poSet !== []) {
|
|
|
+ $pool = array_values(array_filter($pool, function ($r) use ($poSet) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $id = (int)($r['ID'] ?? 0);
|
|
|
+ if ($id <= 0) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return !isset($poSet[$id]);
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $filtered = $this->filterProcuremenIndexPool(
|
|
|
+ $pool,
|
|
|
+ $monthStart,
|
|
|
+ $monthEnd,
|
|
|
+ $applyMonthRange,
|
|
|
+ $search,
|
|
|
+ $filterArr,
|
|
|
+ $opArr
|
|
|
+ );
|
|
|
+
|
|
|
+ $sortField = 'ID';
|
|
|
+ if (is_string($sort) && $sort !== '') {
|
|
|
+ $parts = explode(',', $sort);
|
|
|
+ $sortField = preg_replace('/^[ab]\./i', '', trim($parts[0]));
|
|
|
+ if ($sortField === '') {
|
|
|
+ $sortField = 'ID';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $ord = strtoupper((string)$order) === 'ASC' ? 1 : -1;
|
|
|
+ $nFiltered = count($filtered);
|
|
|
+ if ($nFiltered > 1) {
|
|
|
+ // 默认按主键 ID 排序时,用 array_multisort 比 usort+闭包略省开销
|
|
|
+ if ($sortField === 'ID') {
|
|
|
+ $sortKeys = [];
|
|
|
+ foreach ($filtered as $i => $row) {
|
|
|
+ $sortKeys[$i] = (int)($row['ID'] ?? 0);
|
|
|
+ }
|
|
|
+ $dir = $ord === 1 ? SORT_ASC : SORT_DESC;
|
|
|
+ array_multisort($sortKeys, $dir, SORT_NUMERIC, $filtered);
|
|
|
+ } else {
|
|
|
+ usort($filtered, function ($a, $b) use ($sortField, $ord) {
|
|
|
+ $va = $a[$sortField] ?? null;
|
|
|
+ $vb = $b[$sortField] ?? null;
|
|
|
+ $sa = (string)$va;
|
|
|
+ $sb = (string)$vb;
|
|
|
+ if ($sa === $sb) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return strcmp($sa, $sb) * $ord;
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $offset = max(0, (int)$offset);
|
|
|
+ $limit = max(1, (int)$limit);
|
|
|
+
|
|
|
+ $rows = array_slice($filtered, $offset, $limit);
|
|
|
+ foreach ($rows as &$rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $rid = (int)($rw['ID'] ?? 0);
|
|
|
+ if ($wffTab === 'pending' || $wffTab === 'done' || $wffTab === 'picked') {
|
|
|
+ $rw['_iss_out'] = true;
|
|
|
+ } else {
|
|
|
+ $rw['_iss_out'] = $rid > 0 && isset($issuedSet[$rid]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($rw);
|
|
|
+
|
|
|
+ if ($wffTab === 'pending' && count($rows) > 0) {
|
|
|
+ $idList = [];
|
|
|
+ foreach ($rows as $rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $id = (int)($rw['ID'] ?? 0);
|
|
|
+ if ($id > 0) {
|
|
|
+ $idList[$id] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $sumMap = $this->OrderDetailSummary(array_keys($idList));
|
|
|
+ foreach ($rows as &$rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $rid = (int)($rw['ID'] ?? 0);
|
|
|
+ $s = isset($sumMap[$rid]) ? $sumMap[$rid] : ['cnt' => 0, 'amt' => 0, 'deliv' => 0];
|
|
|
+ $rw['po_detail_count'] = $s['cnt'];
|
|
|
+ $rw['po_amount_fill_cnt'] = $s['amt'];
|
|
|
+ $rw['po_delivery_fill_cnt'] = $s['deliv'];
|
|
|
+ }
|
|
|
+ unset($rw);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($wffTab === 'picked' && count($rows) > 0) {
|
|
|
+ $idList = [];
|
|
|
+ foreach ($rows as $rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $id = (int)($rw['ID'] ?? 0);
|
|
|
+ if ($id > 0) {
|
|
|
+ $idList[$id] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $nameMap = $this->loadPickedSupplierCompanyByScydgyIds(array_keys($idList));
|
|
|
+ foreach ($rows as &$rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $rid = (int)($rw['ID'] ?? 0);
|
|
|
+ $rw['picked_supplier_name'] = $nameMap[$rid] ?? '';
|
|
|
+ }
|
|
|
+ unset($rw);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($wffTab === 'all' && count($rows) > 0) {
|
|
|
+ $this->mergePurchaseOrder($rows);
|
|
|
+ }
|
|
|
+
|
|
|
+ return json([
|
|
|
+ 'total' => count($filtered),
|
|
|
+ 'rows' => $rows,
|
|
|
+ ]);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ return json([
|
|
|
+ 'total' => 0,
|
|
|
+ 'rows' => [],
|
|
|
+ 'msg' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 已外发判定:主表 purchase_order.scydgy_id ∪ 明细 purchase_order_detail.scydgy_id(与工序列表行 ID 对应)
|
|
|
+ *
|
|
|
+ * @return array<int, true> 列表行 ID => true
|
|
|
+ */
|
|
|
+ protected function loadIssuedScydgySet()
|
|
|
+ {
|
|
|
+ $issuedSet = [];
|
|
|
+ foreach (['purchase_order', 'purchase_order_detail'] as $tbl) {
|
|
|
+ try {
|
|
|
+ $issuedList = Db::table($tbl)->group('scydgy_id')->column('scydgy_id');
|
|
|
+ if (!is_array($issuedList)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ foreach ($issuedList as $ids) {
|
|
|
+ $n = (int)$ids;
|
|
|
+ if ($n > 0) {
|
|
|
+ $issuedSet[$n] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $issuedSet;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 采购确认已选定供应商:明细 purchase_order_detail.status = 1 的工序行 scydgy_id 集合
|
|
|
+ *
|
|
|
+ * @return array<int, true>
|
|
|
+ */
|
|
|
+ protected function loadScydgyIdsWithPickedSupplierDetail(): array
|
|
|
+ {
|
|
|
+ $set = [];
|
|
|
+ try {
|
|
|
+ $rows = Db::table('purchase_order_detail')->where('status', 1)->field('scydgy_id')->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $rows = [];
|
|
|
+ }
|
|
|
+ if (!is_array($rows)) {
|
|
|
+ return $set;
|
|
|
+ }
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $sid = (int)($r['scydgy_id'] ?? $r['SCYDGY_ID'] ?? 0);
|
|
|
+ if ($sid > 0) {
|
|
|
+ $set[$sid] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $set;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 每个工序行对应已选中供应商名称(取 status=1 的首条明细 company_name)
|
|
|
+ *
|
|
|
+ * @param int[] $scydgyIds
|
|
|
+ * @return array<int, string> scydgy_id => company_name
|
|
|
+ */
|
|
|
+ protected function loadPickedSupplierCompanyByScydgyIds(array $scydgyIds): array
|
|
|
+ {
|
|
|
+ $out = [];
|
|
|
+ $scydgyIds = array_values(array_unique(array_filter(array_map('intval', $scydgyIds))));
|
|
|
+ if ($scydgyIds === []) {
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $rows = Db::table('purchase_order_detail')
|
|
|
+ ->where('scydgy_id', 'in', $scydgyIds)
|
|
|
+ ->where('status', 1)
|
|
|
+ ->field('scydgy_id,company_name')
|
|
|
+ ->order('id', 'asc')
|
|
|
+ ->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ try {
|
|
|
+ $rows = Db::table('purchase_order_detail')
|
|
|
+ ->where('scydgy_id', 'in', $scydgyIds)
|
|
|
+ ->where('status', 1)
|
|
|
+ ->field('scydgy_id,company_name')
|
|
|
+ ->order('ID', 'asc')
|
|
|
+ ->select();
|
|
|
+ } catch (\Throwable $e2) {
|
|
|
+ $rows = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!is_array($rows)) {
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $sid = (int)($r['scydgy_id'] ?? $r['SCYDGY_ID'] ?? 0);
|
|
|
+ if ($sid <= 0 || isset($out[$sid])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $name = trim((string)($r['company_name'] ?? ''));
|
|
|
+ $out[$sid] = $name;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 已下发列表汇总:按工序行 ID 统计 purchase_order_detail 条数、已填金额条数、已填货期条数
|
|
|
+ *
|
|
|
+ * @param int[] $scydgyIds
|
|
|
+ * @return array<int, array{cnt:int, amt:int, deliv:int}>
|
|
|
+ */
|
|
|
+ protected function OrderDetailSummary(array $scydgyIds)
|
|
|
+ {
|
|
|
+ $out = [];
|
|
|
+ $scydgyIds = array_values(array_unique(array_filter(array_map('intval', $scydgyIds))));
|
|
|
+ if ($scydgyIds === []) {
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+ $list = [];
|
|
|
+ try {
|
|
|
+ $list = Db::table('purchase_order_detail')
|
|
|
+ ->where('scydgy_id', 'in', $scydgyIds)
|
|
|
+ ->field('id,scydgy_id,amount,delivery')
|
|
|
+ ->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $list = [];
|
|
|
+ }
|
|
|
+ if (!is_array($list)) {
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+ foreach ($list as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $ids = (int)($r['scydgy_id'] ?? 0);
|
|
|
+ if ($ids <= 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!isset($out[$ids])) {
|
|
|
+ $out[$ids] = ['cnt' => 0, 'amt' => 0, 'deliv' => 0];
|
|
|
+ }
|
|
|
+ $out[$ids]['cnt']++;
|
|
|
+ $am = $r['amount'] ?? null;
|
|
|
+ if ($am !== null && $am !== '') {
|
|
|
+ if (!(is_string($am) && trim($am) === '')) {
|
|
|
+ $out[$ids]['amt']++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $dv = $r['delivery'] ?? null;
|
|
|
+ if ($dv !== null && trim((string)$dv) !== '') {
|
|
|
+ $out[$ids]['deliv']++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 列表筛选:月份、dStamp 合法性、快速搜索、Bootstrap Table filter(与原先 Redis 分支一致)
|
|
|
+ *
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ protected function filterProcuremenIndexPool(array $pool, $monthStart, $monthEnd, $applyMonthRange, $search, array $filterArr, array $opArr)
|
|
|
+ {
|
|
|
+ $strContains = function ($haystack, $needle) {
|
|
|
+ if ($needle === '') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $haystack = (string)$haystack;
|
|
|
+ if (function_exists('mb_stripos')) {
|
|
|
+ return mb_stripos($haystack, $needle, 0, 'UTF-8') !== false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return stripos($haystack, $needle) !== false;
|
|
|
+ };
|
|
|
+ $stripAlias = function ($f) {
|
|
|
+ return preg_replace('/^[ab]\./i', '', (string)$f);
|
|
|
+ };
|
|
|
+
|
|
|
+ $searchColNames = [];
|
|
|
+ foreach (explode(',', $this->searchFields) as $colRaw) {
|
|
|
+ $c = $stripAlias(trim($colRaw));
|
|
|
+ if ($c !== '') {
|
|
|
+ $searchColNames[] = $c;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $filtered = [];
|
|
|
+ foreach ($pool as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $ds = isset($r['dStamp']) ? trim((string)$r['dStamp']) : '';
|
|
|
+ if ($ds === '' || stripos($ds, '0000-00-00') === 0
|
|
|
+ || !preg_match('/^[12]\d{3}-\d{1,2}-\d{1,2}/', $ds)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($applyMonthRange) {
|
|
|
+ if (strcmp($ds, $monthStart) < 0 || strcmp($ds, $monthEnd) > 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($search !== '') {
|
|
|
+ $hitSearch = false;
|
|
|
+ foreach ($searchColNames as $c) {
|
|
|
+ $cell = isset($r[$c]) ? (string)$r[$c] : '';
|
|
|
+ if ($cell !== '' && $strContains($cell, $search)) {
|
|
|
+ $hitSearch = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!$hitSearch) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($filterArr as $fk => $fv) {
|
|
|
+ if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', (string)$fk)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (is_array($fv)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($fv === '' && $fv !== '0' && $fv !== 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $sym = strtoupper(trim((string)($opArr[$fk] ?? '=')));
|
|
|
+ $sym = str_replace(['LIKE %...%', 'NOT LIKE %...%'], ['LIKE', 'NOT LIKE'], $sym);
|
|
|
+ $field = $stripAlias($fk);
|
|
|
+ $cell = array_key_exists($field, $r) ? $r[$field] : null;
|
|
|
+ $cellStr = trim((string)$cell);
|
|
|
+
|
|
|
+ switch ($sym) {
|
|
|
+ case '=':
|
|
|
+ if ((string)$cell !== (string)$fv) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case '<>':
|
|
|
+ if ((string)$cell === (string)$fv) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'LIKE':
|
|
|
+ case 'NOT LIKE':
|
|
|
+ $needle = trim((string)$fv);
|
|
|
+ $hit = ($needle !== '' && $strContains($cellStr, $needle));
|
|
|
+ if ($sym === 'LIKE' && !$hit) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ if ($sym === 'NOT LIKE' && $hit) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case '>':
|
|
|
+ case '>=':
|
|
|
+ case '<':
|
|
|
+ case '<=':
|
|
|
+ if (!is_numeric($cell) && $cellStr === '') {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ $cv = is_numeric($cell) ? (float)$cell : (float)$cellStr;
|
|
|
+ $vv = (float)$fv;
|
|
|
+ if ($sym === '>' && !($cv > $vv)) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ if ($sym === '>=' && !($cv >= $vv)) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ if ($sym === '<' && !($cv < $vv)) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ if ($sym === '<=' && !($cv <= $vv)) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'BETWEEN':
|
|
|
+ case 'NOT BETWEEN':
|
|
|
+ case 'BETWEEN TIME':
|
|
|
+ case 'NOT BETWEEN TIME':
|
|
|
+ $rawR = is_array($fv) ? implode(',', $fv) : (string)$fv;
|
|
|
+ $rawR = str_replace(' - ', ',', $rawR);
|
|
|
+ $arr = array_slice(array_map('trim', explode(',', $rawR)), 0, 2);
|
|
|
+ if (count($arr) < 2 || $arr[0] === '' || $arr[1] === '') {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ $lo = $arr[0];
|
|
|
+ $hi = $arr[1];
|
|
|
+ $in = ($cellStr >= $lo && $cellStr <= $hi);
|
|
|
+ $isNot = (strpos($sym, 'NOT') !== false);
|
|
|
+ if ($isNot ? $in : !$in) {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'NULL':
|
|
|
+ case 'IS NULL':
|
|
|
+ if ($cell !== null && $cell !== '') {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'NOT NULL':
|
|
|
+ case 'IS NOT NULL':
|
|
|
+ if ($cell === null || $cell === '') {
|
|
|
+ continue 3;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $filtered[] = $r;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $filtered;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 审核弹窗提交
|
|
|
+ * 查询订单 purchase_order 数据查有无记录,无则插、有则改(全部写在本方法内)
|
|
|
+ */
|
|
|
+ public function snapshotToProcure()
|
|
|
+ {
|
|
|
+ if (!$this->request->isPost() || !$this->request->isAjax()) {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+ $row = json_decode($this->request->post('row_json', ''), true);
|
|
|
+ if (!is_array($row)) {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $ids = (int)($row['ID'] ?? $row['id'] ?? 0);
|
|
|
+ if ($ids <= 0) {
|
|
|
+ throw new \Exception('无效的行主键 ID');
|
|
|
+ }
|
|
|
+
|
|
|
+ $exists = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+
|
|
|
+ $data = [
|
|
|
+ 'scydgy_id' => $ids,
|
|
|
+ 'CCYDH' => $row['CCYDH'] ?? null,
|
|
|
+ 'CYJMC' => $row['CYJMC'] ?? null,
|
|
|
+ 'CDXMC' => $row['CDXMC'] ?? null,
|
|
|
+ 'CGYBH' => $row['CGYBH'] ?? null,
|
|
|
+ 'CGYMC' => $row['CGYMC'] ?? null,
|
|
|
+ 'CDW' => $row['CDW'] ?? null,
|
|
|
+ 'NGZL' => $row['NGZL'] ?? null,
|
|
|
+ 'CDF' => $row['CDF'] ?? null,
|
|
|
+ 'cGzzxMc' => $row['cGzzxMc'] ?? null,
|
|
|
+ 'MBZ' => $row['MBZ'] ?? null,
|
|
|
+ 'bwjg' => $row['bwjg'] ?? null,
|
|
|
+ 'iStatus' => $row['iStatus'] ?? null,
|
|
|
+ 'dStamp' => $row['dStamp'] ?? null,
|
|
|
+ 'dputrecord' => $row['dputrecord'] ?? null,
|
|
|
+ 'cywyxm' => $row['cywyxm'] ?? null,
|
|
|
+ 'This_quantity' => $row['This_quantity'] ?? $row['this_quantity'] ?? null,
|
|
|
+ 'ceilingPrice' => $row['ceilingPrice'] ?? $row['ceiling_price'] ?? null,
|
|
|
+ ];
|
|
|
+
|
|
|
+ if ($exists) {
|
|
|
+ $upd = $data;
|
|
|
+ unset($upd['scydgy_id'], $upd['status']);
|
|
|
+ Db::table('purchase_order')->where('scydgy_id', $ids)->update($upd);
|
|
|
+ } else {
|
|
|
+ // 新增:不写 status(空/走库默认);仅写创建时间
|
|
|
+ $data['createtime'] = date('Y-m-d H:i:s');
|
|
|
+ Db::table('purchase_order')->insert($data);
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $this->error($e->getMessage());
|
|
|
+ }
|
|
|
+ // 操作记录仅在「审核提交」时写入(review POST + addOrderLog),此处同步不写日志,避免与下发记录重复
|
|
|
+ $this->success('操作成功');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 未发列表
|
|
|
+ * 「完结」或「仅保存本次数量/最高限价」:有则改、无则插
|
|
|
+ * POST finish=1(默认):并置 status=1;finish=0:更新时不改 status;新增不写 status。
|
|
|
+ */
|
|
|
+ public function completeDirectly()
|
|
|
+ {
|
|
|
+ if (!$this->request->isPost() || !$this->request->isAjax()) {
|
|
|
+ $this->error(__('参数错误'));
|
|
|
+ }
|
|
|
+ $row = json_decode($this->request->post('row_json', ''), true);
|
|
|
+ if (!is_array($row)) {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+ $finishRaw = $this->request->post('finish', '1');
|
|
|
+ $asComplete = ($finishRaw === '1' || $finishRaw === 1 || $finishRaw === true);
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 获取工序行 ID,对应 purchase_order.scydgy_id;有则改、无则插
|
|
|
+ $ids = (int)($row['ID'] ?? $row['id'] ?? 0);
|
|
|
+ if ($ids <= 0) {
|
|
|
+ throw new \Exception('无效的行主键 ID');
|
|
|
+ }
|
|
|
+
|
|
|
+ $exists = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+
|
|
|
+ $data = [
|
|
|
+ 'scydgy_id' => $ids,
|
|
|
+ 'CCYDH' => $row['CCYDH'] ?? null,
|
|
|
+ 'CYJMC' => $row['CYJMC'] ?? null,
|
|
|
+ 'CDXMC' => $row['CDXMC'] ?? null,
|
|
|
+ 'CGYBH' => $row['CGYBH'] ?? null,
|
|
|
+ 'CGYMC' => $row['CGYMC'] ?? null,
|
|
|
+ 'CDW' => $row['CDW'] ?? null,
|
|
|
+ 'NGZL' => $row['NGZL'] ?? null,
|
|
|
+ 'CDF' => $row['CDF'] ?? null,
|
|
|
+ 'cGzzxMc' => $row['cGzzxMc'] ?? null,
|
|
|
+ 'MBZ' => $row['MBZ'] ?? null,
|
|
|
+ 'bwjg' => $row['bwjg'] ?? null,
|
|
|
+ 'iStatus' => $row['iStatus'] ?? null,
|
|
|
+ 'dStamp' => $row['dStamp'] ?? null,
|
|
|
+ 'dputrecord' => $row['dputrecord'] ?? null,
|
|
|
+ 'cywyxm' => $row['cywyxm'] ?? null,
|
|
|
+ 'This_quantity' => $row['This_quantity'] ?? $row['this_quantity'] ?? null,
|
|
|
+ 'ceilingPrice' => $row['ceilingPrice'] ?? $row['ceiling_price'] ?? null,
|
|
|
+ ];
|
|
|
+
|
|
|
+ if ($asComplete) {
|
|
|
+ $data['status'] = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($exists) {
|
|
|
+ $upd = $data;
|
|
|
+ unset($upd['scydgy_id']);
|
|
|
+ if (!$asComplete) {
|
|
|
+ unset($upd['status']);
|
|
|
+ }
|
|
|
+ Db::table('purchase_order')->where('scydgy_id', $ids)->update($upd);
|
|
|
+ } else {
|
|
|
+ // 新增:不写 status;完结时 $data 已含 status=1
|
|
|
+ $data['createtime'] = date('Y-m-d H:i:s');
|
|
|
+ Db::table('purchase_order')->insert($data);
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $this->error($e->getMessage());
|
|
|
+ }
|
|
|
+ $poIdLog = null;
|
|
|
+ try {
|
|
|
+ $rpo = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+ if (is_array($rpo)) {
|
|
|
+ $tid = (int)($rpo['id'] ?? $rpo['ID'] ?? 0);
|
|
|
+ $poIdLog = $tid > 0 ? $tid : null;
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $poIdLog = null;
|
|
|
+ }
|
|
|
+ $q = isset($data['This_quantity']) ? trim((string)$data['This_quantity']) : '';
|
|
|
+ $p = isset($data['ceilingPrice']) ? trim((string)$data['ceilingPrice']) : '';
|
|
|
+ if ($asComplete) {
|
|
|
+ $this->addOrderLog(
|
|
|
+ $ids,
|
|
|
+ 'mark_complete',
|
|
|
+ '点击「完结」,已标记为已完结;本次数量「' . ($q !== '' ? $q : '') . '」,最高限价「' . ($p !== '' ? $p : '') . '」',
|
|
|
+ $poIdLog
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ $this->addOrderLog(
|
|
|
+ $ids,
|
|
|
+ 'save_qty_price',
|
|
|
+ '保存本次数量、最高限价:本次数量「' . ($q !== '' ? $q : '') . '」,最高限价「' . ($p !== '' ? $p : '') . '」',
|
|
|
+ $poIdLog
|
|
|
+ );
|
|
|
+ }
|
|
|
+ $this->success("操作成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 未发列表:从 purchase_order 合并已填的本次数量、最高限价(若表中有对应列)
|
|
|
+ *
|
|
|
+ * @param array<int, array> $rows 引用传递当前页行
|
|
|
+ */
|
|
|
+ protected function mergePurchaseOrder(array &$rows)
|
|
|
+ {
|
|
|
+ if ($rows === []) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $ids = [];
|
|
|
+ foreach ($rows as $rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $id = (int)($rw['ID'] ?? 0);
|
|
|
+ if ($id > 0) {
|
|
|
+ $ids[$id] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $idList = array_keys($ids);
|
|
|
+ if ($idList === []) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $list = Db::table('purchase_order')
|
|
|
+ ->where('scydgy_id', 'in', $idList)
|
|
|
+ ->field('scydgy_id,This_quantity,ceilingPrice')
|
|
|
+ ->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!is_array($list)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $byId = [];
|
|
|
+ foreach ($list as $dbRow) {
|
|
|
+ if (!is_array($dbRow) || !isset($dbRow['scydgy_id'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $byId[(int)$dbRow['scydgy_id']] = $dbRow;
|
|
|
+ }
|
|
|
+ foreach ($rows as &$rw) {
|
|
|
+ if (!is_array($rw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $sid = (int)($rw['ID'] ?? 0);
|
|
|
+ if ($sid <= 0 || !isset($byId[$sid])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $db = $byId[$sid];
|
|
|
+ if (array_key_exists('This_quantity', $db) && $db['This_quantity'] !== null && $db['This_quantity'] !== '') {
|
|
|
+ $rw['This_quantity'] = $db['This_quantity'];
|
|
|
+ }
|
|
|
+ if (array_key_exists('ceilingPrice', $db) && $db['ceilingPrice'] !== null && $db['ceilingPrice'] !== '') {
|
|
|
+ $rw['ceilingPrice'] = $db['ceilingPrice'];
|
|
|
+ } elseif (array_key_exists('ceiling_price', $db) && $db['ceiling_price'] !== null && $db['ceiling_price'] !== '') {
|
|
|
+ $rw['ceilingPrice'] = $db['ceiling_price'];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($rw);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 采购确认:明细选中 status=1、未选 status=0;同时将 purchase_order 主表 status 置为 1
|
|
|
+ */
|
|
|
+ public function purchaseConfirmPick()
|
|
|
+ {
|
|
|
+ if (!$this->request->isPost() || !$this->request->isAjax()) {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+ $scydgyId = trim((string)$this->request->post('scydgy_id', ''));
|
|
|
+ $sid = (int)$scydgyId;
|
|
|
+ if ($sid <= 0) {
|
|
|
+ $this->error('参数无效');
|
|
|
+ }
|
|
|
+
|
|
|
+ $parseIdList = function ($raw) {
|
|
|
+ if (is_array($raw)) {
|
|
|
+ $arr = $raw;
|
|
|
+ } else {
|
|
|
+ $decoded = json_decode((string)$raw, true);
|
|
|
+ $arr = is_array($decoded) ? $decoded : [];
|
|
|
+ }
|
|
|
+
|
|
|
+ return array_values(array_unique(array_filter(array_map('intval', $arr))));
|
|
|
+ };
|
|
|
+
|
|
|
+ $selectedRaw = $this->request->post('selected_ids', null);
|
|
|
+ if ($selectedRaw === null || $selectedRaw === '') {
|
|
|
+ $legacy = (int)$this->request->post('selected_id', 0);
|
|
|
+ $selectedIds = $legacy > 0 ? [$legacy] : [];
|
|
|
+ } else {
|
|
|
+ $selectedIds = $parseIdList($selectedRaw);
|
|
|
+ }
|
|
|
+
|
|
|
+ $unselectedIds = $parseIdList($this->request->post('unselected_ids', '[]'));
|
|
|
+
|
|
|
+ if ($selectedIds === []) {
|
|
|
+ $this->error('请至少勾选一条明细');
|
|
|
+ }
|
|
|
+
|
|
|
+ $inter = array_intersect($selectedIds, $unselectedIds);
|
|
|
+ if ($inter !== []) {
|
|
|
+ $this->error('选中与未选中 ID 不能重复');
|
|
|
+ }
|
|
|
+
|
|
|
+ $allIds = $this->purchaseOrderDetail($sid);
|
|
|
+ if ($allIds === []) {
|
|
|
+ $this->error('未找到该工序行的下发明细');
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($selectedIds as $id) {
|
|
|
+ if (!in_array($id, $allIds, true)) {
|
|
|
+ $this->error('选中 ID 不属于当前工序行');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ foreach ($unselectedIds as $id) {
|
|
|
+ if (!in_array($id, $allIds, true)) {
|
|
|
+ $this->error('未选中 ID 不属于当前工序行');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $union = array_values(array_unique(array_merge($selectedIds, $unselectedIds)));
|
|
|
+ sort($union, SORT_NUMERIC);
|
|
|
+ $expect = $allIds;
|
|
|
+ sort($expect, SORT_NUMERIC);
|
|
|
+ if ($union !== $expect) {
|
|
|
+ $this->error('请提交当前工序下全部明细 ID(选中 + 未选中)');
|
|
|
+ }
|
|
|
+
|
|
|
+ $purchaseOrderId = (int)$this->request->post('purchase_order_id', 0);
|
|
|
+
|
|
|
+ Db::startTrans();
|
|
|
+ try {
|
|
|
+ Db::table('purchase_order_detail')->where('scydgy_id', $sid)->update(['status' => 0]);
|
|
|
+ $aff = 0;
|
|
|
+ try {
|
|
|
+ $aff = (int)Db::table('purchase_order_detail')->where('scydgy_id', $sid)->where('id', 'in', $selectedIds)->update(['status' => 1]);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $aff = (int)Db::table('purchase_order_detail')->where('scydgy_id', $sid)->where('ID', 'in', $selectedIds)->update(['status' => 1]);
|
|
|
+ }
|
|
|
+ if ($aff < 1) {
|
|
|
+ throw new \Exception('更新选中状态失败');
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($purchaseOrderId > 0) {
|
|
|
+ $poAff = (int)Db::table('purchase_order')
|
|
|
+ ->where('id', $purchaseOrderId)
|
|
|
+ ->where('scydgy_id', $sid)
|
|
|
+ ->update(['status' => 1]);
|
|
|
+ if ($poAff < 1) {
|
|
|
+ throw new \Exception('主表订单不存在或与工序行不匹配');
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $poAff = (int)Db::table('purchase_order')->where('scydgy_id', $sid)->update(['status' => 1]);
|
|
|
+ if ($poAff < 1) {
|
|
|
+ throw new \Exception('未找到');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Db::commit();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Db::rollback();
|
|
|
+ $this->error($e->getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ $poIdFinal = $purchaseOrderId;
|
|
|
+ if ($poIdFinal <= 0) {
|
|
|
+ try {
|
|
|
+ $rpo = Db::table('purchase_order')->where('scydgy_id', $sid)->find();
|
|
|
+ if (is_array($rpo)) {
|
|
|
+ $poIdFinal = (int)($rpo['id'] ?? $rpo['ID'] ?? 0);
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $poIdFinal = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $pickNames = [];
|
|
|
+ foreach ($selectedIds as $pid) {
|
|
|
+ $dr = $this->purchaseOrderDetai($sid, $pid);
|
|
|
+ $nm = trim((string)($dr['company_name'] ?? ''));
|
|
|
+ $pickNames[] = $nm !== '' ? $nm : ('明细#' . $pid);
|
|
|
+ }
|
|
|
+ $this->addOrderLog(
|
|
|
+ $sid,
|
|
|
+ 'purchase_confirm',
|
|
|
+ '采购确认:已选中供应商「' . implode('、', $pickNames) . '」;',
|
|
|
+ $poIdFinal > 0 ? $poIdFinal : null
|
|
|
+ );
|
|
|
+
|
|
|
+ $ccydh = '';
|
|
|
+ $cyjmc = '';
|
|
|
+ try {
|
|
|
+ $po = Db::table('purchase_order')->where('scydgy_id', $sid)->find();
|
|
|
+ if (is_array($po)) {
|
|
|
+ $ccydh = trim((string)($po['CCYDH'] ?? ''));
|
|
|
+ $cyjmc = trim((string)($po['CYJMC'] ?? ''));
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ }
|
|
|
+ if ($ccydh === '' || $cyjmc === '') {
|
|
|
+ $any = $this->purchaseOrderDetai($sid, $selectedIds[0] ?? 0);
|
|
|
+ if (is_array($any)) {
|
|
|
+ if ($ccydh === '') {
|
|
|
+ $ccydh = trim((string)($any['CCYDH'] ?? ''));
|
|
|
+ }
|
|
|
+ if ($cyjmc === '') {
|
|
|
+ $cyjmc = trim((string)($any['CYJMC'] ?? ''));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $sendSmsSafe = function ($phone, $content) {
|
|
|
+ $phone = trim((string)$phone);
|
|
|
+ if ($phone === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $this->smsbao($phone, $content);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('采购确认短信失败 phone=' . $phone . ' ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ //批量发送手机短信:采购确认通过
|
|
|
+ foreach ($selectedIds as $pid) {
|
|
|
+ $dr = $this->purchaseOrderDetai($sid, $pid);
|
|
|
+ if (!is_array($dr)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $cname = trim((string)($dr['company_name'] ?? ''));
|
|
|
+
|
|
|
+ $sms = "您好,{$cname}:\n\n"
|
|
|
+ . "您参与的外发加工订单采购确认结果:已通过。\n"
|
|
|
+ . "订单号:{$ccydh}\n"
|
|
|
+ . "印件名称:{$cyjmc}\n\n";
|
|
|
+ $sendSmsSafe((string)($dr['phone'] ?? ''), $sms);
|
|
|
+ }
|
|
|
+ //批量发送手机短信:采购确认未通过
|
|
|
+ foreach ($unselectedIds as $pid) {
|
|
|
+ $dr = $this->purchaseOrderDetai($sid, $pid);
|
|
|
+ if (!is_array($dr)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $cname = trim((string)($dr['company_name'] ?? ''));
|
|
|
+ $sms = "您好,{$cname}:\n\n"
|
|
|
+ . "您参与的外发加工订单采购确认结果:未通过。\n"
|
|
|
+ . "订单号:{$ccydh}\n"
|
|
|
+ . "印件名称:{$cyjmc}\n\n";
|
|
|
+ $sendSmsSafe((string)($dr['phone'] ?? ''), $sms);
|
|
|
+ }
|
|
|
+
|
|
|
+ $line = sprintf(
|
|
|
+ 'purchaseConfirmPick scydgy_id=%s purchase_order_id=%d selected_ids=%s unselected_ids=%s',
|
|
|
+ $scydgyId,
|
|
|
+ $purchaseOrderId,
|
|
|
+ json_encode($selectedIds, JSON_UNESCAPED_UNICODE),
|
|
|
+ json_encode($unselectedIds, JSON_UNESCAPED_UNICODE)
|
|
|
+ );
|
|
|
+ Log::write($line, 'notice');
|
|
|
+
|
|
|
+ $pdfPublicPath = '';
|
|
|
+ try {
|
|
|
+ $pdfPublicPath = (string)$this->savePurchaseConfirmDetailPdf($sid, $poIdFinal);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('采购确认PDF异常: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->success('操作成功', '', [
|
|
|
+ 'scydgy_id' => $scydgyId,
|
|
|
+ 'purchase_order_id' => $purchaseOrderId,
|
|
|
+ 'selected_ids' => $selectedIds,
|
|
|
+ 'unselected_ids' => $unselectedIds,
|
|
|
+ 'purchase_confirm_pdf' => $pdfPublicPath,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return int[]
|
|
|
+ */
|
|
|
+ protected function purchaseOrderDetail(int $scydgyId): array
|
|
|
+ {
|
|
|
+ if ($scydgyId <= 0) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $list = Db::table('purchase_order_detail')->where('scydgy_id', $scydgyId)->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ if (!is_array($list)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ $ids = [];
|
|
|
+ foreach ($list as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $pk = (int)($r['id'] ?? $r['ID'] ?? 0);
|
|
|
+ if ($pk > 0) {
|
|
|
+ $ids[] = $pk;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return array_values(array_unique($ids));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前登录用户信息 [id, 展示名]
|
|
|
+ */
|
|
|
+ protected function GetUseName(): array
|
|
|
+ {
|
|
|
+ $id = 0;
|
|
|
+ $name = '';
|
|
|
+ try {
|
|
|
+ if ($this->auth && $this->auth->isLogin()) {
|
|
|
+ $u = $this->auth->getUserInfo();
|
|
|
+ if (is_array($u)) {
|
|
|
+ $id = (int)($u['id'] ?? 0);
|
|
|
+ $name = trim((string)($u['nickname'] ?? ''));
|
|
|
+ if ($name === '') {
|
|
|
+ $name = trim((string)($u['username'] ?? ''));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ }
|
|
|
+ if ($name === '') {
|
|
|
+ $name = '未知用户';
|
|
|
+ }
|
|
|
+
|
|
|
+ return [$id, $name];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 外发采购操作日志(表未建时仅写 runtime 日志,不中断业务)
|
|
|
+ */
|
|
|
+ protected function addOrderLog(int $scydgyId, string $action, string $content, ?int $purchaseOrderId = null): void
|
|
|
+ {
|
|
|
+ if ($scydgyId <= 0 || $content === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ list($adminId, $adminName) = $this->GetUseName();
|
|
|
+ $cut = function ($s, $max) {
|
|
|
+ if (function_exists('mb_substr')) {
|
|
|
+ return mb_substr($s, 0, $max, 'UTF-8');
|
|
|
+ }
|
|
|
+ return strlen($s) <= $max ? $s : substr($s, 0, $max);
|
|
|
+ };
|
|
|
+ try {
|
|
|
+ Db::table('purchase_order_oper_log')->insert([
|
|
|
+ 'scydgy_id' => $scydgyId,
|
|
|
+ 'purchase_order_id' => $purchaseOrderId,
|
|
|
+ 'admin_id' => $adminId,
|
|
|
+ 'admin_name' => $cut($adminName, 64),
|
|
|
+ 'action' => $cut((string)$action, 64),
|
|
|
+ 'content' => $cut($content, 1000),
|
|
|
+ // 表结构 createtime 为 int Unix 时间戳;日期字符串会导致插入失败或时间为 0,详情「操作记录」为空
|
|
|
+ 'createtime' => time(),
|
|
|
+ ]);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('procuremen addOrderLog: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按主键取一条
|
|
|
+ */
|
|
|
+ protected function purchaseOrderDetai(int $scydgyId, int $pk): array
|
|
|
+ {
|
|
|
+ if ($scydgyId <= 0 || $pk <= 0) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $one = Db::table('purchase_order_detail')->where('scydgy_id', $scydgyId)->where('id', $pk)->find();
|
|
|
+ if (is_array($one)) {
|
|
|
+ return $one;
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $one = Db::table('purchase_order_detail')->where('scydgy_id', $scydgyId)->where('ID', $pk)->find();
|
|
|
+ if (is_array($one)) {
|
|
|
+ return $one;
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 主表/明细时间字段统一为可读字符串(用于详情进度)
|
|
|
+ */
|
|
|
+ protected function formatProcuremenDetailTime($v): string
|
|
|
+ {
|
|
|
+ if ($v === null || $v === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ if (is_numeric($v) && (int)$v > 946684800) {
|
|
|
+ return date('Y-m-d H:i:s', (int)$v);
|
|
|
+ }
|
|
|
+ $s = trim((string)$v);
|
|
|
+ if ($s !== '' && stripos($s, '0000-00-00') !== 0) {
|
|
|
+ return $s;
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 交货日期仅展示 YYYY-MM-DD(详情表、列表展示用)
|
|
|
+ */
|
|
|
+ protected function formatDeliveryYmd($v): string
|
|
|
+ {
|
|
|
+ if ($v === null || $v === '') {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ $s = trim((string)$v);
|
|
|
+ if ($s === '' || preg_match('/^0000-00-00/i', $s)) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $s, $m)) {
|
|
|
+ return $m[1];
|
|
|
+ }
|
|
|
+ $ts = strtotime($s);
|
|
|
+
|
|
|
+ return ($ts !== false && $ts > 0) ? date('Y-m-d', $ts) : $s;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 供应商已接单:金额与交货日期均已填写(与列表汇总逻辑一致)
|
|
|
+ */
|
|
|
+ protected function detailRowSupplierAccepted(array $r): bool
|
|
|
+ {
|
|
|
+ $am = $r['amount'] ?? null;
|
|
|
+ $okAmt = $am !== null && $am !== '' && !(is_string($am) && trim($am) === '');
|
|
|
+ $dv = isset($r['delivery']) ? trim((string)$r['delivery']) : '';
|
|
|
+ $okDel = $dv !== '' && !preg_match('/^0000-00-00/i', $dv);
|
|
|
+
|
|
|
+ return $okAmt && $okDel;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 详情弹层:进度步骤 + 订单摘要 + 下发明细(已下发 / 已完结均可用)
|
|
|
+ *
|
|
|
+ * @param array $main purchase_order 一行
|
|
|
+ * @param array $details purchase_order_detail 多行(已预处理 createtime_text)
|
|
|
+ * @return array{steps: array, orderSummary: array, orderSummaryGrid: array, detailRows: array}
|
|
|
+ */
|
|
|
+ protected function buildProcuremenDetailsViewData(array $main, array $details): array
|
|
|
+ {
|
|
|
+ $issueCnt = count($details);
|
|
|
+ $acceptCnt = 0;
|
|
|
+ $pickedName = '';
|
|
|
+ $pickedTime = '';
|
|
|
+ foreach ($details as $dr) {
|
|
|
+ if (!is_array($dr)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($this->detailRowSupplierAccepted($dr)) {
|
|
|
+ $acceptCnt++;
|
|
|
+ }
|
|
|
+ if ((int)($dr['status'] ?? 0) === 1) {
|
|
|
+ if ($pickedName === '') {
|
|
|
+ $pickedName = trim((string)($dr['company_name'] ?? ''));
|
|
|
+ }
|
|
|
+ if ($pickedTime === '') {
|
|
|
+ $pickedTime = trim((string)($dr['createtime_text'] ?? ''));
|
|
|
+ if ($pickedTime === '') {
|
|
|
+ $pickedTime = $this->formatProcuremenDetailTime($dr['createtime'] ?? null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $hasMain = $main !== [];
|
|
|
+ $poTime = $this->formatProcuremenDetailTime($main['createtime'] ?? null);
|
|
|
+ $mainStatus = isset($main['status']) ? (int)$main['status'] : 0;
|
|
|
+
|
|
|
+ $supplierTime = '';
|
|
|
+ if ($acceptCnt > 0) {
|
|
|
+ foreach ($details as $dr) {
|
|
|
+ if (!is_array($dr) || !$this->detailRowSupplierAccepted($dr)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $t = $this->formatProcuremenDetailTime($dr['updatetime'] ?? $dr['createtime'] ?? null);
|
|
|
+ if ($t !== '' && ($supplierTime === '' || strcmp($t, $supplierTime) < 0)) {
|
|
|
+ $supplierTime = $t;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $doneTime = '';
|
|
|
+ if ($mainStatus === 1) {
|
|
|
+ $doneTime = $this->formatProcuremenDetailTime($main['updatetime'] ?? null);
|
|
|
+ if ($doneTime === '') {
|
|
|
+ foreach ($details as $dr) {
|
|
|
+ if (!is_array($dr)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $t = $this->formatProcuremenDetailTime($dr['updatetime'] ?? $dr['createtime'] ?? null);
|
|
|
+ if ($t !== '' && ($doneTime === '' || strcmp($t, $doneTime) > 0)) {
|
|
|
+ $doneTime = $t;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($doneTime === '') {
|
|
|
+ $doneTime = $poTime;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $step1Done = $hasMain;
|
|
|
+ $step2Done = $hasMain;
|
|
|
+ $step3Done = $acceptCnt > 0;
|
|
|
+ $step4Done = false;
|
|
|
+ foreach ($details as $dr) {
|
|
|
+ if (is_array($dr) && (int)($dr['status'] ?? 0) === 1) {
|
|
|
+ $step4Done = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $step5Done = $mainStatus === 1;
|
|
|
+
|
|
|
+ // 主表已完结:中间环节按业务视为已全部完成(不显示灰色「未到达」)
|
|
|
+ if ($mainStatus === 1) {
|
|
|
+ $step3Done = true;
|
|
|
+ $step4Done = true;
|
|
|
+ if ($supplierTime === '') {
|
|
|
+ $fillT = $doneTime !== '' ? $doneTime : $poTime;
|
|
|
+ if ($fillT !== '') {
|
|
|
+ $supplierTime = $fillT;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($pickedTime === '') {
|
|
|
+ $fillT = $doneTime !== '' ? $doneTime : $poTime;
|
|
|
+ if ($fillT !== '') {
|
|
|
+ $pickedTime = $fillT;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $steps = [
|
|
|
+ [
|
|
|
+ 'title' => '未发',
|
|
|
+ 'subtitle' => '',
|
|
|
+ 'time' => $step1Done ? '—' : '',
|
|
|
+ 'done' => $step1Done,
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ 'title' => '已发未结束',
|
|
|
+ 'subtitle' => '',
|
|
|
+ 'time' => $step2Done ? $poTime : '',
|
|
|
+ 'done' => $step2Done,
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ 'title' => '供应商接单',
|
|
|
+ 'subtitle' => '下发 ' . $issueCnt . ' / 接单 ' . $acceptCnt,
|
|
|
+ 'time' => $step3Done ? $supplierTime : '',
|
|
|
+ 'done' => $step3Done,
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ 'title' => '采购确认',
|
|
|
+ 'subtitle' => $pickedName !== '' ? ('选中供应商:' . $pickedName) : '',
|
|
|
+ 'time' => $step4Done ? ($pickedTime !== '' ? $pickedTime : '') : '',
|
|
|
+ 'done' => $step4Done,
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ 'title' => '已完结',
|
|
|
+ 'subtitle' => '',
|
|
|
+ 'time' => $step5Done ? $doneTime : '',
|
|
|
+ 'done' => $step5Done,
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+
|
|
|
+ $currentIdx = count($steps) - 1;
|
|
|
+ foreach ($steps as $i => $s) {
|
|
|
+ if (!$s['done']) {
|
|
|
+ $currentIdx = $i;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $nSteps = count($steps);
|
|
|
+ foreach ($steps as $idx => &$s) {
|
|
|
+ $s['current'] = ($idx === $currentIdx);
|
|
|
+ if ($idx === 0) {
|
|
|
+ $s['pdf_left_bg'] = '';
|
|
|
+ } else {
|
|
|
+ $s['pdf_left_bg'] = !empty($steps[$idx - 1]['done']) ? '#1890ff' : '#e0e0e0';
|
|
|
+ }
|
|
|
+ if ($idx >= $nSteps - 1) {
|
|
|
+ $s['pdf_right_bg'] = '';
|
|
|
+ } else {
|
|
|
+ $s['pdf_right_bg'] = !empty($s['done']) ? '#1890ff' : '#e0e0e0';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($s);
|
|
|
+
|
|
|
+ $labelMap = [
|
|
|
+ 'CCYDH' => '订单号',
|
|
|
+ 'CYJMC' => '印件名称',
|
|
|
+ 'CDXMC' => '订单名称',
|
|
|
+ 'CGYMC' => '工序名称',
|
|
|
+ 'CGYBH' => '工序编号',
|
|
|
+ 'CDW' => '单位',
|
|
|
+ 'NGZL' => '工作量',
|
|
|
+ 'CDF' => '单价',
|
|
|
+ 'cGzzxMc' => '工作中心',
|
|
|
+ 'MBZ' => '备注',
|
|
|
+ 'This_quantity' => '本次数量',
|
|
|
+ 'ceilingPrice' => '最高限价',
|
|
|
+ ];
|
|
|
+ $orderSummary = [];
|
|
|
+ foreach ($labelMap as $key => $lab) {
|
|
|
+ if (!array_key_exists($key, $main)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $val = $main[$key];
|
|
|
+ if ($val === null) {
|
|
|
+ $val = '';
|
|
|
+ } elseif (!is_scalar($val)) {
|
|
|
+ $val = json_encode($val, JSON_UNESCAPED_UNICODE);
|
|
|
+ } else {
|
|
|
+ $val = (string)$val;
|
|
|
+ }
|
|
|
+ $orderSummary[] = ['label' => $lab, 'value' => $val];
|
|
|
+ }
|
|
|
+
|
|
|
+ $orderSummaryGrid = [];
|
|
|
+ $n = count($orderSummary);
|
|
|
+ for ($i = 0; $i < $n; $i += 2) {
|
|
|
+ $left = $orderSummary[$i];
|
|
|
+ $hasRight = ($i + 1) < $n;
|
|
|
+ $orderSummaryGrid[] = [
|
|
|
+ 'l1' => $left['label'],
|
|
|
+ 'v1' => $left['value'],
|
|
|
+ 'l2' => $hasRight ? $orderSummary[$i + 1]['label'] : '',
|
|
|
+ 'v2' => $hasRight ? $orderSummary[$i + 1]['value'] : '',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'steps' => $steps,
|
|
|
+ 'orderSummary' => $orderSummary,
|
|
|
+ 'orderSummaryGrid' => $orderSummaryGrid,
|
|
|
+ 'detailRows' => $details,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载并 assign 外发采购「详情」弹窗所需变量(与 {@see details()} 页面一致,供模板 / PDF 共用)。
|
|
|
+ *
|
|
|
+ * @return array{ok: bool, ccydh: string} ok 表示存在主表或至少一条下发明细(否则 PDF 无可写内容)
|
|
|
+ */
|
|
|
+ protected function prepareProcuremenDetailsView(string $ids): array
|
|
|
+ {
|
|
|
+ $main = [];
|
|
|
+ try {
|
|
|
+ $one = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+ $main = is_array($one) ? $one : [];
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $main = [];
|
|
|
+ }
|
|
|
+ $details = [];
|
|
|
+ try {
|
|
|
+ $details = Db::table('purchase_order_detail')->where('scydgy_id', $ids)->order('id', 'asc')->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $details = [];
|
|
|
+ }
|
|
|
+ if (!is_array($details)) {
|
|
|
+ $details = [];
|
|
|
+ }
|
|
|
+ foreach ($details as &$r) {
|
|
|
+ if (is_array($r) && isset($r['ID']) && !isset($r['id'])) {
|
|
|
+ $r['id'] = $r['ID'];
|
|
|
+ }
|
|
|
+ if (isset($r['createtime'])) {
|
|
|
+ if (is_numeric($r['createtime']) && (int)$r['createtime'] > 946684800) {
|
|
|
+ $r['createtime_text'] = date('Y-m-d H:i:s', (int)$r['createtime']);
|
|
|
+ } else {
|
|
|
+ $r['createtime_text'] = (string)$r['createtime'];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $r['createtime_text'] = '';
|
|
|
+ }
|
|
|
+ $r['delivery_ymd'] = $this->formatDeliveryYmd($r['delivery'] ?? null);
|
|
|
+ }
|
|
|
+ unset($r);
|
|
|
+
|
|
|
+ $operLogs = [];
|
|
|
+ try {
|
|
|
+ $operLogs = Db::table('purchase_order_oper_log')->where('scydgy_id', $ids)->order('id', 'asc')->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $operLogs = [];
|
|
|
+ }
|
|
|
+ if (!is_array($operLogs)) {
|
|
|
+ $operLogs = [];
|
|
|
+ }
|
|
|
+ foreach ($operLogs as &$lg) {
|
|
|
+ if (!is_array($lg)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $ct = isset($lg['createtime']) ? (int)$lg['createtime'] : 0;
|
|
|
+ $lg['createtime_text'] = $ct > 946684800 ? date('Y-m-d H:i:s', $ct) : '';
|
|
|
+ }
|
|
|
+ unset($lg);
|
|
|
+
|
|
|
+ $bundle = $this->buildProcuremenDetailsViewData($main, $details);
|
|
|
+ $ccydh = isset($main['CCYDH']) ? trim((string)$main['CCYDH']) : '';
|
|
|
+ if ($ccydh === '') {
|
|
|
+ foreach ($details as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $ccydh = trim((string)($r['CCYDH'] ?? ''));
|
|
|
+ if ($ccydh !== '') {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->view->assign('ccydh', $ccydh);
|
|
|
+ $this->view->assign('steps', $bundle['steps']);
|
|
|
+ $this->view->assign('orderSummary', $bundle['orderSummary']);
|
|
|
+ $this->view->assign('orderSummaryGrid', $bundle['orderSummaryGrid']);
|
|
|
+ $this->view->assign('detailRows', $bundle['detailRows']);
|
|
|
+ $this->view->assign('operLogs', $operLogs);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'ok' => $main !== [] || $details !== [],
|
|
|
+ 'ccydh' => $ccydh,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 详情弹层:状态进度 + 订单信息 + 下发明细(列表「已下发」「已完结」均可打开)
|
|
|
+ */
|
|
|
+ public function details()
|
|
|
+ {
|
|
|
+ $ids = $this->request->param('ids', $this->request->param('id', ''));
|
|
|
+ if (is_array($ids)) {
|
|
|
+ $ids = isset($ids[0]) ? $ids[0] : '';
|
|
|
+ }
|
|
|
+ $ids = trim((string)$ids);
|
|
|
+ if ($ids === '') {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+ $this->prepareProcuremenDetailsView($ids);
|
|
|
+
|
|
|
+ /* 弹层内不套 default 布局,避免出现「控制台 / Control panel」整块标题区 */
|
|
|
+ $restoreLayout = !empty($this->layout) ? ('layout/' . $this->layout) : false;
|
|
|
+ $this->view->engine->layout(false);
|
|
|
+ try {
|
|
|
+ return $this->view->fetch('procuremen/details_dialog_shell');
|
|
|
+ } finally {
|
|
|
+ if ($restoreLayout) {
|
|
|
+ $this->view->engine->layout($restoreLayout);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 审核弹窗提交:GET 打开弹窗;POST 保存下发(主表 + 明细 + 操作日志)
|
|
|
+ */
|
|
|
+ public function review()
|
|
|
+ {
|
|
|
+ if ($this->request->isPost()) {
|
|
|
+ $rowJson = $this->request->post('row_json', '');
|
|
|
+ $companiesJson = $this->request->post('companies_json', '[]');
|
|
|
+ $row = json_decode($rowJson, true);
|
|
|
+ if (!is_array($row)) {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+
|
|
|
+ $companies = json_decode($companiesJson, true);
|
|
|
+ if (!is_array($companies) || count($companies) === 0) {
|
|
|
+ $this->error('请至少选择一个下发单位');
|
|
|
+ }
|
|
|
+
|
|
|
+ $toDb = function ($value) {
|
|
|
+ if ($value === null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (is_scalar($value)) {
|
|
|
+ return $value;
|
|
|
+ }
|
|
|
+
|
|
|
+ return json_encode($value, JSON_UNESCAPED_UNICODE);
|
|
|
+ };
|
|
|
+
|
|
|
+ Db::startTrans();
|
|
|
+ try {
|
|
|
+ $ids = (int)($row['ID'] ?? $row['id'] ?? 0);
|
|
|
+ if ($ids <= 0) {
|
|
|
+ throw new \Exception('无效的行主键 ID');
|
|
|
+ }
|
|
|
+
|
|
|
+ $exists = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+ $data = [
|
|
|
+ 'scydgy_id' => $ids,
|
|
|
+ 'CCYDH' => $row['CCYDH'] ?? null,
|
|
|
+ 'CYJMC' => $row['CYJMC'] ?? null,
|
|
|
+ 'CDXMC' => $row['CDXMC'] ?? null,
|
|
|
+ 'CGYBH' => $row['CGYBH'] ?? null,
|
|
|
+ 'CGYMC' => $row['CGYMC'] ?? null,
|
|
|
+ 'CDW' => $row['CDW'] ?? null,
|
|
|
+ 'NGZL' => $row['NGZL'] ?? null,
|
|
|
+ 'CDF' => $row['CDF'] ?? null,
|
|
|
+ 'cGzzxMc' => $row['cGzzxMc'] ?? null,
|
|
|
+ 'MBZ' => $row['MBZ'] ?? null,
|
|
|
+ 'bwjg' => $row['bwjg'] ?? null,
|
|
|
+ 'iStatus' => $row['iStatus'] ?? null,
|
|
|
+ 'dStamp' => $row['dStamp'] ?? null,
|
|
|
+ 'dputrecord' => $row['dputrecord'] ?? null,
|
|
|
+ 'cywyxm' => $row['cywyxm'] ?? null,
|
|
|
+ 'This_quantity' => $row['This_quantity'] ?? $row['this_quantity'] ?? null,
|
|
|
+ 'ceilingPrice' => $row['ceilingPrice'] ?? $row['ceiling_price'] ?? null,
|
|
|
+ 'status' => 0,
|
|
|
+ ];
|
|
|
+
|
|
|
+ if ($exists) {
|
|
|
+ $upd = $data;
|
|
|
+ unset($upd['scydgy_id']);
|
|
|
+ Db::table('purchase_order')->where('scydgy_id', $ids)->update($upd);
|
|
|
+ } else {
|
|
|
+ $data['createtime'] = date('Y-m-d H:i:s');
|
|
|
+ Db::table('purchase_order')->insert($data);
|
|
|
+ }
|
|
|
+
|
|
|
+ $row['scydgy_id'] = (int)($row['ID'] ?? $row['id'] ?? 0);
|
|
|
+ unset($row['ID'], $row['id'], $row['_iss_out'], $row['ids'], $row['purchase_order_id']);
|
|
|
+
|
|
|
+ foreach ($companies as $c) {
|
|
|
+ if (!is_array($c)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $one = [
|
|
|
+ 'scydgy_id' => $toDb($row['scydgy_id']),
|
|
|
+ 'CCYDH' => $toDb($row['CCYDH'] ?? null),
|
|
|
+ 'CYJMC' => $toDb($row['CYJMC'] ?? null),
|
|
|
+ 'company_name' => isset($c['name']) ? (string)$c['name'] : null,
|
|
|
+ 'email' => isset($c['email']) ? (string)$c['email'] : null,
|
|
|
+ 'phone' => isset($c['phone']) ? (string)$c['phone'] : null,
|
|
|
+ 'createtime' => date('Y-m-d H:i:s'),
|
|
|
+ 'status' => 0,
|
|
|
+ 'status_name' => "未提交",
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 先校验邮箱与手机号,再写入明细(写入后根据主键生成手机端直达链接 focus_eid)
|
|
|
+ $toEmail = isset($c['email']) ? trim((string)$c['email']) : '';
|
|
|
+ $companyName = isset($c['name']) ? trim((string)$c['name']) : '外协单位';
|
|
|
+
|
|
|
+ if ($toEmail === '' || !filter_var($toEmail, FILTER_VALIDATE_EMAIL)) {
|
|
|
+ throw new \Exception($companyName . ' 未填写有效邮箱,无法发送邮件,未写入数据');
|
|
|
+ }
|
|
|
+
|
|
|
+ $phone = isset($c['phone']) ? trim((string)$c['phone']) : '';
|
|
|
+ if ($phone === '') {
|
|
|
+ $cname = isset($c['name']) ? trim((string)$c['name']) : '';
|
|
|
+ throw new \Exception(($cname !== '' ? $cname : '外协单位') . ' 未填写手机号,无法发送短信,未写入数据');
|
|
|
+ }
|
|
|
+
|
|
|
+ Db::table('purchase_order_detail')->insert($one);
|
|
|
+ $detailId = (int)Db::getLastInsID();
|
|
|
+ $mprocUrl = $this->buildMprocMobileOrderUrl($detailId);
|
|
|
+ $mprocUrlEsc = htmlspecialchars($mprocUrl, ENT_QUOTES, 'UTF-8');
|
|
|
+
|
|
|
+ // 发送邮箱
|
|
|
+ $mailConfig = Config::get('Mailer');
|
|
|
+ if (!is_array($mailConfig) || empty($mailConfig['host']) || empty($mailConfig['addr'])) {
|
|
|
+ throw new \Exception('邮件未配置');
|
|
|
+ }
|
|
|
+ $mail = new PHPMailer(true);
|
|
|
+ $mail->isSMTP();
|
|
|
+ $mail->Host = $mailConfig['host'];
|
|
|
+ $mail->SMTPAuth = true;
|
|
|
+ $mail->Username = $mailConfig['addr'];
|
|
|
+ $mail->Password = $mailConfig['pass'];
|
|
|
+ $mail->SMTPSecure = $mailConfig['security'];
|
|
|
+ $mail->Port = $mailConfig['port'];
|
|
|
+ $mail->CharSet = $mailConfig['charset'];
|
|
|
+
|
|
|
+ $mail->setFrom($mailConfig['addr'], $mailConfig['name']);
|
|
|
+ $mail->addAddress($toEmail, $companyName);
|
|
|
+
|
|
|
+ $mail->isHTML(true);
|
|
|
+ $mail->Subject = '【外发加工订单通知】';
|
|
|
+ $mail->Body = "
|
|
|
+ 您好,{$companyName}:<br><br>
|
|
|
+ 您有新的外发加工订单待处理:<br>
|
|
|
+ 订单号:{$row['CCYDH']}<br>
|
|
|
+ 印件名称:{$row['CYJMC']}<br><br>
|
|
|
+ 请点击下面链接,登录后可直接打开本条订单(无需再搜索):<br>
|
|
|
+ <a href=\"{$mprocUrlEsc}\">{$mprocUrlEsc}</a><br><br>
|
|
|
+ 登录后可在手机端填写金额与交货日期。<br><br>
|
|
|
+ 请及时查收并处理,谢谢!
|
|
|
+ ";
|
|
|
+
|
|
|
+ $mail->send();
|
|
|
+
|
|
|
+ $ccydh = isset($row['CCYDH']) ? (string)$row['CCYDH'] : '';
|
|
|
+ $cyjmc = isset($row['CYJMC']) ? (string)$row['CYJMC'] : '';
|
|
|
+ $smsContent = "您好,{$companyName}:\n\n"
|
|
|
+ . "您有新的外发加工订单待处理:\n"
|
|
|
+ . "订单号:{$ccydh}\n"
|
|
|
+ . "印件名称:{$cyjmc}\n\n"
|
|
|
+ . "手机打开链接(登录后直达本条):\n"
|
|
|
+ . $mprocUrl . "\n\n"
|
|
|
+ . '请及时查收并处理,谢谢!';
|
|
|
+ $this->smsbao($phone, $smsContent);
|
|
|
+ }
|
|
|
+ Db::commit();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Db::rollback();
|
|
|
+ $this->error($e->getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ $poIdLog = null;
|
|
|
+ try {
|
|
|
+ $rpo = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+ if (is_array($rpo)) {
|
|
|
+ $tid = (int)($rpo['id'] ?? $rpo['ID'] ?? 0);
|
|
|
+ $poIdLog = $tid > 0 ? $tid : null;
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $poIdLog = null;
|
|
|
+ }
|
|
|
+ $names = [];
|
|
|
+ foreach ($companies as $c) {
|
|
|
+ if (!is_array($c)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $nm = trim((string)($c['name'] ?? $c['company_name'] ?? ''));
|
|
|
+ if ($nm !== '') {
|
|
|
+ $names[] = $nm;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $n = count($names);
|
|
|
+ $nameStr = $n ? implode('、', $names) : '(未查询到单位名称)';
|
|
|
+ $this->addOrderLog(
|
|
|
+ $ids,
|
|
|
+ 'review_issue',
|
|
|
+ '审核共选择 ' . $n . ' 家单位:' . $nameStr,
|
|
|
+ $poIdLog
|
|
|
+ );
|
|
|
+ $this->success('操作成功');
|
|
|
+ }
|
|
|
+
|
|
|
+ return $this->view->fetch();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 审核弹窗获取公司列表
|
|
|
+ */
|
|
|
+ public function reviewCompanies()
|
|
|
+ {
|
|
|
+ if (!$this->request->isAjax()) {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+
|
|
|
+ $list = [];
|
|
|
+ try {
|
|
|
+ $rows = Db::table('customer')->order('id', 'desc')->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $this->success('', '', []);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!is_array($rows)) {
|
|
|
+ $this->success('', '', []);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $detailColCandidates = [
|
|
|
+ 'detail', 'mingxi', 'remark', 'memo', 'notes', 'description',
|
|
|
+ 'company_detail', 'company_desc', 'address',
|
|
|
+ ];
|
|
|
+
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ if (!is_array($row)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $norm = [];
|
|
|
+ foreach ($row as $k => $v) {
|
|
|
+ $norm[is_string($k) ? strtolower($k) : $k] = $v;
|
|
|
+ }
|
|
|
+ $row = $norm;
|
|
|
+
|
|
|
+ $id = isset($row['id']) ? (string)$row['id'] : '';
|
|
|
+
|
|
|
+ $companyName = '';
|
|
|
+ foreach (['company_name', 'name'] as $nk) {
|
|
|
+ if (!empty($row[$nk])) {
|
|
|
+ $companyName = trim((string)$row[$nk]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $username = '';
|
|
|
+ foreach (['username', 'contact', 'linkman', 'contacts'] as $uk) {
|
|
|
+ if (isset($row[$uk]) && trim((string)$row[$uk]) !== '') {
|
|
|
+ $username = trim((string)$row[$uk]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $email = isset($row['email']) ? trim((string)$row['email']) : '';
|
|
|
+ $phone = isset($row['phone']) ? trim((string)$row['phone']) : '';
|
|
|
+
|
|
|
+ $category = '';
|
|
|
+ foreach (['company_type', 'category', 'type_name'] as $ck) {
|
|
|
+ if (isset($row[$ck]) && trim((string)$row[$ck]) !== '') {
|
|
|
+ $category = trim((string)$row[$ck]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $detail = '';
|
|
|
+ foreach ($detailColCandidates as $dk) {
|
|
|
+ if (!isset($row[$dk])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $dv = trim((string)$row[$dk]);
|
|
|
+ if ($dv !== '') {
|
|
|
+ $detail = $dv;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $list[] = [
|
|
|
+ 'id' => $id,
|
|
|
+ 'name' => $companyName,
|
|
|
+ 'company_name' => $companyName,
|
|
|
+ 'username' => $username,
|
|
|
+ 'email' => $email,
|
|
|
+ 'phone' => $phone,
|
|
|
+ 'category' => $category,
|
|
|
+ 'company_type' => $category,
|
|
|
+ 'detail' => $detail,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->success('', '', $list);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 采购确认-下发明细弹窗:
|
|
|
+ * 查 purchase_order_detail;ids 为工序行 scydgy.ID,对应明细 scydgy_id
|
|
|
+ */
|
|
|
+ public function outward_detail()
|
|
|
+ {
|
|
|
+ $ids = $this->request->param('ids', $this->request->param('id', ''));
|
|
|
+ if (is_array($ids)) {
|
|
|
+ $ids = isset($ids[0]) ? $ids[0] : '';
|
|
|
+ }
|
|
|
+ $ids = trim((string)$ids);
|
|
|
+ if ($ids === '') {
|
|
|
+ $this->error(__('Invalid parameters'));
|
|
|
+ }
|
|
|
+
|
|
|
+ $wffTab = trim((string)$this->request->param('wff_tab', 'all'));
|
|
|
+ if (!in_array($wffTab, ['all', 'pending', 'picked', 'done'], true)) {
|
|
|
+ $wffTab = 'all';
|
|
|
+ }
|
|
|
+ $headTitle = $wffTab === 'pending' ? '采购确认(明细)' : '下发明细';
|
|
|
+
|
|
|
+ $rows = [];
|
|
|
+ try {
|
|
|
+ $rows = Db::table('purchase_order_detail')->where('scydgy_id', $ids)->order('id', 'desc')->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $rows = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($rows as &$r) {
|
|
|
+ if (is_array($r) && isset($r['ID']) && !isset($r['id'])) {
|
|
|
+ $r['id'] = $r['ID'];
|
|
|
+ }
|
|
|
+ if (isset($r['createtime'])) {
|
|
|
+ if (is_numeric($r['createtime']) && (int)$r['createtime'] > 946684800) {
|
|
|
+ $r['createtime_text'] = date('Y-m-d H:i:s', (int)$r['createtime']);
|
|
|
+ } else {
|
|
|
+ $r['createtime_text'] = (string)$r['createtime'];
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ $r['createtime_text'] = '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ unset($r);
|
|
|
+
|
|
|
+ $purchaseOrderId = 0;
|
|
|
+ try {
|
|
|
+ $po = Db::table('purchase_order')->where('scydgy_id', $ids)->find();
|
|
|
+ if (is_array($po)) {
|
|
|
+ $purchaseOrderId = (int)($po['id'] ?? $po['ID'] ?? 0);
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $purchaseOrderId = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->view->assign('rows', $rows ?: []);
|
|
|
+ $this->view->assign('rowCount', count($rows ?: []));
|
|
|
+ $this->view->assign('scydgyId', $ids);
|
|
|
+ $this->view->assign('purchaseOrderId', $purchaseOrderId);
|
|
|
+ $this->view->assign('headTitle', $headTitle);
|
|
|
+ $this->view->assign('showPurchaseConfirm', $wffTab === 'pending' ? 1 : 0);
|
|
|
+ $this->view->assign('detailColspan', $wffTab === 'pending' ? 10 : 9);
|
|
|
+
|
|
|
+ /* 采购确认(pending)需在 iframe 内加载 require-backend,以便勾选与提交;其它 tab 仅只读表格,用轻量壳避免整站 JS/CSS 二次初始化导致弹窗极慢 */
|
|
|
+ if ($wffTab === 'pending') {
|
|
|
+ return $this->view->fetch();
|
|
|
+ }
|
|
|
+
|
|
|
+ $restoreLayout = !empty($this->layout) ? ('layout/' . $this->layout) : false;
|
|
|
+ $this->view->engine->layout(false);
|
|
|
+ try {
|
|
|
+ $bodyHtml = $this->view->fetch('procuremen/outward_detail');
|
|
|
+ $this->view->assign('dialogBody', $bodyHtml);
|
|
|
+
|
|
|
+ return $this->view->fetch('procuremen/outward_detail_lite_shell');
|
|
|
+ } finally {
|
|
|
+ if ($restoreLayout) {
|
|
|
+ $this->view->engine->layout($restoreLayout);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 月份外发明细导出
|
|
|
+ */
|
|
|
+ public function export_month_outward()
|
|
|
+ {
|
|
|
+ $this->request->filter(['strip_tags', 'trim']);
|
|
|
+ $ym = trim((string)$this->request->get('ym', date('Y-m')));
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ $ym = date('Y-m');
|
|
|
+ }
|
|
|
+ $monthStart = $ym . '-01 00:00:00';
|
|
|
+ $monthEnd = date('Y-m-t 23:59:59', strtotime($monthStart));
|
|
|
+ $unixStart = (int)strtotime($monthStart);
|
|
|
+ $unixEnd = (int)strtotime($monthEnd);
|
|
|
+
|
|
|
+ $rows = [];
|
|
|
+ try {
|
|
|
+ $rows = Db::table('purchase_order_detail')
|
|
|
+ ->whereRaw(
|
|
|
+ '(TRIM(CAST(createtime AS CHAR(64))) LIKE ?)'
|
|
|
+ . ' OR ((createtime REGEXP \'^[0-9]+$\') AND CAST(createtime AS UNSIGNED) BETWEEN ? AND ?)',
|
|
|
+ [$ym . '%', $unixStart, $unixEnd]
|
|
|
+ )
|
|
|
+ ->order('CCYDH', 'asc')
|
|
|
+ ->order('company_name', 'asc')
|
|
|
+ ->order('id', 'asc')
|
|
|
+ ->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $rows = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $groups = [];
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $k = (string)($r['CCYDH'] ?? '') . "\x1f" . (string)($r['company_name'] ?? '');
|
|
|
+ if (!isset($groups[$k])) {
|
|
|
+ $groups[$k] = [];
|
|
|
+ }
|
|
|
+ $groups[$k][] = $r;
|
|
|
+ }
|
|
|
+ ksort($groups, SORT_STRING);
|
|
|
+
|
|
|
+ $spreadsheet = new Spreadsheet();
|
|
|
+ $sheet = $spreadsheet->getActiveSheet();
|
|
|
+ $sheet->setTitle('外发明细');
|
|
|
+
|
|
|
+ $mon = (int)substr($ym, 5, 2);
|
|
|
+ $sheet->mergeCells('A1:H1');
|
|
|
+ $sheet->setCellValue('A1', $mon . '月外发明细');
|
|
|
+ $sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
|
|
|
+ $sheet->getStyle('A1')->getAlignment()
|
|
|
+ ->setHorizontal(Alignment::HORIZONTAL_CENTER)
|
|
|
+ ->setVertical(Alignment::VERTICAL_CENTER);
|
|
|
+
|
|
|
+ $headers = ['序号', '传票号', '传票名称', '外加工单位', '订法', '客户名称', '工序', '加工金额'];
|
|
|
+ $col = 'A';
|
|
|
+ foreach ($headers as $h) {
|
|
|
+ $sheet->setCellValue($col . '2', $h);
|
|
|
+ $col++;
|
|
|
+ }
|
|
|
+ $sheet->getStyle('A2:H2')->getFont()->setBold(true);
|
|
|
+ $sheet->getStyle('A2:H2')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
|
|
+
|
|
|
+ $rowNum = 3;
|
|
|
+ $sumSubtotalCounts = 0;
|
|
|
+ $grandAmount = 0.0;
|
|
|
+
|
|
|
+ if (empty($groups)) {
|
|
|
+ $sheet->mergeCells('A3:H3');
|
|
|
+ $sheet->setCellValue('A3', '(当月暂无外发明细)');
|
|
|
+ $sheet->getStyle('A3')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
|
|
+ $rowNum = 4;
|
|
|
+ } else {
|
|
|
+ foreach ($groups as $items) {
|
|
|
+ if (empty($items)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $groupLineCount = count($items);
|
|
|
+ $subAmount = 0.0;
|
|
|
+ $i = 0;
|
|
|
+ foreach ($items as $r) {
|
|
|
+ $i++;
|
|
|
+ $amt = $this->procuremenExportAmount($r);
|
|
|
+ $subAmount += $amt;
|
|
|
+
|
|
|
+ $sheet->setCellValue('A' . $rowNum, $i);
|
|
|
+ $sheet->setCellValue('B' . $rowNum, (string)($r['CCYDH'] ?? ''));
|
|
|
+ $sheet->setCellValue('C' . $rowNum, (string)($r['CYJMC'] ?? ''));
|
|
|
+ $sheet->setCellValue('D' . $rowNum, (string)($r['company_name'] ?? ''));
|
|
|
+ $sheet->setCellValue('E' . $rowNum, (string)($r['CDF'] ?? ''));
|
|
|
+ $sheet->setCellValue('F' . $rowNum, (string)($r['cGzzxMc'] ?? ''));
|
|
|
+ $sheet->setCellValue('G' . $rowNum, $this->procuremenExportGxText($r));
|
|
|
+ $sheet->setCellValue('H' . $rowNum, $amt);
|
|
|
+ $sheet->getStyle('H' . $rowNum)->getNumberFormat()->setFormatCode('"¥"#,##0.00');
|
|
|
+ $rowNum++;
|
|
|
+ }
|
|
|
+
|
|
|
+ $sumSubtotalCounts += $groupLineCount;
|
|
|
+ $grandAmount += $subAmount;
|
|
|
+
|
|
|
+ $sheet->setCellValue('A' . $rowNum, $groupLineCount);
|
|
|
+ $sheet->mergeCells('G' . $rowNum . ':H' . $rowNum);
|
|
|
+ $sheet->setCellValue('G' . $rowNum, '¥ ' . number_format($subAmount, 2, '.', ','));
|
|
|
+ $sheet->getStyle('G' . $rowNum)->getAlignment()
|
|
|
+ ->setHorizontal(Alignment::HORIZONTAL_RIGHT)
|
|
|
+ ->setVertical(Alignment::VERTICAL_CENTER);
|
|
|
+ $rowNum++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $sheet->setCellValue('A' . $rowNum, '总合');
|
|
|
+ $sheet->setCellValue('B' . $rowNum, $sumSubtotalCounts);
|
|
|
+ $sheet->mergeCells('G' . $rowNum . ':H' . $rowNum);
|
|
|
+ $sheet->setCellValue('G' . $rowNum, '¥ ' . number_format($grandAmount, 2, '.', ','));
|
|
|
+ $sheet->getStyle('A' . $rowNum . ':H' . $rowNum)->getFont()->setBold(true);
|
|
|
+ $sheet->getStyle('G' . $rowNum)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT);
|
|
|
+
|
|
|
+ $lastRow = $rowNum;
|
|
|
+ $sheet->getStyle('A1:H' . $lastRow)->applyFromArray([
|
|
|
+ 'borders' => [
|
|
|
+ 'allBorders' => [
|
|
|
+ 'borderStyle' => Border::BORDER_THIN,
|
|
|
+ 'color' => ['rgb' => '000000'],
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ ]);
|
|
|
+ $sheet->getStyle('A3:H' . $lastRow)->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
|
|
|
+ // 长文案列:加宽 + 自动换行,避免导出后被截断
|
|
|
+ if ($lastRow >= 3) {
|
|
|
+ $sheet->getStyle('C3:C' . $lastRow)->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_TOP);
|
|
|
+ $sheet->getStyle('D3:D' . $lastRow)->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_TOP);
|
|
|
+ $sheet->getStyle('F3:F' . $lastRow)->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_TOP);
|
|
|
+ $sheet->getStyle('G3:G' . $lastRow)->getAlignment()->setWrapText(true)->setVertical(Alignment::VERTICAL_TOP);
|
|
|
+ }
|
|
|
+ $sheet->getColumnDimension('A')->setWidth(7);
|
|
|
+ $sheet->getColumnDimension('B')->setWidth(16);
|
|
|
+ $sheet->getColumnDimension('C')->setWidth(52);
|
|
|
+ $sheet->getColumnDimension('D')->setWidth(32);
|
|
|
+ $sheet->getColumnDimension('E')->setWidth(12);
|
|
|
+ $sheet->getColumnDimension('F')->setWidth(44);
|
|
|
+ $sheet->getColumnDimension('G')->setWidth(26);
|
|
|
+ $sheet->getColumnDimension('H')->setWidth(13);
|
|
|
+ $sheet->getRowDimension(1)->setRowHeight(28);
|
|
|
+
|
|
|
+ $fileBase = '外发明细_' . str_replace('-', '', $ym);
|
|
|
+ $filename = $fileBase . '.xlsx';
|
|
|
+
|
|
|
+ if (ob_get_length()) {
|
|
|
+ ob_end_clean();
|
|
|
+ }
|
|
|
+ $asciiName = 'outward_' . str_replace('-', '', $ym) . '.xlsx';
|
|
|
+ header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
|
+ header('Content-Disposition: attachment;filename="' . $asciiName . '"; filename*=UTF-8\'\'' . rawurlencode($filename));
|
|
|
+ header('Cache-Control: max-age=0');
|
|
|
+ $writer = new Xlsx($spreadsheet);
|
|
|
+ $writer->save('php://output');
|
|
|
+ $spreadsheet->disconnectWorksheets();
|
|
|
+ unset($spreadsheet);
|
|
|
+ exit;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出用:工序列文案
|
|
|
+ */
|
|
|
+ protected function procuremenExportGxText(array $r)
|
|
|
+ {
|
|
|
+ $a = trim((string)($r['CDXMC'] ?? ''));
|
|
|
+ $b = trim((string)($r['CGYMC'] ?? ''));
|
|
|
+ if ($a !== '' && $b !== '') {
|
|
|
+ return $a . ':' . $b;
|
|
|
+ }
|
|
|
+ return $a !== '' ? $a : $b;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出用:加工金额(表中有 amount/jje/jgje 等则读取,否则 0)
|
|
|
+ */
|
|
|
+ protected function procuremenExportAmount(array $r)
|
|
|
+ {
|
|
|
+ foreach (['amount', 'jje', 'jgje', 'processing_amount'] as $k) {
|
|
|
+ if (!array_key_exists($k, $r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $v = $r[$k];
|
|
|
+ if ($v === null || $v === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (is_numeric($v)) {
|
|
|
+ return (float)$v;
|
|
|
+ }
|
|
|
+ $v = preg_replace('/[^\d\.\-]/', '', (string)$v);
|
|
|
+ if ($v !== '' && is_numeric($v)) {
|
|
|
+ return (float)$v;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订单号等用于 PDF 文件名片段(去掉路径非法字符)
|
|
|
+ */
|
|
|
+ protected function sanitizePurchaseConfirmPdfOrderKey(string $ccydh): string
|
|
|
+ {
|
|
|
+ $s = trim($ccydh);
|
|
|
+ if ($s === '') {
|
|
|
+ return 'ORDER';
|
|
|
+ }
|
|
|
+ $s = preg_replace('@[\\\\/:*?"<>|\\s]+@u', '_', $s);
|
|
|
+ $s = trim($s, '._-');
|
|
|
+ if ($s === '') {
|
|
|
+ return 'ORDER';
|
|
|
+ }
|
|
|
+ if (function_exists('mb_substr')) {
|
|
|
+ return mb_substr($s, 0, 80, 'UTF-8');
|
|
|
+ }
|
|
|
+
|
|
|
+ return strlen($s) <= 80 ? $s : substr($s, 0, 80);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 采购确认 PDF 相对路径(与 OSS 对象键一致,无前导斜杠):
|
|
|
+ * xinhua/年/月/日/scydgy_id/订单号_scydgy_id.pdf
|
|
|
+ *
|
|
|
+ * @return array{objectKey: string, webPath: string}
|
|
|
+ */
|
|
|
+ protected function buildPurchaseConfirmPdfPaths(int $scydgyId, string $ccydhRaw): array
|
|
|
+ {
|
|
|
+ $sid = (int)$scydgyId;
|
|
|
+ $safeOrder = $this->sanitizePurchaseConfirmPdfOrderKey($ccydhRaw);
|
|
|
+ $y = date('Y');
|
|
|
+ $m = date('m');
|
|
|
+ $d = date('d');
|
|
|
+ $basename = $safeOrder . '_' . $sid . '.pdf';
|
|
|
+ $objectKey = 'xinhua/' . $y . '/' . $m . '/' . $d . '/' . $sid . '/' . $basename;
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'objectKey' => $objectKey,
|
|
|
+ 'webPath' => '/' . $objectKey,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 采购确认成功后:用与「详情」弹窗相同的模板片段渲染 HTML,再存为 PDF(改 details_fragment 后 PDF 同步变化)。
|
|
|
+ * 优先上传至阿里云 OSS(application/config.php 的 oss 节点);失败或未配置时回退到 public 下与 objectKey 相同目录结构。
|
|
|
+ * 成功后将相对路径写入 purchase_order.pdf_url(形如 /xinhua/年/月/日/scydgy_id/订单号_scydgy_id.pdf)。
|
|
|
+ *
|
|
|
+ * @return string OSS 返回 https 完整 URL;本地回退为以 / 开头的 Web 路径;失败返回空串
|
|
|
+ */
|
|
|
+ protected function savePurchaseConfirmDetailPdf(int $scydgyId, int $purchaseOrderId): string
|
|
|
+ {
|
|
|
+ if ($scydgyId <= 0) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ $ids = trim((string)$scydgyId);
|
|
|
+ $prep = $this->prepareProcuremenDetailsView($ids);
|
|
|
+ if (!$prep['ok']) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ $ccydh = $prep['ccydh'];
|
|
|
+
|
|
|
+ $paths = $this->buildPurchaseConfirmPdfPaths((int)$scydgyId, $ccydh);
|
|
|
+ $objectKey = $paths['objectKey'];
|
|
|
+ $webPath = $paths['webPath'];
|
|
|
+
|
|
|
+ $meta = sprintf('工序行ID %s | 主表订单ID %d | PDF生成时间 %s', $ids, (int)$purchaseOrderId, date('Y-m-d H:i:s'));
|
|
|
+ $this->view->assign([
|
|
|
+ 'pdf_export' => 1,
|
|
|
+ 'pdfMetaLine' => $meta,
|
|
|
+ ]);
|
|
|
+ // 关闭后台 layout,避免 default 布局里的「控制台」面包屑等被打进 PDF
|
|
|
+ $restoreLayout = !empty($this->layout) ? ('layout/' . $this->layout) : false;
|
|
|
+ $this->view->engine->layout(false);
|
|
|
+ try {
|
|
|
+ $html = $this->view->fetch('procuremen/details_pdf_shell');
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ if ($restoreLayout) {
|
|
|
+ $this->view->engine->layout($restoreLayout);
|
|
|
+ }
|
|
|
+ Log::write('采购确认PDF模板渲染失败: ' . $e->getMessage(), 'error');
|
|
|
+ $this->view->assign(['pdf_export' => '', 'pdfMetaLine' => '']);
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ if ($restoreLayout) {
|
|
|
+ $this->view->engine->layout($restoreLayout);
|
|
|
+ }
|
|
|
+ $this->view->assign(['pdf_export' => '', 'pdfMetaLine' => '']);
|
|
|
+
|
|
|
+ $tempDir = ROOT_PATH . 'runtime' . DIRECTORY_SEPARATOR . 'mpdf_tmp';
|
|
|
+ if (!is_dir($tempDir)) {
|
|
|
+ @mkdir($tempDir, 0755, true);
|
|
|
+ }
|
|
|
+ $tempPdf = $tempDir . DIRECTORY_SEPARATOR . uniqid('pc_pdf_', true) . '.pdf';
|
|
|
+ try {
|
|
|
+ $mpdf = new \Mpdf\Mpdf([
|
|
|
+ 'mode' => 'utf-8',
|
|
|
+ 'format' => 'A4',
|
|
|
+ 'margin_left' => 12,
|
|
|
+ 'margin_right' => 12,
|
|
|
+ 'margin_top' => 14,
|
|
|
+ 'margin_bottom' => 14,
|
|
|
+ 'tempDir' => $tempDir,
|
|
|
+ 'autoScriptToLang' => true,
|
|
|
+ 'autoLangToFont' => true,
|
|
|
+ ]);
|
|
|
+ // 与当前站点 HTTP_HOST 不同的占位 base,使 basepathIsLocal=false,避免 mPDF CssManager
|
|
|
+ // 在 parse_url 得到有 scheme 无 host 时对 $tr['host'] 触发「Undefined index: host」(日志已复现)。
|
|
|
+ $mpdf->SetBasePath('http://127.0.0.1/');
|
|
|
+ $mpdf->WriteHTML($html);
|
|
|
+ $mpdf->Output($tempPdf, \Mpdf\Output\Destination::FILE);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('采购确认PDF写入失败: ' . $e->getMessage(), 'error');
|
|
|
+ if (is_file($tempPdf)) {
|
|
|
+ @unlink($tempPdf);
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ $ossUrl = AliyunOss::uploadLocalFile($tempPdf, $objectKey);
|
|
|
+ if ($ossUrl !== '') {
|
|
|
+ @unlink($tempPdf);
|
|
|
+ $this->persistPurchaseOrderPdfUrl((int)$scydgyId, $webPath);
|
|
|
+
|
|
|
+ return $ossUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ $pi = pathinfo($objectKey);
|
|
|
+ $dirRel = isset($pi['dirname']) ? (string)$pi['dirname'] : 'xinhua';
|
|
|
+ $baseFile = isset($pi['basename']) ? (string)$pi['basename'] : '';
|
|
|
+ if ($baseFile === '') {
|
|
|
+ @unlink($tempPdf);
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ $dir = ROOT_PATH . 'public' . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $dirRel);
|
|
|
+ if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
|
|
|
+ Log::write('采购确认PDF目录创建失败: ' . $dir, 'error');
|
|
|
+ @unlink($tempPdf);
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ $fullPath = $dir . DIRECTORY_SEPARATOR . $baseFile;
|
|
|
+ if (!@copy($tempPdf, $fullPath)) {
|
|
|
+ Log::write('采购确认PDF复制到本地失败: ' . $fullPath, 'error');
|
|
|
+ @unlink($tempPdf);
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ @unlink($tempPdf);
|
|
|
+ $this->persistPurchaseOrderPdfUrl((int)$scydgyId, $webPath);
|
|
|
+
|
|
|
+ return $webPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 采购确认 PDF 生成成功后写入 purchase_order.pdf_url(列不存在时仅记日志)
|
|
|
+ */
|
|
|
+ protected function persistPurchaseOrderPdfUrl(int $scydgyId, string $webPath): void
|
|
|
+ {
|
|
|
+ if ($scydgyId <= 0 || $webPath === '') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ Db::table('purchase_order')->where('scydgy_id', $scydgyId)->update(['pdf_url' => $webPath]);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('purchase_order.pdf_url 更新失败 scydgy_id=' . $scydgyId . ' ' . $e->getMessage(), 'notice');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 短信宝发送;失败抛异常(供事务回滚,避免「已入库但未通知」与「通知失败仍入库」)
|
|
|
+ * @throws \Exception
|
|
|
+ */
|
|
|
+ protected function smsbao($phone, $content)
|
|
|
+ {
|
|
|
+ $statusStr = [
|
|
|
+ '0' => '短信发送成功',
|
|
|
+ '-1' => '参数不全',
|
|
|
+ '-2' => '服务器空间不支持,请确认支持curl或者fsocket,联系您的空间商解决或者更换空间!',
|
|
|
+ '30' => '密码错误',
|
|
|
+ '40' => '账号不存在',
|
|
|
+ '41' => '余额不足',
|
|
|
+ '42' => '帐户已过期',
|
|
|
+ '43' => 'IP地址限制',
|
|
|
+ '50' => '内容含有敏感词',
|
|
|
+ ];
|
|
|
+ $smsapi = 'http://api.smsbao.com/';
|
|
|
+ $user = 'zhuwei123';
|
|
|
+ $pass = md5('1d1e605c101e4c1f8a156c6d7b19f126');
|
|
|
+ $sendurl = $smsapi . 'sms?u=' . $user . '&p=' . $pass . '&m=' . $phone . '&c=' . urlencode($content);
|
|
|
+ $result = @file_get_contents($sendurl);
|
|
|
+ if ($result === false) {
|
|
|
+ \think\Log::record('smsbao 请求失败 phone=' . $phone, 'error');
|
|
|
+ throw new \Exception('短信接口请求失败,请检查网络或稍后再试(未写入数据)');
|
|
|
+ }
|
|
|
+ $result = trim((string)$result);
|
|
|
+ if ($result !== '0') {
|
|
|
+ $msg = isset($statusStr[$result]) ? $statusStr[$result] : ('返回码 ' . $result);
|
|
|
+ \think\Log::record('smsbao 发送失败 phone=' . $phone . ' code=' . $result . ' ' . $msg, 'error');
|
|
|
+ throw new \Exception('短信发送失败:' . $msg . '(' . $phone . '),未写入数据');
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|