|
|
@@ -4,22 +4,942 @@ namespace app\api\controller;
|
|
|
|
|
|
use app\common\controller\Api;
|
|
|
use think\Db;
|
|
|
-use Overtrue\Pinyin;
|
|
|
+use think\exception\HttpResponseException;
|
|
|
|
|
|
/**
|
|
|
- * 中间表数据同步
|
|
|
+ * MES-ERP 工单同步
|
|
|
*/
|
|
|
class Synchronization extends Api
|
|
|
{
|
|
|
protected $noNeedLogin = ['*'];
|
|
|
protected $noNeedRight = ['*'];
|
|
|
|
|
|
- public function SynchronizationData()
|
|
|
+ /** @var int 同步状态:待同步 */
|
|
|
+ const SYNC_STATUS_PENDING = 0;
|
|
|
+ /** @var int 同步状态:成功 */
|
|
|
+ const SYNC_STATUS_SUCCESS = 1;
|
|
|
+ /** @var int 同步状态:失败 */
|
|
|
+ const SYNC_STATUS_FAILED = 2;
|
|
|
+
|
|
|
+ /** @var int 操作状态:成功 */
|
|
|
+ const OPERATE_STATUS_SUCCESS = 1;
|
|
|
+ /** @var int 操作状态:失败 */
|
|
|
+ const OPERATE_STATUS_FAILED = 2;
|
|
|
+ /** @var int 操作状态:处理中 */
|
|
|
+ const OPERATE_STATUS_PROCESSING = 3;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * ERP SQL Server 表映射(请按实际 ERP 表结构填写 table 与 fields)
|
|
|
+ * fields:MES 源字段名 => ERP 目标字段名;固定值以 = 开头,如 '=M' => 'is_mfg_wrk'
|
|
|
+ * 报工 sfb_id 规则:K+年月日(同步日期)+四位自增,如 K2606100201
|
|
|
+ */
|
|
|
+ protected $erpTableMap = [
|
|
|
+ // 1-工单数据:来源 工单_基本资料
|
|
|
+ 'work_order' => [
|
|
|
+ 'table' => 'mps_mfg_ord',
|
|
|
+ 'fields' => [
|
|
|
+ '订单编号' => 'ordno',
|
|
|
+ '=M' => 'is_mfg_wrk',
|
|
|
+ '生产款号' => 'itemno',
|
|
|
+ '接单日期' => 'cr_date',
|
|
|
+ '落货日期' => 'rel_date',
|
|
|
+ '同步日期' => 'due_date',
|
|
|
+ '订单数量' => 'qty_ord',
|
|
|
+ 'remark' => 'remark',
|
|
|
+ '计划制造工分' => 'curprc',
|
|
|
+ '=0001' => 'ocode',
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ // 2-工艺数据:来源 工单_基础工艺资料
|
|
|
+ 'process' => [
|
|
|
+ 'table' => 'mps_mfg_routing',
|
|
|
+ 'fields' => [
|
|
|
+ 'work_order' => 'ordno',
|
|
|
+ '=M' => 'is_mfg_wrk',
|
|
|
+ '生产款号' => 'itemno',
|
|
|
+ 'process_code' => 'op',
|
|
|
+ 'process_name' => 'routiname',
|
|
|
+ 'standard_score' => 'preprc',
|
|
|
+ '=0001' => 'ocode',
|
|
|
+ '订单数量' => 'qty_ord',
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ // 3-报工数据:来源工序产量核查汇总(sfb_id 按同步日期自动生成)
|
|
|
+ 'report' => [
|
|
|
+ 'table' => 'wfc_shift_feedback',
|
|
|
+ 'fields' => [
|
|
|
+ '员工编号' => 'emp_no',
|
|
|
+ '=1' => 'shift_id',
|
|
|
+ '订单编号' => 'ordno',
|
|
|
+ '生产款号' => 'itemno',
|
|
|
+ '工序号' => 'op',
|
|
|
+ '工序名称' => 'routiname',
|
|
|
+ '数量' => 'qty_comp',
|
|
|
+ '开工日期' => 'op_begdt',
|
|
|
+ '完工日期' => 'op_enddt',
|
|
|
+ '填报日期' => 'tran_date',
|
|
|
+ '=0202' => 'centerno',
|
|
|
+ '=0001.02.02' => 'detno',
|
|
|
+ '=0001' => 'ocode',
|
|
|
+ '工分' => 'preprc',
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 完工工单列表(含同步状态)
|
|
|
+ * @ApiMethod (GET)
|
|
|
+ * @param page 页码
|
|
|
+ * @param limit 每页条数
|
|
|
+ * @param search 搜索(订单编号/款号/款式/客户)
|
|
|
+ * @param sync_status 同步状态筛选:unsynced|pending|synced|failed|cancelled
|
|
|
+ */
|
|
|
+ public function getCompletedWorkOrderList()
|
|
|
+ {
|
|
|
+ if (!$this->request->isGet()) {
|
|
|
+ $this->error('请求方法错误');
|
|
|
+ }
|
|
|
+ $param = $this->request->param();
|
|
|
+ $page = max(1, (int)(isset($param['page']) ? $param['page'] : 1));
|
|
|
+ $limit = max(15, min(100, (int)(isset($param['limit']) ? $param['limit'] : 15)));
|
|
|
+
|
|
|
+ $baseWhere = function ($query) use ($param) {
|
|
|
+ $query->whereNull('Mod_rq')
|
|
|
+ ->whereNotNull('工单完工日期')
|
|
|
+ ->where('工单完工日期', '<>', '');
|
|
|
+ if (!empty($param['search'])) {
|
|
|
+ $query->where('订单编号|生产款号|客户编号|款式|工单完工日期', 'like', '%' . trim($param['search']) . '%');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ $countQuery = \db('工单_基本资料');
|
|
|
+ $baseWhere($countQuery);
|
|
|
+ $total = (int)$countQuery->count();
|
|
|
+
|
|
|
+ $listQuery = \db('工单_基本资料');
|
|
|
+ $baseWhere($listQuery);
|
|
|
+ $list = $listQuery
|
|
|
+ ->field('Uniqid,订单编号,生产款号,款式,客户编号,工单完工日期,remark,gd_statu,Sys_id,Sys_rq')
|
|
|
+ ->order('工单完工日期 desc,订单编号 desc')
|
|
|
+ ->page($page, $limit)
|
|
|
+ ->select();
|
|
|
+ if (!empty($list)) {
|
|
|
+ $orderNos = array_column($list, '订单编号');
|
|
|
+ $statusMap = $this->batchResolveDisplaySyncStatus($orderNos);
|
|
|
+ foreach ($list as &$row) {
|
|
|
+ $orderNo = $row['订单编号'];
|
|
|
+ $statusInfo = isset($statusMap[$orderNo]) ? $statusMap[$orderNo] : $this->defaultDisplaySyncStatus();
|
|
|
+ $row['sync_status'] = $statusInfo['code'];
|
|
|
+ $row['sync_status_text'] = $statusInfo['text'];
|
|
|
+ $row['同步状态'] = $statusInfo['text'];
|
|
|
+ $row['last_sync_time'] = $statusInfo['sync_time'];
|
|
|
+ $row['last_sync_msg'] = $statusInfo['sync_msg'];
|
|
|
+ $row['last_sync_log_id'] = $statusInfo['sync_log_id'];
|
|
|
+ }
|
|
|
+ unset($row);
|
|
|
+
|
|
|
+ if (!empty($param['sync_status'])) {
|
|
|
+ $filter = $param['sync_status'];
|
|
|
+ $list = array_values(array_filter($list, function ($row) use ($filter) {
|
|
|
+ return $row['sync_status'] === $filter;
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->success('成功', [
|
|
|
+ 'total' => $total,
|
|
|
+ 'page' => $page,
|
|
|
+ 'limit' => $limit,
|
|
|
+ 'list' => $list ?: [],
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步到 ERP
|
|
|
+ * @ApiMethod (POST)
|
|
|
+ * @param work_order_no 工单编号
|
|
|
+ * @param sync_module 1-工单 2-工艺 3-报工 4-全量
|
|
|
+ * @param operator 操作人
|
|
|
+ * @param operate_source backend|api|crontab,默认 backend
|
|
|
+ * @param operate_type sync|manual|auto,默认 manual
|
|
|
+ */
|
|
|
+ public function syncWorkOrder()
|
|
|
+ {
|
|
|
+ $this->executeSync('sync');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重试同步(仅上次失败时可重试)
|
|
|
+ * @ApiMethod (POST)
|
|
|
+ */
|
|
|
+ public function retrySyncWorkOrder()
|
|
|
+ {
|
|
|
+ $params = $this->request->post();
|
|
|
+ $workOrderNo = isset($params['work_order_no']) ? trim($params['work_order_no']) : '';
|
|
|
+ if ($workOrderNo === '') {
|
|
|
+ $this->error('工单编号不能为空');
|
|
|
+ }
|
|
|
+ $statusInfo = $this->resolveDisplaySyncStatus($workOrderNo);
|
|
|
+ if ($statusInfo['code'] !== 'failed') {
|
|
|
+ $this->error('仅同步失败的工单可重试');
|
|
|
+ }
|
|
|
+ $this->executeSync('retry');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 撤回同步
|
|
|
+ * @ApiMethod (POST)
|
|
|
+ * @param work_order_no 工单编号
|
|
|
+ * @param operator 操作人
|
|
|
+ * @param reason 撤回原因
|
|
|
+ */
|
|
|
+ public function cancelSyncWorkOrder()
|
|
|
+ {
|
|
|
+ if (!$this->request->isPost()) {
|
|
|
+ $this->error('请求方法错误');
|
|
|
+ }
|
|
|
+ $params = $this->request->post();
|
|
|
+ $workOrderNo = isset($params['work_order_no']) ? trim($params['work_order_no']) : '';
|
|
|
+ $operator = isset($params['operator']) ? trim($params['operator']) : '';
|
|
|
+ $reason = isset($params['reason']) ? trim($params['reason']) : '';
|
|
|
+
|
|
|
+ if ($workOrderNo === '') {
|
|
|
+ $this->error('工单编号不能为空');
|
|
|
+ }
|
|
|
+ if ($operator === '') {
|
|
|
+ $this->error('操作人不能为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ $workOrder = $this->fetchWorkOrderBasic($workOrderNo);
|
|
|
+ if (empty($workOrder)) {
|
|
|
+ $this->error('工单不存在');
|
|
|
+ }
|
|
|
+
|
|
|
+ $statusInfo = $this->resolveDisplaySyncStatus($workOrderNo);
|
|
|
+ if ($statusInfo['code'] !== 'synced') {
|
|
|
+ $this->error('仅已同步的工单可撤回');
|
|
|
+ }
|
|
|
+
|
|
|
+ $operTime = date('Y-m-d H:i:s');
|
|
|
+ $operateParam = json_encode([
|
|
|
+ 'work_order_no' => $workOrderNo,
|
|
|
+ 'reason' => $reason,
|
|
|
+ ], JSON_UNESCAPED_UNICODE);
|
|
|
+ $operateContent = '撤回同步工单' . $workOrderNo . ($reason !== '' ? ',原因:' . $reason : '');
|
|
|
+
|
|
|
+ $operationLogId = $this->insertOperationLog([
|
|
|
+ 'operate_type' => 'cancel',
|
|
|
+ 'operate_user' => $operator,
|
|
|
+ 'operate_user_name' => isset($params['operate_user_name']) ? $params['operate_user_name'] : $operator,
|
|
|
+ 'operate_time' => $operTime,
|
|
|
+ 'sync_log_id' => $statusInfo['sync_log_id'],
|
|
|
+ 'work_order_no' => $workOrderNo,
|
|
|
+ 'style_no' => $workOrder['生产款号'],
|
|
|
+ 'operate_content' => $operateContent,
|
|
|
+ 'operate_param' => $operateParam,
|
|
|
+ 'operate_status' => self::OPERATE_STATUS_PROCESSING,
|
|
|
+ 'operate_source' => isset($params['operate_source']) ? $params['operate_source'] : 'backend',
|
|
|
+ 'operate_ip' => $this->request->ip(),
|
|
|
+ 'server_ip' => isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ Db::startTrans();
|
|
|
+ try {
|
|
|
+ $this->deleteErpDataByWorkOrder($workOrderNo);
|
|
|
+
|
|
|
+ Db::name('mes_erp_operation_log')
|
|
|
+ ->where('id', $operationLogId)
|
|
|
+ ->update([
|
|
|
+ 'operate_status' => self::OPERATE_STATUS_SUCCESS,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if (!empty($statusInfo['sync_log_id'])) {
|
|
|
+ Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->where('id', $statusInfo['sync_log_id'])
|
|
|
+ ->update([
|
|
|
+ 'sync_msg' => '已撤回' . ($reason !== '' ? ':' . $reason : ''),
|
|
|
+ 'update_time' => $operTime,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ Db::commit();
|
|
|
+ } catch (HttpResponseException $e) {
|
|
|
+ throw $e;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Db::rollback();
|
|
|
+ Db::name('mes_erp_operation_log')
|
|
|
+ ->where('id', $operationLogId)
|
|
|
+ ->update([
|
|
|
+ 'operate_status' => self::OPERATE_STATUS_FAILED,
|
|
|
+ 'error_msg' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ $this->error('撤回失败:' . $e->getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->success('撤回成功');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步日志列表
|
|
|
+ * @ApiMethod (GET)
|
|
|
+ * @param work_order_no 工单编号
|
|
|
+ */
|
|
|
+ public function getSyncLogList()
|
|
|
+ {
|
|
|
+ if (!$this->request->isGet()) {
|
|
|
+ $this->error('请求方法错误');
|
|
|
+ }
|
|
|
+ $workOrderNo = trim($this->request->param('work_order_no', ''));
|
|
|
+ if ($workOrderNo === '') {
|
|
|
+ $this->error('工单编号不能为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ $list = Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->where('work_order_no', $workOrderNo)
|
|
|
+ ->order('id desc')
|
|
|
+ ->select();
|
|
|
+
|
|
|
+ foreach ($list as &$row) {
|
|
|
+ $row['sync_status_text'] = $this->syncStatusText($row['sync_status']);
|
|
|
+ $row['sync_module_text'] = $this->syncModuleText($row['sync_module']);
|
|
|
+ }
|
|
|
+ unset($row);
|
|
|
+
|
|
|
+ $this->success('成功', $list ?: []);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 操作过程记录列表
|
|
|
+ * @ApiMethod (GET)
|
|
|
+ * @param work_order_no 工单编号
|
|
|
+ * @param sync_log_id 同步日志ID(可选)
|
|
|
+ */
|
|
|
+ public function getOperationLogList()
|
|
|
+ {
|
|
|
+ if (!$this->request->isGet()) {
|
|
|
+ $this->error('请求方法错误');
|
|
|
+ }
|
|
|
+ $workOrderNo = trim($this->request->param('work_order_no', ''));
|
|
|
+ $syncLogId = $this->request->param('sync_log_id');
|
|
|
+
|
|
|
+ $query = Db::name('mes_erp_operation_log')->order('id desc');
|
|
|
+ if ($workOrderNo !== '') {
|
|
|
+ $query->where('work_order_no', $workOrderNo);
|
|
|
+ }
|
|
|
+ if ($syncLogId !== null && $syncLogId !== '') {
|
|
|
+ $query->where('sync_log_id', intval($syncLogId));
|
|
|
+ }
|
|
|
+ if ($workOrderNo === '' && ($syncLogId === null || $syncLogId === '')) {
|
|
|
+ $this->error('请传入 work_order_no 或 sync_log_id');
|
|
|
+ }
|
|
|
+
|
|
|
+ $list = $query->select();
|
|
|
+ foreach ($list as &$row) {
|
|
|
+ $row['operate_status_text'] = $this->operateStatusText($row['operate_status']);
|
|
|
+ }
|
|
|
+ unset($row);
|
|
|
+
|
|
|
+ $this->success('成功', $list ?: []);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 执行同步核心逻辑
|
|
|
+ * @param string $operateType sync|retry|manual|auto
|
|
|
+ */
|
|
|
+ private function executeSync($operateType)
|
|
|
+ {
|
|
|
+ if (!$this->request->isPost()) {
|
|
|
+ $this->error('请求方法错误');
|
|
|
+ }
|
|
|
+ $params = $this->request->post();
|
|
|
+ $workOrderNo = isset($params['work_order_no']) ? trim($params['work_order_no']) : '';
|
|
|
+ $syncModule = isset($params['sync_module']) ? (string)$params['sync_module'] : '4';
|
|
|
+ $operator = isset($params['operator']) ? trim($params['operator']) : '';
|
|
|
+
|
|
|
+ if ($workOrderNo === '') {
|
|
|
+ $this->error('工单编号不能为空');
|
|
|
+ }
|
|
|
+ if ($operator === '') {
|
|
|
+ $this->error('操作人不能为空');
|
|
|
+ }
|
|
|
+ if (!in_array($syncModule, ['1', '2', '3', '4'], true)) {
|
|
|
+ $this->error('同步模块参数错误');
|
|
|
+ }
|
|
|
+
|
|
|
+ $workOrder = $this->fetchWorkOrderBasic($workOrderNo);
|
|
|
+ if (empty($workOrder)) {
|
|
|
+ $this->error('工单不存在');
|
|
|
+ }
|
|
|
+ if (empty($workOrder['工单完工日期'])) {
|
|
|
+ $this->error('该工单尚未完工,不能同步');
|
|
|
+ }
|
|
|
+
|
|
|
+ $operTime = date('Y-m-d H:i:s');
|
|
|
+ $operateSource = isset($params['operate_source']) ? $params['operate_source'] : 'backend';
|
|
|
+ $requestOperateType = isset($params['operate_type']) ? $params['operate_type'] : 'manual';
|
|
|
+ if ($operateType === 'retry') {
|
|
|
+ $requestOperateType = 'retry';
|
|
|
+ }
|
|
|
+
|
|
|
+ $operateParam = json_encode([
|
|
|
+ 'work_order_no' => $workOrderNo,
|
|
|
+ 'sync_module' => $syncModule,
|
|
|
+ 'operator' => $operator,
|
|
|
+ ], JSON_UNESCAPED_UNICODE);
|
|
|
+
|
|
|
+ $operationLogId = $this->insertOperationLog([
|
|
|
+ 'operate_type' => $requestOperateType,
|
|
|
+ 'operate_user' => $operator,
|
|
|
+ 'operate_user_name' => isset($params['operate_user_name']) ? $params['operate_user_name'] : $operator,
|
|
|
+ 'operate_time' => $operTime,
|
|
|
+ 'sync_log_id' => null,
|
|
|
+ 'work_order_no' => $workOrderNo,
|
|
|
+ 'style_no' => $workOrder['生产款号'],
|
|
|
+ 'operate_content' => $this->buildSyncOperateContent($workOrderNo, $syncModule, $requestOperateType),
|
|
|
+ 'operate_param' => $operateParam,
|
|
|
+ 'operate_status' => self::OPERATE_STATUS_PROCESSING,
|
|
|
+ 'operate_source' => $operateSource,
|
|
|
+ 'operate_ip' => $this->request->ip(),
|
|
|
+ 'server_ip' => isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : '',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $seqRange = $this->fetchReportSeqRange($workOrderNo);
|
|
|
+ $syncLogId = $this->insertSyncLog([
|
|
|
+ 'work_order_no' => $workOrderNo,
|
|
|
+ 'style_no' => $workOrder['生产款号'],
|
|
|
+ 'complete_date' => $this->formatDateValue($workOrder['工单完工日期']),
|
|
|
+ 'operator' => $operator,
|
|
|
+ 'sync_time' => $operTime,
|
|
|
+ 'report_start_seq' => $seqRange['start'],
|
|
|
+ 'report_end_seq' => $seqRange['end'],
|
|
|
+ 'sync_status' => self::SYNC_STATUS_PENDING,
|
|
|
+ 'sync_module' => $syncModule,
|
|
|
+ 'sync_msg' => '同步处理中',
|
|
|
+ 'erp_sync_id' => '',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ Db::name('mes_erp_operation_log')
|
|
|
+ ->where('id', $operationLogId)
|
|
|
+ ->update(['sync_log_id' => $syncLogId]);
|
|
|
+
|
|
|
+ try {
|
|
|
+ $erpSyncId = $this->pushDataToErp($workOrderNo, $syncModule, $workOrder);
|
|
|
+ Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->where('id', $syncLogId)
|
|
|
+ ->update([
|
|
|
+ 'sync_status' => self::SYNC_STATUS_SUCCESS,
|
|
|
+ 'sync_msg' => '同步成功',
|
|
|
+ 'erp_sync_id' => $erpSyncId,
|
|
|
+ 'update_time' => date('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ Db::name('mes_erp_operation_log')
|
|
|
+ ->where('id', $operationLogId)
|
|
|
+ ->update(['operate_status' => self::OPERATE_STATUS_SUCCESS]);
|
|
|
+ } catch (HttpResponseException $e) {
|
|
|
+ throw $e;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->where('id', $syncLogId)
|
|
|
+ ->update([
|
|
|
+ 'sync_status' => self::SYNC_STATUS_FAILED,
|
|
|
+ 'sync_msg' => $e->getMessage(),
|
|
|
+ 'update_time' => date('Y-m-d H:i:s'),
|
|
|
+ ]);
|
|
|
+ Db::name('mes_erp_operation_log')
|
|
|
+ ->where('id', $operationLogId)
|
|
|
+ ->update([
|
|
|
+ 'operate_status' => self::OPERATE_STATUS_FAILED,
|
|
|
+ 'error_msg' => $e->getMessage(),
|
|
|
+ ]);
|
|
|
+ $this->error('同步失败:' . $e->getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ $this->success('同步成功', [
|
|
|
+ 'sync_log_id' => $syncLogId,
|
|
|
+ 'operation_log_id' => $operationLogId,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 推送数据到 ERP(db2 / SQL Server)
|
|
|
+ * @param string $workOrderNo
|
|
|
+ * @param string $syncModule
|
|
|
+ * @param array $workOrder
|
|
|
+ * @return string ERP 同步标识
|
|
|
+ * @throws \Exception
|
|
|
+ */
|
|
|
+ private function pushDataToErp($workOrderNo, $syncModule, $workOrder)
|
|
|
+ {
|
|
|
+ $erpDb = $this->getErpDb();
|
|
|
+ $modules = $syncModule === '4' ? ['1', '2', '3'] : [$syncModule];
|
|
|
+ $inserted = [];
|
|
|
+ $syncDate = date('Y-m-d');
|
|
|
+ $workOrder['同步日期'] = $syncDate;
|
|
|
+
|
|
|
+ $erpDb->startTrans();
|
|
|
+ try {
|
|
|
+ foreach ($modules as $module) {
|
|
|
+ if ($module === '1') {
|
|
|
+ $rows = [$workOrder];
|
|
|
+ $count = $this->insertErpRows($erpDb, 'work_order', $rows);
|
|
|
+ $inserted['work_order'] = $count;
|
|
|
+ } elseif ($module === '2') {
|
|
|
+ $rows = $this->fetchWorkOrderProcessList($workOrderNo);
|
|
|
+ if (empty($rows)) {
|
|
|
+ throw new \Exception('工单工艺数据为空');
|
|
|
+ }
|
|
|
+ foreach ($rows as &$processRow) {
|
|
|
+ $processRow['生产款号'] = $workOrder['生产款号'];
|
|
|
+ $processRow['订单数量'] = $workOrder['订单数量'];
|
|
|
+ }
|
|
|
+ unset($processRow);
|
|
|
+ $count = $this->insertErpRows($erpDb, 'process', $rows);
|
|
|
+ $inserted['process'] = $count;
|
|
|
+ } elseif ($module === '3') {
|
|
|
+ $rows = $this->fetchProductionCheckRows($workOrderNo, $syncDate);
|
|
|
+ if (empty($rows)) {
|
|
|
+ throw new \Exception('报工数据为空,请先完成报工');
|
|
|
+ }
|
|
|
+ $count = $this->insertErpRows($erpDb, 'report', $rows, $syncDate);
|
|
|
+ $inserted['report'] = $count;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $erpDb->commit();
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $erpDb->rollback();
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $workOrderNo . '_' . $syncModule . '_' . time();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 写入 ERP 表
|
|
|
+ */
|
|
|
+ private function insertErpRows($erpDb, $tableKey, array $sourceRows, $syncDate = null)
|
|
|
+ {
|
|
|
+ $config = $this->erpTableMap[$tableKey];
|
|
|
+ if (empty($config['table'])) {
|
|
|
+ throw new \Exception('请先在 Synchronization.php 中配置 ERP [' . $tableKey . '] 表名');
|
|
|
+ }
|
|
|
+
|
|
|
+ $sfbSeq = 0;
|
|
|
+ $sfbPrefix = '';
|
|
|
+ if ($tableKey === 'report') {
|
|
|
+ $syncDate = $syncDate ?: date('Y-m-d');
|
|
|
+ $sfbPrefix = 'K' . date('ymd', strtotime($syncDate));
|
|
|
+ $sfbSeq = $this->fetchMaxSfbSeq($erpDb, $sfbPrefix);
|
|
|
+ }
|
|
|
+
|
|
|
+ $erpRows = [];
|
|
|
+ foreach ($sourceRows as $sourceRow) {
|
|
|
+ $mapped = $this->mapSourceRowToErp($sourceRow, $config['fields']);
|
|
|
+ if ($tableKey === 'report') {
|
|
|
+ $sfbSeq++;
|
|
|
+ $mapped['sfb_id'] = $sfbPrefix . str_pad((string)$sfbSeq, 4, '0', STR_PAD_LEFT);
|
|
|
+ }
|
|
|
+ if (!empty($mapped)) {
|
|
|
+ $erpRows[] = $mapped;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (empty($erpRows)) {
|
|
|
+ throw new \Exception('ERP [' . $tableKey . '] 字段映射未配置或映射结果为空');
|
|
|
+ }
|
|
|
+
|
|
|
+ $result = $erpDb->name($config['table'])->insertAll($erpRows);
|
|
|
+ if ($result === false || $result === 0) {
|
|
|
+ throw new \Exception('写入 ERP [' . $tableKey . '] 失败');
|
|
|
+ }
|
|
|
+ return (int)$result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询 ERP 报工表当日 sfb_id 最大序号(K+年月日+四位自增)
|
|
|
+ * 须对末四位做数值 MAX,字符串 MAX 在序号≥10 时会算错导致主键冲突
|
|
|
+ */
|
|
|
+ private function fetchMaxSfbSeq($erpDb, $sfbPrefix)
|
|
|
+ {
|
|
|
+ $row = $erpDb->query(
|
|
|
+ 'SELECT MAX(CAST(RIGHT(sfb_id, 4) AS INT)) AS max_seq FROM wfc_shift_feedback WHERE sfb_id LIKE ?',
|
|
|
+ [$sfbPrefix . '%']
|
|
|
+ );
|
|
|
+ if (empty($row) || !isset($row[0]['max_seq']) || $row[0]['max_seq'] === null) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return (int)$row[0]['max_seq'];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 撤回时删除 ERP 数据(需配置表名及工单编号字段)
|
|
|
+ */
|
|
|
+ private function deleteErpDataByWorkOrder($workOrderNo)
|
|
|
+ {
|
|
|
+ $erpDb = $this->getErpDb();
|
|
|
+ foreach (['work_order', 'process', 'report'] as $tableKey) {
|
|
|
+ $config = $this->erpTableMap[$tableKey];
|
|
|
+ if (empty($config['table'])) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $workOrderField = $this->resolveErpWorkOrderField($tableKey, $config['fields']);
|
|
|
+ if ($workOrderField === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $erpDb->name($config['table'])->where($workOrderField, $workOrderNo)->delete();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * MES 行映射为 ERP 行
|
|
|
+ */
|
|
|
+ private function mapSourceRowToErp(array $sourceRow, array $fieldMap)
|
|
|
+ {
|
|
|
+ $erpRow = [];
|
|
|
+ foreach ($fieldMap as $mesField => $erpField) {
|
|
|
+ if ($erpField === '' || $erpField === null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ($mesField !== '' && $mesField[0] === '=') {
|
|
|
+ $erpRow[$erpField] = substr($mesField, 1);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (array_key_exists($mesField, $sourceRow)) {
|
|
|
+ $erpRow[$erpField] = $sourceRow[$mesField];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $erpRow;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function resolveErpWorkOrderField($tableKey, array $fieldMap)
|
|
|
+ {
|
|
|
+ $candidates = ['订单编号', 'work_order', 'work_order_no'];
|
|
|
+ if ($tableKey === 'work_order') {
|
|
|
+ $candidates = ['订单编号', 'work_order_no'];
|
|
|
+ }
|
|
|
+ foreach ($candidates as $mesField) {
|
|
|
+ if (isset($fieldMap[$mesField]) && $fieldMap[$mesField] !== '') {
|
|
|
+ return $fieldMap[$mesField];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 工序产量核查数据(与 WorkOrderProcess::checkProcessProduction 一致,扁平化为报工行)
|
|
|
+ */
|
|
|
+ private function fetchProductionCheckRows($workOrderNo, $syncDate = null)
|
|
|
+ {
|
|
|
+ $syncDate = $syncDate ?: date('Y-m-d');
|
|
|
+ $list = Db::name('设备_工分计酬')->alias('a')
|
|
|
+ ->join('工单_基本资料 b', 'a.work_order = b.订单编号 AND b.Mod_rq IS NULL', 'LEFT')
|
|
|
+ ->where('a.work_order', $workOrderNo)
|
|
|
+ ->whereNull('a.del_rq')
|
|
|
+ ->field('b.订单编号,b.生产款号,b.款式,a.process_code as 工序号,a.process_name as 工序名称,a.staff_no as 员工编号,a.staff_name as 员工姓名,
|
|
|
+ SUM(a.number) as 数量,MIN(a.date) as 开工日期,MAX(a.date) as 完工日期,
|
|
|
+ SUM(a.standard_score) as 工分,SUM(a.production_score) as 工时,SUM(a.salary) as 工资')
|
|
|
+ ->group('b.订单编号,b.生产款号,b.款式,a.process_code,a.process_name,a.staff_no,a.staff_name')
|
|
|
+ ->order('a.process_code asc,a.staff_no asc')
|
|
|
+ ->select();
|
|
|
+
|
|
|
+ if (empty($list)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $rows = [];
|
|
|
+ foreach ($list as $row) {
|
|
|
+ $rows[] = [
|
|
|
+ '订单编号' => $row['订单编号'],
|
|
|
+ '生产款号' => $row['生产款号'],
|
|
|
+ '款式' => $row['款式'],
|
|
|
+ '工序号' => $row['工序号'],
|
|
|
+ '工序名称' => $row['工序名称'],
|
|
|
+ '员工编号' => $row['员工编号'],
|
|
|
+ '员工姓名' => $row['员工姓名'],
|
|
|
+ '数量' => floatval($row['数量']),
|
|
|
+ '开工日期' => !empty($row['开工日期']) ? date('Y-m-d', strtotime($row['开工日期'])) : '',
|
|
|
+ '完工日期' => !empty($row['完工日期']) ? date('Y-m-d', strtotime($row['完工日期'])) : '',
|
|
|
+ '填报日期' => $syncDate,
|
|
|
+ '工分' => round(floatval($row['工分']), 4),
|
|
|
+ '工时' => round(floatval($row['工时']), 4),
|
|
|
+ '工资' => round(floatval($row['工资']), 4),
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return $rows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function fetchWorkOrderBasic($workOrderNo)
|
|
|
+ {
|
|
|
+ return Db::table('工单_基本资料')
|
|
|
+ ->where('订单编号', $workOrderNo)
|
|
|
+ ->whereNull('Mod_rq')
|
|
|
+ ->find();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function fetchWorkOrderProcessList($workOrderNo)
|
|
|
+ {
|
|
|
+ return Db::table('工单_基础工艺资料')
|
|
|
+ ->where('work_order', $workOrderNo)
|
|
|
+ ->whereNull('del_rq')
|
|
|
+ ->field('work_order,part_code,part_name,process_code,process_name,big_process,
|
|
|
+ standard_hour,standard_minutes,standard_score,money,coefficient,remark')
|
|
|
+ ->order('process_code')
|
|
|
+ ->select();
|
|
|
+ }
|
|
|
+
|
|
|
+ private function fetchReportSeqRange($workOrderNo)
|
|
|
+ {
|
|
|
+ $range = Db::table('设备_工分计酬')
|
|
|
+ ->where('work_order', $workOrderNo)
|
|
|
+ ->whereNull('del_rq')
|
|
|
+ ->field('MIN(id) as min_id,MAX(id) as max_id')
|
|
|
+ ->find();
|
|
|
+ return [
|
|
|
+ 'start' => !empty($range['min_id']) ? (string)$range['min_id'] : '',
|
|
|
+ 'end' => !empty($range['max_id']) ? (string)$range['max_id'] : '',
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function insertSyncLog(array $data)
|
|
|
+ {
|
|
|
+ $data['create_time'] = date('Y-m-d H:i:s');
|
|
|
+ $data['update_time'] = $data['create_time'];
|
|
|
+ return (int)Db::name('mes_erp_work_order_sync_log')->insertGetId($data);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function insertOperationLog(array $data)
|
|
|
+ {
|
|
|
+ $data['create_time'] = date('Y-m-d H:i:s');
|
|
|
+ return (int)Db::name('mes_erp_operation_log')->insertGetId($data);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function getErpDb()
|
|
|
+ {
|
|
|
+ return Db::connect(config('database.db2'));
|
|
|
+ }
|
|
|
+
|
|
|
+ private function batchResolveDisplaySyncStatus(array $orderNos)
|
|
|
+ {
|
|
|
+ $orderNos = array_values(array_unique(array_filter($orderNos)));
|
|
|
+ if (empty($orderNos)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ $defaultStatus = $this->defaultDisplaySyncStatus();
|
|
|
+ $result = [];
|
|
|
+ foreach ($orderNos as $orderNo) {
|
|
|
+ $result[$orderNo] = $defaultStatus;
|
|
|
+ }
|
|
|
+
|
|
|
+ $latestSyncRows = Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->whereIn('work_order_no', $orderNos)
|
|
|
+ ->order('id desc')
|
|
|
+ ->select();
|
|
|
+ $latestSyncMap = [];
|
|
|
+ foreach ($latestSyncRows as $row) {
|
|
|
+ if (!isset($latestSyncMap[$row['work_order_no']])) {
|
|
|
+ $latestSyncMap[$row['work_order_no']] = $row;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $successSyncRows = Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->whereIn('work_order_no', $orderNos)
|
|
|
+ ->where('sync_status', self::SYNC_STATUS_SUCCESS)
|
|
|
+ ->order('id desc')
|
|
|
+ ->select();
|
|
|
+ $latestSuccessSyncMap = [];
|
|
|
+ foreach ($successSyncRows as $row) {
|
|
|
+ if (!isset($latestSuccessSyncMap[$row['work_order_no']])) {
|
|
|
+ $latestSuccessSyncMap[$row['work_order_no']] = $row;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $cancelRows = Db::name('mes_erp_operation_log')
|
|
|
+ ->whereIn('work_order_no', $orderNos)
|
|
|
+ ->where('operate_type', 'cancel')
|
|
|
+ ->where('operate_status', self::OPERATE_STATUS_SUCCESS)
|
|
|
+ ->order('id desc')
|
|
|
+ ->select();
|
|
|
+ $latestCancelMap = [];
|
|
|
+ foreach ($cancelRows as $row) {
|
|
|
+ if (!isset($latestCancelMap[$row['work_order_no']])) {
|
|
|
+ $latestCancelMap[$row['work_order_no']] = $row;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ foreach ($orderNos as $orderNo) {
|
|
|
+ $latestSync = isset($latestSyncMap[$orderNo]) ? $latestSyncMap[$orderNo] : null;
|
|
|
+ if (empty($latestSync)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $latestSuccessSync = isset($latestSuccessSyncMap[$orderNo]) ? $latestSuccessSyncMap[$orderNo] : null;
|
|
|
+ if (!empty($latestSuccessSync)) {
|
|
|
+ $cancelAfterSync = isset($latestCancelMap[$orderNo]) ? $latestCancelMap[$orderNo] : null;
|
|
|
+ if (!empty($cancelAfterSync) && $cancelAfterSync['operate_time'] >= $latestSuccessSync['sync_time']) {
|
|
|
+ $result[$orderNo] = [
|
|
|
+ 'code' => 'cancelled',
|
|
|
+ 'text' => '已撤回',
|
|
|
+ 'sync_time' => $latestSuccessSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSuccessSync['id'],
|
|
|
+ ];
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $result[$orderNo] = [
|
|
|
+ 'code' => 'synced',
|
|
|
+ 'text' => '已同步',
|
|
|
+ 'sync_time' => $latestSuccessSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSuccessSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSuccessSync['id'],
|
|
|
+ ];
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((int)$latestSync['sync_status'] === self::SYNC_STATUS_FAILED) {
|
|
|
+ $result[$orderNo] = [
|
|
|
+ 'code' => 'failed',
|
|
|
+ 'text' => '同步失败',
|
|
|
+ 'sync_time' => $latestSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSync['id'],
|
|
|
+ ];
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $result[$orderNo] = [
|
|
|
+ 'code' => 'pending',
|
|
|
+ 'text' => '待同步',
|
|
|
+ 'sync_time' => $latestSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSync['id'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function resolveDisplaySyncStatus($workOrderNo)
|
|
|
+ {
|
|
|
+ $latestSync = Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->where('work_order_no', $workOrderNo)
|
|
|
+ ->order('id desc')
|
|
|
+ ->find();
|
|
|
+
|
|
|
+ if (empty($latestSync)) {
|
|
|
+ return $this->defaultDisplaySyncStatus();
|
|
|
+ }
|
|
|
+
|
|
|
+ $latestSuccessSync = Db::name('mes_erp_work_order_sync_log')
|
|
|
+ ->where('work_order_no', $workOrderNo)
|
|
|
+ ->where('sync_status', self::SYNC_STATUS_SUCCESS)
|
|
|
+ ->order('id desc')
|
|
|
+ ->find();
|
|
|
+
|
|
|
+ if (!empty($latestSuccessSync)) {
|
|
|
+ $cancelAfterSync = Db::name('mes_erp_operation_log')
|
|
|
+ ->where('work_order_no', $workOrderNo)
|
|
|
+ ->where('operate_type', 'cancel')
|
|
|
+ ->where('operate_status', self::OPERATE_STATUS_SUCCESS)
|
|
|
+ ->where('operate_time', '>=', $latestSuccessSync['sync_time'])
|
|
|
+ ->order('id desc')
|
|
|
+ ->find();
|
|
|
+ if (!empty($cancelAfterSync)) {
|
|
|
+ return [
|
|
|
+ 'code' => 'cancelled',
|
|
|
+ 'text' => '已撤回',
|
|
|
+ 'sync_time' => $latestSuccessSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSuccessSync['id'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return [
|
|
|
+ 'code' => 'synced',
|
|
|
+ 'text' => '已同步',
|
|
|
+ 'sync_time' => $latestSuccessSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSuccessSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSuccessSync['id'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((int)$latestSync['sync_status'] === self::SYNC_STATUS_FAILED) {
|
|
|
+ return [
|
|
|
+ 'code' => 'failed',
|
|
|
+ 'text' => '同步失败',
|
|
|
+ 'sync_time' => $latestSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSync['id'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'code' => 'pending',
|
|
|
+ 'text' => '待同步',
|
|
|
+ 'sync_time' => $latestSync['sync_time'],
|
|
|
+ 'sync_msg' => $latestSync['sync_msg'],
|
|
|
+ 'sync_log_id' => $latestSync['id'],
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function defaultDisplaySyncStatus()
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ 'code' => 'unsynced',
|
|
|
+ 'text' => '未同步',
|
|
|
+ 'sync_time' => '',
|
|
|
+ 'sync_msg' => '',
|
|
|
+ 'sync_log_id' => null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function buildSyncOperateContent($workOrderNo, $syncModule, $operateType)
|
|
|
+ {
|
|
|
+ $moduleText = $this->syncModuleText($syncModule);
|
|
|
+ $typeMap = [
|
|
|
+ 'sync' => '同步',
|
|
|
+ 'retry' => '重试同步',
|
|
|
+ 'manual' => '手动同步',
|
|
|
+ 'auto' => '自动同步',
|
|
|
+ ];
|
|
|
+ $typeText = isset($typeMap[$operateType]) ? $typeMap[$operateType] : '同步';
|
|
|
+ return $typeText . '工单' . $workOrderNo . ',模块:' . $moduleText;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function syncStatusText($status)
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ self::SYNC_STATUS_PENDING => '待同步',
|
|
|
+ self::SYNC_STATUS_SUCCESS => '同步成功',
|
|
|
+ self::SYNC_STATUS_FAILED => '同步失败',
|
|
|
+ ];
|
|
|
+ return isset($map[(int)$status]) ? $map[(int)$status] : '未知';
|
|
|
+ }
|
|
|
+
|
|
|
+ private function syncModuleText($module)
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ '1' => '工单数据',
|
|
|
+ '2' => '工艺数据',
|
|
|
+ '3' => '报工数据',
|
|
|
+ '4' => '全量同步',
|
|
|
+ ];
|
|
|
+ return isset($map[(string)$module]) ? $map[(string)$module] : (string)$module;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function operateStatusText($status)
|
|
|
+ {
|
|
|
+ $map = [
|
|
|
+ self::OPERATE_STATUS_SUCCESS => '成功',
|
|
|
+ self::OPERATE_STATUS_FAILED => '失败',
|
|
|
+ self::OPERATE_STATUS_PROCESSING => '处理中',
|
|
|
+ ];
|
|
|
+ return isset($map[(int)$status]) ? $map[(int)$status] : '未知';
|
|
|
+ }
|
|
|
+
|
|
|
+ private function formatDateValue($value)
|
|
|
{
|
|
|
- if ($this->request->isGet() === false){
|
|
|
- $this->error('请求错误');
|
|
|
+ if (empty($value)) {
|
|
|
+ return null;
|
|
|
}
|
|
|
- $db2 = Db::connect(config('database.db2'));
|
|
|
-
|
|
|
+ $timestamp = strtotime($value);
|
|
|
+ return $timestamp ? date('Y-m-d', $timestamp) : null;
|
|
|
}
|
|
|
-}
|
|
|
+}
|