diff --git a/src/ApiConnectors/ProjectApiConnector.php b/src/ApiConnectors/ProjectApiConnector.php new file mode 100644 index 00000000..31d9dd68 --- /dev/null +++ b/src/ApiConnectors/ProjectApiConnector.php @@ -0,0 +1,65 @@ + + */ +class ProjectApiConnector extends BaseApiConnector +{ + /** + * @param Project $project + * @return Project + * @throws Exception + */ + public function send(Project $project): Project + { + foreach ($this->sendAll([$project]) as $each) { + return $each->unwrap(); + } + } + + /** + * + * @param Project[] $projects + * @return MappedResponseCollection + * @throws Exception + */ + public function sendAll(array $projects): MappedResponseCollection + { + Assert::allIsInstanceOf($projects, Project::class); + + /** @var Response[] $responses */ + $responses = []; + + foreach ($this->getProcessXmlService()->chunk($projects) as $chunk) { + $projectDocument = new ProjectDocument; + + foreach ($chunk as $project) { + $projectDocument->addProject($project); + } + + $responses[] = $this->sendXmlDocument($projectDocument); + + } + + return $this->getProcessXmlService()->mapAll( + $responses, + "dimension", + function (Response $subresponse): Project { + return ProjectMapper::map($subresponse); + } + ); + } +} diff --git a/src/DomDocuments/ProjectDocument.php b/src/DomDocuments/ProjectDocument.php new file mode 100644 index 00000000..2e5cefd1 --- /dev/null +++ b/src/DomDocuments/ProjectDocument.php @@ -0,0 +1,130 @@ +getOffice(); + + // Transaction->status + $this->rootElement->setAttribute('status', $project->getStatus()); + + // Transaction > Office + if(!is_null($office)) { + $officeElement = $this->createNodeWithTextContent('office', $office->getCode()); + $this->rootElement->appendChild($officeElement); + } + + // Transaction > Type + $typeElement = $this->createNodeWithTextContent('type', 'PRJ'); + $this->rootElement->appendChild($typeElement); + + // Transaction > Code + if(!is_null($project->getCode())) { + $codeElement = $this->createNodeWithTextContent('code', $project->getCode()); + $this->rootElement->appendChild($codeElement); + } + + // Transaction > Name + $nameElement = $this->createNodeWithTextContent('name', $project->getName()); + $this->rootElement->appendChild($nameElement); + + // Transaction > Short name + $shortNameElement = $this->createNodeWithTextContent('shortname', $project->getShortName()); + $this->rootElement->appendChild($shortNameElement); + + // Transaction > Projects + $projectsElement = $this->createElement('projects'); + + // Transaction > Projects > Invoice description + if(!is_null($project->getInvoiceDescription())) { + $invoiceDescriptionElement = $this->createNodeWithTextContent('invoicedescription', + $project->getInvoiceDescription()); + $projectsElement->appendChild($invoiceDescriptionElement); + } + + // Transaction > Projects > authoriser + // Todo: set the attributes on the authoriser element + $authoriserElement = $this->createNodeWithTextContent('authoriser', $project->getAuthoriser()); + $projectsElement->appendChild($authoriserElement); + + // Transaction > Projects > customer + // Todo: set the attributes on the customer element + $customerElement = $this->createNodeWithTextContent('customer', $project->getCustomer()); + $projectsElement->appendChild($customerElement); + + // Transaction > Projects > billable + // Todo: set the attributes on the billable element + $billableElement = $this->createNodeWithTextContent('billable', $project->getBillable()); + $projectsElement->appendChild($billableElement); + + // Transaction > Projects > rate + // Todo: set the attributes on the rate element + if(!is_null($project->getRate())) { + $rateElement = $this->createNodeWithTextContent('rate', $project->getRate()); + $projectsElement->appendChild($rateElement); + } + + // Transaction > Projects > Quantities + if(!is_null($project->getQuantities())) { + $projectsElement->appendChild($this->createQuantityElement($project)); + } + } + + /** + * @param Project $project + * @return \DOMElement + */ + private function createQuantityElement(Project $project): \DOMElement + { + $quantityTags = [ + 'label', + 'rate', + 'billable', + 'mandatory', + ]; + + $quantitiesElement = $this->createElement('quantities'); + + foreach ($project->getQuantities() as $quantity) { + // Create quantity tags + foreach ($quantityTags as $tag) { + // Get value + $getter = 'get' . ucfirst($tag); + $value = $quantity->{$getter}(); + if(is_bool($value)) { + $value = ($value) ? 'true' : 'false'; + } + + $tagElement = $this->createNodeWithTextContent($tag, $value); + $quantitiesElement->appendChild($tagElement); + } + } + + return $quantitiesElement; + } + +} diff --git a/src/Enums/ProjectStatus.php b/src/Enums/ProjectStatus.php new file mode 100644 index 00000000..a9d41c95 --- /dev/null +++ b/src/Enums/ProjectStatus.php @@ -0,0 +1,17 @@ +behaviour; + } + + /** + * READONLY attribute + * Set the behaviour attribute + * @param mixed $behaviour + */ + public function setBehaviour($behaviour): void + { + $this->behaviour = $behaviour; + } +} diff --git a/src/Fields/InUseField.php b/src/Fields/InUseField.php new file mode 100644 index 00000000..e915656b --- /dev/null +++ b/src/Fields/InUseField.php @@ -0,0 +1,28 @@ +inuse; + } + + /** + * READONLY attribute + * Set the inuse attribute + * @param mixed $inuse + */ + public function setInuse($inuse): void + { + $this->inuse = $inuse; + } +} diff --git a/src/Fields/TouchedField.php b/src/Fields/TouchedField.php new file mode 100644 index 00000000..d5c383ba --- /dev/null +++ b/src/Fields/TouchedField.php @@ -0,0 +1,28 @@ +touched; + } + + /** + * READONLY attribute + * Set the touched attribute + * @param int $touched + */ + public function setTouched(int $touched): void + { + $this->touched = $touched; + } +} diff --git a/src/Fields/UIDField.php b/src/Fields/UIDField.php new file mode 100644 index 00000000..42fc930a --- /dev/null +++ b/src/Fields/UIDField.php @@ -0,0 +1,28 @@ +UID; + } + + /** + * READONLY attribute + * Set the UID attribute + * @param mixed $UID + */ + public function setUID($UID): void + { + $this->UID = $UID; + } +} diff --git a/src/Mappers/BaseMapper.php b/src/Mappers/BaseMapper.php index d98027bb..3512adc2 100644 --- a/src/Mappers/BaseMapper.php +++ b/src/Mappers/BaseMapper.php @@ -3,6 +3,8 @@ namespace PhpTwinfield\Mappers; use Money\Currency; +use PhpTwinfield\BaseObject; +use PhpTwinfield\Message\Message; use PhpTwinfield\Office; use PhpTwinfield\Util; use Webmozart\Assert\Assert; @@ -10,6 +12,9 @@ abstract class BaseMapper { /** + * @param \DOMDocument $document + * @param string $tag + * @param callable $setter * @throws \PhpTwinfield\Exception */ protected static function setFromTagValue(\DOMDocument $document, string $tag, callable $setter): void @@ -41,6 +46,11 @@ protected static function setFromTagValue(\DOMDocument $document, string $tag, c \call_user_func($setter, $value); } + /** + * @param \DOMDocument $document + * @param string $tag + * @return null|string + */ protected static function getValueFromTag(\DOMDocument $document, string $tag): ?string { /** @var \DOMNodeList $nodelist */ @@ -62,6 +72,11 @@ protected static function getValueFromTag(\DOMDocument $document, string $tag): return $element->textContent; } + /** + * @param \DOMElement $element + * @param string $fieldTagName + * @return null|string + */ protected static function getField(\DOMElement $element, string $fieldTagName): ?string { $fieldElement = $element->getElementsByTagName($fieldTagName)->item(0); @@ -75,4 +90,47 @@ protected static function getField(\DOMElement $element, string $fieldTagName): return $fieldElement->textContent; } -} \ No newline at end of file + + /** + * @param \DOMElement $element + * @param string $tag + * @param callable $setter + * @throws \PhpTwinfield\Exception + */ + protected static function setValueFromElementTag(\DOMElement $element, string $tag, callable $setter) + { + $value = self::getField($element, $tag); + + if ($value === null) { + return; + } + + if ($tag === "validfrom") { + \call_user_func($setter, Util::parseDate($value)); + return; + } + + if ($tag === "validto") { + \call_user_func($setter, Util::parseDate($value)); + return; + } + + \call_user_func($setter, $value); + } + + /** + * @param BaseObject $object + * @param \DOMElement $element + */ + protected static function checkForMessage(BaseObject $object, \DOMElement $element): void + { + if ($element->hasAttribute('msg')) { + $message = new Message; + $message->setType($element->getAttribute('msgtype')); + $message->setMessage($element->getAttribute('msg')); + $message->setField($element->nodeName); + + $object->addMessage($message); + } + } +} diff --git a/src/Mappers/ProjectMapper.php b/src/Mappers/ProjectMapper.php new file mode 100644 index 00000000..efa0a4a4 --- /dev/null +++ b/src/Mappers/ProjectMapper.php @@ -0,0 +1,99 @@ +getResponseDocument(); + + // TODO: If name and shortname of the type attribute are needed, implement those here + $projectTags = [ + 'office' => 'setOffice', + 'type' => 'setType', + 'code' => 'setCode', + 'name' => 'setName', + 'shortname' => 'setShortName', + 'uid' => 'setUID', + 'inuse' => 'setInUse', + 'behaviour' => 'setBehaviour', + 'touched' => 'setTouched', + 'beginperiod' => 'setBeginPeriod', + 'beginyear' => 'setBeginYear', + 'endperiod' => 'setEndPeriod', + 'endyear' => 'setEndYear', + ]; + + $project = new Project; + + foreach ($projectTags as $tag => $method) { + self::setFromTagValue($responseDOM, $tag, [$project, $method]); + } + + // Set the status attribute + $element = $responseDOM->documentElement; + + if (!empty($element->getAttribute("status"))) { + $type = strtoupper($element->getAttribute("status")); + $project->setStatus(ProjectStatus::{$type}()); + } + + // The tags in the projects node + $projectsDOMTag = $responseDOM->getElementsByTagName('projects'); + if (isset($projectsDOMTag) && $projectsDOMTag->length > 0) { + $projectsDOM = $projectsDOMTag->item(0); + + // TODO: test whether to use 'validtill' or 'validto' since the documentation says both + $supplementalTags = [ + 'invoicedescription' => 'setInvoiceDescription', + 'authoriser' => 'setAuthoriser', + 'customer' => 'setCustomer', + 'billable' => 'setBillable', + 'rate' => 'setRate', + 'validfrom' => 'setValidFrom', + 'validtill' => 'setValidTo', + ]; + + foreach ($supplementalTags as $tag => $method) { + // TODO: implement the attributes on the fields here + self::setValueFromElementTag($projectsDOM, $tag, [$project, $method]); + } + + // Parse the quantity lines + foreach ($projectsDOM->getElementsByTagName('quantities') as $domElement) { + self::checkForMessage($project, $domElement); + + $quantityTags = [ + 'label' => 'setLabel', + 'rate' => 'setRate', + 'billable' => 'setBillable', + 'mandatory' => 'setMandatory', + ]; + + $quantity = new ProjectQuantity; + + foreach ($quantityTags as $tag => $method) { + self::setValueFromElementTag($domElement, $tag, [$quantity, $method]); + } + + $project->addQuantity($quantity); + } + } + + // TODO: implement the financials tag. + + return $project; + } +} diff --git a/src/Project.php b/src/Project.php new file mode 100644 index 00000000..e0984281 --- /dev/null +++ b/src/Project.php @@ -0,0 +1,425 @@ +beginPeriod; + } + + /** + * @param mixed $beginPeriod + * @return Project + */ + public function setBeginPeriod($beginPeriod) + { + $this->beginPeriod = $beginPeriod; + return $this; + } + + /** + * Get the beginYear attribute + * @return mixed + */ + public function getBeginYear() + { + return $this->beginYear; + } + + /** + * @param mixed $beginYear + * @return Project + */ + public function setBeginYear($beginYear) + { + $this->beginYear = $beginYear; + return $this; + } + + /** + * Get the endPeriod attribute + * @return mixed + */ + public function getEndPeriod() + { + return $this->endPeriod; + } + + /** + * @param mixed $endPeriod + * @return Project + */ + public function setEndPeriod($endPeriod) + { + $this->endPeriod = $endPeriod; + return $this; + } + + /** + * Get the endYear attribute + * @return mixed + */ + public function getEndYear() + { + return $this->endYear; + } + + /** + * @param mixed $endYear + * @return Project + */ + public function setEndYear($endYear) + { + $this->endYear = $endYear; + return $this; + } + + /** + * Get the code attribute + * @return mixed + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + * @return Project + */ + public function setCode(string $code) + { + $this->code = $code; + return $this; + } + + /** + * Get the shortName attribute + * @return mixed + */ + public function getShortName() + { + return $this->shortName; + } + + /** + * Short name of the project + * Should be 20 chars or less + * @param string $shortName + * @return Project + */ + public function setShortName(string $shortName) + { + Assert::maxLength($shortName, 20); + + $this->shortName = $shortName; + + return $this; + } + + /** + * Get the name attribute + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * Name of the project + * Should be 80 chars or less + * @param string $name + * @return Project + */ + public function setName(string $name) + { + Assert::maxLength($name, 80); + + $this->name = $name; + + return $this; + } + + /** + * @return \DateTimeInterface|null + */ + public function getValidFrom(): ?\DateTimeInterface + { + return $this->validFrom; + } + + /** + * @param \DateTimeInterface $date + * @return $this + */ + public function setValidFrom(\DateTimeInterface $date) + { + $this->validFrom = $date; + return $this; + } + + /** + * @param string $dateString + * @return $this + * @throws Exception + */ + public function setValidFromString(?string $dateString) + { + if(is_null($dateString)) { + $this->validTo = null; + return $this; + } + + $this->setValidFrom(Util::parseDate($dateString)); + return $this; + } + + /** + * @return \DateTimeInterface|null + */ + public function getValidTo(): ?\DateTimeInterface + { + return $this->validTo; + } + + /** + * @param \DateTimeInterface $date + * @return $this + */ + public function setValidTo(\DateTimeInterface $date) + { + $this->validTo = $date; + return $this; + } + + /** + * @param string $dateString + * @return $this + * @throws Exception + */ + public function setValidToString(?string $dateString) + { + if(is_null($dateString)) { + $this->validTo = null; + return $this; + } + + $this->setValidTo(Util::parseDate($dateString)); + return $this; + } + + /** + * Get the invoiceDescription attribute + * @return mixed + */ + public function getInvoiceDescription() + { + return $this->invoiceDescription; + } + + /** + * This field can be used to enter a longer project description which will be available on the invoice template. + * @param string $invoiceDescription + * @return Project + */ + public function setInvoiceDescription(string $invoiceDescription) + { + $this->invoiceDescription = $invoiceDescription; + return $this; + } + + /** + * Get the authoriser attribute + * @return mixed + */ + public function getAuthoriser() + { + return $this->authoriser; + } + + /** + * A specific authoriser for a project. + * @param mixed $authoriser + * @return Project + */ + public function setAuthoriser($authoriser) + { + $this->authoriser = $authoriser; + return $this; + } + + /** + * Get the customer attribute + * @return mixed + */ + public function getCustomer() + { + return $this->customer; + } + + /** + * A project always needs to be linked to a customer. + * Choose to have the customer ‘inherited’ (from an activity) or you can specify the customer here. + * @param mixed $customer + * @return Project + */ + public function setCustomer($customer) + { + $this->customer = $customer; + return $this; + } + + /** + * Get the billable attribute + * @return mixed + */ + public function getBillable() + { + return $this->billable; + } + + /** + * @param bool $billable + * @return Project + */ + public function setBillable(bool $billable) + { + $this->billable = $billable; + return $this; + } + + /** + * Get the rate attribute + * @return mixed + */ + public function getRate() + { + return $this->rate; + } + + /** + * @param mixed $rate + * @return Project + */ + public function setRate($rate) + { + $this->rate = $rate; + return $this; + } + + /** + * Get the quantities attribute + * @return array + */ + public function getQuantities() + { + return $this->quantities; + } + + /** + * @param array $quantities + * @return Project + */ + public function setQuantities(array $quantities) + { + Assert::allIsInstanceOf($quantities, ProjectQuantity::class); + + $this->quantities = $quantities; + + return $this; + } + + /** + * @param ProjectQuantity $quantity + * @return Project + */ + public function addQuantity(ProjectQuantity $quantity) + { + $this->quantities[] = $quantity; + + return $this; + } + + /** + * Get the status attribute + * @return mixed + */ + public function getStatus() + { + return $this->status; + } + + /** + * Set the status attribute + * @param ProjectStatus $status + * @return Project + */ + public function setStatus(ProjectStatus $status) + { + $this->status = $status; + + return $this; + } + + /** + * Get the type attribute + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * Set the type attribute + * @param string $type + * @return Project + */ + public function setType(string $type) + { + $this->type = $type; + return $this; + } +} diff --git a/src/ProjectQuantity.php b/src/ProjectQuantity.php new file mode 100644 index 00000000..bd380d1b --- /dev/null +++ b/src/ProjectQuantity.php @@ -0,0 +1,90 @@ +label; + } + + /** + * @param mixed $label + * @return ProjectQuantity + */ + public function setLabel($label) + { + $this->label = $label; + return $this; + } + + /** + * Get the rate attribute + * @return mixed + */ + public function getRate() + { + return $this->rate; + } + + /** + * @param mixed $rate + * @return ProjectQuantity + */ + public function setRate($rate) + { + $this->rate = $rate; + return $this; + } + + /** + * Get the billable attribute + * @return mixed + */ + public function getBillable() + { + return $this->billable; + } + + /** + * @param bool $billable + * @return ProjectQuantity + */ + public function setBillable(bool $billable) + { + $this->billable = $billable; + return $this; + } + + /** + * Get the mandatory attribute + * @return mixed + */ + public function getMandatory() + { + return $this->mandatory; + } + + /** + * @param bool $mandatory + * @return ProjectQuantity + */ + public function setMandatory(bool $mandatory) + { + $this->mandatory = $mandatory; + return $this; + } +}