Parcourir la source

Merge branch 'master' of www.gitinn.com:zaixianjiaoyu/sourcecode

KaysonCui il y a 5 ans
Parent
commit
7502908d02
29 fichiers modifiés avec 679 ajouts et 474 suppressions
  1. 11 2
      front/project/www/components/Date/index.js
  2. 1 1
      front/project/www/components/More/index.js
  3. 103 138
      front/project/www/routes/examination/list/page.js
  4. 4 4
      front/project/www/routes/examination/main/page.js
  5. 1 1
      front/project/www/routes/exercise/list/page.js
  6. 3 4
      front/project/www/routes/exercise/main/page.js
  7. 157 226
      front/project/www/routes/my/course/page.js
  8. 59 32
      front/project/www/routes/my/order/page.js
  9. 4 0
      front/project/www/stores/order.js
  10. 1 1
      front/project/www/stores/user.js
  11. 1 1
      server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRelationMapper.xml
  12. 7 1
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java
  13. 17 4
      server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java
  14. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java
  15. 4 4
      server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java
  16. 55 10
      server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java
  17. 1 1
      server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java
  18. 3 3
      server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserListDto.java
  19. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseExtendDto.java
  20. 47 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/OrderInvoiceDto.java
  21. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseAppointmentCommentDto.java
  22. 10 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java
  23. 52 0
      server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderDetailDto.java
  24. 27 18
      server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java
  25. 26 21
      server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java
  26. 34 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserInvoiceService.java
  27. 16 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java
  28. 13 0
      server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderService.java
  29. 1 1
      server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

+ 11 - 2
front/project/www/components/Date/index.js

@@ -31,10 +31,19 @@ export default class extends Component {
   render() {
     const { show, hideInput, disabledDate, theme = '', value, onChange } = this.props;
     return (
-      <div className={`g-date-block ${hideInput ? 'hide-input' : ''}`}>
+      <div
+        ref={ref => {
+          if (!this.ref) {
+            this.ref = ref;
+            this.setState({ load: false });
+          }
+        }}
+        className={`g-date-block ${hideInput ? 'hide-input' : ''}`}
+      >
         <DatePicker
-          open={show}
+          open={show && this.ref}
           value={value}
+          getCalendarContainer={() => this.ref.parentNode}
           dropdownClassName={`g-date ${theme} ${hideInput ? 'hide-input' : ''}`}
           disabledDate={disabledDate}
           dateRender={date => this.dateRender(date)}

+ 1 - 1
front/project/www/components/More/index.js

@@ -10,7 +10,7 @@ function More(props) {
       overlay={
         <Menu onClick={key => onClick && onClick(key)}>
           {menu.map(item => {
-            if (!item.key) return null;
+            if (!item || !item.key) return null;
             return <Menu.Item key={item.key}>{item.label}</Menu.Item>;
           })}
         </Menu>

+ 103 - 138
front/project/www/routes/examination/list/page.js

@@ -32,36 +32,17 @@ export default class extends Page {
               <div>
                 <ProgressText progress={progress} size="small" />
               </div>
-              {this.state.showPrev && (
-                <div className="prev">
-                  <div className="night f-s-16">
-                    {record.title}
-                    {record.prevPaper && record.prevPaper.paperNo > 0
-                      ? String.fromCharCode(64 + record.prevPaper.paperNo)
-                      : ''}
-                  </div>
-                </div>
-              )}
             </div>,
-            <div className="table-row" style={{ paddingRight: 40 }}>
-              <div className="night f-s-16">
-                {record.title}
-                {record.paper && record.paper.paperNo > 0 ? String.fromCharCode(64 + record.paper.paperNo) : ''}
-              </div>
-              <div>
-                <ProgressText progress={progress} size="small" />
-              </div>
-              {this.state.showPrev && (
-                <div className="prev">
-                  <div className="night f-s-16">
-                    {record.title}
-                    {record.prevPaper && record.prevPaper.paperNo > 0
-                      ? String.fromCharCode(64 + record.prevPaper.paperNo)
-                      : ''}
-                  </div>
+            this.state.showPrev && record.prevReport && (
+              <div className="table-row prev t-3">
+                <div className="night f-s-16">
+                  {record.title}
+                  {record.prevPaper && record.prevPaper.paperNo > 0
+                    ? String.fromCharCode(64 + record.prevPaper.paperNo)
+                    : ''}
                 </div>
-              )}
-            </div>,
+              </div>
+            ),
           ];
         },
       },
@@ -76,29 +57,15 @@ export default class extends Page {
                 {record.report ? `${record.report.score.totalScore}分${record.report.score.totalRank}th` : '-分-th'}
               </div>
               <div className="f-s-12">全站: {Math.round(record.totalScore / record.totalTimes)}分</div>
-              {this.state.showPrev && record.prevReport && (
-                <div className="prev">
-                  <div className="night f-s-16 f-w-b">
-                    {record.prevReport.score.totalScore}分{record.prevReport.score.totalRank}th
-                  </div>
-                  <div className="f-s-12">全站: {Math.round(record.secondTotalScore / record.secondTotalTimes)}分</div>
-                </div>
-              )}
             </div>,
-            <div className="table-row">
-              <div className="night f-s-16 f-w-b">
-                {record.report ? `${record.report.score.totalScore}分${record.report.score.totalRank}th` : '-分-th'}
-              </div>
-              <div className="f-s-12">全站: {Math.round(record.totalScore / record.totalTimes)}分</div>
-              {this.state.showPrev && record.prevReport && (
-                <div className="prev">
-                  <div className="night f-s-16 f-w-b">
-                    {record.prevReport.score.totalScore}分{record.prevReport.score.totalRank}th
-                  </div>
-                  <div className="f-s-12">全站: {Math.round(record.secondTotalScore / record.secondTotalTimes)}分</div>
+            this.state.showPrev && record.prevReport && (
+              <div className="table-row prev t-3">
+                <div className="night f-s-16 f-w-b">
+                  {record.prevReport.score.totalScore}分{record.prevReport.score.totalRank}th
                 </div>
-              )}
-            </div>,
+                <div className="f-s-12">全站: {Math.round(record.secondTotalScore / record.secondTotalTimes)}分</div>
+              </div>
+            ),
           ];
         },
       },
@@ -107,22 +74,22 @@ export default class extends Page {
         width: 110,
         align: 'left',
         render: record => {
-          return (
+          return [
             <div className="table-row">
               <div className="night f-s-16 f-w-b">
                 {record.report ? `${record.report.score.verbalScore}分${record.report.score.verbalRank}th` : '-分-th'}
               </div>
               <div className="f-s-12">全站: {Math.round(record.verbalScore / record.totalTimes)}分</div>
-              {this.state.showPrev && record.prevReport && (
-                <div className="prev">
-                  <div className="night f-s-16 f-w-b">
-                    {record.prevReport.score.verbalScore}分{record.prevReport.score.verbalRank}th
-                  </div>
-                  <div className="f-s-12">全站: {Math.round(record.secondVerbalScore / record.secondTotalTimes)}分</div>
+            </div>,
+            this.state.showPrev && record.prevReport && (
+              <div className="table-row prev t-3">
+                <div className="night f-s-16 f-w-b">
+                  {record.prevReport.score.verbalScore}分{record.prevReport.score.verbalRank}th
                 </div>
-              )}
-            </div>
-          );
+                <div className="f-s-12">全站: {Math.round(record.secondVerbalScore / record.secondTotalTimes)}分</div>
+              </div>
+            ),
+          ];
         },
       },
       {
@@ -130,22 +97,21 @@ export default class extends Page {
         width: 110,
         align: 'left',
         render: record => {
-          return (
-            <div className="table-row">
+          return [<div className="table-row">
+            <div className="night f-s-16 f-w-b">
+              {record.report ? `${record.report.score.quantScore}分${record.report.score.quantRank}th` : '-分-th'}
+            </div>
+            <div className="f-s-12">全站: {Math.round(record.quantScore / record.totalTimes)}分</div>
+          </div>,
+          this.state.showPrev && record.prevReport && (
+            <div className="table-row prev t-3">
               <div className="night f-s-16 f-w-b">
-                {record.report ? `${record.report.score.quantScore}分${record.report.score.quantRank}th` : '-分-th'}
+                {record.prevReport.score.quantScore}分{record.prevReport.score.quantRank}th
               </div>
-              <div className="f-s-12">全站: {Math.round(record.quantScore / record.totalTimes)}分</div>
-              {this.state.showPrev && record.prevReport && (
-                <div className="prev">
-                  <div className="night f-s-16 f-w-b">
-                    {record.prevReport.score.quantScore}分{record.prevReport.score.quantRank}th
-                  </div>
-                  <div className="f-s-12">全站: {Math.round(record.secondQuantScore / record.secondTotalTimes)}分</div>
-                </div>
-              )}
+              <div className="f-s-12">全站: {Math.round(record.secondQuantScore / record.secondTotalTimes)}分</div>
             </div>
-          );
+          ),
+          ];
         },
       },
       {
@@ -153,22 +119,21 @@ export default class extends Page {
         width: 110,
         align: 'left',
         render: record => {
-          return (
-            <div className="table-row">
+          return [<div className="table-row">
+            <div className="night f-s-16 f-w-b">
+              {record.report ? `${record.report.score.irScore}分${record.report.score.irRank}th` : '-分-th'}
+            </div>
+            <div className="f-s-12">全站: {Math.round(record.irScore / record.totalTimes)}分</div>
+          </div>,
+          this.state.showPrev && record.prevReport && (
+            <div className="table-row prev t-3">
               <div className="night f-s-16 f-w-b">
-                {record.report ? `${record.report.score.irScore}分${record.report.score.irRank}th` : '-分-th'}
+                {record.prevReport.score.irScore}分{record.prevReport.score.irRank}th
               </div>
-              <div className="f-s-12">全站: {Math.round(record.irScore / record.totalTimes)}分</div>
-              {this.state.showPrev && record.prevReport && (
-                <div className="prev">
-                  <div className="night f-s-16 f-w-b">
-                    {record.prevReport.score.irScore}分{record.prevReport.score.irRank}th
-                  </div>
-                  <div className="f-s-12">全站: {Math.round(record.secondIrScore / record.secondTotalTimes)}分</div>
-                </div>
-              )}
+              <div className="f-s-12">全站: {Math.round(record.secondIrScore / record.secondTotalTimes)}分</div>
             </div>
-          );
+          ),
+          ];
         },
       },
       {
@@ -176,18 +141,17 @@ export default class extends Page {
         width: 100,
         align: 'left',
         render: record => {
-          return (
-            <div className="table-row">
-              <div>{record.report && formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
-              <div>{record.report && formatDate(record.report.updateTime, 'HH:mm')}</div>
-              {this.state.showPrev && record.prevReport && (
-                <div className="prev">
-                  <div>{formatDate(record.prevReport.updateTime, 'YYYY-MM-DD')}</div>
-                  <div>{formatDate(record.prevReport.updateTime, 'HH:mm')}</div>
-                </div>
-              )}
+          return [<div className="table-row">
+            <div>{record.report && formatDate(record.report.updateTime, 'YYYY-MM-DD')}</div>
+            <div>{record.report && formatDate(record.report.updateTime, 'HH:mm')}</div>
+          </div>,
+          this.state.showPrev && record.prevReport && (
+            <div className="table-row prev t-3">
+              <div>{formatDate(record.prevReport.updateTime, 'YYYY-MM-DD')}</div>
+              <div>{formatDate(record.prevReport.updateTime, 'HH:mm')}</div>
             </div>
-          );
+          ),
+          ];
         },
       },
       {
@@ -195,29 +159,31 @@ export default class extends Page {
         width: 110,
         align: 'left',
         render: record => {
-          return (
-            <div className="table-row p-t-1">
-              {!record.report && (
-                <IconButton
-                  className="m-r-5"
-                  type="start"
-                  tip="Start"
-                  onClick={() => {
-                    Question.startLink('examination', record);
-                  }}
-                />
-              )}
-              {record.report && !record.report.isFinish && (
-                <IconButton
-                  type="continue"
-                  tip="Continue"
-                  onClick={() => {
-                    Question.continueLink('examination', record);
-                  }}
-                />
-              )}
-            </div>
-          );
+          return [<div className="table-row p-t-1">
+            {!record.report && (
+              <IconButton
+                className="m-r-5"
+                type="start"
+                tip="Start"
+                onClick={() => {
+                  Question.startLink('examination', record);
+                }}
+              />
+            )}
+            {record.report && !record.report.isFinish && (
+              <IconButton
+                type="continue"
+                tip="Continue"
+                onClick={() => {
+                  Question.continueLink('examination', record);
+                }}
+              />
+            )}
+          </div>,
+          this.state.showPrev && record.prevReport && (
+            <div className="table-row prev" />
+          ),
+          ];
         },
       },
       {
@@ -225,33 +191,32 @@ export default class extends Page {
         width: 50,
         align: 'right',
         render: record => {
-          return (
-            <div className="table-row p-t-1">
-              {record.report && record.report.isFinish && (
+          return [<div className="table-row p-t-1">
+            {record.report && record.report.isFinish && (
+              <IconButton
+                className="m-r-5"
+                type="report"
+                tip="Report"
+                onClick={() => {
+                  Question.reportLink(record);
+                }}
+              />
+            )}
+          </div>,
+          this.state.showPrev && (
+            <div className="prev">
+              {record.prevReport && (
                 <IconButton
-                  className="m-r-5"
                   type="report"
                   tip="Report"
                   onClick={() => {
-                    Question.reportLink(record);
+                    Question.reportPrevLink(record);
                   }}
                 />
               )}
-              {this.state.showPrev && (
-                <div className="prev">
-                  {record.prevReport && (
-                    <IconButton
-                      type="report"
-                      tip="Report"
-                      onClick={() => {
-                        Question.reportPrevLink(record);
-                      }}
-                    />
-                  )}
-                </div>
-              )}
             </div>
-          );
+          ),
+          ];
         },
       },
     ];

+ 4 - 4
front/project/www/routes/examination/main/page.js

@@ -141,13 +141,13 @@ export default class extends Page {
           },
           {
             title: '剩余',
-            number: row.userNumber ? row.questionNumber - row.userNumber : '-',
+            number: row.userNumber ? row.paperNumber - row.userNumber : '-',
             unit: '套',
           },
         ];
-        row.pieValue = formatPercent(row.userNumber, row.questionNumber);
-        row.pieText = formatPercent(row.userNumber, row.questionNumber, false);
-        row.pieSubText = `共${row.questionNumber}套`;
+        row.pieValue = formatPercent(row.userNumber, row.paperNumber);
+        row.pieText = formatPercent(row.userNumber, row.paperNumber, false);
+        row.pieSubText = `共${row.paperNumber}套`;
 
         return row;
       });

+ 1 - 1
front/project/www/routes/exercise/list/page.js

@@ -141,7 +141,7 @@ export default class extends Page {
     const { id } = this.params;
     Main.getExerciseParent(id).then(result => {
       const navs = result.map(row => {
-        row.title = `${row.titleZh}${row.titleEn}`;
+        row.title = row.level > 2 ? row.titleZh : `${row.titleZh}${row.titleEn}`;
         return row;
       });
       this.inited = true;

+ 3 - 4
front/project/www/routes/exercise/main/page.js

@@ -458,8 +458,7 @@ export default class extends Page {
       const now = new Date().getTime();
       courseMap.open = result.filter(row => !row.isUsed && (!row.useEndTime || new Date(row.useEndTime).getTime() > now));
       courseMap.end = result.filter(row => (!row.isSuspend || (row.isSuspend && row.restoreTime)) && new Date(row.useEndTime).getTime() < now);
-
-      courseMap.process = result.filter(row => row.isUsed && (row.isSuspend || row.restoreTime) && new Date(row.useEndTime).getTime() >= now);
+      courseMap.process = result.filter(row => row.isUsed && ((!row.isSuspend && new Date(row.useEndTime).getTime() >= now) || (row.isSuspend && !row.restoreTime)));
       this.setState({ courseMap });
     });
 
@@ -746,8 +745,8 @@ export default class extends Page {
             return <Card1
               title={`${row.course.title}${row.vsNo > 0 ? `V${row.vsNo}` : ''}${row.number > 0 ? `(${row.number}课时)` : ''}`}
               tag={CourseModuleMap[row.course.courseModule]}
-              status={row.isStop && !row.isSuspend ? 'stop' : 'ing'}
-              list={row.papers.map(r => {
+              status={row.isSuspend && !row.restoreTime ? 'stop' : 'ing'}
+              list={(row.papers || []).map(r => {
                 let progress = 0;
                 if (r.report) {
                   progress = formatPercent(r.report.userNumber, r.report.questionNumber);

+ 157 - 226
front/project/www/routes/my/course/page.js

@@ -32,20 +32,16 @@ export default class extends Page {
   }
 
   formatRecord(row) {
-    if (row.number > 1 && row.number === row.appointments.length) {
-      row.status = 'end';
-    } else if (row.useEndTime) {
-      if (new Date(row.useEndTime).getTime() > new Date().getTime()) {
-        row.status = 'end';
-      } else if (row.isSuspend && !row.restoreTime) {
+    if (row.useEndTime) {
+      if (row.isSuspend && !row.restoreTime) {
         row.status = 'suspend';
+      } else if (new Date(row.useEndTime).getTime() < new Date().getTime()) {
+        row.status = 'end';
       } else {
         row.status = 'ing';
       }
-    } else if (new Date(row.useEndTime).getTime() < new Date().getTime()) {
-      row.status = 'end';
     } else {
-      row.status = 'no';
+      row.status = 'not';
     }
     row.paperMap = {};
     row.appointmentPaperMap = {};
@@ -78,15 +74,22 @@ export default class extends Page {
       row.currentNo = 0;
     }
     // 如果已经最新预约结束,则添加一个空记录作为最新预约
-    if (!row.appointments || row.appointments.length === 0) {
-      row.appointments = [{}];
-    } else {
+    if (row.appointments) {
       row.appointments.forEach(r => {
         r.paper = row.appointmentPaperMap[r.id];
-        r.noteList = (row.comments || []).map(c => c.type === 'note' && c.appointmentId === r.id);
-        r.supplyList = (row.comments || []).map(c => c.type === 'supply' && c.appointmentId === r.id);
+        r.noteList = (row.comments || []).filter(c => c.type === 'note' && c.appointmentId === r.id);
+        r.supplyList = (row.comments || []).filter(c => c.type === 'supply' && c.appointmentId === r.id);
       });
       // 是否是最后一课时,是否过预约时间
+      const last = row.appointments.length - 1;
+      const appointment = row.appointments[last];
+
+      if (new Date(appointment.endTime).getTime() < new Date().getTime()) {
+        // row.status = 'end';
+        if (row.number !== row.appointments.length) {
+          row.appointments.push([{}]);
+        }
+      }
     }
 
     row.days = new Date(row.userEndTime);
@@ -126,7 +129,7 @@ export default class extends Page {
     });
   }
 
-  onAction() {}
+  onAction() { }
 
   onTabChange(tab) {
     const data = { tab };
@@ -200,7 +203,8 @@ export default class extends Page {
       });
   }
 
-  submitAppointmentComment(data) {
+  submitAppointmentComment(data, type) {
+    data.type = type;
     if (data.id) {
       My.editAppointmentComment(data).then(() => {
         this.refreshDetail(data.recordId);
@@ -326,7 +330,7 @@ export default class extends Page {
           onChange={key => this.onStatusChange(key)}
         />
         {this[`renderTab${tab}`]()}
-        <Modal show={showTime} className="clock-modal" title="打卡表" width={460} onClose={() => {}}>
+        <Modal show={showTime} className="clock-modal" title="打卡表" width={460} onClose={() => this.setState({ showTime: false })}>
           <div>
             <div className="d-i-b w-3">
               <div className="t-2 t-s-14">听课频率</div>
@@ -354,7 +358,9 @@ export default class extends Page {
           </div>
           <DatePlane
             hideInput
-            disabledDate={current => {
+            show={showTime}
+            onChange={() => { }}
+            disabledDate={(current) => {
               const date = current.format('YYYY-MM-DD');
               return data.stopTimeMap[date];
             }}
@@ -440,7 +446,7 @@ export default class extends Page {
           title="上传笔记"
           width={630}
           confirmText="提交"
-          onConfirm={() => this.submitAppointmentComment(note)}
+          onConfirm={() => this.submitAppointmentComment(note, 'note')}
           onCancel={() => this.setState({ showUploadNote: false, note: {} })}
         >
           <textarea
@@ -455,15 +461,19 @@ export default class extends Page {
           />
           <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
             <Button theme="file">上传文件</Button>
-            <span className="m-l-1 t-3 t-s-14">支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件</span>
+            <span className="m-l-1 t-3 t-s-14">{note.file ? `上传文件:${note.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span>
             <div className="fixed">
-              <span className="f-w-b t-s-18">放开文件立刻上传{supply.file && `上传文件:${supply.name} 成功`}</span>
+              <span className="f-w-b t-s-18">放开文件立刻上传</span>
             </div>
             <FileUpload
               type="none"
               onDragEnter={() => this.setState({ draging: true })}
               onDragLeave={() => this.setState({ draging: false })}
-              onUpload={file => this.uploadNote(file)}
+              onUpload={({ file }) => Common.upload(file).then(result => {
+                note.file = result.url;
+                note.name = file.name;
+                this.setState({ note });
+              })}
             />
           </div>
           <div className="b-b m-t-2" />
@@ -497,7 +507,7 @@ export default class extends Page {
           title="上传留言"
           width={630}
           confirmText="提交"
-          onConfirm={() => this.submitAppointmentComment(supply)}
+          onConfirm={() => this.submitAppointmentComment(supply, 'supply')}
           onCancel={() => this.setState({ showUploadSupply: false, supply: {} })}
         >
           <textarea
@@ -512,15 +522,19 @@ export default class extends Page {
           />
           <div className={`t-c drag-upload ${this.state.draging ? 'draging' : ''}`}>
             <Button theme="file">上传文件</Button>
-            <span className="m-l-1 t-3 t-s-14">支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件</span>
+            <span className="m-l-1 t-3 t-s-14">{supply.file ? `上传文件:${supply.name} 成功` : '支持 docx, xls, PDF, rar, zip, PNG, JPG 等类型的文件'}</span>
             <div className="fixed">
-              <span className="f-w-b t-s-18">放开文件立刻上传{supply.file && `上传文件:${supply.name} 成功`}</span>
+              <span className="f-w-b t-s-18">放开文件立刻上传</span>
             </div>
             <FileUpload
               type="none"
               onDragEnter={() => this.setState({ draging: true })}
               onDragLeave={() => this.setState({ draging: false })}
-              onUpload={file => this.uploadSupply(file)}
+              onUpload={({ file }) => Common.upload(file).then(result => {
+                supply.file = result.url;
+                supply.name = file.name;
+                this.setState({ supply });
+              })}
             />
           </div>
           <div className="b-b m-t-2" />
@@ -531,7 +545,6 @@ export default class extends Page {
 
   renderTabonline() {
     const { list = [] } = this.state;
-    console.log(list);
     return list.map(item => {
       return (
         <CourseOnline
@@ -561,44 +574,23 @@ export default class extends Page {
   renderTabvs() {
     const { list = [] } = this.state;
     return list.map(item => {
-      return (
-        <CourseVs
-          data={item}
-          user={this.props.user}
-          refreshDetail={recordId => {
-            this.refreshDetail(recordId);
-          }}
-          onRestore={() => {
-            this.setState({ showRestore: true, restore: item });
-          }}
-          onSuspend={() => {
-            this.setState({ showSuspend: true, suspend: item });
-          }}
-          onComment={() => {
-            this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } });
-          }}
-          closeCommentTips={() => this.closeCommentTips(item.id)}
-          onNote={appointment => this.setState({ showNote: true, appointment, data: item })}
-          onUploadNote={(appointment, row) => {
-            this.setState({ showUploadNote: true, appointment, data: item, note: row || {} });
-          }}
-          onUploadSupply={(appointment, row) => {
-            this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} });
-          }}
-          onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)}
-          onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)}
-          onSupply={appointment => this.setState({ whoSupply: true, appointment, data: item })}
-          onUploadQuestion={(appointment, file, name) => {
-            this.submitQuestionFile({
-              id: appointment.id,
-              recordId: appointment.recordId,
-              questionFile: file,
-              questionFileName: name,
-            });
-          }}
-          setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)}
-        />
-      );
+      return <CourseVs data={item} user={this.props.user} refreshDetail={(recordId) => {
+        this.refreshDetail(recordId);
+      }} onRestore={() => {
+        this.setState({ showRestore: true, restore: item });
+      }} onSuspend={() => {
+        this.setState({ showSuspend: true, suspend: item });
+      }} onComment={() => {
+        this.setState({ showComment: true, comment: { channel: 'course-vs', position: item.course.id } });
+      }} closeCommentTips={() => this.closeCommentTips(item.id)}
+        onUploadNote={(appointment, row) => this.setState({ showUploadNote: true, appointment, data: item, note: row || {} })}
+        onUploadSupply={(appointment, row) => this.setState({ showUploadSupply: true, appointment, data: item, supply: row || {} })}
+        onDeleteNote={(appointment, row) => this.deleteAppointmentComment(row)}
+        onDeleteSupply={(appointment, row) => this.deleteAppointmentComment(row)}
+        onNote={(appointment) => this.setState({ showNote: true, appointment, data: item })}
+        onSupply={(appointment) => this.setState({ showSupply: true, appointment, data: item })}
+        onUploadQuestion={(appointment, file, name) => this.submitQuestionFile({ id: appointment.id, recordId: appointment.recordId, questionFile: file, questionFileName: name })}
+        setCCTalkName={(appointment, cctalkName) => this.setCCTalkName(appointment.recordId, cctalkName)} />;
     });
   }
 }
@@ -832,9 +824,7 @@ class CourseOnline extends Component {
               <div className="t1">授课老师</div>
               <div className="t2">{data.course.teacher}</div>
               <div className="t1">有效期</div>
-              <div className="t2">
-                {formatDate(data.useStartTime, 'YYYY-MM-DD')}至{formatDate(data.useEndTime, 'YYYY-MM-DD')}
-              </div>
+              <div className="t-s-12">{formatDate(data.useStartTime, 'YYYY-MM-DD')}<br />至{formatDate(data.useEndTime, 'YYYY-MM-DD')}</div>
             </div>
           </div>
           <div className="right">
@@ -982,21 +972,22 @@ const statusMap = {
   },
   3: (appointment, data) => {
     if (!data.cctalkName) return 'not';
+    // if (new Date(appointment.endTime).getTime() > new Date()) return 'not';
     return '';
   },
-  4: appointment => {
-    if (!appointment.noteList && appointment.noteList.length === 0) return 'not';
+  4: (appointment) => {
+    if (!appointment.noteList || appointment.noteList.length === 0) return 'not';
     return '';
   },
-  5: appointment => {
-    if (!appointment.supplyList && appointment.supplyList.length === 0) return 'not';
+  5: (appointment) => {
+    if (!appointment.supplyList || appointment.supplyList.length === 0) return 'not';
     return '';
   },
   6: () => {
     return '';
   },
-  7: appointment => {
-    const { paper } = appointment;
+  7: (appointment) => {
+    const { paper = {} } = appointment;
     if (!paper.report || formatPercent(paper.report.userNumber, paper.report.questionNumber) < 100) return 'not';
     return '';
   },
@@ -1070,7 +1061,7 @@ class CourseVs extends Component {
           title: '课后补充',
           key: 'supply',
           render: (text, record) => {
-            this.props.onSupply(record);
+            return <a onClick={() => this.props.onSupply(record)}>查看</a>;
           },
         },
       ],
@@ -1119,7 +1110,7 @@ class CourseVs extends Component {
 
   renderIng() {
     const { data, onComment, closeCommentTips } = this.props;
-    const { tab, showTips } = this.state;
+    const { tab, showTips, open } = this.state;
     return (
       <div className="education-item ing">
         <div className="title">
@@ -1141,12 +1132,10 @@ class CourseVs extends Component {
             />
           </div>
         </div>
-        {showTips && (
-          <div className="continue" onClick={() => closeCommentTips()}>
-            <Assets name="notice" />
-            课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
-          </div>
-        )}
+        {showTips && <div className="continue">
+          <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
+          课程已过半,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
+        </div>}
         <div className="detail">
           <div className="left">
             <Assets name="sun_blue" src={data.course.cover} />
@@ -1171,20 +1160,21 @@ class CourseVs extends Component {
               </div>
             </div>
           </div>
+          <div className="open">
+            <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
+          </div>
         </div>
-        {(data.course.vsType === 'system' || data.course.vsType === 'answer') && (
-          <Tabs
-            className="t-l"
-            type="line"
-            theme="theme"
-            size="small"
-            width={80}
-            active={tab}
-            tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
-            onChange={key => this.setState({ tab: key })}
-          />
-        )}
-        {tab === 'ing' ? this.renderTimeLine() : this.renderTable()}
+        {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs
+          className="t-l"
+          type="line"
+          theme="theme"
+          size="small"
+          width={80}
+          active={tab}
+          tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
+          onChange={key => this.setState({ tab: key })}
+        />}
+        {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())}
       </div>
     );
   }
@@ -1258,12 +1248,10 @@ class CourseVs extends Component {
             />
           </div>
         </div>
-        {showTips && (
-          <div className="continue" onClick={() => closeCommentTips()}>
-            <Assets name="notice" />
-            课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
-          </div>
-        )}
+        {showTips && <div className="continue">
+          <Icon className='close m-r-5 t-3' type="close-circle" theme="filled" onClick={() => closeCommentTips()} />
+          课程已结束,可以来写写评价啦<a onClick={() => onComment()}>去写评价 ></a>
+        </div>}
         <div className="detail">
           <div className="left">
             <Assets name="sun_blue" src={data.course.cover} />
@@ -1292,19 +1280,17 @@ class CourseVs extends Component {
             <GIcon name={open ? 'up' : 'down'} onClick={() => this.setState({ open: !open })} />
           </div>
         </div>
-        {(data.course.vsType === 'system' || data.course.vsType === 'answer') && (
-          <Tabs
-            className="t-l"
-            type="line"
-            theme="theme"
-            size="small"
-            width={80}
-            active={tab}
-            tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
-            onChange={key => this.setState({ tab: key })}
-          />
-        )}
-        {tab === 'ing' ? this.renderTimeLine() : this.renderTable()}
+        {open && (data.course.vsType === 'system' || data.course.vsType === 'answer') && <Tabs
+          className="t-l"
+          type="line"
+          theme="theme"
+          size="small"
+          width={80}
+          active={tab}
+          tabs={[{ key: 'ing', title: '授课中' }, { key: 'end', title: '已结课' }]}
+          onChange={key => this.setState({ tab: key })}
+        />}
+        {open && (tab === 'ing' ? this.renderTimeLine() : this.renderTable())}
       </div>
     );
   }
@@ -1377,41 +1363,24 @@ class CourseVs extends Component {
     let status = '';
     return [
       <div className="class-hour">
-        <div className="text">
-          课时 {appointment.no}:{appointment.title}
-        </div>
-        <div className="right">
+        {data.number > 1 && <div className="text">课时 {appointment.no}:{appointment.title}</div>}
+        {data.number > 1 && <div className="right">
           <GIcon name="prev" onClick={() => this.setState({ index: index === 0 ? index : index - 1 })} />
           <span>上一课时</span>
           <span>下一课时</span>
-          <GIcon
-            name="next"
-            onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })}
-          />
-        </div>
+          <GIcon name="next" onClick={() => this.setState({ index: index >= data.appointments.length - 1 ? index : index + 1 })} />
+        </div>}
       </div>,
       <div className="time-line">
         {list.map(item => {
           if (status === '') {
             // 上一阶段完成
-            status = statusMap(appointment, data);
+            status = statusMap[item](appointment, data);
           } else {
             // 上一阶段未完成
             status = 'end';
           }
-          return (
-            <TimeLineItem
-              type={item}
-              user={this.props.user}
-              appointment={appointment}
-              data={data}
-              status={status}
-              onUploadNote={onUploadNote}
-              onUploadSupply={onUploadSupply}
-              onUploadQuestion={onUploadQuestion}
-              setCCTalkName={setCCTalkName}
-            />
-          );
+          return <TimeLineItem type={`${item}`} user={this.props.user} appointment={appointment} data={data} status={status} onUploadNote={onUploadNote} onUploadSupply={onUploadSupply} onUploadQuestion={onUploadQuestion} setCCTalkName={setCCTalkName} />;
         })}
       </div>,
     ];
@@ -1420,7 +1389,7 @@ class CourseVs extends Component {
   renderTable() {
     const { data = {} } = this.props;
     const { appointments = [] } = data;
-    return <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments} />;
+    return <UserTable size="small" columns={this.columns[data.course.vsType]} data={appointments.filter(row => row.id)} />;
   }
 }
 class TimeLineItem extends Component {
@@ -1442,7 +1411,7 @@ class TimeLineItem extends Component {
     return (
       <div className={`time-line-item ${status}`}>
         <div className="icon-title">
-          <GIcon name={iconMap[type]} active={status !== 'not'} noHover />
+          <GIcon name={iconMap[type]} active={!status} noHover />
           <div className="title">{titleMap[type]}</div>
         </div>
         <div className="time-line-detail">{this.renderDetail()}</div>
@@ -1472,7 +1441,7 @@ class TimeLineItem extends Component {
             return (
               <span>
                 请尽快与老师预约上课时间,老师微信:{data.teacher.wechat}扫码加微信{' '}
-                <Dropdown overlay={<Assets name="qrcode" />}>
+                <Dropdown overlay={<Assets name="qrcode" src={data.teacher.qr} />}>
                   <span>
                     <Assets className="m-l-1" name="erweima" />
                   </span>
@@ -1492,15 +1461,9 @@ class TimeLineItem extends Component {
           case 'end':
             return <span className="link">点此上传</span>;
           case 'not':
-            return (
-              <FileUpload
-                onUpload={file => {
-                  return Common.upload(file).then(result => onUploadQuestion(appointment, result.url, file.name));
-                }}
-              >
-                <span className="link">点此上传</span>
-              </FileUpload>
-            );
+            return <FileUpload onUpload={(file) => {
+              return Common.upload({ file }).then((result => onUploadQuestion(appointment, result.url, file.name)));
+            }}><span className="link">点此上传</span></FileUpload>;
           default:
             return (
               <a href={appointment.questionFile} target="_blank">
@@ -1511,16 +1474,19 @@ class TimeLineItem extends Component {
       case '3':
         switch (status) {
           case 'end':
-            return <input placeholder="请输入CCtalk用户名查看授课频道" />;
+            return data.cctalkName ? <span>
+              CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
+            </span> : <div><input style={{ width: 200 }} className='b-c-1 p-l-1 p-r-1 t-s-12 m-r-1' placeholder="请输入CCtalk用户名查看授课频道" onChange={(e) => {
+              this.setState({ cctalkName: e.target.value });
+            }} /><Button size="small" radius disabled>提交</Button></div>;
           case 'not':
-            return (
-              <input
-                placeholder="请输入CCtalk用户名查看授课频道"
-                onChange={e => {
-                  setCCTalkName(e.target.value);
-                }}
-              />
-            );
+            return data.cctalkName ? <span>
+              CCtalk 频道号 :{appointment.cctalkChannel} <a className="link" href="" target="_black">CC talk使用手册</a>
+            </span> : <div><input style={{ width: 200 }} className='b-c-1 p-l-1 p-r-1 t-s-12 m-r-1' placeholder="请输入CCtalk用户名查看授课频道" onChange={(e) => {
+              this.setState({ cctalkName: e.target.value });
+            }} /><Button size="small" radius onClick={() => {
+              if (this.state.cctalkName) setCCTalkName(appointment, this.state.cctalkName);
+            }} >提交</Button></div>;
           default:
             return (
               <span>
@@ -1536,47 +1502,30 @@ class TimeLineItem extends Component {
           case 'end':
             return <span className="link">点此上传</span>;
           case 'not':
-            return (
-              <span className="link" onClick={() => onUploadNote(appointment)}>
-                点此上传
-              </span>
-            );
+            return <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>;
           default:
             return (
               <div>
                 <div>
-                  <span className="link" onClick={() => onUploadNote(appointment)}>
-                    点此上传
-                  </span>
+                  <span className="link" onClick={() => onUploadNote(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>点此上传</span>
                 </div>
                 <div className="note-list">
                   {appointment.noteList.map(row => {
-                    return (
-                      <Note
-                        user={this.props.user}
-                        teacher={data.teacher}
-                        data={row}
-                        reply={!row.userId}
-                        actionList={[
-                          row.userId && { key: 'edit', title: '编辑' },
-                          row.userId && { key: 'delete', title: '删除' },
-                        ]}
-                        onAction={key => {
-                          switch (key) {
-                            case 'edit':
-                              onUploadNote(appointment, row);
-                              break;
-                            case 'delete':
-                              onDeleteNote(appointment, row);
-                              break;
-                            case 'reply':
-                              onUploadNote(appointment, { parentId: row.id });
-                              break;
-                            default:
-                          }
-                        }}
-                      />
-                    );
+                    console.log(row);
+                    return <Note user={this.props.user} teacher={data.teacher} data={row} reply={!row.userId} actionList={row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null} onAction={(key) => {
+                      switch (key) {
+                        case 'edit':
+                          onUploadNote(appointment, row);
+                          break;
+                        case 'delete':
+                          onDeleteNote(appointment, row);
+                          break;
+                        case 'reply':
+                          onUploadNote(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId });
+                          break;
+                        default:
+                      }
+                    }} />;
                   })}
                 </div>
               </div>
@@ -1587,47 +1536,29 @@ class TimeLineItem extends Component {
           case 'end':
             return <span className="link">写留言</span>;
           case 'not':
-            return (
-              <span className="link" onClick={() => onUploadSupply(appointment)}>
-                写留言
-              </span>
-            );
+            return <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>;
           default:
             return (
               <div>
                 <div>
-                  <span className="link" onClick={() => onUploadSupply(appointment)}>
-                    写留言
-                  </span>
+                  <span className="link" onClick={() => onUploadSupply(appointment, { appointmentId: appointment.id, recordId: appointment.recordId })}>写留言</span>
                 </div>
                 <div className="note-list">
                   {appointment.supplyList.map(row => {
-                    return (
-                      <Note
-                        user={this.props.user}
-                        teacher={data.teacher}
-                        data={row}
-                        reply={!row.userId}
-                        actionList={[
-                          row.userId && { key: 'edit', title: '编辑' },
-                          row.userId && { key: 'delete', title: '删除' },
-                        ]}
-                        onAction={key => {
-                          switch (key) {
-                            case 'edit':
-                              onUploadSupply(appointment, row);
-                              break;
-                            case 'delete':
-                              onDeleteSupply(appointment, row);
-                              break;
-                            case 'reply':
-                              onUploadSupply(appointment, { parentId: row.id });
-                              break;
-                            default:
-                          }
-                        }}
-                      />
-                    );
+                    return <Note user={this.props.user} teacher={data.teacher} data={row} reply={!row.userId} actionList={row.userId ? [{ key: 'edit', label: '编辑' }, { key: 'delete', label: '删除' }] : null} onAction={(key) => {
+                      switch (key) {
+                        case 'edit':
+                          onUploadSupply(appointment, row);
+                          break;
+                        case 'delete':
+                          onDeleteSupply(appointment, row);
+                          break;
+                        case 'reply':
+                          onUploadSupply(appointment, { parentId: row.id, appointmentId: appointment.id, recordId: appointment.recordId });
+                          break;
+                        default:
+                      }
+                    }} />;
                   })}
                 </div>
               </div>

+ 59 - 32
front/project/www/routes/my/order/page.js

@@ -2,6 +2,7 @@ import React from 'react';
 import './index.less';
 import { Icon, Radio } from 'antd';
 import Page from '@src/containers/Page';
+import { asyncSMessage } from '@src/services/AsyncTools';
 import { getMap, formatMoney, formatDate } from '@src/services/Tools';
 import UserLayout from '../../../layouts/User';
 import menu from '../index';
@@ -14,42 +15,57 @@ import { RecordSource, ServiceKey } from '../../../../Constant';
 
 const RecordSourceMap = getMap(RecordSource, 'value', 'label');
 const ServiceKeyMap = getMap(ServiceKey, 'value', 'label');
+function formatTitle(record) {
+  if (record.productType === 'course-package') {
+    return (record.coursePackage || {}).title;
+  }
+  if (record.productType === 'course') {
+    return (record.course || {}).title;
+  }
+  if (record.productType === 'data') {
+    return (record.data || {}).title;
+  }
+  if (record.productType === 'service') {
+    return `${ServiceKeyMap[record.service]}`;
+  }
+  return '';
+}
 const columns = [
-  { title: '订单编号', key: 'orderId' },
+  { title: '订单编号', key: 'id' },
   {
     title: '服务',
     key: 'title',
     render: (text, record) => {
-      if (record.productType === 'course-package') {
-        return (record.coursePackage || {}).title;
-      }
-      if (record.productType === 'course') {
-        return (record.course || {}).title;
-      }
-      if (record.productType === 'data') {
-        return (record.data || {}).title;
+      const actionList = [];
+      if (record.canInvoice && !record.hasInvoice) {
+        actionList.push({ key: 'invoice', label: '开发票' });
       }
-      if (record.productType === 'service') {
-        return `${ServiceKeyMap[text]}`;
+      actionList.push({ key: 'detail', label: '订单详情' });
+      const onAction = (key) => {
+        switch (key) {
+          case 'invoice':
+            this.setState({ showInvoice: true, invoice: { orderId: record.id, money: record.invoiceMoney } });
+            break;
+          case 'detail':
+            openLink(`/order/detail/${record.id}`);
+            break;
+          default:
+        }
+      };
+      let content = '';
+      if (record.checkouts.length > 3) {
+        content = [<div className="flex-block">{formatTitle(record.checkouts[0])}<br />等{record.checkouts.length}个商品</div>];
+      } else {
+        content = record.checkouts.map(row => {
+          return formatTitle(row);
+        }).join(<br />);
       }
-      return (
-        <div className="t-2">
-          <div className="flex-layout m-b-5">
-            <div className="flex-block">教学资料-01</div>
-            <More menu={[{ label: '开发票', key: '1' }, { label: '订单详情', key: '2' }]}>
-              <IconButton type="more" />
-            </More>
-          </div>
-          <div className="flex-layout">
-            <div className="flex-block">
-              教学资料-01
-              <br />
-              等4个商品
-            </div>
-            <IconButton type="more" />
-          </div>
+      return <div className="t-2">
+        <div className="flex-layout m-b-5">
+          {content}
+          <More actionList={actionList} onAction={onAction} ><IconButton type="more" /></More>
         </div>
-      );
+      </div>;
     },
   },
   {
@@ -61,8 +77,8 @@ const columns = [
   },
   {
     title: '付款方式',
-    key: 'source',
-    render: text => {
+    key: 'payMethod',
+    render: (text) => {
       return RecordSourceMap[text];
     },
   },
@@ -97,6 +113,17 @@ export default class extends Page {
     this.search({ page });
   }
 
+  submitInvoice(invoice) {
+    Order.openInvoice(invoice)
+      .then(() => {
+        this.refresh();
+        this.setState({ showFinish: true, showInvoice: false, invoice: {} });
+      })
+      .catch(e => {
+        asyncSMessage(e.message, 'error');
+      });
+  }
+
   renderView() {
     const { config } = this.props;
     return <UserLayout active={config.key} menu={menu} center={this.renderTable()} />;
@@ -114,7 +141,7 @@ export default class extends Page {
           total={total}
           current={page}
         />
-        <Modal title="开发票" width={630} btnType="link" confirmText="申请" onConfirm={() => {}} onCancel={() => {}}>
+        <Modal title="开发票" width={630} btnType="link" confirmText="申请" onConfirm={() => { }} onCancel={() => { }}>
           <div className="input-layout m-b-2 t-2 t-s-16">
             <div className="label m-r-5">发票类型:</div>
             <div className="input-block">
@@ -159,7 +186,7 @@ export default class extends Page {
             <div className="input-block">¥ 210010.0</div>
           </div>
         </Modal>
-        <Modal title="申请成功" width={630} confirmText="好的,知道了" btnAlign="center" onConfirm={() => {}}>
+        <Modal title="申请成功" width={630} confirmText="好的,知道了" btnAlign="center" onConfirm={() => { }}>
           <div className="t-2 t-s-18">
             <Icon className="t-5 m-r-5" type="check" />
             我们会在三个工作日内将电子发票发送至您的绑定邮箱:

+ 4 - 0
front/project/www/stores/order.js

@@ -69,6 +69,10 @@ export default class OrderStore extends BaseStore {
   useRecord(id, isSubscribe) {
     return this.apiPost('/order/record/use', { id, isSubscribe });
   }
+
+  openInvoice({ orderId, invoiceType, title, identity }) {
+    return this.apiPost('/order/invoice/open', { orderId, invoiceType, title, identity });
+  }
 }
 
 export const Order = new OrderStore({ key: 'order' });

+ 1 - 1
front/project/www/stores/user.js

@@ -20,7 +20,7 @@ export default class UserStore extends BaseStore {
     if (this.state.login || this.adminLogin) {
       this.refreshToken().then(() => {
         if (this.adminLogin) {
-          window.location.href = window.location.href.substr(0, window.location.href.indexOf('?'));
+          window.location.href = window.location.href.replace(`token=${this.adminLogin}`, '').replace('&&', '&');
         }
       });
     }

+ 1 - 1
server/data/src/main/java/com/qxgmat/data/relation/mapping/UserOrderRelationMapper.xml

@@ -19,7 +19,7 @@
     select
     <include refid="Id_Column_List" />
     from `user_order` uo
-    where uo.is_speed = 0
+    where (uo.is_speed = 0 or (uo.is_speed = 1 and uo.pay_status>1)
     <if test="userId != null">
       and uo.`user_id` = #{userId,jdbcType=VARCHAR}
     </if>

+ 7 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/admin/CourseController.java

@@ -632,11 +632,15 @@ public class CourseController {
     @ApiOperation(value = "添加vs学员", httpMethod = "POST")
     private Response<Boolean> addStudentVs(@RequestBody @Validated UserCourseRecordDto dto){
         UserOrderRecord entity = Transform.dtoToEntity(dto);
+        Course course = courseService.get(dto.getCourseId());
         entity.setProductType(ProductType.COURSE.key);
         entity.setOrderId(0);
         entity.setProductId(dto.getCourseId());
         entity.setTeacherId(dto.getTeacherId());
         entity.setSource(RecordSource.BACKEND.key);
+        entity.setNumber(dto.getNumber());
+        entity.setExpireDays(course.getExpireDays());
+        entity.setUseExpireDays(courseExtendService.computeExpire(dto.getNumber(), course));
         // 预约后开通: 有效期开通后计算
         orderFlowService.addRecord(entity);
 
@@ -648,11 +652,13 @@ public class CourseController {
     private Response<Boolean> editStudentVs(@RequestBody @Validated UserCourseRecordDto dto){
         UserOrderRecord entity = Transform.dtoToEntity(dto);
         UserOrderRecord in = userOrderRecordService.get(entity.getId());
-        if (in.getIsUsed() > 0){
+        if (in.getIsUsed() > 0 && !in.getNumber().equals(dto.getNumber())){
             // 已开通,重新计算有效期
             Course course = courseService.get(dto.getCourseId());
             Integer expireDay = courseExtendService.computeExpire(dto.getNumber(), course);
             entity.setEndTime(Tools.addDate(in.getStartTime(), expireDay));
+            entity.setNumber(dto.getNumber());
+            entity.setUseExpireDays(expireDay);
         }
         userOrderRecordService.edit(entity);
         return ResponseHelp.success(true);

+ 17 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/admin/UserController.java

@@ -631,6 +631,10 @@ public class UserController {
         }
         UserCourseAppointment entity = Transform.dtoToEntity(dto);
         entity = userCourseAppointmentService.addAppointment(entity);
+        // 第一次预约,开通
+        if (entity.getNo() == 1){
+            orderFlowService.useRecord(record.getUserId(), record.getId());
+        }
         managerLogService.log(request);
         return ResponseHelp.success(Transform.convert(entity, CourseTime.class));
     }
@@ -810,14 +814,21 @@ public class UserController {
     }
 
     @RequestMapping(value = "/invoice/download", method = RequestMethod.PUT)
-    @ApiOperation(value = "下载资料", httpMethod = "PUT")
+    @ApiOperation(value = "下载发票", httpMethod = "PUT")
     public Response<Boolean> downloadInvoice(
             @RequestParam(required = false) Integer[] ids,
             HttpServletRequest request, HttpServletResponse response) throws IOException {
         managerLogService.log(request);
         userInvoiceService.download(ids);
-        List<UserInvoice> p = userInvoiceService.select(ids);
-        String columnNames[]={"ID","项目名","销售人","负责人","所用技术","备注"};//列名
+        List<UserInvoice> list = userInvoiceService.select(ids);
+        Collection userIds = Transform.getIds(list, UserInvoice.class, "userId");
+        List<User> userList = usersService.select(userIds);
+        Map userMap = Transform.getMap(userList, User.class, "id");
+        Collection orderIds = Transform.getIds(list, UserInvoice.class, "orderId");
+        List<UserOrder> orderList = userOrderService.select(orderIds);
+        Map orderMap = Transform.getMap(orderList, User.class, "id");
+
+        String columnNames[]={"申请时间","用户昵称","用户ID","用户手机号","开票今金额","开票内容", "发票抬头", "纳税人识别号", "收件邮箱"};//列名
         String filename = "发票";
         //生成一个Excel文件
         // 创建excel工作簿
@@ -832,8 +843,10 @@ public class UserController {
             cell.setCellValue(columnNames[i]);
         }
         int i = 0;
-        for(UserInvoice invoice : p){
+        for(UserInvoice invoice : list){
             i+=1;
+            User user = (User) userMap.get(invoice.getUserId());
+            UserOrder userOrder = (UserOrder) orderMap.get(invoice.getOrderId());
             Row r = sheet.createRow(i);
             Cell cell = r.createCell(0);
             cell.setCellValue(invoice.getId());

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/api/CourseController.java

@@ -419,7 +419,7 @@ public class CourseController {
             userOrderRecordList = userOrderRecordService.listWithStudyAdmin(1, 1000, new String[]{CourseModule.VIDEO.key, CourseModule.ONLINE.key}, structId, courseId, user.getId(), null,null, null);
         } else if (module == CourseModule.VS){
             // 1v1课程:只有系统授课有作业
-            userOrderRecordList = userOrderRecordService.listWithVs(1, 1000, VsCourseType.COACH, courseId, user.getId(), null, null);
+            userOrderRecordList = userOrderRecordService.listWithVs(1, 1000, VsCourseType.SYSTEM, courseId, user.getId(), null, null);
         }else{
             throw new ParameterException("课程类型错误");
         }

+ 4 - 4
server/gateway-api/src/main/java/com/qxgmat/controller/api/MyController.java

@@ -1628,6 +1628,7 @@ public class MyController {
         Transform.combine(pr, progressMap, UserCourseDetailDto.class, "id", "progress", UserCourseProgressExtendDto.class);
         Map<Object, Collection<UserCourseRecord>> recordMap = userCourseRecordService.groupByRecordId(recordIds);
         for(UserCourseDetailDto dto : pr){
+            dto.setTotalDays(courseExtendService.computeCourseDay(map.get(dto.getId())));
             Collection<CourseNo> courseNos = courseNoMap.get(dto.getProductId());
             if (courseNos == null || courseNos.size() == 0) continue;
             Collection<UserCourseProgress> list = progressMap.get(dto.getId());
@@ -1635,7 +1636,6 @@ public class MyController {
             dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNos, list));
             Collection<UserCourseRecord> userCourseRecords = recordMap.get(dto.getId());
             dto.setTotalTime(courseExtendService.computeCourseTime(userCourseRecords));
-            dto.setTotalDays(courseExtendService.computeCourseDay(map.get(dto.getId()), userCourseRecords));
         }
 
         // 获取每个科目的所有作业
@@ -1652,7 +1652,7 @@ public class MyController {
                     finish += 1;
                 }
             }
-            dto.setPreviewProgress(finish * 100 / list.size());
+            dto.setPreviewProgress(list.size()> 0 ? finish * 100 / list.size(): 0);
         }
 
         // 绑定老师
@@ -1721,7 +1721,7 @@ public class MyController {
         List<UserCourseRecord> recordList = userCourseRecordService.allWithRecord(recordId);
         if(progressList != null)dto.setCurrentNo(courseExtendService.computeCourseNoCurrent(courseNoList, progressList));
         if(recordList != null)dto.setTotalTime(courseExtendService.computeCourseTime(recordList));
-        if(recordList != null)dto.setTotalDays(courseExtendService.computeCourseDay(record, recordList));
+        dto.setTotalDays(courseExtendService.computeCourseDay(record));
 
         // 获取每个科目的所有作业
         List<UserPreviewPaperRelation> previewList = previewService.list(1, 1000, recordId, user.getId(), null,null);
@@ -1734,7 +1734,7 @@ public class MyController {
                 finish += 1;
             }
         }
-        dto.setPreviewProgress(finish * 100 / previewList.size());
+        dto.setPreviewProgress(previewList.size() > 0 ? finish * 100 / previewList.size(): 0);
 
         // 绑定老师
         CourseTeacher teacher = courseTeacherService.get(record.getTeacherId());

+ 55 - 10
server/gateway-api/src/main/java/com/qxgmat/controller/api/OrderController.java

@@ -1,5 +1,6 @@
 package com.qxgmat.controller.api;
 
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.github.pagehelper.Page;
 import com.nuliji.tools.PageMessage;
@@ -19,10 +20,7 @@ import com.qxgmat.dto.extend.CourseDataExtendDto;
 import com.qxgmat.dto.extend.CourseExtendDto;
 import com.qxgmat.dto.extend.CoursePackageExtendDto;
 import com.qxgmat.dto.extend.UserOrderRecordExtendDto;
-import com.qxgmat.dto.request.OrderConfirmDto;
-import com.qxgmat.dto.request.PayOrderDto;
-import com.qxgmat.dto.request.RecordAddDto;
-import com.qxgmat.dto.request.RecordUseDto;
+import com.qxgmat.dto.request.*;
 import com.qxgmat.dto.response.UserOrderDetailDto;
 import com.qxgmat.dto.response.UserOrderRecordListDto;
 import com.qxgmat.help.ShiroHelp;
@@ -80,6 +78,9 @@ public class OrderController {
     @Autowired
     private CourseDataService courseDataService;
 
+    @Autowired
+    private UserInvoiceService userInvoiceService;
+
     @RequestMapping(value = "/checkout/all", method = RequestMethod.GET)
     @ApiOperation(value = "全部购物车", notes = "全部购物车", httpMethod = "GET")
     public Response<UserOrderDetailDto> allCheckout(HttpServletRequest request) throws Exception {
@@ -190,7 +191,7 @@ public class OrderController {
 
     @RequestMapping(value = "/list", method = RequestMethod.GET)
     @ApiOperation(value = "获取订单列表", notes = "获取订单列表", httpMethod = "GET")
-    public Response<PageMessage<UserOrderRecordListDto>> listOrder(
+    public Response<PageMessage<UserOrderDetailDto>> listOrder(
             @RequestParam(required = false, defaultValue = "1") int page,
             @RequestParam(required = false, defaultValue = "100") int size,
             @RequestParam(required = false, defaultValue = "id") String order,
@@ -199,8 +200,15 @@ public class OrderController {
         User user = (User) shiroHelp.getLoginUser();
 
         Page<UserOrder> p = userOrderService.list(page, size, user.getId(), order, DirectionStatus.ValueOf(direction));
+        Map<Integer, UserOrder> map = new HashMap<>();
 
-        List<UserOrderRecordListDto> pr = Transform.convert(p, UserOrderRecordListDto.class);
+        List<UserOrderDetailDto> pr = Transform.convert(p, UserOrderDetailDto.class);
+        Collection orderIds = Transform.getIds(p, UserOrder.class, "id");
+
+        List<UserOrderRecord> recordList = userOrderRecordService.allByUser(user.getId(), orderIds).stream().filter(row->row.getParentId()==0).collect(Collectors.toList());
+        List<UserOrderRecordListDto> records = Transform.convert(recordList, UserOrderRecordListDto.class);
+        Map<Object, List<UserOrderRecordListDto>> recordMap = Transform.getMapList(records, UserOrderRecordListDto.class, "orderId");
+        Transform.combine(pr, recordMap, UserOrderDetailDto.class, "id", "checkouts");
 
         // 绑定服务
         Map<Object, JSONObject> serviceList = new HashMap<Object, JSONObject>(){{
@@ -211,21 +219,33 @@ public class OrderController {
             Setting qxCatSetting = settingService.getByKey(SettingKey.SERVICE_QX_CAT);
             put(ServiceKey.QX_CAT.key, qxCatSetting.getValue());
         }};
-        List<UserOrderRecordListDto> prService = pr.stream().filter((row)-> row.getProductType().equals(ProductType.SERVICE.key)).collect(Collectors.toList());
+        List<UserOrderRecordListDto> prService = records.stream().filter((row)-> row.getProductType().equals(ProductType.SERVICE.key)).collect(Collectors.toList());
         Transform.combine(prService, serviceList, UserOrderRecordListDto.class, "service", "serviceInfo");
 
         // 绑定课程
-        List<UserOrderRecordListDto> prCourse = pr.stream().filter((row)-> row.getProductType().equals(ProductType.COURSE.key)).collect(Collectors.toList());
+        List<UserOrderRecordListDto> prCourse = records.stream().filter((row)-> row.getProductType().equals(ProductType.COURSE.key)).collect(Collectors.toList());
         Collection courseIds = Transform.getIds(prCourse, UserOrderRecordListDto.class, "productId");
         List<Course> courseList = courseService.select(courseIds);
         Transform.combine(prCourse, courseList, UserOrderRecordListDto.class, "productId", "course", Course.class, "id", CourseExtendDto.class);
 
         // 绑定资料
-        List<UserOrderRecordListDto> prData = pr.stream().filter((row)-> row.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
+        List<UserOrderRecordListDto> prData = records.stream().filter((row)-> row.getProductType().equals(ProductType.DATA.key)).collect(Collectors.toList());
         Collection dataIds = Transform.getIds(prData, UserOrderRecordListDto.class, "productId");
         List<CourseData> dataList = courseDataService.select(dataIds);
         Transform.combine(prData, dataList, UserOrderRecordListDto.class, "productId", "data", CourseData.class, "id", CourseDataExtendDto.class);
 
+        // 绑定发票
+        List<UserInvoice> userInvoiceList = userInvoiceService.listByOrder(user.getId(), orderIds);
+        Map invoiceMap = Transform.getMap(userInvoiceList, UserInvoice.class, "orderId", "id");
+
+        for(UserOrderDetailDto dto : pr){
+            dto.setHasInvoice(invoiceMap.containsKey(dto.getId()));
+            UserOrder userOrder = map.get(dto.getId());
+            JSONArray value = userOrder.getProductTypes();
+            if (value.contains(ProductType.COURSE.key)){
+                dto.setCanInvoice(true);
+            }
+        }
 
         return ResponseHelp.success(pr, page, size, p.getTotal());
     }
@@ -295,7 +315,7 @@ public class OrderController {
     }
 
     @RequestMapping(value = "/record/detail", method = RequestMethod.GET)
-    @ApiOperation(value = "获取订单记录", notes = "获取订单记录", httpMethod = "GET")
+    @ApiOperation(value = "获取订单记录详情", notes = "获取订单记录", httpMethod = "GET")
     public Response<UserOrderRecord> getOrderRecord(
             @RequestParam(required = true) Integer id
     )  {
@@ -318,6 +338,25 @@ public class OrderController {
         return ResponseHelp.success(record);
     }
 
+    @RequestMapping(value = "/invoice/open", method = RequestMethod.POST)
+    @ApiOperation(value = "申请发票", notes = "申请发票", httpMethod = "POST")
+    public Response<Boolean> openInvoice(@RequestBody @Validated OrderInvoiceDto dto)  {
+        User user = (User) shiroHelp.getLoginUser();
+        UserInvoice userInvoice = Transform.dtoToEntity(dto);
+        UserInvoice in = userInvoiceService.getByOrder(user.getId(), userInvoice.getOrderId());
+        if (in != null) {
+            throw new ParameterException("该订单已开发票");
+        }
+        UserOrder order = userOrderService.get(userInvoice.getOrderId());
+        JSONArray value = order.getProductTypes();
+        if (!value.contains(ProductType.COURSE.key)){
+            throw new ParameterException("该订单无法开发票");
+        }
+        userInvoice.setUserId(user.getId());
+        userInvoiceService.add(userInvoice);
+        return ResponseHelp.success(true);
+    }
+
     /**
      * 统一处理订单以及完整购物车返回信息
      * @param userId
@@ -350,6 +389,12 @@ public class OrderController {
         List<CoursePackage> packageList = coursePackageService.select(packageIds);
         dto.setPackages(Transform.convert(packageList, CoursePackageExtendDto.class));
 
+        // 发票
+        UserInvoice invoice = userInvoiceService.getByOrder(userId, order.getId());
+        dto.setHasInvoice(invoice != null);
+        if (courseCheckout.size() > 0){
+            dto.setCanInvoice(true);
+        }
         return dto;
     }
 }

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/controller/api/TextbookController.java

@@ -139,7 +139,7 @@ public class TextbookController
                     UserOrderRecord record = userOrderRecordService.getUnUseService(user.getId(), ServiceKey.TEXTBOOK);
                     dto.setUnUseRecord(Transform.convert(record, UserServiceRecordExtendDto.class));
                 }
-                Collection questionNoIds = Transform.getIds(list, QuestionNo.class, "id");
+                Collection questionNoIds = Transform.getIds(list, TextbookQuestion.class, "id");
                 List<UserQuestion> userQuestionList = userQuestionService.listByQuestionNo(user.getId(), QuestionModule.TEXTBOOK, questionNoIds);
                 userQuestionStatMap = userQuestionService.statQuestionNoMap(userQuestionList);
 

+ 3 - 3
server/gateway-api/src/main/java/com/qxgmat/dto/admin/response/UserListDto.java

@@ -22,7 +22,7 @@ public class UserListDto {
 
     private Integer realStatus;
 
-    private Integer prepareStatus;
+    private String prepareStatus;
 
     private Integer inviteNumber;
 
@@ -86,11 +86,11 @@ public class UserListDto {
         this.inviteNumber = inviteNumber;
     }
 
-    public Integer getPrepareStatus() {
+    public String getPrepareStatus() {
         return prepareStatus;
     }
 
-    public void setPrepareStatus(Integer prepareStatus) {
+    public void setPrepareStatus(String prepareStatus) {
         this.prepareStatus = prepareStatus;
     }
 

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/extend/CourseExtendDto.java

@@ -15,6 +15,8 @@ public class CourseExtendDto {
 
     private String courseModule;
 
+    private String vsType;
+
     private String title;
 
     private String teacher;
@@ -106,4 +108,12 @@ public class CourseExtendDto {
     public void setExpireDays(Integer expireDays) {
         this.expireDays = expireDays;
     }
+
+    public String getVsType() {
+        return vsType;
+    }
+
+    public void setVsType(String vsType) {
+        this.vsType = vsType;
+    }
 }

+ 47 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/OrderInvoiceDto.java

@@ -0,0 +1,47 @@
+package com.qxgmat.dto.request;
+
+import com.nuliji.tools.annotation.Dto;
+import com.qxgmat.data.dao.entity.UserInvoice;
+
+@Dto(entity = UserInvoice.class)
+public class OrderInvoiceDto {
+    private Integer orderId;
+
+    private String invoiceType;
+
+    private String title;
+
+    private String identity;
+
+    public Integer getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(Integer orderId) {
+        this.orderId = orderId;
+    }
+
+    public String getInvoiceType() {
+        return invoiceType;
+    }
+
+    public void setInvoiceType(String invoiceType) {
+        this.invoiceType = invoiceType;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getIdentity() {
+        return identity;
+    }
+
+    public void setIdentity(String identity) {
+        this.identity = identity;
+    }
+}

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/request/UserCourseAppointmentCommentDto.java

@@ -19,6 +19,8 @@ public class UserCourseAppointmentCommentDto {
 
     private String file;
 
+    private String name;
+
     public Integer getId() {
         return id;
     }
@@ -74,4 +76,12 @@ public class UserCourseAppointmentCommentDto {
     public void setType(String type) {
         this.type = type;
     }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
 }

+ 10 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserCourseDetailDto.java

@@ -33,6 +33,8 @@ public class UserCourseDetailDto {
 
     private Date useEndTime;
 
+    private String cctalkName;
+
     private Integer isUsed;
 
     private Integer isStop;
@@ -330,4 +332,12 @@ public class UserCourseDetailDto {
     public void setProgress(Collection<UserCourseProgressExtendDto> progress) {
         this.progress = progress;
     }
+
+    public String getCctalkName() {
+        return cctalkName;
+    }
+
+    public void setCctalkName(String cctalkName) {
+        this.cctalkName = cctalkName;
+    }
 }

+ 52 - 0
server/gateway-api/src/main/java/com/qxgmat/dto/response/UserOrderDetailDto.java

@@ -12,6 +12,12 @@ import java.math.BigDecimal;
 import java.util.List;
 
 public class UserOrderDetailDto {
+    private Integer id;
+
+    private String payMethod;
+
+    private String transactionNo;
+
     private BigDecimal money;
 
     private BigDecimal originMoney;
@@ -20,6 +26,12 @@ public class UserOrderDetailDto {
 
     private JSONArray gift;
 
+    private BigDecimal invoiceMoney;
+
+    private Boolean hasInvoice;
+
+    private Boolean canInvoice;
+
     private List<UserOrderRecordExtendDto> checkouts;
 
     private List<CourseExtendDto> courses;
@@ -91,4 +103,44 @@ public class UserOrderDetailDto {
     public void setGift(JSONArray gift) {
         this.gift = gift;
     }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getPayMethod() {
+        return payMethod;
+    }
+
+    public void setPayMethod(String payMethod) {
+        this.payMethod = payMethod;
+    }
+
+    public String getTransactionNo() {
+        return transactionNo;
+    }
+
+    public void setTransactionNo(String transactionNo) {
+        this.transactionNo = transactionNo;
+    }
+
+    public Boolean getHasInvoice() {
+        return hasInvoice;
+    }
+
+    public void setHasInvoice(Boolean hasInvoice) {
+        this.hasInvoice = hasInvoice;
+    }
+
+    public Boolean getCanInvoice() {
+        return canInvoice;
+    }
+
+    public void setCanInvoice(Boolean canInvoice) {
+        this.canInvoice = canInvoice;
+    }
 }

+ 27 - 18
server/gateway-api/src/main/java/com/qxgmat/service/extend/CourseExtendService.java

@@ -42,7 +42,7 @@ public class CourseExtendService {
         // day / 10
         Integer expireDays = course.getExpirePreDays();
 
-        return Math.min(vsNumber / 10, 0)  * expireDays;
+        return Math.max(vsNumber / 10, 1) * expireDays;
     }
 
     /**
@@ -232,29 +232,38 @@ public class CourseExtendService {
     /**
      * 计算用户该课程的总学习天数
      * @param  record
-     * @param userCourseRecords
      * @return
      */
-    public int computeCourseDay(UserOrderRecord record, Collection<UserCourseRecord> userCourseRecords){
-        Date min = null;
-        Date max = null;
-        for(UserCourseRecord userCourseRecord:userCourseRecords){
-            if(min==null || min.after(userCourseRecord.getCreateTime())){
-                min = userCourseRecord.getCreateTime();
-            }
-            if (max == null || max.before(userCourseRecord.getCreateTime())){
-                max = userCourseRecord.getCreateTime();
-            }
+    public int computeCourseDay(UserOrderRecord record){
+        if(record.getUseEndTime() == null){
+            return 0;
         }
         int suspend = 0;
-//        if(record.getIsSuspend() > 0){
-//            if(record.getRestoreTime() != null){
-//
-//            }else{
-//                suspend = 0;
+        if(record.getIsSuspend() > 0){
+            if(record.getRestoreTime() == null){
+                suspend = (int)(Tools.day(new Date()).getTime() - Tools.day(record.getSuspendTime()).getTime())/86400000;
+            }else{
+                suspend = (int)(Tools.day(record.getRestoreTime()).getTime() - Tools.day(record.getSuspendTime()).getTime())/86400000;
+            }
+        }
+        if (record.getUseEndTime().before(new Date())){
+            return (int)(Tools.day(record.getUseEndTime()).getTime() - Tools.day(record.getUseStartTime()).getTime())/86400000 - suspend;
+        }else{
+            return (int)(Tools.day(new Date()).getTime() - Tools.day(record.getUseStartTime()).getTime())/86400000 - suspend;
+        }
+//        Date min = null;
+//        Date max = null;
+//        for(UserCourseRecord userCourseRecord:userCourseRecords){
+//            if(min==null || min.after(userCourseRecord.getCreateTime())){
+//                min = userCourseRecord.getCreateTime();
+//            }
+//            if (max == null || max.before(userCourseRecord.getCreateTime())){
+//                max = userCourseRecord.getCreateTime();
 //            }
 //        }
-        return (int)(Tools.day(max).getTime() - Tools.day(min).getTime()/86400000) - suspend;
+//        int suspend = 0;
+//        if (min == null) return 0;
+//        return (int)((Tools.day(max).getTime() - Tools.day(min).getTime())/86400000) - suspend;
     }
 
     /**

+ 26 - 21
server/gateway-api/src/main/java/com/qxgmat/service/extend/OrderFlowService.java

@@ -415,14 +415,14 @@ public class OrderFlowService {
             order.setMoney(money);
             order.setOriginMoney(originMoney);
             // 课程金额进行开票处理
-            order.setInvoiceMoney(courseMoney);
+            order.setInvoiceMoney(courseMoney.add(vsMoney));
         }));
 
         initRecordCallback.put(ProductType.COURSE, ((order, record) -> {
             Course course = courseService.get(record.getProductId());
 
             // 记录销售数量
-            courseService.accumulation(course.getId(), 0, record.getParentId() ==0 ? 1:0, record.getParentId() != 0 ? 1:0);
+            courseService.accumulation(course.getId(), 0, record.getParentId() == null || record.getProductId() ==0 ? 1:0, record.getParentId() != null && record.getProductId() != 0 ? 1:0);
 
             CourseModule courseModule = CourseModule.ValueOf(course.getCourseModule());
             // 小班课程不会存在记录,在线视频和1v1授课都有有效期
@@ -545,28 +545,31 @@ public class OrderFlowService {
                 // 设置结束有效期
                 endTime = Tools.addDate(startTime, expireDay);
             }
-            UserCourse userCourse = userCourseService.getCourseBase(record.getUserId(), record.getProductId());
-            if(userCourse == null){
-                userCourse = UserCourse.builder()
-                        .userId(record.getUserId())
-                        .courseId(course.getId())
-                        .recordId(record.getId())
-                        .startTime(startTime)
-                        .expireTime(endTime)
-                        .build();
-                userCourse = userCourseService.add(userCourse);
-            }else{
-                if (userCourse.getExpireTime().before(time)){
-                    // 已到期 - 续期
-                    userCourse.setStartTime(startTime);
-                    userCourse.setExpireTime(endTime);
-                    userCourse.setRecordId(record.getId());
+            if (course.getCourseModule().equals(CourseModule.VIDEO.key)){
+                UserCourse userCourse = userCourseService.getCourseBase(record.getUserId(), record.getProductId());
+                if(userCourse == null){
+                    userCourse = UserCourse.builder()
+                            .userId(record.getUserId())
+                            .courseId(course.getId())
+                            .recordId(record.getId())
+                            .startTime(startTime)
+                            .expireTime(endTime)
+                            .build();
+                    userCourse = userCourseService.add(userCourse);
                 }else{
-                    // 未到期 - 报错
-                    throw new ParameterException("已开通当前课程");
+                    if (userCourse.getExpireTime().before(time)){
+                        // 已到期 - 续期
+                        userCourse.setStartTime(startTime);
+                        userCourse.setExpireTime(endTime);
+                        userCourse.setRecordId(record.getId());
+                    }else{
+                        // 未到期 - 报错
+                        throw new ParameterException("已开通当前课程");
+                    }
+                    userCourse = userCourseService.edit(userCourse);
                 }
-                userCourse = userCourseService.edit(userCourse);
             }
+
             record.setUseStartTime(startTime);
             if (endTime!=null )record.setUseEndTime(endTime);
             return record;
@@ -915,6 +918,8 @@ public class OrderFlowService {
      */
     @Transactional
     public UserOrderRecord addRecord(UserOrderRecord record){
+        if (record.getExpireDays() == null) record.setExpireDays(0);
+        if (record.getUseExpireDays() == null) record.setUseExpireDays(0);
         InitRecord callback = initRecordCallback.get(ProductType.ValueOf(record.getProductType()));
         // 虚拟order
         callback.callback(UserOrder.builder().build(), record);

+ 34 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserInvoiceService.java

@@ -25,6 +25,40 @@ public class UserInvoiceService extends AbstractService {
     @Resource
     private UserInvoiceMapper userInvoiceMapper;
 
+    public UserInvoice getByOrder(Integer userId, Integer orderId){
+        Example example = new Example(UserInvoice.class);
+        if(userId != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("userId", userId)
+            );
+        }
+        if(orderId != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("orderId", orderId)
+            );
+        }
+        return one(userInvoiceMapper, example);
+    }
+
+    public List<UserInvoice> listByOrder(Integer userId, Collection orderIds){
+        Example example = new Example(UserInvoice.class);
+        if(userId != null){
+            example.and(
+                    example.createCriteria()
+                            .andEqualTo("userId", userId)
+            );
+        }
+        if(orderIds != null){
+            example.and(
+                    example.createCriteria()
+                            .andIn("orderId", orderIds)
+            );
+        }
+        return select(userInvoiceMapper, example);
+    }
+
     public Page<UserInvoice> listAdmin(int page, int size, Integer userId, Boolean isDownload, Boolean isFinish, String order, DirectionStatus direction){
         Example example = new Example(UserInvoice.class);
         if(userId != null){

+ 16 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderRecordService.java

@@ -51,6 +51,22 @@ public class UserOrderRecordService extends AbstractService {
     }
 
     /**
+     * 获取订单的所有记录
+     * @param userId
+     * @param orderIds
+     * @return
+     */
+    public List<UserOrderRecord> allByUser(Integer userId, Collection orderIds){
+        Example example = new Example(UserOrderRecord.class);
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("userId", userId)
+                        .andIn("orderId", orderIds)
+        );
+        return select(userOrderRecordMapper, example);
+    }
+
+    /**
      * 获取所有到期的未恢复记录
      * @param startTime
      * @param endTime

+ 13 - 0
server/gateway-api/src/main/java/com/qxgmat/service/inline/UserOrderService.java

@@ -36,6 +36,15 @@ public class UserOrderService extends AbstractService {
         put("", "uo");
     }};
 
+    /**
+     * 已支付列表
+     * @param page
+     * @param size
+     * @param userId
+     * @param order
+     * @param direction
+     * @return
+     */
     public Page<UserOrder> list(int page, int size, Integer userId, String order, DirectionStatus direction){
         Example example = new Example(UserOrder.class);
         if(userId != null){
@@ -44,6 +53,10 @@ public class UserOrderService extends AbstractService {
                             .andEqualTo("userId", userId)
             );
         }
+        example.and(
+                example.createCriteria()
+                        .andEqualTo("payStatus", 1)
+        );
 
         if(order == null || order.isEmpty()) order = "id";
         switch(direction){

+ 1 - 1
server/gateway-api/src/main/java/com/qxgmat/task/ScheduledTask.java

@@ -283,7 +283,7 @@ public class ScheduledTask {
             List<UserCourseProgress> progressList = userCourseProgressService.listCourse(record.getId(), course.getId());
             List<UserCourseRecord> records = userCourseRecordService.allWithRecord(record.getId());
             Integer currentNo = courseExtendService.computeCourseNoCurrent(courseNoList, progressList);
-            Integer days = courseExtendService.computeCourseDay(record, records);
+            Integer days = courseExtendService.computeCourseDay(record);
             if (days/currentNo > 2){
                 continue;
             }