如何自定义销售订单流程以在成功完成销售订单时触发自动 "adding contract" 流程

How to customize the sales order process to trigger an automatic "adding contract" process when sales order is successfully completed

我们希望将我们的网络订阅服务纳入 Acumatica,这意味着我们将服务作为具有开始日期和到期日期的订阅产品进行销售,我们希望能够通过添加销售订单进行销售,然后adding/changing 与该产品关联的额外 "contract" 来处理订阅 expiration/renewal 问题。

我们的想法是以某种方式自定义销售订单流程 运行 每次完成销售订单时自动进行某种检查 - 如果订阅产品在该订单中,我们希望流程是根据订单信息自动触发 add/update 合约。

是否可以通过定制来完成?

只想提一下,我一直在使用 Web 服务 API 将我们的电子商务与 Acumatica 集成,我知道我可以通过轮询订单 table 然后使用网络来实现这一点服务 API 添加合同,但是,在我看来,如果可行的话,最好通过某种定制在 Acumatica 内部执行此操作。

有谁知道这种定制是否可以完成以及如果可以如何做?

谢谢。

已编辑:

看了@Gabriel 和@Hybridzz 的回复,我尝试了一段代码如下:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Avalara.AvaTax.Adapter;
using Avalara.AvaTax.Adapter.TaxService;
using PX.CCProcessingBase;
using PX.Common;
using PX.Data;
using PX.Objects.AP;
using PX.Objects.AR;
using PX.Objects.CA;
using PX.Objects.CM;
using PX.Objects.CR;
using PX.Objects.CS;
using PX.Objects.EP;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.PO;
using PX.Objects.TX;
using AvaMessage = Avalara.AvaTax.Adapter.Message;
using POLine = PX.Objects.PO.POLine;
using POOrder = PX.Objects.PO.POOrder;
using PX.Objects;
using PX.Objects.SO;
using PX.Objects.CT;

namespace PX.Objects.SO
{

  public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
  {
    public delegate void PersistDelegate();
    [PXOverride]
    public void Persist(PersistDelegate baseMethod)
    {
        using (PXTransactionScope ts = new PXTransactionScope())
        {

           // Create, setup and activate contracts
           ContractMaint contractMaint = PXGraph.CreateInstance<ContractMaint>();
           CTBillEngine engine = PXGraph.CreateInstance<CTBillEngine>();
           //var tranExt = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
           string contractCD = "1234567";
           DateTime startDate = new DateTime(2015,1,1);
           Contract contract = SetupActivateContract(contractMaint, contractCD, startDate , 13128,14330, engine);
        }
        baseMethod();
    }

private Contract SetupActivateContract(ContractMaint contractMaint, string contractCD, DateTime? invoiceDate, int? customerID, 
    int? customerLocationID, CTBillEngine engine)
{
    contractMaint.Clear();

    // Initialize new contract
    Contract contract = (Contract)contractMaint.Contracts.Cache.CreateInstance();
    contract.ContractCD = contractCD;
    contract = contractMaint.Contracts.Insert(contract);

    // Lookup contract template ID
    Contract template = PXSelect<Contract,
                            Where<Contract.isTemplate, Equal<boolTrue>, And<Contract.contractCD, Equal<Required<Contract.contractCD>>>>>
                        .Select(Base, "MMS");
    if (template == null) throw new PXException("The MMS contract template was not found.");

    // Set required fields
    contract.TemplateID = template.ContractID;
    contract.CustomerID = customerID;
    contract = contractMaint.Contracts.Update(contract);
    contract.LocationID = customerLocationID;
    contract.StartDate = invoiceDate;
    contract.ActivationDate = invoiceDate;
    ContractMaint.SetExpireDate(contract);
    contract = contractMaint.Contracts.Update(contract);

    // Save generated contract
    contractMaint.Save.Press();
    // Setup and activate the contract
    engine.SetupAndActivate(contract.ContractID, contract.ActivationDate);

     return contract;
   }
 }
}

代码已通过验证和发布,没有任何问题,但是,当我尝试添加销售订单时,我没有看到任何合同按预期添加到数据库中。我确实添加了一些 "throw exception" 语句来确保在销售订单过程中确实调用了这段代码,但我只是不明白为什么没有添加合同。

请注意这是我第一次尝试进行定制,虽然我在网络服务方面有一些经验API,但可能有一些我不知道的基本知识。

如有任何帮助,我们将不胜感激。

您可以覆盖销售订单图的 Persist SOOrderEntry

[PXOverride]
public void Persist(Action persit)
{
    using (PXTransactionScope ts = new PXTransactionScope())
    {
       persit(); // this will call base graph Persist();
       //If no error the document save is completed, but still wrapped in a transaction and you can do your logic below this
    }
}

本主题包含在(尚未发布的)定制培训中。培训以一家名为 "YogiFon" 的虚构移动 phone 公司为中心。下达发票时,系统将检查发票是否包含库存代码为 "SIMCARD" 的项目,并在下达过程中自动设置合同。作为此自定义的一部分,两个自定义字段已添加到发票行,让用户输入 phone 号码和 SIM 卡 ID。这些字段与合同属性一起存储。

需要两个图扩展,一个用于 ARReleaseProcess 图,另一个用于 SOInvoiceEntry 图。原始示例是我写的,感谢 Ruslan Devyatko 的审阅。

A​​RReleaseProcess 扩展:

public class ARReleaseProcess_Extension : PXGraphExtension<ARReleaseProcess>
{
    public bool SetupContract = false;

    public delegate void PersistDelegate();
    [PXOverride]
    public void Persist(PersistDelegate baseMethod)
    {
        // use ARDocument.Current
        ARRegister invoice = (ARRegister)Base.Caches[typeof(ARRegister)].Current;
        List<Contract> setupContracts = new List<Contract>();

        if (SetupContract)
        {
            // Create, setup and activate contracts
            ContractMaint contractMaint = PXGraph.CreateInstance<ContractMaint>();
            CTBillEngine engine = PXGraph.CreateInstance<CTBillEngine>();

            int seq = 1;

            //reuse ARTran_TranType_RefNbr from ARReleaseProcess
            foreach (ARTran tran in
                PXSelect<ARTran,
                    Where<ARTran.tranType, Equal<Required<ARInvoice.docType>>,
                        And<ARTran.refNbr, Equal<Required<ARInvoice.refNbr>>,
                        And<ARTranExt.usrSIMCardID, IsNotNull,
                        And<ARTranExt.usrContractID, IsNull>>>>,
                    OrderBy<Asc<ARTran.tranType, Asc<ARTran.refNbr, Asc<ARTran.lineNbr>>>>>.
                Select(Base, invoice.DocType, invoice.RefNbr))
            {
                // Create, setup and activate contract for a particular SOInvoice line
                var tranExt = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
                string contractCD = String.Format("{0}{1:00}", invoice.RefNbr, seq);
                Contract contract = SetupActivateContract(contractMaint, contractCD, invoice.DocDate, invoice.CustomerID, 
                    invoice.CustomerLocationID, tranExt.UsrSIMCardID, tranExt.UsrPhoneNumber, engine);
                setupContracts.Add(contract);

                // Associate generated contract with the SOInvoice line
                tranExt.UsrContractID = contract.ContractID;
                Base.ARTran_TranType_RefNbr.Cache.Update(tran);

                seq++;
            }
        }

        baseMethod();
    }

    private Contract SetupActivateContract(ContractMaint contractMaint, string contractCD, DateTime? invoiceDate, int? customerID, 
        int? customerLocationID, string simCardID, string phoneNumber, CTBillEngine engine)
    {
        contractMaint.Clear();

        // Initialize new contract
        Contract contract = (Contract)contractMaint.Contracts.Cache.CreateInstance();
        contract.ContractCD = contractCD;
        contract = contractMaint.Contracts.Insert(contract);

        // Lookup contract template ID
        Contract template = PXSelect<Contract,
                                Where<Contract.isTemplate, Equal<boolTrue>, And<Contract.contractCD, Equal<Required<Contract.contractCD>>>>>
                            .Select(Base, "SIMCARD");
        if (template == null) throw new PXException("The SIMCARD contract template was not found.");

        // Set required fields
        contract.TemplateID = template.ContractID;
        contract.CustomerID = customerID;
        contract = contractMaint.Contracts.Update(contract);
        contract.LocationID = customerLocationID;
        contract.StartDate = invoiceDate;
        contract.ActivationDate = invoiceDate;
        ContractMaint.SetExpireDate(contract);
        contract = contractMaint.Contracts.Update(contract);

        // Store SIM/Phone Number into attributes
        foreach (CSAnswers attribute in contractMaint.Answers.Select())
        {
            switch (attribute.AttributeID)
            {
                case "SIMCARDID":
                    attribute.Value = simCardID;
                    contractMaint.Answers.Update(attribute);
                    break;
                case "PHONENUM":
                    attribute.Value = phoneNumber;
                    contractMaint.Answers.Update(attribute);
                    break;
            }
        }
        // Save generated contract
        contractMaint.Save.Press();
        // Setup and activate the contract
        engine.SetupAndActivate(contract.ContractID, contract.ActivationDate);

        return contract;
    }
}

SOInvoiceEntry 扩展名:

public class SOInvoiceEntry_Extension : PXGraphExtension<SOInvoiceEntry>
{
    #region Event Handlers
    protected void ARTran_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
    {
        if (InvokeBaseHandler != null)
            InvokeBaseHandler(cache, e);
        var row = (ARTran)e.Row;

        if (row == null) return;

        // The SIM Card ID and the Phone Number fields are only editable when the SIMCARD item is used
        // In real life you would have a flag in InventoryItem to indicate that, rather than hardcoding based on InventoryCD
        InventoryItem item = (InventoryItem)PXSelectorAttribute.Select<ARTran.inventoryID>(Base.Transactions.Cache, row);
        bool enableFields = item != null && item.InventoryCD.StartsWith("SIMCARD");
        PXUIFieldAttribute.SetEnabled<ARTranExt.usrSIMCardID>(cache, row, enableFields);
        PXUIFieldAttribute.SetEnabled<ARTranExt.usrPhoneNumber>(cache, row, enableFields);
    }
    #endregion

    public PXAction<ARInvoice> release;
    [PXUIField(DisplayName = "Release", Visible = false)]
    [PXButton()]
    public IEnumerable Release(PXAdapter adapter)
    {
        PXGraph.InstanceCreated.AddHandler<ARReleaseProcess>((graph) =>
        {
            // Create, setup and activate contracts while releasing SOInvoice
            graph.GetExtension<ARReleaseProcess_Extension>().SetupContract = true;
        });
        return Base.release.Press(adapter);
    }
}