778 lines
30 KiB
C#
778 lines
30 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using Lskj.CommandKernel;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using Newtonsoft.Json.Serialization;
|
|
|
|
namespace Lskj.AgentBridge
|
|
{
|
|
public interface IWorkflowProcedureGateway
|
|
{
|
|
BusinessAdapterReadiness GetReadiness(string workflow, string moduleCode);
|
|
DataTable Read(
|
|
string workflow,
|
|
string action,
|
|
string moduleCode,
|
|
CommandExecutionContext context,
|
|
IDictionary<string, object> payload);
|
|
BusinessWriteResult Write(
|
|
string workflow,
|
|
string action,
|
|
string moduleCode,
|
|
CommandExecutionContext context,
|
|
IDictionary<string, object> payload,
|
|
string idempotencyKey,
|
|
string inputFingerprint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Optional gateway extension used by live command rechecks. Keeping it
|
|
/// separate preserves compatibility with offline contract probes and old
|
|
/// customer gateways while allowing production gateways to bind readiness
|
|
/// to the exact command session.
|
|
/// </summary>
|
|
public interface IContextualWorkflowProcedureGateway
|
|
{
|
|
BusinessAdapterReadiness GetReadiness(
|
|
string workflow,
|
|
string moduleCode,
|
|
CommandExecutionContext context);
|
|
}
|
|
|
|
public sealed class ProcedurePurchaseWorkflowAdapter : IPurchaseInvoiceWorkflowAdapter,
|
|
IPurchaseInvoiceIntentResolver,
|
|
IContextualBusinessWorkflowAdapterReadiness
|
|
{
|
|
private const string Workflow = "purchase";
|
|
private readonly IWorkflowProcedureGateway _gateway;
|
|
private readonly IBusinessAdapterReadinessAttestor _readinessAttestor;
|
|
|
|
public ProcedurePurchaseWorkflowAdapter(string moduleCode, IWorkflowProcedureGateway gateway)
|
|
: this(moduleCode, gateway, null)
|
|
{
|
|
}
|
|
|
|
public ProcedurePurchaseWorkflowAdapter(
|
|
string moduleCode,
|
|
IWorkflowProcedureGateway gateway,
|
|
IBusinessAdapterReadinessAttestor readinessAttestor)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(moduleCode))
|
|
throw new ArgumentException("采购模块编号不能为空。", "moduleCode");
|
|
if (gateway == null) throw new ArgumentNullException("gateway");
|
|
ModuleCode = moduleCode.Trim();
|
|
_gateway = gateway;
|
|
_readinessAttestor = readinessAttestor;
|
|
}
|
|
|
|
public string ModuleCode { get; private set; }
|
|
|
|
public BusinessAdapterReadiness GetReadiness()
|
|
{
|
|
return GetReadiness(null);
|
|
}
|
|
|
|
public BusinessAdapterReadiness GetReadiness(CommandExecutionContext context)
|
|
{
|
|
if (context == null)
|
|
return GetReadinessWithoutContext();
|
|
IBusinessAdapterReadinessOverride readinessOverride =
|
|
_readinessAttestor as IBusinessAdapterReadinessOverride;
|
|
if (readinessOverride != null)
|
|
{
|
|
IContextualBusinessAdapterReadinessOverride contextualOverride =
|
|
_readinessAttestor as IContextualBusinessAdapterReadinessOverride;
|
|
if (contextualOverride == null)
|
|
throw ContextualReadinessRequired();
|
|
return contextualOverride.GetReadiness(Workflow, ModuleCode, context);
|
|
}
|
|
IContextualWorkflowProcedureGateway contextualGateway =
|
|
_gateway as IContextualWorkflowProcedureGateway;
|
|
if (contextualGateway == null)
|
|
throw ContextualReadinessRequired();
|
|
BusinessAdapterReadiness readiness = contextualGateway.GetReadiness(
|
|
Workflow,
|
|
ModuleCode,
|
|
context);
|
|
return _readinessAttestor == null
|
|
? readiness
|
|
: _readinessAttestor.Attest(Workflow, ModuleCode, readiness);
|
|
}
|
|
|
|
private BusinessAdapterReadiness GetReadinessWithoutContext()
|
|
{
|
|
IBusinessAdapterReadinessOverride readinessOverride =
|
|
_readinessAttestor as IBusinessAdapterReadinessOverride;
|
|
if (readinessOverride != null)
|
|
return readinessOverride.GetReadiness(Workflow, ModuleCode);
|
|
BusinessAdapterReadiness readiness = _gateway.GetReadiness(Workflow, ModuleCode);
|
|
return _readinessAttestor == null
|
|
? readiness
|
|
: _readinessAttestor.Attest(Workflow, ModuleCode, readiness);
|
|
}
|
|
|
|
private static CommandKernelException ContextualReadinessRequired()
|
|
{
|
|
return new CommandKernelException(
|
|
"adapter_readiness_context_required",
|
|
"运行时业务适配器就绪复核必须绑定显式 ERP 会话。",
|
|
6);
|
|
}
|
|
|
|
public bool InvoiceNumberExists(
|
|
string supplierCode,
|
|
string invoiceNumber,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataRow row = ProcedureRows.Single(_gateway.Read(
|
|
Workflow,
|
|
"invoice_exists",
|
|
ModuleCode,
|
|
RequireContext(context),
|
|
new Dictionary<string, object>
|
|
{
|
|
{ "supplierCode", supplierCode },
|
|
{ "invoiceNumber", invoiceNumber }
|
|
}), "purchase.invoice_exists");
|
|
return ProcedureRows.Boolean(row, "exists");
|
|
}
|
|
|
|
public IList<PurchaseSupplierCandidate> ResolveSuppliers(
|
|
string reference,
|
|
string taxId,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataTable table = ReadCandidates(
|
|
"resolve_supplier",
|
|
context,
|
|
new Dictionary<string, object>
|
|
{
|
|
{ "reference", reference },
|
|
{ "taxId", taxId }
|
|
});
|
|
List<PurchaseSupplierCandidate> result =
|
|
new List<PurchaseSupplierCandidate>();
|
|
HashSet<string> codes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
PurchaseSupplierCandidate candidate = new PurchaseSupplierCandidate
|
|
{
|
|
Code = ProcedureRows.RequiredString(row, "supplier_code"),
|
|
Name = ProcedureRows.RequiredString(row, "supplier_name"),
|
|
TaxId = ProcedureRows.OptionalString(row, "supplier_tax_id")
|
|
};
|
|
if (!codes.Add(candidate.Code))
|
|
throw ProcedureRows.Protocol("purchase.resolve_supplier", "供应商候选编码重复。");
|
|
result.Add(candidate);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public IList<PurchaseCurrencyCandidate> ResolveCurrencies(
|
|
string reference,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataTable table = ReadCandidates(
|
|
"resolve_currency",
|
|
context,
|
|
new Dictionary<string, object> { { "reference", reference } });
|
|
List<PurchaseCurrencyCandidate> result =
|
|
new List<PurchaseCurrencyCandidate>();
|
|
HashSet<string> codes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
PurchaseCurrencyCandidate candidate = new PurchaseCurrencyCandidate
|
|
{
|
|
Code = ProcedureRows.RequiredString(row, "currency_code"),
|
|
Name = ProcedureRows.RequiredString(row, "currency_name")
|
|
};
|
|
if (!codes.Add(candidate.Code))
|
|
throw ProcedureRows.Protocol("purchase.resolve_currency", "币种候选编码重复。");
|
|
result.Add(candidate);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public IList<PurchaseMaterialCandidate> ResolveMaterials(
|
|
PurchaseInvoiceIntentLine line,
|
|
string supplierCode,
|
|
CommandExecutionContext context)
|
|
{
|
|
if (line == null) throw new ArgumentNullException("line");
|
|
DataTable table = ReadCandidates(
|
|
"resolve_material",
|
|
context,
|
|
new Dictionary<string, object>
|
|
{
|
|
{ "lineId", line.LineId },
|
|
{ "reference", line.MaterialReference },
|
|
{ "specification", line.Specification },
|
|
{ "unit", line.Unit },
|
|
{ "supplierCode", supplierCode }
|
|
});
|
|
List<PurchaseMaterialCandidate> result =
|
|
new List<PurchaseMaterialCandidate>();
|
|
HashSet<string> codes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
PurchaseMaterialCandidate candidate = new PurchaseMaterialCandidate
|
|
{
|
|
Code = ProcedureRows.RequiredString(row, "material_code"),
|
|
Name = ProcedureRows.RequiredString(row, "material_name"),
|
|
Specification = ProcedureRows.OptionalString(row, "specification"),
|
|
Unit = ProcedureRows.RequiredString(row, "unit")
|
|
};
|
|
if (!codes.Add(candidate.Code))
|
|
throw ProcedureRows.Protocol("purchase.resolve_material", "物料候选编码重复。");
|
|
result.Add(candidate);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public IList<PurchaseSourceLine> QueryOpenSourceLines(
|
|
PurchaseInvoiceDraft draft,
|
|
CommandExecutionContext context)
|
|
{
|
|
if (draft == null) throw new ArgumentNullException("draft");
|
|
DataTable table = _gateway.Read(
|
|
Workflow,
|
|
"open_sources",
|
|
ModuleCode,
|
|
RequireContext(context),
|
|
WorkflowProcedurePayload.From(draft));
|
|
if (table == null || table.Rows.Count > 10000)
|
|
throw ProcedureRows.Protocol("purchase.open_sources", "来源结果为空或超过 10000 行。");
|
|
|
|
List<PurchaseSourceLine> result = new List<PurchaseSourceLine>();
|
|
HashSet<string> identities = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
PurchaseSourceLine line = new PurchaseSourceLine
|
|
{
|
|
SourceOrderId = ProcedureRows.RequiredString(row, "source_order_id"),
|
|
SourceOrderNumber = ProcedureRows.RequiredString(row, "source_order_number"),
|
|
SourceLineId = ProcedureRows.RequiredString(row, "source_line_id"),
|
|
SupplierCode = ProcedureRows.RequiredString(row, "supplier_code"),
|
|
CurrencyCode = ProcedureRows.RequiredString(row, "currency_code"),
|
|
MaterialCode = ProcedureRows.RequiredString(row, "material_code"),
|
|
Unit = ProcedureRows.RequiredString(row, "unit"),
|
|
RemainingQuantity = ProcedureRows.Decimal(row, "remaining_quantity"),
|
|
UnitPrice = ProcedureRows.Decimal(row, "unit_price"),
|
|
TaxRate = ProcedureRows.Decimal(row, "tax_rate"),
|
|
ExchangeRate = ProcedureRows.Decimal(row, "exchange_rate"),
|
|
Closed = ProcedureRows.Boolean(row, "closed")
|
|
};
|
|
if (line.RemainingQuantity < 0 || line.UnitPrice < 0
|
|
|| line.TaxRate < 0 || line.TaxRate > 1
|
|
|| line.ExchangeRate <= 0)
|
|
throw ProcedureRows.Protocol(
|
|
"purchase.open_sources",
|
|
"来源数量、单价、税率或汇率越界。");
|
|
if (!identities.Add(line.UniqueKey))
|
|
throw ProcedureRows.Protocol("purchase.open_sources", "来源行标识重复。");
|
|
result.Add(line);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public BusinessWriteResult CreatePurchaseDocument(
|
|
PurchaseInvoiceCreateRequest request,
|
|
CommandExecutionContext context,
|
|
string idempotencyKey,
|
|
string inputFingerprint)
|
|
{
|
|
if (request == null || request.Draft == null)
|
|
throw new ArgumentNullException("request");
|
|
return _gateway.Write(
|
|
Workflow,
|
|
"create_document",
|
|
ModuleCode,
|
|
RequireContext(context),
|
|
WorkflowProcedurePayload.From(request),
|
|
idempotencyKey,
|
|
inputFingerprint);
|
|
}
|
|
|
|
private static CommandExecutionContext RequireContext(CommandExecutionContext context)
|
|
{
|
|
if (context == null) throw new ArgumentNullException("context");
|
|
return context;
|
|
}
|
|
|
|
private DataTable ReadCandidates(
|
|
string action,
|
|
CommandExecutionContext context,
|
|
IDictionary<string, object> payload)
|
|
{
|
|
DataTable table = _gateway.Read(
|
|
Workflow,
|
|
action,
|
|
ModuleCode,
|
|
RequireContext(context),
|
|
payload);
|
|
if (table == null || table.Rows.Count > 20)
|
|
throw ProcedureRows.Protocol(
|
|
"purchase." + action,
|
|
"候选结果为空或超过 20 行。");
|
|
return table;
|
|
}
|
|
}
|
|
|
|
public sealed class ProcedureLeaveWorkflowAdapter : ILeaveWorkflowAdapter,
|
|
IContextualLeaveValidationProvider, ILeaveIntentResolver,
|
|
IContextualBusinessWorkflowAdapterReadiness
|
|
{
|
|
private const string Workflow = "leave";
|
|
private readonly IWorkflowProcedureGateway _gateway;
|
|
private readonly IBusinessAdapterReadinessAttestor _readinessAttestor;
|
|
|
|
public ProcedureLeaveWorkflowAdapter(string moduleCode, IWorkflowProcedureGateway gateway)
|
|
: this(moduleCode, gateway, null)
|
|
{
|
|
}
|
|
|
|
public ProcedureLeaveWorkflowAdapter(
|
|
string moduleCode,
|
|
IWorkflowProcedureGateway gateway,
|
|
IBusinessAdapterReadinessAttestor readinessAttestor)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(moduleCode))
|
|
throw new ArgumentException("请假模块编号不能为空。", "moduleCode");
|
|
if (gateway == null) throw new ArgumentNullException("gateway");
|
|
ModuleCode = moduleCode.Trim();
|
|
_gateway = gateway;
|
|
_readinessAttestor = readinessAttestor;
|
|
}
|
|
|
|
public string ModuleCode { get; private set; }
|
|
|
|
public BusinessAdapterReadiness GetReadiness()
|
|
{
|
|
return GetReadiness(null);
|
|
}
|
|
|
|
public BusinessAdapterReadiness GetReadiness(CommandExecutionContext context)
|
|
{
|
|
if (context == null)
|
|
return GetReadinessWithoutContext();
|
|
IBusinessAdapterReadinessOverride readinessOverride =
|
|
_readinessAttestor as IBusinessAdapterReadinessOverride;
|
|
if (readinessOverride != null)
|
|
{
|
|
IContextualBusinessAdapterReadinessOverride contextualOverride =
|
|
_readinessAttestor as IContextualBusinessAdapterReadinessOverride;
|
|
if (contextualOverride == null)
|
|
throw ContextualReadinessRequired();
|
|
return contextualOverride.GetReadiness(Workflow, ModuleCode, context);
|
|
}
|
|
IContextualWorkflowProcedureGateway contextualGateway =
|
|
_gateway as IContextualWorkflowProcedureGateway;
|
|
if (contextualGateway == null)
|
|
throw ContextualReadinessRequired();
|
|
BusinessAdapterReadiness readiness = contextualGateway.GetReadiness(
|
|
Workflow,
|
|
ModuleCode,
|
|
context);
|
|
return _readinessAttestor == null
|
|
? readiness
|
|
: _readinessAttestor.Attest(Workflow, ModuleCode, readiness);
|
|
}
|
|
|
|
private BusinessAdapterReadiness GetReadinessWithoutContext()
|
|
{
|
|
IBusinessAdapterReadinessOverride readinessOverride =
|
|
_readinessAttestor as IBusinessAdapterReadinessOverride;
|
|
if (readinessOverride != null)
|
|
return readinessOverride.GetReadiness(Workflow, ModuleCode);
|
|
BusinessAdapterReadiness readiness = _gateway.GetReadiness(Workflow, ModuleCode);
|
|
return _readinessAttestor == null
|
|
? readiness
|
|
: _readinessAttestor.Attest(Workflow, ModuleCode, readiness);
|
|
}
|
|
|
|
private static CommandKernelException ContextualReadinessRequired()
|
|
{
|
|
return new CommandKernelException(
|
|
"adapter_readiness_context_required",
|
|
"运行时业务适配器就绪复核必须绑定显式 ERP 会话。",
|
|
6);
|
|
}
|
|
|
|
public string GetCurrentEmployeeId(CommandExecutionContext context)
|
|
{
|
|
return ProcedureRows.RequiredString(LeaveContext(context), "current_employee_id");
|
|
}
|
|
|
|
public bool CanApplyForOthers(CommandExecutionContext context)
|
|
{
|
|
return ProcedureRows.Boolean(LeaveContext(context), "can_apply_for_others");
|
|
}
|
|
|
|
public bool IsLeaveTypeEnabled(string leaveTypeCode, CommandExecutionContext context)
|
|
{
|
|
DataRow row = ProcedureRows.Single(Read(
|
|
"type_enabled",
|
|
context,
|
|
new Dictionary<string, object> { { "leaveTypeCode", leaveTypeCode } }),
|
|
"leave.type_enabled");
|
|
return ProcedureRows.Boolean(row, "enabled");
|
|
}
|
|
|
|
public bool IsLeaveFlowTypeEnabled(
|
|
string flowTypeCode,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataRow row = ProcedureRows.Single(Read(
|
|
"flow_type_enabled",
|
|
context,
|
|
new Dictionary<string, object>
|
|
{
|
|
{ "flowTypeCode", flowTypeCode }
|
|
}),
|
|
"leave.flow_type_enabled");
|
|
return ProcedureRows.Boolean(row, "enabled");
|
|
}
|
|
|
|
public IList<LeaveTypeCandidate> ResolveLeaveTypes(
|
|
string query,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataTable table = Read(
|
|
"resolve_type",
|
|
context,
|
|
new Dictionary<string, object> { { "query", query } });
|
|
if (table == null || table.Rows.Count > 10)
|
|
throw ProcedureRows.Protocol(
|
|
"leave.resolve_type",
|
|
"结果表不能为空且候选不能超过 10 行。");
|
|
List<LeaveTypeCandidate> result = new List<LeaveTypeCandidate>();
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
result.Add(new LeaveTypeCandidate
|
|
{
|
|
Code = ProcedureRows.RequiredString(row, "leave_type_code"),
|
|
Name = ProcedureRows.RequiredString(row, "leave_type_name")
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public IList<LeaveFlowTypeCandidate> ResolveLeaveFlowTypes(
|
|
string employeeId,
|
|
decimal calculatedHours,
|
|
string query,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataTable table = Read(
|
|
"resolve_flow_type",
|
|
context,
|
|
new Dictionary<string, object>
|
|
{
|
|
{ "employeeId", employeeId },
|
|
{ "calculatedHours", calculatedHours },
|
|
{ "query", query }
|
|
});
|
|
if (table == null || table.Rows.Count > 20)
|
|
throw ProcedureRows.Protocol(
|
|
"leave.resolve_flow_type",
|
|
"结果表不能为空且候选不能超过 20 行。");
|
|
List<LeaveFlowTypeCandidate> result =
|
|
new List<LeaveFlowTypeCandidate>();
|
|
foreach (DataRow row in table.Rows)
|
|
{
|
|
result.Add(new LeaveFlowTypeCandidate
|
|
{
|
|
Code = ProcedureRows.RequiredString(row, "flow_type_code"),
|
|
Name = ProcedureRows.RequiredString(row, "flow_type_name")
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public LeaveCalendarRange ResolveCalendarRange(
|
|
string employeeId,
|
|
DateTime localDate,
|
|
LeaveDayPart dayPart,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataRow row = ProcedureRows.Single(Read(
|
|
"resolve_calendar_range",
|
|
context,
|
|
new Dictionary<string, object>
|
|
{
|
|
{ "employeeId", employeeId },
|
|
{ "localDate", localDate.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) },
|
|
{ "dayPart", DayPartName(dayPart) }
|
|
}),
|
|
"leave.resolve_calendar_range");
|
|
bool available = ProcedureRows.Boolean(row, "available");
|
|
LeaveCalendarRange result = new LeaveCalendarRange
|
|
{
|
|
Available = available,
|
|
ReasonCode = ProcedureRows.RequiredString(row, "reason_code")
|
|
};
|
|
if (!available) return result;
|
|
result.StartLocal = ProcedureRows.DateTime(row, "start_local");
|
|
result.EndLocal = ProcedureRows.DateTime(row, "end_local");
|
|
result.Hours = ProcedureRows.Decimal(row, "hours");
|
|
result.TimeZoneId = ProcedureRows.RequiredString(row, "time_zone_id");
|
|
return result;
|
|
}
|
|
|
|
public DateTime GetCurrentLocalTime(CommandExecutionContext context)
|
|
{
|
|
return ProcedureRows.DateTime(LeaveContext(context), "now_local");
|
|
}
|
|
|
|
public decimal CalculateHours(
|
|
string employeeId,
|
|
DateTime startLocal,
|
|
DateTime endLocal,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataRow row = ProcedureRows.Single(Read(
|
|
"calculate_hours",
|
|
context,
|
|
TimePayload(employeeId, startLocal, endLocal)),
|
|
"leave.calculate_hours");
|
|
decimal hours = ProcedureRows.Decimal(row, "hours");
|
|
if (hours < 0) throw ProcedureRows.Protocol("leave.calculate_hours", "工时不能小于零。");
|
|
return hours;
|
|
}
|
|
|
|
public bool HasConflict(
|
|
string employeeId,
|
|
DateTime startLocal,
|
|
DateTime endLocal,
|
|
CommandExecutionContext context)
|
|
{
|
|
DataRow row = ProcedureRows.Single(Read(
|
|
"has_conflict",
|
|
context,
|
|
TimePayload(employeeId, startLocal, endLocal)),
|
|
"leave.has_conflict");
|
|
return ProcedureRows.Boolean(row, "has_conflict");
|
|
}
|
|
|
|
decimal IWorkingTimeCalculator.CalculateHours(
|
|
string employeeId,
|
|
DateTime startLocal,
|
|
DateTime endLocal)
|
|
{
|
|
throw new CommandKernelException(
|
|
"execution_context_required",
|
|
"请假工时查询必须携带 ERP 执行上下文。",
|
|
6);
|
|
}
|
|
|
|
bool ILeaveConflictProvider.HasConflict(
|
|
string employeeId,
|
|
DateTime startLocal,
|
|
DateTime endLocal)
|
|
{
|
|
throw new CommandKernelException(
|
|
"execution_context_required",
|
|
"请假冲突查询必须携带 ERP 执行上下文。",
|
|
6);
|
|
}
|
|
|
|
public BusinessWriteResult CreateLeaveDraft(
|
|
LeaveRequestDraft draft,
|
|
CommandExecutionContext context,
|
|
string idempotencyKey,
|
|
string inputFingerprint)
|
|
{
|
|
if (draft == null) throw new ArgumentNullException("draft");
|
|
return Write(
|
|
"create_draft",
|
|
context,
|
|
WorkflowProcedurePayload.From(draft),
|
|
idempotencyKey,
|
|
inputFingerprint);
|
|
}
|
|
|
|
public bool CanSubmitLeave(
|
|
string recordId,
|
|
CommandExecutionContext context,
|
|
out string reason)
|
|
{
|
|
DataRow row = ProcedureRows.Single(Read(
|
|
"can_submit",
|
|
context,
|
|
new Dictionary<string, object> { { "recordId", recordId } }),
|
|
"leave.can_submit");
|
|
reason = ProcedureRows.OptionalString(row, "reason");
|
|
return ProcedureRows.Boolean(row, "can_submit");
|
|
}
|
|
|
|
public BusinessWriteResult SubmitLeave(
|
|
string recordId,
|
|
CommandExecutionContext context,
|
|
string idempotencyKey,
|
|
string inputFingerprint)
|
|
{
|
|
return Write(
|
|
"submit",
|
|
context,
|
|
new Dictionary<string, object> { { "recordId", recordId } },
|
|
idempotencyKey,
|
|
inputFingerprint);
|
|
}
|
|
|
|
private DataRow LeaveContext(CommandExecutionContext context)
|
|
{
|
|
return ProcedureRows.Single(
|
|
Read("context", context, new Dictionary<string, object>()),
|
|
"leave.context");
|
|
}
|
|
|
|
private DataTable Read(
|
|
string action,
|
|
CommandExecutionContext context,
|
|
IDictionary<string, object> payload)
|
|
{
|
|
if (context == null) throw new ArgumentNullException("context");
|
|
return _gateway.Read(Workflow, action, ModuleCode, context, payload);
|
|
}
|
|
|
|
private BusinessWriteResult Write(
|
|
string action,
|
|
CommandExecutionContext context,
|
|
IDictionary<string, object> payload,
|
|
string idempotencyKey,
|
|
string inputFingerprint)
|
|
{
|
|
if (context == null) throw new ArgumentNullException("context");
|
|
return _gateway.Write(
|
|
Workflow,
|
|
action,
|
|
ModuleCode,
|
|
context,
|
|
payload,
|
|
idempotencyKey,
|
|
inputFingerprint);
|
|
}
|
|
|
|
private static IDictionary<string, object> TimePayload(
|
|
string employeeId,
|
|
DateTime startLocal,
|
|
DateTime endLocal)
|
|
{
|
|
return new Dictionary<string, object>
|
|
{
|
|
{ "employeeId", employeeId },
|
|
{ "startLocal", startLocal },
|
|
{ "endLocal", endLocal }
|
|
};
|
|
}
|
|
|
|
private static string DayPartName(LeaveDayPart dayPart)
|
|
{
|
|
switch (dayPart)
|
|
{
|
|
case LeaveDayPart.Morning:
|
|
return "morning";
|
|
case LeaveDayPart.Afternoon:
|
|
return "afternoon";
|
|
case LeaveDayPart.FullDay:
|
|
return "full_day";
|
|
default:
|
|
throw ProcedureRows.Protocol(
|
|
"leave.resolve_calendar_range",
|
|
"dayPart 无效。");
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static class WorkflowProcedurePayload
|
|
{
|
|
private static readonly JsonSerializer Serializer = JsonSerializer.Create(
|
|
new JsonSerializerSettings
|
|
{
|
|
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
|
NullValueHandling = NullValueHandling.Ignore,
|
|
TypeNameHandling = TypeNameHandling.None,
|
|
MaxDepth = 64
|
|
});
|
|
|
|
public static IDictionary<string, object> From(object value)
|
|
{
|
|
if (value == null) throw new ArgumentNullException("value");
|
|
lock (Serializer)
|
|
{
|
|
return JObject.FromObject(value, Serializer)
|
|
.ToObject<Dictionary<string, object>>();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static class ProcedureRows
|
|
{
|
|
public static DataRow Single(DataTable table, string operation)
|
|
{
|
|
if (table == null || table.Rows.Count != 1)
|
|
throw Protocol(operation, "结果必须恰好包含一行。");
|
|
return table.Rows[0];
|
|
}
|
|
|
|
public static string RequiredString(DataRow row, string name)
|
|
{
|
|
string value = OptionalString(row, name);
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
throw Protocol(name, "缺少必填字符串字段。");
|
|
return value.Trim();
|
|
}
|
|
|
|
public static string OptionalString(DataRow row, string name)
|
|
{
|
|
object value = Value(row, name);
|
|
return value == null || value == DBNull.Value ? string.Empty : Convert.ToString(value);
|
|
}
|
|
|
|
public static decimal Decimal(DataRow row, string name)
|
|
{
|
|
try { return Convert.ToDecimal(Value(row, name), CultureInfo.InvariantCulture); }
|
|
catch { throw Protocol(name, "字段不是有效 decimal。"); }
|
|
}
|
|
|
|
public static bool Boolean(DataRow row, string name)
|
|
{
|
|
object value = Value(row, name);
|
|
if (value is bool) return (bool)value;
|
|
string text = Convert.ToString(value, CultureInfo.InvariantCulture);
|
|
if (text == "1" || text.Equals("true", StringComparison.OrdinalIgnoreCase)) return true;
|
|
if (text == "0" || text.Equals("false", StringComparison.OrdinalIgnoreCase)) return false;
|
|
throw Protocol(name, "字段不是有效 bool。");
|
|
}
|
|
|
|
public static DateTime DateTime(DataRow row, string name)
|
|
{
|
|
try { return Convert.ToDateTime(Value(row, name), CultureInfo.InvariantCulture); }
|
|
catch { throw Protocol(name, "字段不是有效 DateTime。"); }
|
|
}
|
|
|
|
public static CommandKernelException Protocol(string operation, string message)
|
|
{
|
|
return new CommandKernelException(
|
|
"adapter_protocol_error",
|
|
"业务过程契约无效(" + operation + "):" + message,
|
|
6);
|
|
}
|
|
|
|
private static object Value(DataRow row, string name)
|
|
{
|
|
if (row == null || row.Table == null)
|
|
throw Protocol(name, "结果行为空。");
|
|
DataColumn column = row.Table.Columns.Cast<DataColumn>().FirstOrDefault(item =>
|
|
item.ColumnName.Equals(name, StringComparison.OrdinalIgnoreCase));
|
|
if (column == null || row[column] == DBNull.Value)
|
|
throw Protocol(name, "结果缺少字段。");
|
|
return row[column];
|
|
}
|
|
}
|
|
}
|