From 371dca508e07bce332a23a762a8393a66ed97351 Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Fri, 29 Apr 2022 09:51:11 -0700 Subject: [PATCH 01/17] Ended at no longer changes on status change --- assets/style.css | 10 +++++++--- pages/project.php | 6 +++--- services/StoryService.php | 4 +++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/assets/style.css b/assets/style.css index c5b839d..b1adea6 100644 --- a/assets/style.css +++ b/assets/style.css @@ -687,9 +687,13 @@ h4.sectionHeader { } */ .projectId { - /* border-radius: 12px; */ - /* padding: 3px 12px; */ - /* display: inline-block; */ + font-weight: bold; + border-radius: 12px; + text-align: center; + color: #111; + display: inline-block; + padding: 2px 9px; + line-height: 30px !important; font-weight: bold; line-height: 30px; } diff --git a/pages/project.php b/pages/project.php index 01347db..512833b 100644 --- a/pages/project.php +++ b/pages/project.php @@ -204,7 +204,7 @@ foreach ($otherStories as $row) { $totalTasks++; - $endedAt = ($row['status'] == 2 || $row['status'] == 4) + $endedAt = ($row['is_complete_state'] || $row['is_billable_state']) ? formatDate($row['ended_at'], 'Y-m-d') : null; @@ -214,7 +214,7 @@ $class = ''; $class .= (!isBool($row['is_billable_state'])) ? ' notBillable' : ''; - $class .= ($row['status'] == 3) ? ' handOff' : ''; + // $class .= ($row['status'] == 3) ? ' handOff' : ''; $renderedOtherStories .= template( 'admin/snippets/collection_table_other_entry', @@ -302,5 +302,5 @@ */ function getLabel(array $row): string { - return "
" . $row['show_id'] . "
"; + return "
" . $row['show_id'] . "
"; } diff --git a/services/StoryService.php b/services/StoryService.php index 368aef6..a11eb16 100644 --- a/services/StoryService.php +++ b/services/StoryService.php @@ -240,6 +240,8 @@ public function updateStoryStatus(array $data): void } elseif (isBool($status['is_billable_state'])) { $hours = 1; } + + $endedAt = $story['ended_at'] ? $story['ended_at'] : date('Y-m-d H:i:s'); $statement = $this->db->prepare(' UPDATE story @@ -250,7 +252,7 @@ public function updateStoryStatus(array $data): void $statement->bindParam(':status', $data['status']); $statement->bindParam(':hours', $hours); $statement->bindParam(':id', $data['id']); - $statement->bindParam(':ended_at', date('Y-m-d H:i:s')); + $statement->bindParam(':ended_at', $endedAt); $statement->execute(); $status = $this->settingService->getStoryStatusById($data['status']); From 1ef8c81c5c265fb084df60b07c18de9a91a86b81 Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Fri, 29 Apr 2022 09:53:39 -0700 Subject: [PATCH 02/17] Fix missing icon --- services/StoryService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/StoryService.php b/services/StoryService.php index a11eb16..59329ba 100644 --- a/services/StoryService.php +++ b/services/StoryService.php @@ -49,7 +49,7 @@ public function buildStoryOptions( global $storyStatuses; $options = (!$skipMoveCollection) - ? "" . putIcon('icofont-undo') . "" + ? "" . putIcon('icofont-box') . "" : ''; if (!$skipStatuses) { From e4b1c040f8995051b8ccf1605f84ccdf314beb0c Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Fri, 29 Apr 2022 10:02:28 -0700 Subject: [PATCH 03/17] Various small fixes --- README.md | 13 +++---------- assets/style.css | 2 +- pages/report.php | 4 ++++ templates/report/hand_off_report.php | 4 ++-- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 59af877..b472b6e 100644 --- a/README.md +++ b/README.md @@ -223,6 +223,7 @@ Some ways you can contribute include: - Donate to support additional development - Build features for the core platform - Translate the application into a new language or dialect +- Build new report templates - Create yearly tax files: create one for your regional burdens and share with the world! - Theming - Dashboard themes (no heavy javascript-based solutions) @@ -236,16 +237,8 @@ Some ways you can contribute include: # Roadmap -- Editing of companies, projects, rates, types, and statuses +- Edit companies and project basics - Task notes and files -- PRoject hand off lists -- Project turn-over to-do lists that can be generated and sent to clients much like invoices -- Basic estimated tax help for American freelancers - - Automate grabbing regions from github and auto creating - - Save generated tax burden page - - Select your tax strategies - - Reminders of est taxes being due -- Bulk actions on stories - Expanded language support and language packs -- Various themes - List invoice / tax directory contents +- Better help bubbles, especially for first time users diff --git a/assets/style.css b/assets/style.css index b1adea6..dad14d8 100644 --- a/assets/style.css +++ b/assets/style.css @@ -469,7 +469,7 @@ footer span { } footer { - margin-top: 0px; + margin-top: 24px; padding: 9px; text-align: right; font-size: 80%; diff --git a/pages/report.php b/pages/report.php index 8b559ec..6c1c366 100644 --- a/pages/report.php +++ b/pages/report.php @@ -1,5 +1,6 @@ getProjectById($_GET['project_id']); $company = $companyService->getCompanyById($project['company_id']); $clientCompany = $companyService->getCompanyById($project['client_id']); +$collections = $collectionService->getCollectionByProject($project['id']); $results = $projectService->getStoriesByFilters( $_GET['project_id'], @@ -49,6 +52,7 @@ 'project' => $project, 'company' => $company, 'stories' => $results, + 'collections' => $collections, 'logo' => logo(), 'css' => file_get_contents('assets/alternatve_view.css'), ], diff --git a/templates/report/hand_off_report.php b/templates/report/hand_off_report.php index eebfbaf..de0a259 100644 --- a/templates/report/hand_off_report.php +++ b/templates/report/hand_off_report.php @@ -3,7 +3,7 @@ - <?php echo $project['title']; ?> + <?php echo $title ? $title : 'Project Hand Off'; ?> + @@ -16,6 +17,7 @@
From f1e0a949a5258525f5d83cc74307eadf2865efa9 Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Mon, 2 May 2022 09:16:44 -0700 Subject: [PATCH 11/17] Adds a file navigator for generated files --- README.md | 6 +-- assets/alternatve_view.css | 2 +- assets/castlamp_logo.png | Bin 0 -> 6036 bytes includes/helpers.php | 6 ++- includes/system.php | 3 ++ index.php | 6 +++ pages/files.php | 73 ++++++++++++++++++++++++++++ pages/invoice.php | 2 +- pages/project.php | 2 +- pages/report.php | 2 +- pages/taxRender.php | 2 +- settings.sample.php | 3 ++ templates/admin/files.php | 64 ++++++++++++++++++++++++ templates/admin/snippets/header.php | 1 + 14 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 assets/castlamp_logo.png create mode 100644 pages/files.php create mode 100644 templates/admin/files.php diff --git a/README.md b/README.md index cd0691b..17e7560 100644 --- a/README.md +++ b/README.md @@ -175,13 +175,13 @@ While you can certainly continue to use this with a local PHP server, once beta ### How long will beta last? -Depending on how much feedback I get, I'm hoping to release v1 of ATOS mid-summer. +Depending on how much feedback I get, I'm hoping to release v1 of ATOS mid-summer. Should the project find good feedback and success, I'll commit to extensive code clean up in line with some of my other modern projects like Zenbership v2. ### Why isn't this using modern PHP tools like Composer? -The goal was always a zero-dependency application that can be setup in seconds. Sqlite3 allows for easy bsckups and makes your data highly portable. By adding complexity in the form of package managers and the such, it adds extra steps I don't want to put people through. +The goal was always a zero-dependency application that can be setup in seconds. While I could ship with a vendor folder, I want this to be without bloat whenever possible. Sqlite3 allows for easy bsckups and makes your data highly portable. By adding complexity in the form of package managers and the such, it adds extra steps I don't want to put people through. -In thoery, assuming you have PHP 8.1 installed locally, all you have to do is unzip the latest release and start the PHP server. I intend to keep it that way: *simplicity is poetry in code!* +In thoery, assuming you have PHP 8.1 installed locally, all you have to do is unzip the latest release and start the PHP server. ### How should I handle non-payment of a collection? diff --git a/assets/alternatve_view.css b/assets/alternatve_view.css index 960ab56..a43c232 100644 --- a/assets/alternatve_view.css +++ b/assets/alternatve_view.css @@ -671,7 +671,7 @@ h4 { } #logoArea img { - max-height: 80px; + max-height: 100px; max-width: 150px; width: auto; height: auto; diff --git a/assets/castlamp_logo.png b/assets/castlamp_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..132d8570d2ce8a9e25658e76a36e7036ad9254e3 GIT binary patch literal 6036 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' . $altText . '' + $file = '/' . ltrim(getSetting(AtosSettings::LOGO_FILE), '/'); + + return (file_exists(ATOS_HOME_DIR . $file)) + ? '
' . $altText . '
' : ''; } diff --git a/includes/system.php b/includes/system.php index 8be5938..bcfa511 100644 --- a/includes/system.php +++ b/includes/system.php @@ -32,6 +32,7 @@ enum AtosSettings: string { case DATABASE_FILE_NAME = 'DATABASE_FILE_NAME'; + case LOGO_FILE = 'LOGO_FILE'; case INVOICE_DUE_DATE_IN_DAYS = 'INVOICE_DUE_DATE_IN_DAYS'; case INVOICE_ORDER_BY_DATE_COMPLETED = 'INVOICE_ORDER_BY_DATE_COMPLETED'; case UNORGANIZED_NAME = 'UNORGANIZED_NAME'; @@ -47,6 +48,8 @@ function getSetting(AtosSettings $key, $default = null) switch ($key) { case AtosSettings::DATABASE_FILE_NAME: return returnSetting('DATABASE_FILE_NAME', $default); + case AtosSettings::LOGO_FILE: + return returnSetting('LOGO_FILE', $default); case AtosSettings::INVOICE_DUE_DATE_IN_DAYS: return (int) returnSetting('INVOICE_DUE_DATE_IN_DAYS', $default); case AtosSettings::INVOICE_ORDER_BY_DATE_COMPLETED: diff --git a/index.php b/index.php index b3c15ae..bdf7690 100644 --- a/index.php +++ b/index.php @@ -33,6 +33,12 @@ $requestUri = $rUri['0']; switch (strtolower($requestUri)) { + case '/documents': + require ATOS_HOME_DIR . '/pages/files.php'; + break; + case '/documents/load': + require ATOS_HOME_DIR . '/pages/files.php'; + break; case '/invoice': require ATOS_HOME_DIR . '/pages/invoice.php'; break; diff --git a/pages/files.php b/pages/files.php new file mode 100644 index 0000000..61be5f2 --- /dev/null +++ b/pages/files.php @@ -0,0 +1,73 @@ + 'Generated Documents (ATOS)', + 'files' => $files, + 'invoices' => $invoices, + 'reports' => $reports, + 'taxes' => $tax, + ] +); +exit; diff --git a/pages/invoice.php b/pages/invoice.php index c313fa0..22456f1 100644 --- a/pages/invoice.php +++ b/pages/invoice.php @@ -145,7 +145,7 @@ ); if (!empty($_GET['save']) && $_GET['save'] === '1') { - $filename = cleanFileName($project['title']) . '_' . date('Ymd') . '_' . cleanFileName($collection['title']) . '.html'; + $filename = 'invoice-' . date('Ymd') . '-' . cleanFileName($project['title']) . '-' . cleanFileName($collection['title']) . '.html'; file_put_contents(ATOS_HOME_DIR . '/_generated/' . $filename, $template); diff --git a/pages/project.php b/pages/project.php index fd31575..7d4b8c6 100644 --- a/pages/project.php +++ b/pages/project.php @@ -107,7 +107,7 @@ // Build the collections list. $collections = ''; -$allCollections = $collectionService->getCollectionByProject($project['id']); +$allCollections = $collectionService->getCollectionByProject($project['id'], 9999); $totalCollections = sizeof($allCollections); $at = 0; diff --git a/pages/report.php b/pages/report.php index 6c1c366..1c53882 100644 --- a/pages/report.php +++ b/pages/report.php @@ -66,7 +66,7 @@ ? cleanFileName($_GET['title']) : cleanFileName($project['title']); - $filename = 'report-' . $cleanName . '_' . date('Ymd') . '.html'; + $filename = 'report-' . date('Ymd') . '-' . $cleanName . '-' . '.html'; file_put_contents(ATOS_HOME_DIR . '/_generated/' . $filename, $template); diff --git a/pages/taxRender.php b/pages/taxRender.php index 3515292..a7c8faa 100644 --- a/pages/taxRender.php +++ b/pages/taxRender.php @@ -304,7 +304,7 @@ $template = template('tax/estimate', $changes, true); if (!empty($_GET['save']) && $_GET['save'] === '1') { - $filename = 'tax-' . $year . '-' . date('Ymd') . '.html'; + $filename = 'tax-' . date('Ymd') . '-' . $year . '.html'; file_put_contents(ATOS_HOME_DIR . '/_generated/' . $filename, $template); diff --git a/settings.sample.php b/settings.sample.php index d54dbeb..6b68770 100644 --- a/settings.sample.php +++ b/settings.sample.php @@ -3,6 +3,9 @@ return [ // What is your database file called. 'DATABASE_FILE_NAME' => 'atos.sqlite3', + + // Path to your logo file + 'LOGO_FILE' => 'assets/logo.png', // How many days from issuances of the invoice should payment be due? // Set to zero (0) to not have a due date. diff --git a/templates/admin/files.php b/templates/admin/files.php new file mode 100644 index 0000000..4080237 --- /dev/null +++ b/templates/admin/files.php @@ -0,0 +1,64 @@ + +
+ +

Invoices

+
+ + + + + + + + + + + + + +
Filename
+ +
+
+ +

Reports

+
+ + + + + + + + + + + + + +
Filename
+ +
+
+ +

Taxes

+
+ + + + + + + + + + + + + +
Filename
+ +
+
+ +
\ No newline at end of file diff --git a/templates/admin/snippets/header.php b/templates/admin/snippets/header.php index 74a6655..3d777f2 100644 --- a/templates/admin/snippets/header.php +++ b/templates/admin/snippets/header.php @@ -62,6 +62,7 @@ + Documents Taxes Settings
From a796a01d834bc25bce028e76842da48ac396d7f9 Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Mon, 2 May 2022 09:25:53 -0700 Subject: [PATCH 12/17] Readme update --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 17e7560..bf323ca 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ ATOS is 100% open source and free to use, licensed under the [GNU AGPLv3 License - [Setup](#setup) - [Download and Start](#download-and-start) - [Update Your Default Settings (Optional Step)](#update-your-default-settings-optional-step) - - [Notice: Deploying ATOS To The Web**](#notice-deploying-atos-to-the-web) + - [Notice: Deploying ATOS To The Web](#notice-deploying-atos-to-the-web) - [Notice: Updating Your Logo](#notice-updating-your-logo) - [Notice: Saving Invoices and Tax Overviews](#notice-saving-invoices-and-tax-overviews) - [Notice: Language Files](#notice-language-files) @@ -43,8 +43,10 @@ ATOS is 100% open source and free to use, licensed under the [GNU AGPLv3 License - [What tasks get placed on invoices?](#what-tasks-get-placed-on-invoices) - [Will future versions remain compatible with the beta?](#will-future-versions-remain-compatible-with-the-beta) - [Will future versions require a local PHP Server?](#will-future-versions-require-a-local-php-server) + - [Can companies use this to management projects?](#can-companies-use-this-to-management-projects) - [How long will beta last?](#how-long-will-beta-last) - [Why isn't this using modern PHP tools like Composer?](#why-isnt-this-using-modern-php-tools-like-composer) + - [Are you commiting to maintaining and improving ATOS moving forward?](#are-you-commiting-to-maintaining-and-improving-atos-moving-forward) - [How should I handle non-payment of a collection?](#how-should-i-handle-non-payment-of-a-collection) - [How do I reconcile what I billed and what I got paid?](#how-do-i-reconcile-what-i-billed-and-what-i-got-paid) - [What's the easiest way to import part data?](#whats-the-easiest-way-to-import-part-data) @@ -74,7 +76,7 @@ ATOS requires `PHP 8.1+` and `SQLite3`. Open `settings.sample.php` and update the values as needed. Optionally rename it to `settings.env.php`, otherwise ATOS will do that for you. -#### Notice: Deploying ATOS To The Web** +#### Notice: Deploying ATOS To The Web ATOS was always meant to be used locally. While there shouldn't be any problems deploying it, I don't recommend allowing anyone to access it who you don't trust. There is no concept of "users" in the platform, so anyone with access to the platform will be able to do whatever they want with your data. @@ -173,16 +175,28 @@ Note that you can have stories with a "complete" status that won't appear in the While you can certainly continue to use this with a local PHP server, once beta is completed, the plan is to turn this into either a desktop app or a containerized app. +### Can companies use this to management projects? + +As of now there isn't a concept of "users", so while in theory it could be used, there wouldn't be any way of limiting who can do what in the application, nor any way of knowning who did what. + +Should beta find success, I will be happy to implement more extensive user-based permissions/roles allowing for companies to leverage this more effectively. But again, this was always designed for freelancers, so that was never a consideration going into beta. + ### How long will beta last? Depending on how much feedback I get, I'm hoping to release v1 of ATOS mid-summer. Should the project find good feedback and success, I'll commit to extensive code clean up in line with some of my other modern projects like Zenbership v2. ### Why isn't this using modern PHP tools like Composer? -The goal was always a zero-dependency application that can be setup in seconds. While I could ship with a vendor folder, I want this to be without bloat whenever possible. Sqlite3 allows for easy bsckups and makes your data highly portable. By adding complexity in the form of package managers and the such, it adds extra steps I don't want to put people through. +The goal was always a zero-dependency application that can be setup in seconds. While I could ship with a vendor folder, I want this to be without bloat whenever possible. Sqlite3 allows for easy backups and makes your data highly portable. The lack of a full fledged PHP framekwork makes the package smaller. By adding complexity in the form of package managers and the such, it adds extra steps I don't want to put people through. In thoery, assuming you have PHP 8.1 installed locally, all you have to do is unzip the latest release and start the PHP server. +### Are you commiting to maintaining and improving ATOS moving forward? + +Should ATOS gain a dedicated user-base and receive good feedback during beta, I fully intend to continue working on it. Future work would include a post-beta modernization of the codebase, as well as potential online "cloud-based" versions of the application. But I'll never remove the open source aspect of the application, and it will always be free to use locally. + +I personally use this application for all of my freelancing needs, so one way or another development will continue on it. + ### How should I handle non-payment of a collection? If a client doesn't end up paying, you should set the the task to the `Unpaid` status. This will tell the program that you completed the work, but that it was never billed. From 3172b297069922948a874295bc38f4076ea6dcd0 Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Wed, 4 May 2022 20:40:28 -0400 Subject: [PATCH 13/17] Adds date to documents view --- pages/files.php | 14 ++++++++++---- templates/admin/files.php | 18 +++++++++++++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pages/files.php b/pages/files.php index 61be5f2..4340976 100644 --- a/pages/files.php +++ b/pages/files.php @@ -45,18 +45,24 @@ } $exp = explode('-', $file); + + $entry = [ + 'file' => $file, + 'date' => formatDate($exp['1']), + ]; + switch ($exp[0]) { case 'invoice': - $invoices[] = $file; + $invoices[] = $entry; break; case 'report': - $reports[] = $file; + $reports[] = $entry; break; case 'tax': - $tax[] = $file; + $tax[] = $entry; break; default: - $files[] = $file; + $files[] = $entry; } } diff --git a/templates/admin/files.php b/templates/admin/files.php index 4080237..252638e 100644 --- a/templates/admin/files.php +++ b/templates/admin/files.php @@ -6,6 +6,7 @@ + @@ -13,7 +14,10 @@ + @@ -26,6 +30,7 @@
Date Filename
- + + +
+ @@ -33,7 +38,10 @@ + @@ -46,6 +54,7 @@
Date Filename
- + + +
+ @@ -53,7 +62,10 @@ + From 59e3159787e21ff62af12b65e89a12f0a6ca4e1f Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Wed, 4 May 2022 21:33:38 -0400 Subject: [PATCH 14/17] Adjusts est. payments based on missed amount --- includes/helpers.php | 14 +++++++++----- pages/taxRender.php | 2 +- templates/tax/estimate.php | 28 ++++++++++++++++++++++------ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/includes/helpers.php b/includes/helpers.php index 9db6393..eae8785 100644 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -51,12 +51,16 @@ function cleanFileName(string $input): string * @param $data * @return void */ -function dd($data) +function dd() { - echo "
";
-    print_r($data);
-    echo "
"; - exit; + echo "
";
+
+    foreach (func_get_args() as $x) {
+        print_r($x);
+        echo "\n\n\n";
+    }
+    
+    die;
 }
 
 /**
diff --git a/pages/taxRender.php b/pages/taxRender.php
index a7c8faa..31535a7 100644
--- a/pages/taxRender.php
+++ b/pages/taxRender.php
@@ -182,7 +182,6 @@
 }
 
 // Add recommendations
-$recommendations = [];
 foreach ($finalData as $region => $aTaxRegionBurden) {
     $estTaxes = $aTaxRegionBurden['_class']::ESTIMATED_TAXES_DUE;
 
@@ -242,6 +241,7 @@
     }
 
     $regionTotals[$region] = formatMoney($total * 100);
+    $regionTotals['_' . $region] = $total;
 }
 
 $queryString = '&year=' . $year;
diff --git a/templates/tax/estimate.php b/templates/tax/estimate.php
index 328edb5..095deae 100644
--- a/templates/tax/estimate.php
+++ b/templates/tax/estimate.php
@@ -116,13 +116,15 @@
                         

- TAX BURDEN + TAX BURDEN
+ ()




% of total +

@@ -130,7 +132,7 @@
- + @@ -138,11 +140,23 @@ $sDetails) { $thisRegion = $estimatedTaxes[$region]; + $split = ($difference != 0) + ? ceil($difference / $left) + : 0; + + $sDetails['_amount'] -= ($split + $addPerPayment); + $sDetails['amount'] = formatMoney($sDetails['_amount'] * 100); + + $addPerPayment += $split; + if (array_key_exists($up, $thisRegion)) { $useDate = $estimatedTaxes[$region][$up]['created_at']; $useAmount = $estimatedTaxes[$region][$up]['amount']; @@ -172,7 +186,9 @@ class=""

Days until due:

- + - + From 4cbba52d92941d50678b717f14c6005c38ed14f8 Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Thu, 5 May 2022 10:54:53 -0400 Subject: [PATCH 15/17] Adds project files and links --- _vault/.gitignore | 0 db/migrations.sql | 12 ++ pages/project.php | 19 ++- services/FileLinkService.php | 253 +++++++++++++++++++++++++++++++++++ templates/admin/projects.php | 124 +++++++++++++++++ 5 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 _vault/.gitignore create mode 100644 services/FileLinkService.php diff --git a/_vault/.gitignore b/_vault/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/db/migrations.sql b/db/migrations.sql index 5452220..bd6dd15 100644 --- a/db/migrations.sql +++ b/db/migrations.sql @@ -22,6 +22,18 @@ CREATE TABLE `project` ( CONSTRAINT fk_client_id FOREIGN KEY(client_id) REFERENCES company(id) ); +CREATE TABLE `project_file` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, + `project_id` INTEGER, + `story_id` INTEGER default null, + `is_link` boolean DEFAULT 1, + `title` varchar(255), + `data` text, + CONSTRAINT fk_project_files_project_id FOREIGN KEY(project_id) REFERENCES project(id) ON DELETE CASCADE, + CONSTRAINT fk_project_files_story_id FOREIGN KEY(story_id) REFERENCES story(id) ON DELETE CASCADE +); + CREATE TABLE `story_hour_type` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` varchar(255), diff --git a/pages/project.php b/pages/project.php index 7d4b8c6..af00604 100644 --- a/pages/project.php +++ b/pages/project.php @@ -1,6 +1,7 @@ deleteCollection($_GET); exit; + case 'deleteFileLink': + $fileLinkService->deleteFileLink($_GET); + exit; case 'deleteStory': $storyService->deleteStory($_GET); exit; @@ -64,12 +69,18 @@ case 'createCollection': $collectionService->createCollection($_POST); exit; + case 'createLink': + $fileLinkService->createLink($_POST); + exit; case 'createStory': $storyService->createStory($_POST); exit; case 'updateStories': $storyService->updateStories($_POST); exit; + case 'uploadFile': + $fileLinkService->uploadFile($_POST); + exit; default: redirect('/project', $_GET['id'], null, 'Unknown action'); } @@ -116,7 +127,7 @@ continue; } - $isProjectDefault = isBool($row['is_project_default']); + $isProjectDefault = parseBool($row['is_project_default']); $delete = (!$isProjectDefault) ? "" . putIcon('icofont-delete') . "" @@ -145,7 +156,7 @@ $hours = 0; - $isProjectDefault = isBool($aCollection['is_project_default']); + $isProjectDefault = parseBool($aCollection['is_project_default']); $tripFlag = ($collectionCount > 1 && !$isProjectDefault); @@ -214,7 +225,7 @@ $hours += (int) $row['hours']; - $class = (!isBool($row['is_billable_state'])) ? ' notBillable' : ''; + $class = (!parseBool($row['is_billable_state'])) ? ' notBillable' : ''; $row['title'] = htmlspecialchars($row['title']); @@ -287,6 +298,8 @@ 'templates' => $allTemplates, 'allCollections' => $allCollections, 'collectionsRendered' => $collectionsRendered, + 'files' => $fileLinkService->getFilesForProject($project['id']), + 'links' => $fileLinkService->getLinksForProject($project['id']), 'nextId' => $storyService->generateTicketId($project['id']), 'project' => $project, 'totalCollections' => sizeof($allCollections), diff --git a/services/FileLinkService.php b/services/FileLinkService.php new file mode 100644 index 0000000..88c3ae5 --- /dev/null +++ b/services/FileLinkService.php @@ -0,0 +1,253 @@ +db->prepare(' + INSERT INTO project_file ( + project_id, + is_link, + title, + data + ) + VALUES ( + :project_id, + 1, + :title, + :data + ) + '); + + $statement->bindParam(':project_id', $data['project_id']); + $statement->bindParam(':title', $data['title']); + $statement->bindParam(':data', $data['data']); + $statement->execute(); + + redirect( + '/project', + $data['project_id'], + 'Your link has been added.', + null, + false, + [ + '_showLink' => '1', + ] + ); + } + + /** + * @param array $data + * @return void + */ + public function deleteFileLink(array $data): void + { + $item = $this->get($data['file_id']); + + $isLink = parseBool($item['is_link']); + if (!$isLink) { + @unlink(ATOS_HOME_DIR . '/' . ltrim($item['data'], '/')); + } + + $statement = $this->db->prepare(' + DELETE FROM project_file + WHERE id = :id + '); + + $statement->bindParam(':id', $data['file_id']); + + $statement->execute(); + + redirect( + '/project', + $data['id'], + 'Your item has been deleted.', + null, + false, + [ + '_showLink' => $isLink ? '1' : '', + '_showFile' => !$isLink ? '1' : '', + ] + ); + } + + /** + * @param integer $id + * @return array + */ + public function get(int $id): array + { + $statement = $this->db->prepare(" + SELECT * + FROM project_file + WHERE id = :id + "); + + $statement->bindParam(':id', $id); + + $statement->execute(); + + return $statement->fetch(); + } + + /** + * @param integer $projectId + * @return array + */ + public function getFilesForProject(int $projectId): array + { + $statement = $this->db->prepare(" + SELECT * + FROM project_file + WHERE + is_link = 0 + AND project_id = :project_id + ORDER BY story_id ASC, created_at DESC + "); + + $statement->bindParam(':project_id', $projectId); + + $statement->execute(); + + return $statement->fetchAll(); + } + + /** + * @param integer $storyId + * @return array + */ + public function getFilesForStory(int $storyId): array + { + $statement = $this->db->prepare(" + SELECT * + FROM project_file + WHERE + is_link = 0 + AND story_id = :story_id + ORDER BY story_id ASC, created_at DESC + "); + + $statement->bindParam(':story_id', $storyId); + + $statement->execute(); + + return $statement->fetchAll(); + } + + /** + * @param integer $projectId + * @return array + */ + public function getLinksForProject(int $projectId): array + { + $statement = $this->db->prepare(" + SELECT * + FROM project_file + WHERE + is_link = 1 + AND project_id = :project_id + ORDER BY created_at DESC + "); + + $statement->bindParam(':project_id', $projectId); + + $statement->execute(); + + return $statement->fetchAll(); + } + + /** + * @param integer $storyId + * @return array + */ + public function getLinksForStory(int $storyId): array + { + $statement = $this->db->prepare(" + SELECT * + FROM project_file + WHERE + is_link = 1 + AND story_id = :story_id + ORDER BY created_at DESC + "); + + $statement->bindParam(':story_id', $storyId); + + $statement->execute(); + + return $statement->fetchAll(); + } + + /** + * @param array $data + * @return void + */ + public function uploadFile(array $data): void + { + $uploadFilePath = '_vault/' . $_FILES['data']['name']; + $uploadPath = ATOS_HOME_DIR . '/' . $uploadFilePath; + + $uploaded = move_uploaded_file( + $_FILES['data']['tmp_name'], + $uploadPath + ); + if (!$uploaded) { + redirect( + '/project', + $data['project_id'], + null, + 'Your file could not be uploaded: is the directory writable? (' . $uploadPath . ')' + ); + } + + $statement = $this->db->prepare(' + INSERT INTO project_file ( + project_id, + is_link, + title, + data + ) + VALUES ( + :project_id, + 0, + :title, + :data + ) + '); + + $statement->bindParam(':project_id', $data['project_id']); + $statement->bindParam(':title', $data['title']); + $statement->bindParam(':data', $uploadFilePath); + $statement->execute(); + + redirect( + '/project', + $data['project_id'], + 'Your file has been uploaded to the _vault directory.', + null, + false, + [ + '_showFile' => '1', + ] + ); + } +} diff --git a/templates/admin/projects.php b/templates/admin/projects.php index 3e1789f..3ddbc49 100644 --- a/templates/admin/projects.php +++ b/templates/admin/projects.php @@ -22,6 +22,130 @@

Project

+ + + +
Date Filename
- + + +
Date Due or PaidEstimatedRec. Payment Actual Paid Difference
+ + $
- ()
+ + + + + + + + + + + + + + + + + + + + + +
TitleLink
+ + + + + +
+ + + +
+ +
+ + +
+

Files

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
TitleLocation
+ + + +
+ + + +
+
+
+
From aceac655a21bf6e0121840cdad8e0c9f7d0fec3c Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Thu, 5 May 2022 10:55:02 -0400 Subject: [PATCH 16/17] Renames isBool to parseBool --- README.md | 8 ++--- includes/helpers.php | 44 ++++++++++++++-------------- pages/settings.php | 4 +-- services/BaseService.php | 2 +- services/CollectionService.php | 6 ++-- services/CompanyService.php | 2 -- services/ProjectService.php | 5 +--- services/SettingService.php | 2 -- services/StoryService.php | 31 ++++++++++++++++++-- templates/admin/files.php | 14 ++++----- templates/admin/settings.php | 1 - templates/report/hand_off_report.php | 2 +- 12 files changed, 69 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index bf323ca..6ea99d9 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ ATOS is 100% open source and free to use, licensed under the [GNU AGPLv3 License - [Update Your Default Settings (Optional Step)](#update-your-default-settings-optional-step) - [Notice: Deploying ATOS To The Web](#notice-deploying-atos-to-the-web) - [Notice: Updating Your Logo](#notice-updating-your-logo) - - [Notice: Saving Invoices and Tax Overviews](#notice-saving-invoices-and-tax-overviews) + - [Notice: Saving Invoices, Reports, and Tax Documents](#notice-saving-invoices-reports-and-tax-documents) - [Notice: Language Files](#notice-language-files) - [Notice: Templates Files](#notice-templates-files) - [Notice: PHP 8.1 Requirement](#notice-php-81-requirement) @@ -84,9 +84,9 @@ ATOS was always meant to be used locally. While there shouldn't be any problems You can add your logo to outgoing invoices by simply replacing `assets/logo.png` in the main directory of the project with your actual logo. -#### Notice: Saving Invoices and Tax Overviews +#### Notice: Saving Invoices, Reports, and Tax Documents -If you plan on generating and saving invoices/taxes locally (which I recommend you do), you will need to make sure the `_generated` directory is writable: `chmod 0755 _generated`. +If you plan on generating and saving generated documents locally (which I recommend you do), you will need to make sure the `_generated` and `_vault` directories are writable: `chmod 0755 _generated && chmod 0755 _vault`. Invoices/ and taxes files are saved as HTML. Most computers have reasonable `Print as PDF` options now; please use that feature to print a PDF if required. ATOS hard codes styles, so changing `assets/invoiceStyle.css` or `assets/taxStyle.css` won't affect already saved invoices. @@ -266,9 +266,9 @@ Some ways you can contribute include: # Roadmap +- File and data syncing via a cloud service (dropbox, etc.) - Remove PHP8 requirement in favor of most universally available versions - Edit companies and project basics - Task notes and files - Expanded language support and language packs -- List invoice / tax directory contents - Better help bubbles, especially for first time users diff --git a/includes/helpers.php b/includes/helpers.php index eae8785..318147c 100644 --- a/includes/helpers.php +++ b/includes/helpers.php @@ -73,6 +73,20 @@ function formatDate(string $date, string $format = 'Y/m/d'): string return date($format, strtotime($date)); } +/** + * @param string $key + * @param string $default + * @return string + */ +function language(string $key, string $default = ''): string +{ + global $ATOS_LANGUAGE; + + return (array_key_exists($key, $ATOS_LANGUAGE)) ? $ATOS_LANGUAGE[$key] : $default; + + return $default; +} + /** * SQLite3 and PHP don't play well with booleans, so we'll * create a function to manage this better. @@ -80,7 +94,7 @@ function formatDate(string $date, string $format = 'Y/m/d'): string * @param $value * @return boolean */ -function isBool($value): bool +function parseBool($value): bool { $stringValue = strtolower((string) $value); @@ -92,20 +106,6 @@ function isBool($value): bool ) ? true : false; } -/** - * @param string $key - * @param string $default - * @return string - */ -function language(string $key, string $default = ''): string -{ - global $ATOS_LANGUAGE; - - return (array_key_exists($key, $ATOS_LANGUAGE)) ? $ATOS_LANGUAGE[$key] : $default; - - return $default; -} - /** * @param string $name * @param string $default @@ -147,13 +147,13 @@ function logo(string $altText = ''): string } /** - * @param string $page - * @param string $id - * @param string|null $success - * @param string|null $error - * @param bool $return - * @param array $queryString - * @param string $hash + * @param string $page Base URI we are redirecting to. + * @param string $id ID of the item we are redirecting to. + * @param string|null $success Success message. + * @param string|null $error Error message. + * @param bool $return If true, we return the URL without redirecting to it. + * @param array $queryString Additional values to add to the query string. + * @param string $hash If you want to use a hashbang, ie "vertical scroll" to a certain part of a page. * @return void */ function redirect( diff --git a/pages/settings.php b/pages/settings.php index 33b235d..217390a 100644 --- a/pages/settings.php +++ b/pages/settings.php @@ -126,8 +126,8 @@ ] ), 'icon' => putIcon($aStatus['emoji'], $aStatus['color']), - 'isBillable' => isBool($aStatus['is_billable_state']), - 'isComplete' => isBool($aStatus['is_complete_state']), + 'isBillable' => parseBool($aStatus['is_billable_state']), + 'isComplete' => parseBool($aStatus['is_complete_state']), ], true ); diff --git a/services/BaseService.php b/services/BaseService.php index fd832af..c0abdfd 100644 --- a/services/BaseService.php +++ b/services/BaseService.php @@ -19,7 +19,7 @@ class BaseService public function __construct() { - // TODO: inject this in here + // TODO: you know what to do... global $db; $this->db = $db; diff --git a/services/CollectionService.php b/services/CollectionService.php index 63fc379..99fadd0 100644 --- a/services/CollectionService.php +++ b/services/CollectionService.php @@ -70,7 +70,7 @@ public function deleteCollection(array $data): void { $collection = $this->getCollectionById($data['id']); - $isDefault = isBool($collection['is_project_default']); + $isDefault = parseBool($collection['is_project_default']); if ($isDefault) { redirect( '/project', @@ -208,7 +208,7 @@ public function getStoriesInCollection( bool $showCompleteNotBillable = false ) { $collection = $this->getCollectionById($collectionId); - $isDefaultCollection = isBool($collection['is_project_default']); + $isDefaultCollection = parseBool($collection['is_project_default']); $statusQuery = ''; if (!$isDefaultCollection) { @@ -293,7 +293,7 @@ public function shiftCollection(array $data): void $currentCollection = $this->getCollectionById($story['collection']); $currentStatus = $this->settingService->getStoryStatusById($story['status']); - $isStoryInDefaultCollection = isBool($currentCollection['is_project_default']); + $isStoryInDefaultCollection = parseBool($currentCollection['is_project_default']); if ($isStoryInDefaultCollection) { $useCollection = $this->getLatestCollectionForProject($data['project_id']); diff --git a/services/CompanyService.php b/services/CompanyService.php index 13ff54f..a828168 100644 --- a/services/CompanyService.php +++ b/services/CompanyService.php @@ -4,8 +4,6 @@ use services\BaseService; -// require_once ATOS_HOME_DIR . '/services/BaseService.php'; - /** * ATOS: "Built by freelancer 🙋‍♂️, for freelancers 🕺 🤷 💃🏾 " * diff --git a/services/ProjectService.php b/services/ProjectService.php index 07f6ec3..71db8c5 100644 --- a/services/ProjectService.php +++ b/services/ProjectService.php @@ -4,8 +4,6 @@ use services\BaseService; -// require_once ATOS_HOME_DIR . '/services/BaseService.php'; - /** * ATOS: "Built by freelancer 🙋‍♂️, for freelancers 🕺 🤷 💃🏾 " * @@ -75,10 +73,9 @@ public function createProject(array $data): void systemError($e->getMessage()); } - redirect('/', null, 'Your project has been created; now go got get that bread.'); + redirect('/', null, 'Your project has been created; now go got get that bread!'); } - /** * @param array $data * @return void diff --git a/services/SettingService.php b/services/SettingService.php index b6b7ac4..44546ee 100644 --- a/services/SettingService.php +++ b/services/SettingService.php @@ -4,8 +4,6 @@ use services\BaseService; -// require_once ATOS_HOME_DIR . '/services/BaseService.php'; - /** * ATOS: "Built by freelancer 🙋‍♂️, for freelancers 🕺 🤷 💃🏾 " * diff --git a/services/StoryService.php b/services/StoryService.php index 16126f4..ee9f6b0 100644 --- a/services/StoryService.php +++ b/services/StoryService.php @@ -76,12 +76,37 @@ public function createStory(array $data): void ? $data['show_id'] : $this->generateTicketId($data['project_id']); + $status = $this->settingService->getStoryStatusById($data['status']); + + $ended_at = (parseBool($status['is_complete_state'])) ? date('Y-m-d H:i:s') : null; + $statement = $this->db->prepare(' - INSERT INTO story (show_id, due_at, title, collection, rate_type, type, status) - VALUES (:show_id, :due_at, :title, :collection, :rate_type, :type, :status) + INSERT INTO story ( + show_id, + due_at, + title, + collection, + rate_type, + type, + status, + ended_at, + hours + ) + VALUES ( + :show_id, + :due_at, + :title, + :collection, + :rate_type, + :type, + :status, + :ended_at, + 1 + ) '); $statement->bindParam(':show_id', $id); + $statement->bindParam(':ended_at', $ended_at); $statement->bindParam(':due_at', $data['due_at']); $statement->bindParam(':title', $data['title']); $statement->bindParam(':collection', $data['collection']); @@ -238,7 +263,7 @@ public function updateStoryStatus(array $data): void $hours = 0; if ((int) $story['hours'] > 0) { $hours = $story['hours']; - } elseif (isBool($status['is_billable_state'])) { + } elseif (parseBool($status['is_billable_state'])) { $hours = 1; } diff --git a/templates/admin/files.php b/templates/admin/files.php index 252638e..da9731c 100644 --- a/templates/admin/files.php +++ b/templates/admin/files.php @@ -1,8 +1,8 @@ -
+
-

Invoices

-
+
+

Invoices

@@ -25,8 +25,8 @@
-

Reports

-
+
+

Reports

@@ -49,8 +49,8 @@
-

Taxes

-
+
+

Taxes

diff --git a/templates/admin/settings.php b/templates/admin/settings.php index 498bb6e..3ff8856 100644 --- a/templates/admin/settings.php +++ b/templates/admin/settings.php @@ -2,7 +2,6 @@
-

Task Statuses

diff --git a/templates/report/hand_off_report.php b/templates/report/hand_off_report.php index a909207..88c98b8 100644 --- a/templates/report/hand_off_report.php +++ b/templates/report/hand_off_report.php @@ -66,7 +66,7 @@
- + diff --git a/templates/admin/snippets/collection.php b/templates/admin/snippets/collection.php index bf9c8b1..9ef2df3 100644 --- a/templates/admin/snippets/collection.php +++ b/templates/admin/snippets/collection.php @@ -80,7 +80,7 @@ class="preventLeaving" - + diff --git a/templates/admin/snippets/collection_table_other_entry.php b/templates/admin/snippets/collection_table_other_entry.php index ea4e522..8d4fe9d 100644 --- a/templates/admin/snippets/collection_table_other_entry.php +++ b/templates/admin/snippets/collection_table_other_entry.php @@ -23,7 +23,8 @@
- /> + />
From 7cf2f87edec3834ed8f484ce2bbe70a35f2b2c7a Mon Sep 17 00:00:00 2001 From: Jonathan Belelieu Date: Thu, 5 May 2022 11:08:34 -0400 Subject: [PATCH 17/17] Allows for 0.25 steps in units billed --- README.md | 2 +- assets/alternatve_view.css | 24 +++++++++++++++++++ db/migrations.sql | 2 +- pages/invoice.php | 2 +- services/StoryService.php | 2 +- templates/admin/index.php | 2 +- templates/admin/snippets/collection.php | 2 +- .../snippets/collection_table_other_entry.php | 3 ++- 8 files changed, 32 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6ea99d9..ad1128c 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ If a client doesn't end up paying, you should set the the task to the `Unpaid` s If a client only paid a fraction of an invoice, you have some options: -- Change the task's hours: you can alter the collection, changing the hours billed to `0`, +- Change the task's units: you can alter the collection, changing the units billed to `0`, - Change your tax burdens: if your goal is more accurate tax reporting, you can add a deduction at for the difference between what you billed and what you were paid. For example, if you were owed were `$10,000` and only got paid `$6,000`, within your tax information for that year, a `$4,000` deduction effectively fixes your actual income since deductions come directly out of your billed amount for the year. ### What's the easiest way to import part data? diff --git a/assets/alternatve_view.css b/assets/alternatve_view.css index a43c232..8d52b74 100644 --- a/assets/alternatve_view.css +++ b/assets/alternatve_view.css @@ -688,11 +688,35 @@ button.a { /* */ +blockquote { + font-style: italic; + margin: 16px 0; + color: #333; +} + +blockquote:before, +blockquote:after { + content: '"'; +} .noMarginTop { margin-top: 0px; } +.borderSection table tr th:first-child, +.sunk table tr th:first-child, +.borderSection table tr td:first-child, +.sunk table tr td:first-child { + padding-left: 0px; +} + +.borderSection table tr th:last-child, +.sunk table tr th:last-child, +.borderSection table tr td:last-child, +.sunk table tr td:last-child { + padding-right: 0px; +} + /* */ .columns5050 { diff --git a/db/migrations.sql b/db/migrations.sql index bd6dd15..50f26f3 100644 --- a/db/migrations.sql +++ b/db/migrations.sql @@ -82,7 +82,7 @@ CREATE TABLE `story` ( `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, `due_at` timestamp, `ended_at` timestamp, - `hours` INTEGER, + `hours` NUMERIC, `collection` INTEGER, `rate_type` INTEGER DEFAULT "1", `type` INTEGER, diff --git a/pages/invoice.php b/pages/invoice.php index 22456f1..606e6ea 100644 --- a/pages/invoice.php +++ b/pages/invoice.php @@ -92,7 +92,7 @@ } $dayHours += (int) $aStory['hours']; - $totalHours += (int) $aStory['hours']; + $totalHours += (float) $aStory['hours']; $storyHtml .= template('invoice/snippets/story_table_entry', $aStory, true); } diff --git a/services/StoryService.php b/services/StoryService.php index ee9f6b0..b54d1ed 100644 --- a/services/StoryService.php +++ b/services/StoryService.php @@ -231,7 +231,7 @@ public function updateStories(array $data): void id = :id '); - $hours = (int) $aStory['hours']; + $hours = (float) $aStory['hours']; $type = (int) $aStory['type']; $rateType = (int) $aStory['rate_type']; diff --git a/templates/admin/index.php b/templates/admin/index.php index b9329f2..ee81aff 100644 --- a/templates/admin/index.php +++ b/templates/admin/index.php @@ -65,7 +65,7 @@
Code Contractor ClientHoursUnits Billed
Type CompletedHoursUnits Title