确认排单

This commit is contained in:
邓皓元 2019-04-01 15:49:38 +08:00
parent c2e49f5ee1
commit 45e00b1cd2
19 changed files with 500 additions and 101 deletions

View File

@ -0,0 +1,78 @@
<?php
namespace App\Domains\Export\Services;
use App\Core\Service;
use Illuminate\Http\UploadedFile;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Illuminate\Support\Facades\Validator;
use App\Exceptions\InvalidArgumentException;
class ImportService extends Service
{
/**
* 导出
*/
public static function load(UploadedFile $file, $columns = null)
{
if ($columns !== null) {
if (!is_array($columns)) {
throw new InvalidArgumentException('columns not an array.');
}
$columns = array_map('strtolower', $columns);
}
$array = self::toArray($file);
$title = [];
$data = [];
foreach ($array as $index => $item) {
if (!$index) {
$title = $item;
} else {
$row = [];
foreach ($item as $key => $value) {
$column = strtolower($title[$key]);
if ($columns === null || in_array($column, $columns)) {
$row[$column] = $value;
}
}
array_push($data, $row);
}
}
return $data;
}
/**
* 导入转为数组
*
* @param UploadedFile $file
* @return void
*/
public static function toArray(UploadedFile $file)
{
if (!$file) {
throw new InvalidArgumentException('文件不存在');
}
$extension = $file->getClientOriginalExtension();
Validator::validate(['extension' => $extension], [
'extension' => ['in:xls,xlsx,csv'],
], ['extension.in' => '文件类型不正确']);
$type = ucfirst(strtolower($extension));
$reader = IOFactory::createReader($type);
$spreadsheet = $reader->load($file->getPathname());
$sheetData = $spreadsheet->getActiveSheet()->toArray(null, true, true, false);
return $sheetData;
}
}

View File

@ -7,8 +7,10 @@ use Illuminate\Http\Request;
use App\Exceptions\NotExistException;
use App\Exceptions\NotAllowedException;
use App\Domains\Virtual\Exports\OrderExport;
use App\Exceptions\InvalidArgumentException;
use App\Domains\Config\Services\ConfigService;
use App\Domains\Export\Services\ExportService;
use App\Domains\Export\Services\ImportService;
use App\Domains\Virtual\Services\OrderService;
use App\Domains\Virtual\Services\CommonService;
use App\Domains\Virtual\Exports\OrderCardExport;
@ -271,4 +273,50 @@ class OrderController extends Controller
return res($url, '导出成功', 201);
}
/**
* 排单.
*
* @return \Illuminate\Http\Response
*/
public function ship()
{
$type = $this->request->get('type');
if (!in_array($type, [1, 2])) {
throw new InvalidArgumentException('排单方式不正确');
}
$orderId = $this->request->get('order_id');
$simArray = [];
if ($type == 1) {
$file = $this->request->file('file');
$simArray = ImportService::load($file, ['sim']);
$simArray = array_pluck($simArray, 'sim');
}
if ($type == 2) {
$segments = $this->request->get('segments');
if (!is_array($segments) || empty($segments)) {
throw new InvalidArgumentException('参数错误');
}
foreach ($segments as $segment) {
for ($i = intval($segment['start_no']); $i <= intval($segment['end_no']); $i++) {
array_push($simArray, $i);
}
}
}
$simArray = array_map(function ($item) {
return intval(trim(str_replace("\t", '', $item)));
}, $simArray);
$order = $this->orderService->ship($orderId, $simArray);
return res($order, '排单成功');
}
}

View File

@ -53,6 +53,7 @@ $router->group(['prefix' => 'virtual', 'as' => 'virtual', 'middleware' => ['admi
$router->get('/orders/export', ['as' => 'orders.export', 'uses' => 'OrderController@export']);
$router->get('/orders/cards', ['as' => 'orders.cards', 'uses' => 'OrderController@cards']);
$router->get('/orders/cards-export', ['as' => 'orders.cardsExport', 'uses' => 'OrderController@cardsExport']);
$router->post('/orders/ship', ['as' => 'orders.ship', 'uses' => 'OrderController@ship']);
// 客户管理
$router->get('/cards/index', ['as' => 'cards.index', 'uses' => 'CardController@index']);

View File

@ -3,15 +3,16 @@ namespace App\Domains\Virtual\Services;
use App\Dicts;
use App\Core\Service;
use App\Models\Card\Card;
use App\Models\Virtual\Order;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
use App\Exceptions\NotExistException;
use App\Exceptions\NotAllowedException;
use Illuminate\Support\Facades\Validator;
use App\Models\Virtual\OrderCardPartition;
use App\Domains\Card\Services\CardService;
use App\Exceptions\InvalidArgumentException;
use Dipper\Foundation\Exceptions\HttpException;
use Illuminate\Database\Query\Grammars\Grammar;
use App\Domains\Virtual\Services\CompanyService;
use App\Domains\Virtual\Services\PackageService;
use App\Domains\Virtual\Repositories\OrderRepository;
@ -20,8 +21,6 @@ use App\Models\Real\OrderCardPartition as RealOrderCardPartition;
use App\Domains\Virtual\Repositories\OrderCardPartitionRepository;
use App\Domains\Real\Repositories\OrderRepository as RealOrderRepository;
use App\Domains\Real\Repositories\OrderCardPartitionRepository as RealOrderCardPartitionRepository;
use App\Exceptions\InvalidArgumentException;
use App\Models\Card\Card;
class OrderService extends Service
{
@ -249,6 +248,18 @@ class OrderService extends Service
}
if ($attributes['selected']) {
if($attributes['type'] === 0){
$exists = $this->orderCardPartitionRepository->withConditions([
'type' => 0,
'sim' => array_pluck($attributes['selected'], 'sim')
])->count();
if ($exists) {
DB::rollBack();
throw new NotAllowedException("存在已被其他订单使用的卡");
}
}
try {
$this->upsertOrderCards($attributes['selected'], $node);
$this->orderRepository->forgetCached();
@ -279,39 +290,6 @@ class OrderService extends Service
return $node;
}
protected function upsertOrderCards($array, $node)
{
$table = $this->tables[$node['type']];
$data = [];
foreach ($array as $card) {
$data[] = [
'sim' => $card['sim'],
'counts' => $card['counts'],
'type' => $node['type'],
'order_id' => $node['id'],
'company_id' => $node['company_id'],
'package_id' => $node['package_id'],
'unit_price' => $node['unit_price'],
'created_at' => $node['order_at'],
'updated_at' => date('Y-m-d H:i:s'),
];
}
if (empty($data)) {
return;
}
foreach (array_chunk($data, 1000) as $value) {
DB::table($table)->upsert($value, ['sim', 'order_id', 'deleted_at']);
$simArray = implode(',', array_pluck($value, 'sim'));
DB::statement("select fix_timelines('{{$simArray}}'::INT8[]);");
RealOrderCardPartition::whereIn('order_id', array_pluck($array, 'order_id'))
->whereIn('sim', array_pluck($value, 'sim'))->update(['virtual_order_id' => $node['id']]);
}
}
/**
* 以卡续费/续费包/加油包
*
@ -683,4 +661,105 @@ class OrderService extends Service
return $cards;
}
/**
* 排单
*
* @param int $orderId
* @param array $simArray
* @return Order
*/
public function ship($orderId, array $simArray)
{
if (!$order = $this->orderRepository->find($orderId)) {
throw new NotExistException('订单不存在或已删除');
}
$orderShipments = $this->orderCardPartitionRepository->select([
DB::raw('SUM(counts) as shipments'),
DB::raw('SUM(CASE WHEN refunded_at IS NULL THEN 0 ELSE 1 END) as refunds')
])->where('type', 0)->where('order_id', $orderId)->groupBy('order_id')->first();
$shipments = $orderShipments['shipments'] ?? 0;
$refunds = $orderShipments['refunds'] ?? 0;
if ($order->counts - ($shipments - $refunds) <= 0) {
throw new NotAllowedException('订单已排满');
}
if ($order->counts - ($shipments - $refunds) < count($simArray)) {
throw new NotAllowedException('排单卡量大于订单卡量');
}
$exists = $this->orderCardPartitionRepository->select('sim')->withConditions(['type' => 0, 'sim' => $simArray])->get()->pluck('sim')->toArray();
if (count($exists)) {
$exists = implode(',', $exists);
throw new NotAllowedException("存在已被其他订单使用的卡: ($exists)");
}
$cards = CardService::getMongoCardsInfo($simArray);
if (count($simArray) !== count($cards)) {
$diff = array_diff($simArray, array_pluck($cards, 'sim'));
$diff = implode(',', $diff);
throw new NotExistException("存在未入库的卡: ($diff)");
}
$array = array_map(function ($item) {
return ['sim' => $item, 'counts' => 1];
}, $simArray);
DB::beginTransaction();
try {
$order->order_status = ($order->counts - ($shipments - $refunds) === count($simArray)) ? 2 : 0;
$order->save();
$this->upsertOrderCards($array, $order);
$this->orderRepository->forgetCached();
$this->orderCardPartitionRepository->forgetCached();
app(RealOrderCardPartitionRepository::class)->forgetCached();
} catch (\Exception $e) {
DB::rollBack();
throw new HttpException('操作失败');
}
DB::commit();
return $order;
}
protected function upsertOrderCards($array, $node)
{
$table = $this->tables[$node['type']];
$data = [];
foreach ($array as $card) {
$data[] = [
'sim' => $card['sim'],
'counts' => $card['counts'],
'type' => $node['type'],
'order_id' => $node['id'],
'company_id' => $node['company_id'],
'package_id' => $node['package_id'],
'unit_price' => $node['unit_price'],
'created_at' => $node['order_at'],
'updated_at' => date('Y-m-d H:i:s'),
];
}
if (empty($data)) {
return;
}
foreach (array_chunk($data, 1000) as $value) {
DB::table($table)->upsert($value, ['sim', 'order_id', 'deleted_at']);
$simArray = implode(',', array_pluck($value, 'sim'));
DB::statement("select fix_timelines('{{$simArray}}'::INT8[]);");
RealOrderCardPartition::whereIn('order_id', array_pluck($array, 'order_id'))
->whereIn('sim', array_pluck($value, 'sim'))->update(['virtual_order_id' => $node['id']]);
}
}
}

View File

@ -119,7 +119,7 @@ INSERT INTO vd.virtual_orders ("type", sn, "source", company_id, package_id, pay
MIN(unit_price) * SUM(counts) AS total_price,
MIN(unit_price) * SUM(counts) AS custom_price,
MIN(created_at) AS order_at,
4,
5,
1,
MIN(created_at) AS created_at,
MIN(created_at) AS updated_at

View File

@ -90,3 +90,26 @@ export function destroy(data) {
export function reset(data) {
return service.post('api/virtual/orders/reset', data);
}
/**
* [ship 排单]
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export function ship(data) {
let params = new FormData();
for (const key in data) {
if (data.hasOwnProperty(key)) {
params.append(key, data[key]);
}
}
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
};
return service.post('api/virtual/orders/ship', params, config);
}

View File

@ -186,6 +186,8 @@
@store-success="handleOrderSuccess(1)"
></ui-cards>
<ui-ship :data="shipObj.data" :show.sync="shipObj.show" @update-success="handleOrderSuccess(1)"></ui-ship>
<Modal v-model="orderConfirmShow" width="360">
<p slot="header" style="color:#f60;text-align:center">
<Icon type="ios-information-circle"></Icon>

View File

@ -344,7 +344,6 @@ export default {
this.params.company_name = this.orderObj.company_name;
this.params.package_name = this.orderObj.package_name;
}
window.t = this;
this.index();
}
}

View File

@ -294,18 +294,18 @@ export default {
case '支付宝':
this.params.pay_channel = 'alipay';
break;
// case '余额支付':
// this.params.pay_channel = 'account';
// break;
// case '天猫续费':
// this.params.pay_channel = 'tmall';
// break;
// case '余额支付':
// this.params.pay_channel = 'account';
// break;
// case '天猫续费':
// this.params.pay_channel = 'tmall';
// break;
default:
break;
}
this.params.carrier_operator = order.carrier_operator;
this.params.unit_price = order.unit_price;
this.params.unit_price = Number(order.unit_price);
this.params.order_at = order.order_at;
this.params.transaction_no = order.transaction_no;

View File

@ -5,7 +5,8 @@ export default {
components: {
UiEdit: resolve => require(['views/virtual/orders/edit'], resolve),
UiDetail: resolve => require(['views/virtual/orders/detail'], resolve),
UiCards: resolve => require(['views/virtual/orders/cards'], resolve)
UiCards: resolve => require(['views/virtual/orders/cards'], resolve),
UiShip: resolve => require(['views/virtual/orders/ship'], resolve)
},
data() {
return {
@ -37,6 +38,10 @@ export default {
cardsObj: {
show: false
},
shipObj: {
show: false,
data: null
},
search: {
show: true
},
@ -586,6 +591,12 @@ export default {
if (this.type === 0) {
this.table_titles.splice(7, 0, {
title: '排单量',
key: 'shipments',
width: 80
});
this.table_titles.splice(8, 0, {
title: '退货量',
key: 'refunds',
width: 80
@ -682,7 +693,7 @@ export default {
/**
* [openCards 打开选卡弹窗]
* source 0:选创建订单页面打开 1:选卡按钮打开 2:从排单按钮打开
* source 0:选创建订单页面打开 1:RD按钮打开 2:从排单按钮打开
* @return {[type]} [description]
*/
openCards(bool, source, orderObj = {}) {
@ -780,27 +791,17 @@ export default {
// 订单排单
orderShip() {
let row = this.row;
this.$Modal.confirm({
title: '提示',
content: '请确认订单是否已排单?',
onOk: () => {
this.isShowLoading(true);
API.update({
order_status: 2
}, row.id).then(res => {
if (res.code == 0) {
this.$Message.success('修改成功');
this.orderConfirmShow = false;
this.request();
}
this.isShowLoading(false);
});
}
});
this.shipObj = {
show: true,
data: row
};
},
handleOrderSuccess(value) {
let page = value ? this.list_data.current_page : 1;
this.cardsObj.show = false;
this.shipObj.show = false;
this.orderConfirmShow = false;
this.$store.dispatch('initOrder');
this.index(page);
},

View File

@ -0,0 +1,97 @@
import * as API from 'api/virtual/orders';
export default {
props: {
show: {
type: Boolean,
default: false
},
source: {
type: Number,
default: 0
},
data: {
type: Object,
default: {}
}
},
data() {
return {
my_show: false,
loading: false,
type: 1,
params: {},
file: null,
segments: [{ start_no: '', end_no: '' }]
};
},
watch: {
show(bool) {
this.my_show = bool;
}
},
methods: {
ok() {
let params = {};
params.type = this.type;
params.order_id = this.data.id;
if (this.type === 1) {
if (this.file === null) {
return this.$Message.error('请上传文件');
}
console.log(this.file);
params.file = this.file;
};
if (this.type === 2) {
for (let index = 0; index < this.segments.length; index++) {
const element = this.segments[index];
if (element.start_no === '' || element.end_no === '') {
return this.$Message.error('请输入起止卡号');
}
}
params.segments = this.segments;
};
this.$Modal.confirm({
title: '提示',
content: '请确认是否提交排单?',
onOk: () => {
this.loading = true;
API.ship(params).then(res => {
if (res.code == 0) {
this.$Message.success('修改成功');
this.$emit('update-success');
}
this.loading = false;
});
}
});
},
selectFile(file) {
this.file = file;
return false;
},
visibleChange(bool) {
if (!bool) {
this.$emit('update:show', false);
}
},
clear() {
this.file = null;
this.segments = [{ start_no: '', end_no: '' }];
this.my_show = false;
},
handleAdd() {
this.segments.push({ start_no: '', end_no: '' });
},
handleRemove(index) {
this.segments.splice(index, 1);
}
}
};

View File

@ -0,0 +1,94 @@
<template>
<Modal
:closable="false"
:mask-closable="false"
:title="'确认排单'"
@on-visible-change="visibleChange"
v-model="my_show"
>
<div class="page-edit-wrap uinn-lr20">
<ui-loading :show="loading"></ui-loading>
<ul>
<li class="ui-list" v-if="data !== null">
<div class="ui-list-title">订单编号:</div>
<div class="ui-list-content">
<span class="lh-32">{{data.sn}}</span>
</div>
</li>
<li class="ui-list">
<div class="ui-list-title"></div>
<div class="ui-list-content">
<RadioGroup v-model="type">
<Radio :label="1">导入方式</Radio>
<Radio :label="2">号段方式</Radio>
</RadioGroup>
</div>
</li>
<div v-if="type === 1">
<li class="ui-list">
<div class="ui-list-title">文件导入</div>
<div class="ui-list-content">
<Upload :before-upload="selectFile" action="/" :format="['xls','xlsx','csv']">
<Button icon="ios-cloud-upload-outline">选择文件</Button>
</Upload>
</div>
</li>
<li class="ui-list" v-if="file !== null">
<div class="ui-list-title">已选文件</div>
<div class="ui-list-content">
<span class="lh-32">{{file.name}}</span>
</div>
</li>
</div>
<div v-if="type === 2">
<li class="ui-list">
<div class="ui-list-title">号段范围</div>
<div class="ui-list-content">
<Form ref="formSegments">
<FormItem v-for="(item, index) in segments" :key="index">
<Row>
<Col span="9">
<Input type="text" v-model="item.start_no" placeholder="起始号码"></Input>
</Col>
<Col span="2" class="ta-c">-</Col>
<Col span="9">
<Input type="text" v-model="item.end_no" placeholder="结束号码"></Input>
</Col>
<Col span="1" offset="1" v-if="segments.length > 1">
<Button
size="small"
type="primary"
shape="circle"
icon="md-remove"
@click="handleRemove(index)"
></Button>
</Col>
<Col span="1" offset="1">
<Button
size="small"
type="primary"
shape="circle"
icon="md-add"
@click="handleAdd"
></Button>
</Col>
</Row>
</FormItem>
</Form>
</div>
</li>
</div>
</ul>
</div>
<footer class="ta-c" slot="footer">
<Button @click="clear" class="w-80 umar-r5" ghost type="primary">取消</Button>
<Button :loading="loading" @click="ok" class="w-80" type="primary">确定</Button>
</footer>
</Modal>
</template>
<script src="./js/ship.js"></script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=\favicon.ico><script src=\config.js></script><title></title><link href=/css/chunk-25e2723d.f18e4df8.css rel=prefetch><link href=/css/chunk-996b1e80.5cadf3d0.css rel=prefetch><link href=/js/chunk-00ae0766.3874cd10.js rel=prefetch><link href=/js/chunk-07a274ec.c3ad5dec.js rel=prefetch><link href=/js/chunk-25e2723d.ce806521.js rel=prefetch><link href=/js/chunk-996b1e80.d3b45e46.js rel=prefetch><link href=/css/app.d71a8195.css rel=preload as=style><link href=/css/chunk-vendors.3c3b2e85.css rel=preload as=style><link href=/js/app.0a9cab31.js rel=preload as=script><link href=/js/chunk-vendors.ed6443e8.js rel=preload as=script><link href=/css/chunk-vendors.3c3b2e85.css rel=stylesheet><link href=/css/app.d71a8195.css rel=stylesheet></head><body><noscript><strong>很抱歉如果没有启用JavaScript程序不能正常工作若要继续使用请启用它。</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ed6443e8.js></script><script src=/js/app.0a9cab31.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=\favicon.ico><script src=\config.js></script><title></title><link href=/css/chunk-996b1e80.5cadf3d0.css rel=prefetch><link href=/css/chunk-bb3b064e.bc6b17af.css rel=prefetch><link href=/js/chunk-00ae0766.3874cd10.js rel=prefetch><link href=/js/chunk-07a274ec.c3ad5dec.js rel=prefetch><link href=/js/chunk-996b1e80.d3b45e46.js rel=prefetch><link href=/js/chunk-bb3b064e.b8bb8795.js rel=prefetch><link href=/css/app.d71a8195.css rel=preload as=style><link href=/css/chunk-vendors.3c3b2e85.css rel=preload as=style><link href=/js/app.7ef53e3c.js rel=preload as=script><link href=/js/chunk-vendors.ed6443e8.js rel=preload as=script><link href=/css/chunk-vendors.3c3b2e85.css rel=stylesheet><link href=/css/app.d71a8195.css rel=stylesheet></head><body><noscript><strong>很抱歉如果没有启用JavaScript程序不能正常工作若要继续使用请启用它。</strong></noscript><div id=app></div><script src=/js/chunk-vendors.ed6443e8.js></script><script src=/js/app.7ef53e3c.js></script></body></html>

View File

@ -1,42 +1,11 @@
<?php
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use App\Domains\Virtual\Services\OrderService;
require_once realpath(dirname(__FILE__) . '/TestCase.php');
Schema::table('virtual_order_cards_partition', function ($table) {
$table->timestamp('refunded_at')->nullable()->comment('退货时间');
});
Schema::table('real_order_cards_partition', function ($table) {
$table->timestamp('refunded_at')->nullable()->comment('退货时间');
});
dd();
$handle = fopen(__DIR__ . '/interval.csv', 'w');
$custom_no = DB::connection('vd_old')->table('ckb_custom_handle_log')->select('custom_no')->distinct()->where('type', 11)->get()->pluck('custom_no')->toArray();
fputcsv($handle, ['客户编号', '间隔时间']);
foreach (array_chunk($custom_no, 1000) as $values) {
$res = DB::connection('vd_old')->table('ckb_custom_handle_log')->whereIn('custom_no', $values)->whereIn('type', [10, 11])->orderBy('valid_start_time')->get()->groupBy('custom_no');
foreach ($res as $no => $group) {
echo $no . PHP_EOL;
for ($i=1; $i < count($group); $i++) {
$item1 = $group[$i - 1];
$item2 = $group[$i];
$interval = Carbon::createFromTimestamp(intval($item2->valid_start_time))->diffInMonths(Carbon::createFromTimestamp(intval($item1->valid_end_time)));
fputcsv($handle, [$no, $interval]);
}
}
}
fclose($handle);
dd(1);
app(OrderService::class);