Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
HYH.APSJ
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
佟礼
HYH.APSJ
Commits
c6e3d8ce
Commit
c6e3d8ce
authored
Aug 27, 2026
by
DESKTOP-VKRD9QF\Administration
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 补充宏观排产结果展示字段
parent
24eee50e
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
214 additions
and
10 deletions
+214
-10
MacroPlannerResultController.java
...java/com/aps/controller/MacroPlannerResultController.java
+158
-0
MacroPlannerDataConverter.java
.../com/aps/macroplanner/data/MacroPlannerDataConverter.java
+7
-4
Product.java
src/main/java/com/aps/macroplanner/data/Product.java
+8
-1
SalesDemand.java
src/main/java/com/aps/macroplanner/data/SalesDemand.java
+13
-1
ResultWriter.java
src/main/java/com/aps/macroplanner/output/ResultWriter.java
+14
-2
PispipResult.java
...in/java/com/aps/macroplanner/output/dto/PispipResult.java
+5
-1
SalesDemandResult.java
...va/com/aps/macroplanner/output/dto/SalesDemandResult.java
+9
-1
No files found.
src/main/java/com/aps/controller/MacroPlannerResultController.java
View file @
c6e3d8ce
...
...
@@ -2,6 +2,10 @@ package com.aps.controller;
import
com.aps.common.util.ParamValidator
;
import
com.aps.common.util.R
;
import
com.aps.entity.ApsDemandOrder
;
import
com.aps.entity.MaterialInfo
;
import
com.aps.mapper.ApsDemandOrderMapper
;
import
com.aps.mapper.MaterialInfoMapper
;
import
com.aps.macroplanner.MacroPlannerOptimizer
;
import
com.aps.macroplanner.data.DataValidator
;
import
com.aps.macroplanner.data.MacroPlannerDataConverter
;
...
...
@@ -9,10 +13,12 @@ import com.aps.macroplanner.data.TestDataBuilder;
import
com.aps.macroplanner.output.ResultWriter
;
import
com.aps.macroplanner.output.dto.*
;
import
com.aps.service.MacroPlannerResultService
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.google.ortools.Loader
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
...
...
@@ -51,6 +57,7 @@ import java.util.stream.Collectors;
@RestController
@RequestMapping
(
"/macroResult"
)
@Tag
(
name
=
"MP排产结果"
,
description
=
"MP宏观排产: 运行优化 & 查询物料供应链、产能、生产网络、KPI 结果"
)
@Slf4j
public
class
MacroPlannerResultController
{
@Autowired
...
...
@@ -59,6 +66,12 @@ public class MacroPlannerResultController {
@Autowired
private
MacroPlannerDataConverter
macroPlannerDataConverter
;
@Autowired
private
ApsDemandOrderMapper
apsDemandOrderMapper
;
@Autowired
private
MaterialInfoMapper
materialInfoMapper
;
/**
* 加载结果文件, 校验 sceneId 和文件存在性。
* @return OptimizationResult, 或 null (需要调用方返回 R.failed)
...
...
@@ -110,6 +123,8 @@ public class MacroPlannerResultController {
.
filter
(
s
->
productId
==
null
||
productId
.
isEmpty
()
||
productId
.
equals
(
s
.
getProductId
()))
.
filter
(
s
->
periodIndex
==
null
||
periodIndex
==
s
.
getPeriodIndex
())
.
collect
(
Collectors
.
toList
());
enrichSalesDemandDisplayFields
(
filtered
);
normalizeUnmetReasons
(
filtered
);
// 汇总统计
double
totalDemand
=
filtered
.
stream
().
mapToDouble
(
SalesDemandResult:
:
getDemandQty
).
sum
();
...
...
@@ -195,6 +210,7 @@ public class MacroPlannerResultController {
}
List
<
PispipResult
>
pispips
=
result
.
getPispips
();
enrichPispipProductCodes
(
pispips
);
// 按 productId@spId 分组,保留每周期明细,不跨周期聚合
Map
<
String
,
List
<
PispipResult
>>
grouped
=
pispips
!=
null
...
...
@@ -210,6 +226,7 @@ public class MacroPlannerResultController {
Map
<
String
,
Object
>
s
=
new
LinkedHashMap
<>();
s
.
put
(
"key"
,
entry
.
getKey
());
s
.
put
(
"productId"
,
records
.
get
(
0
).
getProductId
());
s
.
put
(
"productCode"
,
records
.
get
(
0
).
getProductCode
());
s
.
put
(
"spId"
,
records
.
get
(
0
).
getSpId
());
s
.
put
(
"periodCount"
,
records
.
size
());
s
.
put
(
"records"
,
records
);
// 每周期明细, 不聚合
...
...
@@ -392,6 +409,145 @@ public class MacroPlannerResultController {
// ==================== 辅助构建方法 ====================
private
void
enrichSalesDemandDisplayFields
(
List
<
SalesDemandResult
>
demands
)
{
if
(
demands
==
null
||
demands
.
isEmpty
())
{
return
;
}
try
{
Set
<
String
>
demandOrderIds
=
demands
.
stream
()
.
map
(
SalesDemandResult:
:
getSalesDemandId
)
.
filter
(
this
::
hasText
)
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
Map
<
String
,
ApsDemandOrder
>
orderById
=
new
HashMap
<>();
if
(!
demandOrderIds
.
isEmpty
())
{
List
<
ApsDemandOrder
>
orders
=
apsDemandOrderMapper
.
selectList
(
new
LambdaQueryWrapper
<
ApsDemandOrder
>()
.
in
(
ApsDemandOrder:
:
getId
,
demandOrderIds
));
orderById
=
orders
.
stream
()
.
filter
(
o
->
hasText
(
o
.
getId
()))
.
collect
(
Collectors
.
toMap
(
ApsDemandOrder:
:
getId
,
o
->
o
,
(
a
,
b
)
->
a
));
}
Set
<
String
>
materialIds
=
demands
.
stream
()
.
map
(
SalesDemandResult:
:
getProductId
)
.
filter
(
this
::
hasText
)
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
orderById
.
values
().
stream
()
.
map
(
ApsDemandOrder:
:
getMmid
)
.
filter
(
this
::
hasText
)
.
forEach
(
materialIds:
:
add
);
Map
<
String
,
String
>
materialCodeById
=
new
HashMap
<>();
if
(!
materialIds
.
isEmpty
())
{
List
<
MaterialInfo
>
materials
=
materialInfoMapper
.
selectList
(
new
LambdaQueryWrapper
<
MaterialInfo
>()
.
in
(
MaterialInfo:
:
getId
,
materialIds
));
materialCodeById
=
materials
.
stream
()
.
filter
(
m
->
hasText
(
m
.
getId
()))
.
collect
(
Collectors
.
toMap
(
MaterialInfo:
:
getId
,
m
->
firstText
(
m
.
getCode
(),
m
.
getTempcode
(),
m
.
getId
()),
(
a
,
b
)
->
a
));
}
for
(
SalesDemandResult
demand
:
demands
)
{
ApsDemandOrder
order
=
orderById
.
get
(
demand
.
getSalesDemandId
());
if
(!
hasText
(
demand
.
getOrderCode
())
&&
order
!=
null
)
{
demand
.
setOrderCode
(
order
.
getCode
());
}
if
(!
hasText
(
demand
.
getProductCode
()))
{
String
productCode
=
null
;
if
(
order
!=
null
)
{
productCode
=
firstText
(
order
.
getMmcode
(),
materialCodeById
.
get
(
order
.
getMmid
()));
}
productCode
=
firstText
(
productCode
,
materialCodeById
.
get
(
demand
.
getProductId
()),
demand
.
getProductId
());
demand
.
setProductCode
(
productCode
);
}
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"补充销售需求订单编号/产品编号失败, 使用结果文件原始字段返回: {}"
,
e
.
getMessage
());
}
}
private
void
normalizeUnmetReasons
(
List
<
SalesDemandResult
>
demands
)
{
for
(
SalesDemandResult
demand
:
demands
)
{
List
<
String
>
reasons
=
demand
.
getUnmetReasons
();
if
(
reasons
==
null
||
reasons
.
isEmpty
())
{
continue
;
}
List
<
String
>
normalized
=
new
ArrayList
<>();
for
(
String
reason
:
reasons
)
{
if
(
reason
!=
null
&&
reason
.
startsWith
(
"库存耗尽("
)
&&
reason
.
endsWith
(
")"
))
{
normalized
.
add
(
"库存耗尽"
);
normalized
.
add
(
reason
.
substring
(
"库存耗尽"
.
length
()));
}
else
{
normalized
.
add
(
reason
);
}
}
reasons
.
clear
();
reasons
.
addAll
(
normalized
);
}
}
private
void
enrichPispipProductCodes
(
List
<
PispipResult
>
pispips
)
{
if
(
pispips
==
null
||
pispips
.
isEmpty
())
{
return
;
}
Set
<
String
>
productIds
=
pispips
.
stream
()
.
map
(
PispipResult:
:
getProductId
)
.
filter
(
this
::
hasText
)
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
if
(
productIds
.
isEmpty
())
{
return
;
}
Map
<
String
,
String
>
productCodeByKey
=
new
HashMap
<>();
try
{
List
<
MaterialInfo
>
materials
=
materialInfoMapper
.
selectList
(
new
LambdaQueryWrapper
<
MaterialInfo
>()
.
and
(
w
->
w
.
in
(
MaterialInfo:
:
getId
,
productIds
)
.
or
()
.
in
(
MaterialInfo:
:
getCode
,
productIds
)));
for
(
MaterialInfo
material
:
materials
)
{
String
productCode
=
firstText
(
material
.
getCode
(),
material
.
getTempcode
(),
material
.
getId
());
if
(
hasText
(
material
.
getId
()))
{
productCodeByKey
.
put
(
material
.
getId
(),
productCode
);
}
if
(
hasText
(
material
.
getCode
()))
{
productCodeByKey
.
put
(
material
.
getCode
(),
productCode
);
}
}
}
catch
(
Exception
e
)
{
log
.
warn
(
"补充库存产品编号失败, 使用结果文件原始字段返回: {}"
,
e
.
getMessage
());
}
for
(
PispipResult
pispip
:
pispips
)
{
if
(!
hasText
(
pispip
.
getProductCode
()))
{
pispip
.
setProductCode
(
firstText
(
productCodeByKey
.
get
(
pispip
.
getProductId
()),
pispip
.
getProductId
()));
}
}
}
private
boolean
hasText
(
String
value
)
{
return
value
!=
null
&&
!
value
.
trim
().
isEmpty
();
}
private
String
firstText
(
String
...
values
)
{
for
(
String
value
:
values
)
{
if
(
hasText
(
value
))
{
return
value
.
trim
();
}
}
return
null
;
}
/**
* 构建供应链视图: 每个产品-库位的库存流转 + 销售满足情况。
*
...
...
@@ -406,6 +562,7 @@ public class MacroPlannerResultController {
// 1.1 按产品汇总库存视图
List
<
PispipResult
>
pispips
=
result
.
getPispips
();
enrichPispipProductCodes
(
pispips
);
sc
.
put
(
"totalPispipRecords"
,
pispips
.
size
());
// 提取产品列表
...
...
@@ -468,6 +625,7 @@ public class MacroPlannerResultController {
Map
<
String
,
Object
>
summary
=
new
LinkedHashMap
<>();
summary
.
put
(
"productId"
,
first
.
getProductId
());
summary
.
put
(
"productCode"
,
first
.
getProductCode
());
summary
.
put
(
"spId"
,
first
.
getSpId
());
summary
.
put
(
"periodCount"
,
records
.
size
());
summary
.
put
(
"initialInventory"
,
initialInv
);
...
...
src/main/java/com/aps/macroplanner/data/MacroPlannerDataConverter.java
View file @
c6e3d8ce
...
...
@@ -689,7 +689,7 @@ public class MacroPlannerDataConverter {
// 3.1 为每个 Material 创建 Product
for
(
Material
m
:
ctx
.
materialByMaterialId
.
values
())
{
String
name
=
pickName
(
m
.
getName
(),
m
.
getCode
(),
m
.
getId
());
Product
p
=
new
Product
(
m
.
getCode
(),
name
);
Product
p
=
new
Product
(
m
.
getCode
(),
name
,
m
.
getCode
()
);
products
.
add
(
p
);
ctx
.
productByMaterialId
.
put
(
m
.
getId
(),
p
);
}
...
...
@@ -1269,7 +1269,8 @@ public class MacroPlannerDataConverter {
orderEnd
=
deliveryDate
.
plusDays
(
1
);
// +1 使结束时间包含当天
}
else
{
// 无时间信息, 放入最后一个周期
salesDemands
.
add
(
new
SalesDemand
(
p
,
sp
,
lastPeriod
,
qty
,
priority
,
ado
.
getId
()));
salesDemands
.
add
(
new
SalesDemand
(
p
,
sp
,
lastPeriod
,
qty
,
priority
,
ado
.
getId
(),
ado
.
getCode
(),
pickName
(
ado
.
getMmcode
(),
p
.
getId
())));
continue
;
}
...
...
@@ -1290,7 +1291,8 @@ public class MacroPlannerDataConverter {
}
if
(
overlapPeriods
.
isEmpty
())
{
salesDemands
.
add
(
new
SalesDemand
(
p
,
sp
,
lastPeriod
,
qty
,
priority
,
ado
.
getId
()));
salesDemands
.
add
(
new
SalesDemand
(
p
,
sp
,
lastPeriod
,
qty
,
priority
,
ado
.
getId
(),
ado
.
getCode
(),
pickName
(
ado
.
getMmcode
(),
p
.
getId
())));
}
else
{
// 按重叠天数比例拆分需求量: 每期四舍五入取整, 最后一期补差保证总和等于总数
int
totalQty
=
(
int
)
Math
.
round
(
qty
);
...
...
@@ -1306,7 +1308,8 @@ public class MacroPlannerDataConverter {
periodQty
=
(
int
)
Math
.
round
(
qty
*
overlapDays
/
(
double
)
totalRangeDays
);
allocated
+=
periodQty
;
}
salesDemands
.
add
(
new
SalesDemand
(
p
,
sp
,
per
,
periodQty
,
priority
,
ado
.
getId
()));
salesDemands
.
add
(
new
SalesDemand
(
p
,
sp
,
per
,
periodQty
,
priority
,
ado
.
getId
(),
ado
.
getCode
(),
pickName
(
ado
.
getMmcode
(),
p
.
getId
())));
}
}
}
...
...
src/main/java/com/aps/macroplanner/data/Product.java
View file @
c6e3d8ce
...
...
@@ -6,14 +6,21 @@ package com.aps.macroplanner.data;
public
class
Product
{
private
final
String
id
;
private
final
String
name
;
private
final
String
code
;
public
Product
(
String
id
,
String
name
)
{
this
(
id
,
name
,
id
);
}
public
Product
(
String
id
,
String
name
,
String
code
)
{
this
.
id
=
id
;
this
.
name
=
name
;
this
.
code
=
code
;
}
public
String
getId
()
{
return
id
;
}
public
String
getName
()
{
return
name
;
}
public
String
getCode
()
{
return
code
;
}
@Override
public
String
toString
()
{
...
...
src/main/java/com/aps/macroplanner/data/SalesDemand.java
View file @
c6e3d8ce
...
...
@@ -11,6 +11,8 @@ public class SalesDemand {
private
final
double
quantity
;
// 需求总量
private
final
double
priority
;
// 需求优先级
private
final
String
demandOrderId
;
// 来源订单ID (ApsDemandOrder.id), 可空
private
final
String
orderCode
;
private
final
String
productCode
;
public
SalesDemand
(
Product
product
,
StockingPoint
stockingPoint
,
Period
period
,
double
quantity
,
double
priority
)
{
...
...
@@ -19,12 +21,20 @@ public class SalesDemand {
public
SalesDemand
(
Product
product
,
StockingPoint
stockingPoint
,
Period
period
,
double
quantity
,
double
priority
,
String
demandOrderId
)
{
this
(
product
,
stockingPoint
,
period
,
quantity
,
priority
,
demandOrderId
,
null
,
null
);
}
public
SalesDemand
(
Product
product
,
StockingPoint
stockingPoint
,
Period
period
,
double
quantity
,
double
priority
,
String
demandOrderId
,
String
orderCode
,
String
productCode
)
{
this
.
product
=
product
;
this
.
stockingPoint
=
stockingPoint
;
this
.
period
=
period
;
this
.
quantity
=
quantity
;
this
.
priority
=
priority
;
this
.
demandOrderId
=
demandOrderId
;
this
.
orderCode
=
orderCode
;
this
.
productCode
=
productCode
;
}
public
Product
getProduct
()
{
return
product
;
}
...
...
@@ -33,6 +43,8 @@ public class SalesDemand {
public
double
getQuantity
()
{
return
quantity
;
}
public
double
getPriority
()
{
return
priority
;
}
public
String
getDemandOrderId
()
{
return
demandOrderId
;
}
public
String
getOrderCode
()
{
return
orderCode
;
}
public
String
getProductCode
()
{
return
productCode
;
}
public
String
getKey
()
{
return
product
.
getId
()
+
"_"
+
stockingPoint
.
getId
()
+
"_"
+
period
.
getIndex
();
...
...
src/main/java/com/aps/macroplanner/output/ResultWriter.java
View file @
c6e3d8ce
...
...
@@ -410,7 +410,9 @@ public class ResultWriter {
for
(
SalesDemand
sd
:
data
.
getSalesDemands
())
{
SalesDemandResult
sr
=
new
SalesDemandResult
();
sr
.
setSalesDemandId
(
sd
.
getDemandOrderId
()==
null
?
sd
.
getKey
():
sd
.
getDemandOrderId
());
sr
.
setOrderCode
(
sd
.
getOrderCode
());
sr
.
setProductId
(
sd
.
getProduct
().
getId
());
sr
.
setProductCode
(
sd
.
getProductCode
());
sr
.
setSpId
(
sd
.
getStockingPoint
().
getId
());
sr
.
setPeriodIndex
(
sd
.
getPeriod
().
getIndex
());
sr
.
setPeriodStartDate
(
sd
.
getPeriod
().
getStartDate
().
toString
());
...
...
@@ -473,7 +475,8 @@ public class ResultWriter {
}
double
available
=
openingInv
+
totalArrived
;
if
(
available
<
demandQty
)
{
reasons
.
add
(
"库存耗尽(可用"
+
fmt
(
available
)
+
"件, 需求"
+
fmt
(
demandQty
)
+
"件)"
);
addReasonWithDetail
(
reasons
,
"库存耗尽"
,
"(可用"
+
fmt
(
available
)
+
"件, 需求"
+
fmt
(
demandQty
)
+
"件)"
);
}
// 2. 产能瓶颈: 所有生产该产品的单元满负荷 (利用率 > 95%)
...
...
@@ -552,6 +555,11 @@ public class ResultWriter {
}
}
private
void
addReasonWithDetail
(
java
.
util
.
List
<
String
>
reasons
,
String
title
,
String
detail
)
{
reasons
.
add
(
title
);
reasons
.
add
(
detail
);
}
/**
* 分析销售需求的风险 (即使本期已满足)。
*
...
...
@@ -702,6 +710,7 @@ public class ResultWriter {
for
(
Period
p
:
data
.
getPeriods
())
{
PispipResult
pr
=
new
PispipResult
();
pr
.
setProductId
(
prod
.
getId
());
pr
.
setProductCode
(
prod
.
getCode
());
pr
.
setSpId
(
sp
.
getId
());
pr
.
setPeriodIndex
(
p
.
getIndex
());
pr
.
setPeriodStartDate
(
p
.
getStartDate
().
toString
());
...
...
@@ -1221,7 +1230,9 @@ public class ResultWriter {
for
(
SalesDemandResult
sr
:
result
.
getSalesDemands
())
{
jb
.
obj
();
jb
.
key
(
"salesDemandId"
).
val
(
sr
.
getSalesDemandId
());
jb
.
key
(
"orderCode"
).
val
(
sr
.
getOrderCode
());
jb
.
key
(
"productId"
).
val
(
sr
.
getProductId
());
jb
.
key
(
"productCode"
).
val
(
sr
.
getProductCode
());
jb
.
key
(
"spId"
).
val
(
sr
.
getSpId
());
jb
.
key
(
"periodIndex"
).
val
(
sr
.
getPeriodIndex
());
jb
.
key
(
"periodStartDate"
).
val
(
sr
.
getPeriodStartDate
());
...
...
@@ -1251,6 +1262,7 @@ public class ResultWriter {
for
(
PispipResult
pr
:
result
.
getPispips
())
{
jb
.
obj
();
jb
.
key
(
"productId"
).
val
(
pr
.
getProductId
());
jb
.
key
(
"productCode"
).
val
(
pr
.
getProductCode
());
jb
.
key
(
"spId"
).
val
(
pr
.
getSpId
());
jb
.
key
(
"periodIndex"
).
val
(
pr
.
getPeriodIndex
());
jb
.
key
(
"periodStartDate"
).
val
(
pr
.
getPeriodStartDate
());
...
...
src/main/java/com/aps/macroplanner/output/dto/PispipResult.java
View file @
c6e3d8ce
...
...
@@ -11,6 +11,7 @@ import java.util.List;
public
class
PispipResult
{
private
String
productId
;
private
String
productCode
;
private
String
spId
;
private
int
periodIndex
;
private
String
periodStartDate
;
...
...
@@ -103,6 +104,9 @@ public class PispipResult {
public
String
getProductId
()
{
return
productId
;
}
public
void
setProductId
(
String
v
)
{
this
.
productId
=
v
;
}
public
String
getProductCode
()
{
return
productCode
;
}
public
void
setProductCode
(
String
v
)
{
this
.
productCode
=
v
;
}
public
String
getSpId
()
{
return
spId
;
}
public
void
setSpId
(
String
v
)
{
this
.
spId
=
v
;
}
...
...
src/main/java/com/aps/macroplanner/output/dto/SalesDemandResult.java
View file @
c6e3d8ce
...
...
@@ -11,7 +11,9 @@ import java.util.List;
public
class
SalesDemandResult
{
private
String
salesDemandId
;
private
String
orderCode
;
private
String
productId
;
private
String
productCode
;
private
String
spId
;
private
int
periodIndex
;
private
String
periodStartDate
;
...
...
@@ -39,9 +41,15 @@ public class SalesDemandResult {
public
String
getSalesDemandId
()
{
return
salesDemandId
;
}
public
void
setSalesDemandId
(
String
v
)
{
this
.
salesDemandId
=
v
;
}
public
String
getOrderCode
()
{
return
orderCode
;
}
public
void
setOrderCode
(
String
v
)
{
this
.
orderCode
=
v
;
}
public
String
getProductId
()
{
return
productId
;
}
public
void
setProductId
(
String
v
)
{
this
.
productId
=
v
;
}
public
String
getProductCode
()
{
return
productCode
;
}
public
void
setProductCode
(
String
v
)
{
this
.
productCode
=
v
;
}
public
String
getSpId
()
{
return
spId
;
}
public
void
setSpId
(
String
v
)
{
this
.
spId
=
v
;
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment