优化
This commit is contained in:
+1
@@ -33,6 +33,7 @@ public class OverseasProcurementFee {
|
||||
private Long sourceFeeId;
|
||||
private Long parentFeeId;
|
||||
private String requestHash;
|
||||
private String idempotencyKey;
|
||||
private String feeItemCode;
|
||||
private String feeItemName;
|
||||
private BigDecimal amount;
|
||||
|
||||
+3
@@ -3,6 +3,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementFee;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
@Mapper public interface OverseasProcurementFeeMapper extends BaseMapper<OverseasProcurementFee> {
|
||||
/** 调用方持有订单锁时,读取当前退款版本上界。 */
|
||||
@org.apache.ibatis.annotations.Select("SELECT COALESCE(MAX(fee_version),0) FROM ll_overseas_procurement_fee WHERE order_id=#{orderId} AND purpose='REFUND'")
|
||||
int maxRefundVersion(@org.apache.ibatis.annotations.Param("orderId") long orderId);
|
||||
@org.apache.ibatis.annotations.Select("SELECT * FROM ll_overseas_procurement_fee WHERE id=#{id} AND order_id=#{orderId} AND customer_id=#{customerId} FOR UPDATE")
|
||||
OverseasProcurementFee lock(@org.apache.ibatis.annotations.Param("id") long id,
|
||||
@org.apache.ibatis.annotations.Param("orderId") long orderId,
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.lastleg.procurement.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.lastleg.procurement.entity.OverseasProcurementFee;
|
||||
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementFeeMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/** 采购退款的请求身份独立于展示单号,跨日重试仍定位原费用。 */
|
||||
@Repository
|
||||
public class ProcurementFeeNumberRepository {
|
||||
private final OverseasProcurementFeeMapper mapper;
|
||||
|
||||
/** 使用采购费用所属服务的 Mapper。 */
|
||||
public ProcurementFeeNumberRepository(OverseasProcurementFeeMapper mapper) { this.mapper = mapper; }
|
||||
|
||||
/** 上层已持有订单锁,同客户同订单的退款请求只对应一个费用。 */
|
||||
public OverseasProcurementFee findRefund(String customerId, long orderId, String key) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<OverseasProcurementFee>()
|
||||
.eq(OverseasProcurementFee::getCustomerId, customerId)
|
||||
.eq(OverseasProcurementFee::getOrderId, orderId)
|
||||
.eq(OverseasProcurementFee::getPurpose, "REFUND")
|
||||
.eq(OverseasProcurementFee::getIdempotencyKey, key));
|
||||
}
|
||||
|
||||
/** 版本沿用同订单退款序列,业务编号不承担费用版本含义。 */
|
||||
public int nextRefundVersion(long orderId) { return mapper.maxRefundVersion(orderId) + 1; }
|
||||
}
|
||||
+3
-2
@@ -43,7 +43,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Service
|
||||
public class OverseasProcurementService {
|
||||
@Resource private ProcurementOrderNumberGenerator orderNoGenerator;
|
||||
@Resource private com.baiyue.lastleg.service.LastlegDocumentNumberGenerator orderNoGenerator;
|
||||
public static final String REVIEW_PERMISSION = OverseasPermissions.PURCHASE_REVIEW;
|
||||
public static final String MANAGE_PERMISSION = OverseasPermissions.PURCHASE_EDIT;
|
||||
public static final String PENDING_REVIEW = "PENDING_REVIEW";
|
||||
@@ -61,6 +61,7 @@ public class OverseasProcurementService {
|
||||
@Resource private OverseasProcurementFeeMapper procurementFees;
|
||||
@Resource private ProcurementFileGateway fileGateway;
|
||||
|
||||
/** 创建客户采购订单,首次请求分配 CG 日流水。 */
|
||||
@Transactional
|
||||
public OverseasProcurementResponses.Detail create(String customerId, String operatorId,
|
||||
OverseasProcurementRequests.Create request) {
|
||||
@@ -74,7 +75,7 @@ public class OverseasProcurementService {
|
||||
order.setId(IdWorker.getId());
|
||||
order.setCustomerId(customerId);
|
||||
order.setCustomerName(trim(request.customerName()));
|
||||
order.setOrderNo(orderNoGenerator.next(customerId, request.customerName()));
|
||||
order.setOrderNo(orderNoGenerator.next("CG"));
|
||||
order.setPurchaseCategory(request.purchaseCategory() == null ? "" : request.purchaseCategory().trim());
|
||||
order.setProductName(request.productName().trim());
|
||||
order.setProductSpecification(trim(request.productSpecification()));
|
||||
|
||||
+6
-2
@@ -16,6 +16,8 @@ import static com.baiyue.lastleg.procurement.service.ProcurementLifecycleService
|
||||
|
||||
@Service @RequiredArgsConstructor
|
||||
public class ProcurementAdjustmentService {
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.service.LastlegDocumentNumberGenerator documentNumbers;
|
||||
|
||||
private final ProcurementLifecycleService lifecycle;
|
||||
private final ProcurementFinanceService finance;
|
||||
private final JdbcTemplate jdbc;
|
||||
@@ -23,6 +25,7 @@ public class ProcurementAdjustmentService {
|
||||
BigDecimal exchangeRate,String exchangeRateMonth,BigDecimal targetAmount,BigDecimal previousNetAmount,
|
||||
BigDecimal differenceAmount,List<String> feeNos) { }
|
||||
private record Identity(long rootFeeId,Correct request) { }
|
||||
/** 更正原采购费用并以 PG 日流水标识本次更正记录。 */
|
||||
@Transactional
|
||||
public Result correct(String customer,String orderNo,String operator,long rootFeeId,Correct request) {
|
||||
var order=lifecycle.lock(customer,orderNo);var identity=new Identity(rootFeeId,request);
|
||||
@@ -37,7 +40,7 @@ public class ProcurementAdjustmentService {
|
||||
.subtract(chain.stream().filter(f->"REFUNDED".equals(f.getWalletStatus())).map(OverseasProcurementFee::getAmount).reduce(BigDecimal.ZERO,BigDecimal::add));
|
||||
BigDecimal target=request.targetOriginalAmount().multiply(root.getExchangeRate()).setScale(2,RoundingMode.HALF_UP),delta=target.subtract(net);
|
||||
if(target.precision()-target.scale()>18)throw bad("折算人民币金额超出支持范围");
|
||||
long id=IdWorker.getId();String no="PC-"+UUID.randomUUID().toString().replace("-","").substring(0,24).toUpperCase();
|
||||
long id=IdWorker.getId();String no=documentNumbers.next("PG");
|
||||
// The node command carries the exact request hash; this table stores the computed immutable monetary facts.
|
||||
jdbc.update("INSERT INTO ll_procurement_fee_correction(id,order_id,customer_id,correction_no,root_fee_id,idempotency_key,request_hash,target_original_amount,original_currency,exchange_rate,exchange_rate_month,target_amount,previous_net_amount,difference_amount,voucher_file_id,reason,created_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
id,order.getId(),customer,no,rootFeeId,request.idempotencyKey(),lifecycle.requestHash(identity),request.targetOriginalAmount(),root.getOriginalCurrency(),root.getExchangeRate(),root.getExchangeRateMonth(),target,net,delta,request.voucherFileId(),request.reason(),operator);
|
||||
@@ -47,6 +50,7 @@ public class ProcurementAdjustmentService {
|
||||
lifecycle.complete(order,operator,"CORRECT_FEE_TARGET",request.idempotencyKey(),request.reason(),identity);
|
||||
return result(order.getId(),request.idempotencyKey());
|
||||
}
|
||||
/** 替换失败费用,保留原费用链并分配新的 PG 更正记录号。 */
|
||||
@Transactional
|
||||
public Result replaceFailed(String customer,String orderNo,String operator,long originalFeeId,Correct request) {
|
||||
var order=lifecycle.lock(customer,orderNo);var identity=new Identity(originalFeeId,request);
|
||||
@@ -57,7 +61,7 @@ public class ProcurementAdjustmentService {
|
||||
BigDecimal target=request.targetOriginalAmount().multiply(original.getExchangeRate()).setScale(2,RoundingMode.HALF_UP);
|
||||
if("SAMPLE_PAYMENT".equals(original.getPurpose()))lifecycle.requireReplacementBudget(order,request.targetOriginalAmount(),original.getOriginalCurrency(),original.getBusinessDate(),target);
|
||||
if(target.precision()-target.scale()>18)throw bad("折算人民币金额超出支持范围");
|
||||
long id=IdWorker.getId();String no="PC-"+UUID.randomUUID().toString().replace("-","").substring(0,24).toUpperCase();
|
||||
long id=IdWorker.getId();String no=documentNumbers.next("PG");
|
||||
jdbc.update("INSERT INTO ll_procurement_fee_correction(id,order_id,customer_id,correction_no,root_fee_id,idempotency_key,request_hash,target_original_amount,original_currency,exchange_rate,exchange_rate_month,target_amount,previous_net_amount,difference_amount,voucher_file_id,reason,created_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
id,order.getId(),customer,no,originalFeeId,request.idempotencyKey(),lifecycle.requestHash(identity),request.targetOriginalAmount(),original.getOriginalCurrency(),original.getExchangeRate(),original.getExchangeRateMonth(),target,BigDecimal.ZERO,target,request.voucherFileId(),request.reason(),operator);
|
||||
var fee=finance.replaceFailed(customer,orderNo,operator,originalFeeId,request.targetOriginalAmount(),request.voucherFileId(),request.reason());
|
||||
|
||||
+4
-1
@@ -13,6 +13,8 @@ import static com.baiyue.lastleg.procurement.service.ProcurementLifecycleService
|
||||
|
||||
@Service @RequiredArgsConstructor
|
||||
public class ProcurementControlService {
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.service.LastlegDocumentNumberGenerator documentNumbers;
|
||||
|
||||
private final ProcurementLifecycleService lifecycle;
|
||||
private final ProcurementFinanceService finance;
|
||||
private final ProcurementWarehouseDirectory warehouses;
|
||||
@@ -141,5 +143,6 @@ public class ProcurementControlService {
|
||||
return result;
|
||||
}
|
||||
private String serialize(Object value){if(value==null)return null;try{return json.writeValueAsString(value);}catch(Exception e){throw new IllegalStateException(e);}}
|
||||
private static String number(String prefix){return prefix+'-'+UUID.randomUUID().toString().replace("-","").substring(0,24).toUpperCase();}
|
||||
/** 说明和关闭记录使用各自独立的日流水。 */
|
||||
private String number(String prefix){return documentNumbers.next(prefix);}
|
||||
}
|
||||
|
||||
+10
-8
@@ -38,6 +38,9 @@ import java.util.UUID;
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProcurementFinanceService implements WalletResultHandler {
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.procurement.repository.ProcurementFeeNumberRepository feeNumbers;
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.service.LastlegDocumentNumberGenerator documentNumbers;
|
||||
|
||||
private final OverseasProcurementFeeMapper fees;
|
||||
private final OverseasProcurementFileMapper files;
|
||||
private final OverseasProcurementOrderMapper orders;
|
||||
@@ -118,7 +121,7 @@ public class ProcurementFinanceService implements WalletResultHandler {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
OverseasProcurementFee fee = new OverseasProcurementFee();
|
||||
fee.setId(IdWorker.getId()); fee.setOrderId(order.getId()); fee.setCustomerId(order.getCustomerId());
|
||||
fee.setFeeNo("PF-" + UUID.randomUUID().toString().replace("-", "").substring(0, 24).toUpperCase());
|
||||
fee.setFeeNo(documentNumbers.next("PF"));
|
||||
fee.setPurpose(request.purpose().name()); fee.setConfirmationOrigin(origin); fee.setFeeVersion(maxVersion == null ? 1 : maxVersion + 1);
|
||||
fee.setActive(true); fee.setDetermination(request.determination()); fee.setFeeItemCode(code);
|
||||
fee.setFeeItemName(request.purpose() == ProcurementFeePurpose.OTHER ? request.feeItemName().trim() : request.purpose().label());
|
||||
@@ -205,6 +208,7 @@ public class ProcurementFinanceService implements WalletResultHandler {
|
||||
var order = lockOrder(customerId, orderNo);
|
||||
return refundLocked(order,operatorId,originalFeeId,amount,voucherFileId,reason,idempotencyKey,true);
|
||||
}
|
||||
/** 订单锁内先恢复同幂等键退款,首次请求校验后才分配独立 PF 业务号。 */
|
||||
private OverseasProcurementFee refundLocked(OverseasProcurementOrder order,String operatorId,long originalFeeId,
|
||||
BigDecimal amount,long voucherFileId,String reason,String idempotencyKey,boolean checkChain) {
|
||||
String customerId=order.getCustomerId(),orderNo=order.getOrderNo();
|
||||
@@ -212,11 +216,8 @@ public class ProcurementFinanceService implements WalletResultHandler {
|
||||
var request = new Confirmation(ProcurementFeePurpose.REFUND, "REFUND", "退款", "POSITIVE", amount,
|
||||
"CNY", LocalDate.now(), voucherFileId, reason);
|
||||
validate(request);
|
||||
// Identity is independent of wall-clock time, so retrying after midnight returns the same refund.
|
||||
String number = "RF-" + UUID.nameUUIDFromBytes((customerId + ':' + orderNo + ':' + idempotencyKey)
|
||||
.getBytes(java.nio.charset.StandardCharsets.UTF_8)).toString().replace("-", "").toUpperCase();
|
||||
String requestHash = hash(List.of(originalFeeId, amount, voucherFileId, reason));
|
||||
var existing = fees.selectOne(new LambdaQueryWrapper<OverseasProcurementFee>().eq(OverseasProcurementFee::getFeeNo, number));
|
||||
var existing = feeNumbers.findRefund(customerId, order.getId(), idempotencyKey);
|
||||
if (existing != null) {
|
||||
if (!requestHash.equals(existing.getRequestHash())) throw conflict("退款幂等键对应不同原扣款或金额");
|
||||
return existing;
|
||||
@@ -237,10 +238,11 @@ public class ProcurementFinanceService implements WalletResultHandler {
|
||||
if (already.add(amount).compareTo(original.getAmount()) > 0) throw bad("退款超过原扣款可退余额");
|
||||
var voucher = voucher(order, request, true);
|
||||
var fee = new OverseasProcurementFee();
|
||||
String number = documentNumbers.next("PF");
|
||||
fee.setIdempotencyKey(idempotencyKey);
|
||||
fee.setId(IdWorker.getId()); fee.setOrderId(order.getId()); fee.setCustomerId(customerId); fee.setFeeNo(number);
|
||||
fee.setConfirmationOrigin("FINANCIAL");fee.setPurpose("REFUND"); fee.setFeeItemCode("REFUND"); fee.setFeeItemName("关联费用退款");
|
||||
Integer last = jdbc.queryForObject("SELECT COALESCE(MAX(fee_version),0) FROM ll_overseas_procurement_fee WHERE order_id=? AND purpose='REFUND'", Integer.class, order.getId());
|
||||
fee.setFeeVersion(last == null ? 1 : last + 1); fee.setActive(true); fee.setDetermination("POSITIVE");
|
||||
fee.setFeeVersion(feeNumbers.nextRefundVersion(order.getId())); fee.setActive(true); fee.setDetermination("POSITIVE");
|
||||
fee.setAmount(amount); fee.setCurrency("CNY"); fee.setOriginalAmount(amount); fee.setOriginalCurrency("CNY");
|
||||
fee.setExchangeRate(BigDecimal.ONE); fee.setExchangeRateMonth(request.businessDate().toString().substring(0, 7));
|
||||
fee.setBusinessDate(request.businessDate()); fee.setParentFeeId(originalFeeId); fee.setWalletBusinessId(fee.getId());
|
||||
@@ -272,7 +274,7 @@ public class ProcurementFinanceService implements WalletResultHandler {
|
||||
requireSourceCapacity(delta,delta);
|
||||
var proof=voucher(order,new Confirmation(ProcurementFeePurpose.ADJUSTMENT,"ADJUSTMENT","追加费用","POSITIVE",delta,"CNY",root.getBusinessDate(),voucherId,reason),true);
|
||||
var fee=new OverseasProcurementFee();fee.setId(IdWorker.getId());fee.setOrderId(order.getId());fee.setCustomerId(customer);
|
||||
fee.setFeeNo("PF-"+UUID.randomUUID().toString().replace("-","").substring(0,24).toUpperCase());fee.setConfirmationOrigin("FINANCIAL");fee.setPurpose("ADJUSTMENT");
|
||||
fee.setFeeNo(documentNumbers.next("PF"));fee.setConfirmationOrigin("FINANCIAL");fee.setPurpose("ADJUSTMENT");
|
||||
fee.setFeeItemCode("ADJ_"+root.getId());fee.setFeeItemName("原费用正额调整");
|
||||
fee.setFeeVersion(jdbc.queryForObject("SELECT COALESCE(MAX(fee_version),0)+1 FROM ll_overseas_procurement_fee WHERE order_id=? AND purpose='ADJUSTMENT' AND fee_item_code=?",Integer.class,order.getId(),fee.getFeeItemCode()));
|
||||
fee.setActive(true);fee.setDetermination("POSITIVE");fee.setAmount(delta);fee.setCurrency("CNY");fee.setOriginalCurrency("CNY");fee.setOriginalAmount(delta);
|
||||
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package com.baiyue.lastleg.procurement.service;
|
||||
|
||||
import net.sourceforge.pinyin4j.PinyinHelper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
@Component
|
||||
public class ProcurementOrderNumberGenerator {
|
||||
private final JdbcTemplate jdbc;
|
||||
public ProcurementOrderNumberGenerator(JdbcTemplate jdbc) { this.jdbc = jdbc; }
|
||||
|
||||
@Transactional
|
||||
public String next(String customerId, String customerName) {
|
||||
LocalDate date = LocalDate.now();
|
||||
jdbc.update("INSERT INTO ll_procurement_daily_sequence(customer_id,business_date,sequence_value) VALUES(?,?,LAST_INSERT_ID(1)) ON DUPLICATE KEY UPDATE sequence_value=LAST_INSERT_ID(sequence_value+1)",
|
||||
customerId, date);
|
||||
Integer value = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Integer.class);
|
||||
if (value == null || value < 1 || value > 9999) throw new IllegalStateException("客户采购订单当日序号已耗尽");
|
||||
return initials(customerName) + date.format(DateTimeFormatter.BASIC_ISO_DATE) + String.format("%04d", value);
|
||||
}
|
||||
|
||||
static String initials(String customerName) {
|
||||
if (!StringUtils.hasText(customerName)) return "KH";
|
||||
StringBuilder result = new StringBuilder();
|
||||
boolean latinWord = false;
|
||||
for (char value : customerName.trim().toCharArray()) {
|
||||
if (result.length() >= 8) break;
|
||||
if (Character.UnicodeScript.of(value) == Character.UnicodeScript.HAN) {
|
||||
String[] pinyin = PinyinHelper.toHanyuPinyinStringArray(value);
|
||||
if (pinyin != null && pinyin.length > 0 && !pinyin[0].isBlank()) result.append(Character.toUpperCase(pinyin[0].charAt(0)));
|
||||
latinWord = false;
|
||||
} else if (Character.isLetterOrDigit(value)) {
|
||||
if (!latinWord) result.append(Character.toUpperCase(value));
|
||||
latinWord = true;
|
||||
} else {
|
||||
latinWord = false;
|
||||
}
|
||||
}
|
||||
String normalized = result.toString().replaceAll("[^A-Z0-9]", "").toUpperCase(Locale.ROOT);
|
||||
return normalized.isBlank() ? "KH" : normalized.substring(0, Math.min(normalized.length(), 8));
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -19,6 +19,8 @@ import static com.baiyue.lastleg.procurement.service.ProcurementLifecycleService
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ProcurementPhysicalService {
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.service.LastlegDocumentNumberGenerator documentNumbers;
|
||||
|
||||
private final ProcurementLifecycleService lifecycle;
|
||||
private final ProcurementWarehouseDirectory warehouses;
|
||||
private final ProcurementFinanceService finance;
|
||||
@@ -327,5 +329,6 @@ public class ProcurementPhysicalService {
|
||||
}
|
||||
private static void measure(BigDecimal value,int scale){if(value==null || value.signum()<=0 || value.scale()>scale)throw bad("箱规须为正数且最多两位小数,毛重须为正数且最多三位小数");}
|
||||
private static long add(long a,long b){try{return Math.addExact(a,b);}catch(ArithmeticException e){throw bad("数量超过可处理范围");}}
|
||||
private static String number(String prefix){return prefix+'-'+UUID.randomUUID().toString().replace("-","").substring(0,24).toUpperCase();}
|
||||
/** 实收记录使用独立 PR 日流水。 */
|
||||
private String number(String prefix){return documentNumbers.next(prefix);}
|
||||
}
|
||||
|
||||
+4
-1
@@ -19,6 +19,8 @@ import static com.baiyue.lastleg.procurement.service.ProcurementLifecycleService
|
||||
/** Every shipment is a single immutable consumption of this order's entire eligible batch. */
|
||||
@Service @RequiredArgsConstructor
|
||||
public class ProcurementTransportService {
|
||||
@jakarta.annotation.Resource private com.baiyue.lastleg.service.LastlegDocumentNumberGenerator documentNumbers;
|
||||
|
||||
private final ProcurementLifecycleService lifecycle;
|
||||
private final ProcurementPhysicalService physical;
|
||||
private final ProcurementFinanceService finance;
|
||||
@@ -142,6 +144,7 @@ public class ProcurementTransportService {
|
||||
return snapshot(order);
|
||||
}
|
||||
|
||||
/** 确认采购交寄并记录 PJ 日流水,保留原单关联与重放结果。 */
|
||||
@Transactional
|
||||
public Snapshot ship(String customer,String orderNo,String operator,Ship request) {
|
||||
var order=lifecycle.lock(customer,orderNo);
|
||||
@@ -155,7 +158,7 @@ public class ProcurementTransportService {
|
||||
} else {
|
||||
if(request.shippedAt().isBefore(((LocalDateTime)flow.get("domestic_received_at"))))throw bad("交寄时间不能早于国内收货完成");
|
||||
}
|
||||
long id=IdWorker.getId();String number="PS-"+UUID.randomUUID().toString().replace("-","").substring(0,24).toUpperCase();
|
||||
long id=IdWorker.getId();String number=documentNumbers.next("PJ");
|
||||
jdbc.update("INSERT INTO ll_procurement_shipment(id,order_id,customer_id,shipment_no,leg,manifest_id,shipped_at,address_snapshot,created_by) VALUES(?,?,?,?,?,?,?,CAST(? AS JSON),?)",
|
||||
id,order.getId(),customer,number,request.leg().name(),labels.manifestId(),request.shippedAt(),serialize(new Addresses(labels.sender(),labels.receiver())),operator);
|
||||
for(var carton:cargo) {
|
||||
|
||||
Reference in New Issue
Block a user