Compare commits

...

10 Commits

Author SHA1 Message Date
Wian Drs cdacd1dc40 Update account Invoice
update invoice agar tidak langsung di akui sebagai pendapatan dan di akui nya saat invoice di bayar
2026-06-11 12:12:41 +07:00
sean a371fe3bac Merge branch 'main' of github.com:yandrs/accounting 2026-06-04 14:49:04 +07:00
sean c2c206d585 penambahan score di header
penambahan score di header
2026-06-02 13:13:02 +07:00
sean 6439eb79a1 penambahan All Total Time dan Warna Badge
di active menu attendance monthly
2026-06-02 10:37:49 +07:00
sean 77a65abdb5 Active Menu Attendance Monthly
Penambahan Modal rekapitulasi kehadiran perbulan
2026-06-02 08:07:14 +07:00
Prod-Panel 09ce444fd0 Ignore production config file htaccess 2026-05-30 09:43:54 +00:00
Prod-Panel 88f2cd5ea7 Ignore production config files 2026-05-30 09:40:01 +00:00
Wian Drs 28f282988d Merge branch 'main' of github.com:yandrs/accounting 2026-05-30 16:16:05 +07:00
Wian Drs 275652f7f7 Update Cronjob
update cronjob attendance monthly
2026-05-30 16:15:40 +07:00
sean 442980a2f1 Hapus Validasi tanggal, ket di ganti dengan semua kolom harap di isi di jurnal umum
akun tgl ket dan total debit dan kredit jika kosong tidak bisa di simpan muncul validasi warning
2026-05-30 09:22:34 +07:00
12 changed files with 1296 additions and 1020 deletions
+4
View File
@@ -1,5 +1,9 @@
.DS_Store .DS_Store
application/config/config.php
application/config/database.php
.htaccess
application/cache/* application/cache/*
!application/cache/index.html !application/cache/index.html
-9
View File
@@ -1,9 +0,0 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /accounting_dev
# Redirect requests to index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L]
</IfModule>
-6
View File
@@ -1,6 +0,0 @@
<IfModule authz_core_module>
Require all denied
</IfModule>
<IfModule !authz_core_module>
Deny from all
</IfModule>
-533
View File
@@ -1,533 +0,0 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/*
|--------------------------------------------------------------------------
| Base Site URL
|--------------------------------------------------------------------------
|
| URL to your CodeIgniter root. Typically this will be your base URL,
| WITH a trailing slash:
|
| http://example.com/
|
| WARNING: You MUST set this value!
|
| If it is not set, then CodeIgniter will try to guess the protocol and
| path to your installation, but due to security concerns the hostname will
| be set to $_SERVER['SERVER_ADDR'] if available, or localhost otherwise.
| The auto-detection mechanism exists only for convenience during
| development and MUST NOT be used in production!
|
| If you need to allow multiple domains, remember that this file is still
| a PHP script and you can easily do that on your own.
|
*/
$config['base_url'] = 'http://localhost/accounting_dev/';
/*
|--------------------------------------------------------------------------
| Index File
|--------------------------------------------------------------------------
|
| Typically this will be your index.php file, unless you've renamed it to
| something else. If you are using mod_rewrite to remove the page set this
| variable so that it is blank.
|
*/
$config['index_page'] = '';
/*
|--------------------------------------------------------------------------
| URI PROTOCOL
|--------------------------------------------------------------------------
|
| This item determines which server global should be used to retrieve the
| URI string. The default setting of 'REQUEST_URI' works for most servers.
| If your links do not seem to work, try one of the other delicious flavors:
|
| 'REQUEST_URI' Uses $_SERVER['REQUEST_URI']
| 'QUERY_STRING' Uses $_SERVER['QUERY_STRING']
| 'PATH_INFO' Uses $_SERVER['PATH_INFO']
|
| WARNING: If you set this to 'PATH_INFO', URIs will always be URL-decoded!
*/
$config['uri_protocol'] = 'REQUEST_URI';
/*
|--------------------------------------------------------------------------
| URL suffix
|--------------------------------------------------------------------------
|
| This option allows you to add a suffix to all URLs generated by CodeIgniter.
| For more information please see the user guide:
|
| https://codeigniter.com/userguide3/general/urls.html
|
| Note: This option is ignored for CLI requests.
*/
$config['url_suffix'] = '';
/*
|--------------------------------------------------------------------------
| Default Language
|--------------------------------------------------------------------------
|
| This determines which set of language files should be used. Make sure
| there is an available translation if you intend to use something other
| than english.
|
*/
$config['language'] = 'english';
/*
|--------------------------------------------------------------------------
| Default Character Set
|--------------------------------------------------------------------------
|
| This determines which character set is used by default in various methods
| that require a character set to be provided.
|
| See http://php.net/htmlspecialchars for a list of supported charsets.
|
*/
$config['charset'] = 'UTF-8';
/*
|--------------------------------------------------------------------------
| Enable/Disable System Hooks
|--------------------------------------------------------------------------
|
| If you would like to use the 'hooks' feature you must enable it by
| setting this variable to TRUE (boolean). See the user guide for details.
|
*/
$config['enable_hooks'] = FALSE;
/*
|--------------------------------------------------------------------------
| Class Extension Prefix
|--------------------------------------------------------------------------
|
| This item allows you to set the filename/classname prefix when extending
| native libraries. For more information please see the user guide:
|
| https://codeigniter.com/userguide3/general/core_classes.html
| https://codeigniter.com/userguide3/general/creating_libraries.html
|
*/
$config['subclass_prefix'] = 'MY_';
/*
|--------------------------------------------------------------------------
| Composer auto-loading
|--------------------------------------------------------------------------
|
| Enabling this setting will tell CodeIgniter to look for a Composer
| package auto-loader script in application/vendor/autoload.php.
|
| $config['composer_autoload'] = TRUE;
|
| Or if you have your vendor/ directory located somewhere else, you
| can opt to set a specific path as well:
|
| $config['composer_autoload'] = '/path/to/vendor/autoload.php';
|
| For more information about Composer, please visit http://getcomposer.org/
|
| Note: This will NOT disable or override the CodeIgniter-specific
| autoloading (application/config/autoload.php)
*/
$config['composer_autoload'] = FALSE;
/*
|--------------------------------------------------------------------------
| Allowed URL Characters
|--------------------------------------------------------------------------
|
| This lets you specify which characters are permitted within your URLs.
| When someone tries to submit a URL with disallowed characters they will
| get a warning message.
|
| As a security measure you are STRONGLY encouraged to restrict URLs to
| as few characters as possible. By default only these are allowed: a-z 0-9~%.:_-
|
| Leave blank to allow all characters -- but only if you are insane.
|
| The configured value is actually a regular expression character group
| and it will be executed as: ! preg_match('/^[<permitted_uri_chars>]+$/i
|
| DO NOT CHANGE THIS UNLESS YOU FULLY UNDERSTAND THE REPERCUSSIONS!!
|
*/
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-';
/*
|--------------------------------------------------------------------------
| Enable Query Strings
|--------------------------------------------------------------------------
|
| By default CodeIgniter uses search-engine friendly segment based URLs:
| example.com/who/what/where/
|
| You can optionally enable standard query string based URLs:
| example.com?who=me&what=something&where=here
|
| Options are: TRUE or FALSE (boolean)
|
| The other items let you set the query string 'words' that will
| invoke your controllers and its functions:
| example.com/index.php?c=controller&m=function
|
| Please note that some of the helpers won't work as expected when
| this feature is enabled, since CodeIgniter is designed primarily to
| use segment based URLs.
|
*/
$config['enable_query_strings'] = FALSE;
$config['controller_trigger'] = 'c';
$config['function_trigger'] = 'm';
$config['directory_trigger'] = 'd';
/*
|--------------------------------------------------------------------------
| Allow $_GET array
|--------------------------------------------------------------------------
|
| By default CodeIgniter enables access to the $_GET array. If for some
| reason you would like to disable it, set 'allow_get_array' to FALSE.
|
| WARNING: This feature is DEPRECATED and currently available only
| for backwards compatibility purposes!
|
*/
$config['allow_get_array'] = TRUE;
/*
|--------------------------------------------------------------------------
| Error Logging Threshold
|--------------------------------------------------------------------------
|
| You can enable error logging by setting a threshold over zero. The
| threshold determines what gets logged. Threshold options are:
|
| 0 = Disables logging, Error logging TURNED OFF
| 1 = Error Messages (including PHP errors)
| 2 = Debug Messages
| 3 = Informational Messages
| 4 = All Messages
|
| You can also pass an array with threshold levels to show individual error types
|
| array(2) = Debug Messages, without Error Messages
|
| For a live site you'll usually only enable Errors (1) to be logged otherwise
| your log files will fill up very fast.
|
*/
$config['log_threshold'] = 4;
/*
|--------------------------------------------------------------------------
| Error Logging Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/logs/ directory. Use a full server path with trailing slash.
|
*/
$config['log_path'] = '';
/*
|--------------------------------------------------------------------------
| Log File Extension
|--------------------------------------------------------------------------
|
| The default filename extension for log files. The default 'php' allows for
| protecting the log files via basic scripting, when they are to be stored
| under a publicly accessible directory.
|
| Note: Leaving it blank will default to 'php'.
|
*/
$config['log_file_extension'] = '';
/*
|--------------------------------------------------------------------------
| Log File Permissions
|--------------------------------------------------------------------------
|
| The file system permissions to be applied on newly created log files.
|
| IMPORTANT: This MUST be an integer (no quotes) and you MUST use octal
| integer notation (i.e. 0700, 0644, etc.)
*/
$config['log_file_permissions'] = 0644;
/*
|--------------------------------------------------------------------------
| Date Format for Logs
|--------------------------------------------------------------------------
|
| Each item that is logged has an associated date. You can use PHP date
| codes to set your own date formatting
|
*/
$config['log_date_format'] = 'Y-m-d H:i:s';
/*
|--------------------------------------------------------------------------
| Error Views Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/views/errors/ directory. Use a full server path with trailing slash.
|
*/
$config['error_views_path'] = '';
/*
|--------------------------------------------------------------------------
| Cache Directory Path
|--------------------------------------------------------------------------
|
| Leave this BLANK unless you would like to set something other than the default
| application/cache/ directory. Use a full server path with trailing slash.
|
*/
$config['cache_path'] = '';
/*
|--------------------------------------------------------------------------
| Cache Include Query String
|--------------------------------------------------------------------------
|
| Whether to take the URL query string into consideration when generating
| output cache files. Valid options are:
|
| FALSE = Disabled
| TRUE = Enabled, take all query parameters into account.
| Please be aware that this may result in numerous cache
| files generated for the same page over and over again.
| array('q') = Enabled, but only take into account the specified list
| of query parameters.
|
*/
$config['cache_query_string'] = FALSE;
/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| If you use the Encryption class, you must set an encryption key.
| See the user guide for more info.
|
| https://codeigniter.com/userguide3/libraries/encryption.html
|
*/
$config['encryption_key'] = '';
/*
|--------------------------------------------------------------------------
| Session Variables
|--------------------------------------------------------------------------
|
| 'sess_driver'
|
| The storage driver to use: files, database, redis, memcached
|
| 'sess_cookie_name'
|
| The session cookie name, must contain only [0-9a-z_-] characters
|
| 'sess_samesite'
|
| Session cookie SameSite attribute: Lax (default), Strict or None
|
| 'sess_expiration'
|
| The number of SECONDS you want the session to last.
| Setting to 0 (zero) means expire when the browser is closed.
|
| 'sess_save_path'
|
| The location to save sessions to, driver dependent.
|
| For the 'files' driver, it's a path to a writable directory.
| WARNING: Only absolute paths are supported!
|
| For the 'database' driver, it's a table name.
| Please read up the manual for the format with other session drivers.
|
| IMPORTANT: You are REQUIRED to set a valid save path!
|
| 'sess_match_ip'
|
| Whether to match the user's IP address when reading the session data.
|
| WARNING: If you're using the database driver, don't forget to update
| your session table's PRIMARY KEY when changing this setting.
|
| 'sess_time_to_update'
|
| How many seconds between CI regenerating the session ID.
|
| 'sess_regenerate_destroy'
|
| Whether to destroy session data associated with the old session ID
| when auto-regenerating the session ID. When set to FALSE, the data
| will be later deleted by the garbage collector.
|
| Other session cookie settings are shared with the rest of the application,
| except for 'cookie_prefix' and 'cookie_httponly', which are ignored here.
|
*/
$config['sess_driver'] = 'files';
$config['sess_cookie_name'] = 'ci_session';
$config['sess_samesite'] = 'Lax';
$config['sess_expiration'] = 7200;
$config['sess_save_path'] = sys_get_temp_dir();
$config['sess_match_ip'] = FALSE;
$config['sess_time_to_update'] = 300;
$config['sess_regenerate_destroy'] = FALSE;
/*
|--------------------------------------------------------------------------
| Cookie Related Variables
|--------------------------------------------------------------------------
|
| 'cookie_prefix' = Set a cookie name prefix if you need to avoid collisions
| 'cookie_domain' = Set to .your-domain.com for site-wide cookies
| 'cookie_path' = Typically will be a forward slash
| 'cookie_secure' = Cookie will only be set if a secure HTTPS connection exists.
| 'cookie_httponly' = Cookie will only be accessible via HTTP(S) (no javascript)
| 'cookie_samesite' = Cookie's samesite attribute (Lax, Strict or None)
|
| Note: These settings (with the exception of 'cookie_prefix' and
| 'cookie_httponly') will also affect sessions.
|
*/
$config['cookie_prefix'] = '';
$config['cookie_domain'] = '';
$config['cookie_path'] = '/';
$config['cookie_secure'] = FALSE;
$config['cookie_httponly'] = FALSE;
$config['cookie_samesite'] = 'Lax';
/*
|--------------------------------------------------------------------------
| Standardize newlines
|--------------------------------------------------------------------------
|
| Determines whether to standardize newline characters in input data,
| meaning to replace \r\n, \r, \n occurrences with the PHP_EOL value.
|
| WARNING: This feature is DEPRECATED and currently available only
| for backwards compatibility purposes!
|
*/
$config['standardize_newlines'] = FALSE;
/*
|--------------------------------------------------------------------------
| Global XSS Filtering
|--------------------------------------------------------------------------
|
| Determines whether the XSS filter is always active when GET, POST or
| COOKIE data is encountered
|
| WARNING: This feature is DEPRECATED and currently available only
| for backwards compatibility purposes!
|
*/
$config['global_xss_filtering'] = FALSE;
/*
|--------------------------------------------------------------------------
| Cross Site Request Forgery
|--------------------------------------------------------------------------
| Enables a CSRF cookie token to be set. When set to TRUE, token will be
| checked on a submitted form. If you are accepting user data, it is strongly
| recommended CSRF protection be enabled.
|
| 'csrf_token_name' = The token name
| 'csrf_cookie_name' = The cookie name
| 'csrf_expire' = The number in seconds the token should expire.
| 'csrf_regenerate' = Regenerate token on every submission
| 'csrf_exclude_uris' = Array of URIs which ignore CSRF checks
*/
$config['csrf_protection'] = FALSE;
$config['csrf_token_name'] = 'csrf_test_name';
$config['csrf_cookie_name'] = 'csrf_cookie_name';
$config['csrf_expire'] = 7200;
$config['csrf_regenerate'] = TRUE;
$config['csrf_exclude_uris'] = array();
/*
|--------------------------------------------------------------------------
| Output Compression
|--------------------------------------------------------------------------
|
| Enables Gzip output compression for faster page loads. When enabled,
| the output class will test whether your server supports Gzip.
| Even if it does, however, not all browsers support compression
| so enable only if you are reasonably sure your visitors can handle it.
|
| Only used if zlib.output_compression is turned off in your php.ini.
| Please do not use it together with httpd-level output compression.
|
| VERY IMPORTANT: If you are getting a blank page when compression is enabled it
| means you are prematurely outputting something to your browser. It could
| even be a line of whitespace at the end of one of your scripts. For
| compression to work, nothing can be sent before the output buffer is called
| by the output class. Do not 'echo' any values with compression enabled.
|
*/
$config['compress_output'] = FALSE;
/*
|--------------------------------------------------------------------------
| Master Time Reference
|--------------------------------------------------------------------------
|
| Options are 'local' or any PHP supported timezone. This preference tells
| the system whether to use your server's local time as the master 'now'
| reference, or convert it to the configured one timezone. See the 'date
| helper' page of the user guide for information regarding date handling.
|
*/
$config['time_reference'] = 'local';
/*
|--------------------------------------------------------------------------
| Rewrite PHP Short Tags
|--------------------------------------------------------------------------
|
| If your PHP installation does not have short tag support enabled CI
| can rewrite the tags on-the-fly, enabling you to utilize that syntax
| in your view files. Options are TRUE or FALSE (boolean)
|
| Note: You need to have eval() enabled for this to work.
|
*/
$config['rewrite_short_tags'] = FALSE;
/*
|--------------------------------------------------------------------------
| Reverse Proxy IPs
|--------------------------------------------------------------------------
|
| If your server is behind a reverse proxy, you must whitelist the proxy
| IP addresses from which CodeIgniter should trust headers such as
| HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP in order to properly identify
| the visitor's IP address.
|
| You can use both an array or a comma-separated list of proxy addresses,
| as well as specifying whole subnets. Here are a few examples:
|
| Comma-separated: '10.0.1.200,192.168.5.0/24'
| Array: array('10.0.1.200', '192.168.5.0/24')
*/
$config['proxy_ips'] = '';
-98
View File
@@ -1,98 +0,0 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/*
| -------------------------------------------------------------------
| DATABASE CONNECTIVITY SETTINGS
| -------------------------------------------------------------------
| This file will contain the settings needed to access your database.
|
| For complete instructions please consult the 'Database Connection'
| page of the User Guide.
|
| -------------------------------------------------------------------
| EXPLANATION OF VARIABLES
| -------------------------------------------------------------------
|
| ['dsn'] The full DSN string describe a connection to the database.
| ['hostname'] The hostname of your database server.
| ['username'] The username used to connect to the database
| ['password'] The password used to connect to the database
| ['database'] The name of the database you want to connect to
| ['dbdriver'] The database driver. e.g.: mysqli.
| Currently supported:
| cubrid, ibase, mssql, mysql, mysqli, oci8,
| odbc, pdo, postgre, sqlite, sqlite3, sqlsrv
| ['dbprefix'] You can add an optional prefix, which will be added
| to the table name when using the Query Builder class
| ['pconnect'] TRUE/FALSE - Whether to use a persistent connection
| ['db_debug'] TRUE/FALSE - Whether database errors should be displayed.
| ['cache_on'] TRUE/FALSE - Enables/disables query caching
| ['cachedir'] The path to the folder where cache files should be stored
| ['char_set'] The character set used in communicating with the database
| ['dbcollat'] The character collation used in communicating with the database
| NOTE: For MySQL and MySQLi databases, this setting is only used
| as a backup if your server is running PHP < 5.2.3 or MySQL < 5.0.7
| (and in table creation queries made with DB Forge).
| There is an incompatibility in PHP with mysql_real_escape_string() which
| can make your site vulnerable to SQL injection if you are using a
| multi-byte character set and are running versions lower than these.
| Sites using Latin-1 or UTF-8 database character set and collation are unaffected.
| ['swap_pre'] A default table prefix that should be swapped with the dbprefix
| ['encrypt'] Whether or not to use an encrypted connection.
|
| 'mysql' (deprecated), 'sqlsrv' and 'pdo/sqlsrv' drivers accept TRUE/FALSE
| 'mysqli' and 'pdo/mysql' drivers accept an array with the following options:
|
| 'ssl_key' - Path to the private key file
| 'ssl_cert' - Path to the public key certificate file
| 'ssl_ca' - Path to the certificate authority file
| 'ssl_capath' - Path to a directory containing trusted CA certificates in PEM format
| 'ssl_cipher' - List of *allowed* ciphers to be used for the encryption, separated by colons (':')
| 'ssl_verify' - TRUE/FALSE; Whether verify the server certificate or not
|
| ['compress'] Whether or not to use client compression (MySQL only)
| ['stricton'] TRUE/FALSE - forces 'Strict Mode' connections
| - good for ensuring strict SQL while developing
| ['ssl_options'] Used to set various SSL options that can be used when making SSL connections.
| ['failover'] array - A array with 0 or more data for connections if the main should fail.
| ['save_queries'] TRUE/FALSE - Whether to "save" all executed queries.
| NOTE: Disabling this will also effectively disable both
| $this->db->last_query() and profiling of DB queries.
| When you run a query, with this setting set to TRUE (default),
| CodeIgniter will store the SQL statement for debugging purposes.
| However, this may cause high memory usage, especially if you run
| a lot of SQL queries ... disable this to avoid that problem.
|
| The $active_group variable lets you choose which connection group to
| make active. By default there is only one group (the 'default' group).
|
| The $query_builder variables lets you determine whether or not to load
| the query builder class.
*/
$active_group = 'default';
$query_builder = TRUE;
$db['default'] = array(
'dsn' => '',
'hostname' => '103.242.106.56', // tanpa port
'port' => 3310, // port PostgreSQL
'username' => 'accointing_dev',
'password' => 'accointing_dev',
'database' => 'accointing_dev',
'dbdriver' => 'mysqli', // WAJIB: driver PostgreSQL
'dbprefix' => '',
'pconnect' => FALSE,
'db_debug' => (ENVIRONMENT !== 'production'),
'cache_on' => FALSE,
'cachedir' => '',
'char_set' => 'utf8',
'dbcollat' => 'utf8_general_ci', // PostgreSQL abaikan ini, tapi biarkan saja
'swap_pre' => '',
'encrypt' => FALSE,
'compress' => FALSE,
'stricton' => FALSE,
'failover' => array(),
'save_queries' => TRUE
);
@@ -0,0 +1,103 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Attendancemonthly extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->database();
}
public function index()
{
$data = [
"active_menu" => "attendance_monthly"
];
$this->load->view('partials/header', $data);
$this->load->view('employees/attendance_monthly');
$this->load->view('partials/footer');
}
public function get_data()
{
$date = $this->input->get('date') . '-01';
if(!$date){
$date = date('Y-m-d');
}
$data = $this->db->query("
SELECT
a.*,
e.employee_code,
e.full_name
FROM k_attendance_monthly a
LEFT JOIN k_employees e ON e.id = a.employee_id
WHERE a.month = '$date'
ORDER BY a.updated_at DESC
")->result();
$rows = [];
$no = 1;
foreach($data as $row){
$rows[] = [
$no++,
$row->employee_code,
$row->full_name,
tanggal_indo($row->month,'F Y'),
$row->status,
'
<button class="btn btn-sm btn-info btn-detail"
data-id="'.$row->id.'" data-month="'.$row->month.'">Detail</button>
'
];
}
echo json_encode([
'data' => $rows
]);
}
// public function detail($id)
// {
// echo json_encode(
// $this->db->get_where('k_attendance_monthly',['id'=>$id])->row()
// );
// }
public function detail($id, $month)
{
$header = $this->db
->select('a.*, e.full_name, e.employee_code')
->from('k_attendance_monthly a')
->join('k_employees e','e.id = a.employee_id', 'left')
->where('a.id',$id)
->where('a.month',$month)
->get()
->row();
$startDate = $month;
$endDate = substr($month, 0, 7) . date('-t');
$detail = $this->db
->select('a.attendance_date, a.attendance_status, b.shift_name')
->from('k_attendances a')
->join('k_shifts b', 'b.id = a.shift_id', 'left')
->where('a.employee_id', $header->employee_id)
->where('a.attendance_date >=', $startDate)
->where('a.attendance_date <=', $endDate)
->get()
->result();
echo json_encode([
'header'=>$header,
'detail'=>$detail
]);
}
}
+216
View File
@@ -137,4 +137,220 @@ class Cronjob extends CI_Controller {
'period' => $period 'period' => $period
]); ]);
} }
// =====================================================
// CRONJOB UNTUK LIHAT KEHADIRAN SELAMA 1 BULAN
// =====================================================
// | Kriteria | Pengurangan |
// | ---------------------- | ------------------: |
// | Alpha | -20 poin / hari |
// | Terlambat | -2 poin / kejadian |
// | Izin (permit) | -1 poin / hari |
// | Akumulasi telat | -1 poin / 60 menit |
// | Akumulasi pulang cepat | -1 poin / 120 menit |
public function generateMonthlyAttendance()
{
$month = date('Y-m-01');
$startDate = date('Y-m-01', strtotime($month));
$endDate = date('Y-m-t', strtotime($month));
$employees = $this->db
->where('is_active', 1)
->get('k_employees')
->result();
$processed = 0;
$this->db->trans_begin();
try {
foreach ($employees as $employee) {
// Total Hari Kerja
$schedule = $this->db->query("
SELECT
COUNT(*) AS total_hari_kerja
FROM k_employee_shift_schedules
WHERE employee_id = ?
AND status IN ('published','done')
AND schedule_date BETWEEN ? AND ?
", array(
$employee->id,
$startDate,
$endDate
))->row();
$total_hari_kerja = (int) $schedule->total_hari_kerja;
// Rekap Absensi
$attendance = $this->db->query("
SELECT
COALESCE(SUM(attendance_status='present'),0) AS total_present,
COALESCE(SUM(attendance_status='late'),0) AS total_late,
COALESCE(SUM(attendance_status='alpha'),0) AS total_alpha,
COALESCE(SUM(attendance_status='sick'),0) AS total_sick,
COALESCE(SUM(attendance_status='permit'),0) AS total_permit,
COALESCE(SUM(attendance_status='annual'),0) AS total_annual,
COALESCE(SUM(late_minutes),0) AS total_late_minutes,
COALESCE(SUM(early_leave_minutes),0) AS total_early_leave_minutes,
COALESCE(SUM(work_hours),0) AS total_work_hours,
COALESCE(SUM(overtime_hours),0) AS total_overtime_hours
FROM k_attendances
WHERE employee_id = ?
AND attendance_date BETWEEN ? AND ?
", array(
$employee->id,
$startDate,
$endDate
))->row();
$total_present = (int) $attendance->total_present;
$total_late = (int) $attendance->total_late;
$total_alpha = (int) $attendance->total_alpha;
$total_sick = (int) $attendance->total_sick;
$total_permit = (int) $attendance->total_permit;
$total_annual = (int) $attendance->total_annual;
// Kehadiran
$total_hadir = $total_present + $total_late;
$attendance_percentage = 0;
if ($total_hari_kerja > 0) {
$attendance_percentage = round(
($total_hadir / $total_hari_kerja) * 100,
2
);
}
// SCOR
$score = 100;
// Alpha
$score -= ($total_alpha * 20);
// Telat
$score -= ($total_late * 2);
// Izin
$score -= ($total_permit * 1);
// Total menit telat
$score -= floor(
((int) $attendance->total_late_minutes) / 60
);
// Total menit pulang cepat
$score -= floor(
((int) $attendance->total_early_leave_minutes) / 120
);
if ($score < 0) {
$score = 0;
}
// LOGIKA STATUS
if ($score >= 90) {
$status = 'baik sekali';
} elseif ($score >= 75) {
$status = 'baik';
} elseif ($score >= 60) {
$status = 'kurang baik';
} else {
$status = 'buruk';
}
// INSERT OR UPDATE TO k_attendance_monthly
$sql = "
INSERT INTO k_attendance_monthly (
employee_id, month,
total_present, total_late, total_alpha, total_sick, total_permit, total_annual,
total_late_minutes, total_early_leave_minutes,
total_work_hours, total_overtime_hours,
total_hari_kerja, total_hadir, attendance_percentage,
score, status
)
VALUES (
?, ?,
?, ?, ?, ?, ?, ?,
?, ?,
?, ?,
?, ?, ?,
?, ?
)
ON DUPLICATE KEY UPDATE
total_present = VALUES(total_present),
total_late = VALUES(total_late),
total_alpha = VALUES(total_alpha),
total_sick = VALUES(total_sick),
total_permit = VALUES(total_permit),
total_annual = VALUES(total_annual),
total_late_minutes = VALUES(total_late_minutes),
total_early_leave_minutes = VALUES(total_early_leave_minutes),
total_work_hours = VALUES(total_work_hours),
total_overtime_hours = VALUES(total_overtime_hours),
total_hari_kerja = VALUES(total_hari_kerja),
total_hadir = VALUES(total_hadir),
attendance_percentage = VALUES(attendance_percentage),
score = VALUES(score),
status = VALUES(status),
updated_at = CURRENT_TIMESTAMP
";
$this->db->query($sql, array(
$employee->id,
$month,
$total_present,
$total_late,
$total_alpha,
$total_sick,
$total_permit,
$total_annual,
(int)$attendance->total_late_minutes,
(int)$attendance->total_early_leave_minutes,
(float)$attendance->total_work_hours,
(float)$attendance->total_overtime_hours,
$total_hari_kerja,
$total_hadir,
$attendance_percentage,
$score,
$status
));
$processed++;
}
if ($this->db->trans_status() === FALSE) {
throw new Exception('Transaction failed');
}
$this->db->trans_commit();
echo json_encode(array(
'status' => true,
'month' => $month,
'processed' => $processed,
'message' => 'Monthly attendance generated successfully'
));
} catch (Exception $e) {
$this->db->trans_rollback();
echo json_encode(array(
'status' => false,
'month' => $month,
'message' => $e->getMessage()
));
}
}
} }
+514 -357
View File
@@ -3,6 +3,18 @@ defined('BASEPATH') OR exit('No direct script access allowed');
class Invoices extends CI_Controller { class Invoices extends CI_Controller {
// =========================
// 🔥 CONFIG AKUN
// =========================
private $akun_persediaan = 21;
private $akun_hpp = 63;
private $akun_piutang = 19;
private $pendapatan_dimuka = 78; // Pendapatan Diterima Dimuka
private $persediaan_dijual = 80; // Persediaan Dalam Penjualan
private $pendapatan_barang = 62;
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
@@ -405,297 +417,6 @@ class Invoices extends CI_Controller {
]); ]);
} }
public function bayar()
{
$this->output->set_content_type('application/json');
$invoice_id = $this->input->post('invoice_id');
$tanggal_bayar = $this->input->post('tanggal_bayar');
$detail = $this->input->post('detail');
if(!$invoice_id || empty($detail)){
echo json_encode(['status'=>false,'message'=>'Data tidak valid']);
return;
}
$invoice = $this->db->get_where('invoices',['id'=>$invoice_id])->row();
if(!$invoice){
echo json_encode(['status'=>false,'message'=>'Invoice tidak ditemukan']);
return;
}
$customer = $this->db->get_where('customers',['id'=>$invoice->customer_id])->row();
if(!$customer){
echo json_encode(['status'=>false,'message'=>'Customer tidak ditemukan']);
return;
}
// =========================
// HITUNG TOTAL BAYAR
// =========================
$total_bayar_input = 0;
foreach ($detail as $d) {
$total_bayar_input += (float)$d['amount'];
}
if($total_bayar_input <= 0){
echo json_encode(['status'=>false,'message'=>'Total bayar harus > 0']);
return;
}
// =========================
// DATA INVOICE
// =========================
$paid_before = (float)$invoice->total_bayar;
$total_invoice = (float)$invoice->total;
$new_total_bayar = $paid_before + $total_bayar_input;
$sisa_setelah = $total_invoice - $new_total_bayar;
if($new_total_bayar > $total_invoice){
echo json_encode([
'status'=>false,
'message'=>'Pembayaran melebihi total invoice'
]);
return;
}
// =========================
// AKUN
// =========================
$akun_tabungan = 70;
$akun_barang = 62;
$akun_piutang = 19;
// =========================
// HITUNG TOTAL CICILAN
// =========================
$cicilan = $this->db->query("
SELECT ii.*
FROM invoice_installments ii
JOIN (
SELECT invoice_detail_id, MIN(angsuran_ke) as angsuran_ke
FROM invoice_installments
WHERE customer_id = ?
AND status != 'paid'
GROUP BY invoice_detail_id
) x
ON x.invoice_detail_id = ii.invoice_detail_id
AND x.angsuran_ke = ii.angsuran_ke
", [$customer->id])->result();
$total_cicilan = 0;
foreach($cicilan as $c){
$total_cicilan += ($c->nominal - $c->paid_amount);
}
// =========================
// TOTAL PER KATEGORI
// =========================
$total_tabungan = (float) $this->db
->select('SUM(subtotal) as total')
->where('invoice_id', $invoice_id)
->where('account_id', $akun_tabungan)
->get('invoice_details')->row()->total ?? 0;
$total_barang = (float) $this->db
->select('SUM(subtotal) as total')
->where('invoice_id', $invoice_id)
->where('account_id', $akun_barang)
->get('invoice_details')->row()->total ?? 0;
$total_other = (float) $this->db
->select('SUM(subtotal) as total')
->where('invoice_id', $invoice_id)
->where_not_in('account_id', [$akun_tabungan, $akun_barang])
->get('invoice_details')->row()->total ?? 0;
// =========================
// SISA PER KATEGORI
// =========================
$sisa_other = max(0, $total_other - $paid_before);
$sisa_tabungan = max(0, $total_tabungan - max(0, $paid_before - $total_other));
$sisa_barang = max(0, $total_barang - max(0, $paid_before - $total_other - $total_tabungan));
// =========================
// ALOKASI
// =========================
$sisa_bayar = $total_bayar_input;
// =========================
// 1. CICILAN (PER ITEM)
// =========================
$bayar_cicilan = min($sisa_bayar, $total_cicilan);
$sisa_bayar -= $bayar_cicilan;
// =========================
// 2. OTHER
// =========================
$bayar_other = min($sisa_bayar, $sisa_other);
$sisa_bayar -= $bayar_other;
// =========================
// 3. TABUNGAN
// =========================
$bayar_tabungan = min($sisa_bayar, $sisa_tabungan);
$sisa_bayar -= $bayar_tabungan;
// =========================
// 4. BARANG
// =========================
$bayar_barang = min($sisa_bayar, $sisa_barang);
$sisa_bayar -= $bayar_barang;
// =========================
// TRANSACTION
// =========================
$this->db->trans_start();
// UPDATE invoice_installments jika perlu di update
if($bayar_cicilan > 0){
$sisa = $bayar_cicilan; // ✅ FIX
foreach($cicilan as $c){
if($sisa <= 0) break;
$kurang = $c->nominal - $c->paid_amount;
$bayar = min($sisa, $kurang);
$sisa -= $bayar;
$new_paid = $c->paid_amount + $bayar;
$status = ($new_paid >= $c->nominal) ? 'paid' : 'partial';
$this->db->where('id',$c->id)->update('invoice_installments',[
'paid_amount' => $new_paid,
'status' => $status
]);
}
}
$no_ref = 'PAY-' . date('YmdHis');
$this->db->insert('journals', [
'tanggal' => $tanggal_bayar,
'no_ref' => $no_ref,
'keterangan'=> 'Diterima Pembayaran Invoice '.$invoice->no_invoice.' ('.$customer->nama.')',
'ref_type' => 'invoice_payment',
'ref_id' => $invoice_id,
'created_by'=> $this->session->userdata('user_id')
]);
$journal_id = $this->db->insert_id();
// DEBIT
$total_debit = 0;
foreach ($detail as $d) {
if(empty($d['account_id']) || $d['amount'] <= 0) continue;
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $d['account_id'],
'debit' => $d['amount'],
'kredit' => 0
]);
$total_debit += $d['amount'];
}
// =========================
// KREDIT
// =========================
$total_kredit = 0;
$kredit_piutang = $bayar_cicilan + $bayar_other + $bayar_barang;
if($kredit_piutang > 0){
$this->db->insert('journal_details', [
'journal_id'=>$journal_id,
'account_id'=>$akun_piutang,
'debit'=>0,
'kredit'=>$kredit_piutang
]);
$total_kredit += $kredit_piutang;
}
if($bayar_tabungan > 0){
$this->db->insert('journal_details', [
'journal_id'=>$journal_id,
'account_id'=>$akun_tabungan,
'debit'=>0,
'kredit'=>$bayar_tabungan
]);
$total_kredit += $bayar_tabungan;
}
// VALIDASI
if(round($total_debit,2) !== round($total_kredit,2)){
$this->db->trans_rollback();
echo json_encode([
'status'=>false,
'message'=>'Jurnal tidak balance'
]);
return;
}
// PAYMENT
$this->db->insert('payments', [
'invoice_id'=>$invoice_id,
'tanggal'=>$tanggal_bayar,
'jumlah'=>$total_bayar_input,
'metode'=>'manual',
'journal_id'=>$journal_id
]);
// STATUS
if($new_total_bayar >= $total_invoice){
$status = 'paid';
$sisa_setelah = 0;
}elseif($new_total_bayar > 0){
$status = 'partial';
}else{
$status = 'unpaid';
}
$this->db->where('id',$invoice_id)->update('invoices',[
'total_bayar'=>$new_total_bayar,
'sisa_piutang'=>$sisa_setelah,
'status'=>$status,
'updated_at'=>date('Y-m-d H:i:s'),
'updated_by'=>$this->session->userdata('user_id')
]);
$this->db->trans_complete();
// =========================
// RESPONSE
// =========================
if (!$this->db->trans_status()) {
echo json_encode([
'status' => false,
'message' => 'Gagal menyelesaikan pembayaran'
]);
return;
}
log_activity(
'invoice',
'create',
'Melakukan pembayaran invoice ' . $invoice->no_invoice . ' untuk customer a.n ' . $customer->nama,
'success'
);
echo json_encode([
'status'=>true,
'message'=>'Pembayaran berhasil',
'sisa'=>$sisa_setelah
]);
}
// ========================= // =========================
// DELETE // DELETE
// ========================= // =========================
@@ -818,13 +539,6 @@ class Invoices extends CI_Controller {
echo json_encode($data); echo json_encode($data);
} }
// =========================
// 🔥 CONFIG AKUN
// =========================
private $akun_persediaan = 21;
private $akun_hpp = 63;
private $akun_piutang = 19;
// ========================= // =========================
// ADD ITEM - FIXED RESPONSE // ADD ITEM - FIXED RESPONSE
// ========================= // =========================
@@ -909,7 +623,8 @@ class Invoices extends CI_Controller {
'warehouse_id' => $warehouse_id, 'warehouse_id' => $warehouse_id,
'keterangan' => $this->input->post('keterangan'), 'keterangan' => $this->input->post('keterangan'),
'subtotal_asli' => $harga_asli, 'subtotal_asli' => $harga_asli,
'is_cicilan' => $is_cicilan ? 1 : 0 'is_cicilan' => $is_cicilan ? 1 : 0,
'total_hpp_barang' => $total_hpp
]; ];
$this->db->insert('invoice_details', $insert_data); $this->db->insert('invoice_details', $insert_data);
@@ -921,6 +636,7 @@ class Invoices extends CI_Controller {
if($this->input->post('account_id') != 70) { if($this->input->post('account_id') != 70) {
$this->generate_jurnal_item($detail_id, $ket_cicilan_jr); $this->generate_jurnal_item($detail_id, $ket_cicilan_jr);
} }
$this->update_total($invoice_id); $this->update_total($invoice_id);
// ambil customer dari invoice // ambil customer dari invoice
@@ -995,12 +711,15 @@ class Invoices extends CI_Controller {
$nama_item = $this->input->post('nama_item'); $nama_item = $this->input->post('nama_item');
$tanggal = $this->input->post('tanggal'); $tanggal = $this->input->post('tanggal');
$total_hpp = 0;
if($item_id){ if($item_id){
$item = $this->db->get_where('items', ['id'=>$item_id])->row(); $item = $this->db->get_where('items', ['id'=>$item_id])->row();
if($item){ if($item){
$nama_item = $item->nama_barang; $nama_item = $item->nama_barang;
$stok = $this->get_stok($item_id); $stok = $this->get_stok($item_id);
$qty = (float)$this->input->post('qty'); $qty = (float)$this->input->post('qty');
$total_hpp = $this->input->post('qty') * $item->harga_beli;
if($stok < $qty){ if($stok < $qty){
echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup']); echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup']);
return; return;
@@ -1021,7 +740,8 @@ class Invoices extends CI_Controller {
'harga' => $harga, 'harga' => $harga,
'subtotal' => $qty * $harga, 'subtotal' => $qty * $harga,
'warehouse_id' => $this->input->post('warehouse_id'), 'warehouse_id' => $this->input->post('warehouse_id'),
'keterangan' => $this->input->post('keterangan') 'keterangan' => $this->input->post('keterangan'),
'total_hpp_barang' => $total_hpp
]; ];
$this->db->where('id', $id)->update('invoice_details', $update_data); $this->db->where('id', $id)->update('invoice_details', $update_data);
@@ -1112,60 +832,6 @@ class Invoices extends CI_Controller {
echo json_encode($cicilan_raw); echo json_encode($cicilan_raw);
} }
// public function save_cicilan()
// {
// $cicilan_ids = $this->input->post('cicilan_id');
// if (empty($cicilan_ids)) {
// echo json_encode([
// 'status' => false,
// 'message' => 'Tidak ada data dipilih'
// ]);
// return;
// }
// $this->db->trans_start();
// foreach ($cicilan_ids as $id) {
// // ambil data cicilan
// $cicilan = $this->db->get_where('invoice_installments', [
// 'id' => $id
// ])->row();
// if (!$cicilan) continue;
// $sisa = $cicilan->nominal - $cicilan->paid_amount;
// // insert ke invoice_details
// $this->db->insert('invoice_details', [
// 'invoice_id' => $cicilan->invoice_id,
// 'customer_id' => $cicilan->customer_id,
// 'item_id' => $cicilan->item_id,
// 'qty' => 1,
// 'price' => $sisa,
// 'total' => $sisa,
// 'source_cicilan_id'=> $cicilan->id, // penting buat tracking
// 'created_at' => date('Y-m-d H:i:s')
// ]);
// // update status cicilan jadi billed
// $this->db->update('invoice_installments', [
// 'is_billed' => 1
// ], ['id' => $id]);
// }
// $this->db->trans_complete();
// echo json_encode([
// 'status' => $this->db->trans_status()
// ]);
// }
// =========================
// HAPUS HELPER LAMA - TIDAK DIPAKAI LAGI
// =========================
// Hapus fungsi commit() & rollback() karena sudah inline
private function generate_jurnal_item($id, $ket_cicilan_jr = null) private function generate_jurnal_item($id, $ket_cicilan_jr = null)
{ {
// $d = $this->db->get_where('invoice_details', ['id'=>$id])->row(); // $d = $this->db->get_where('invoice_details', ['id'=>$id])->row();
@@ -1254,11 +920,11 @@ class Invoices extends CI_Controller {
]); ]);
// ========================= // =========================
// 🔥 PENDAPATAN (KREDIT) // 🔥 PENDAPATAN (KREDIT) Pendapatan Diterima Dimuka
// ========================= // =========================
$this->db->insert('journal_details', [ $this->db->insert('journal_details', [
'journal_id' => $jid, 'journal_id' => $jid,
'account_id' => $d->account_id, 'account_id' => $this->pendapatan_dimuka,
'debit' => 0, 'debit' => 0,
'kredit' => $nilai_jurnal 'kredit' => $nilai_jurnal
]); ]);
@@ -1271,7 +937,7 @@ class Invoices extends CI_Controller {
// HPP (debit) // HPP (debit)
$this->db->insert('journal_details', [ $this->db->insert('journal_details', [
'journal_id' => $jid, 'journal_id' => $jid,
'account_id' => $this->akun_hpp, 'account_id' => $this->persediaan_dijual,
'debit' => $total_hpp, 'debit' => $total_hpp,
'kredit' => 0 'kredit' => 0
]); ]);
@@ -1689,4 +1355,495 @@ class Invoices extends CI_Controller {
'status' => $this->db->trans_status() 'status' => $this->db->trans_status()
]); ]);
} }
public function bayar()
{
$this->output->set_content_type('application/json');
$invoice_id = $this->input->post('invoice_id');
$tanggal_bayar = $this->input->post('tanggal_bayar');
$detail = $this->input->post('detail');
if (!$invoice_id || empty($detail)) {
echo json_encode(['status' => false, 'message' => 'Data tidak valid']);
return;
}
$invoice = $this->db->get_where('invoices', ['id' => $invoice_id])->row();
if (!$invoice) {
echo json_encode(['status' => false, 'message' => 'Invoice tidak ditemukan']);
return;
}
$customer = $this->db->get_where('customers', ['id' => $invoice->customer_id])->row();
if (!$customer) {
echo json_encode(['status' => false, 'message' => 'Customer tidak ditemukan']);
return;
}
// =========================
// HITUNG TOTAL BAYAR
// =========================
$total_bayar_input = 0;
foreach ($detail as $d) {
$total_bayar_input += (float)$d['amount'];
}
if ($total_bayar_input <= 0) {
echo json_encode(['status' => false, 'message' => 'Total bayar harus > 0']);
return;
}
// =========================
// DATA INVOICE
// =========================
$paid_before = (float)$invoice->total_bayar;
$total_invoice = (float)$invoice->total;
$new_total_bayar = $paid_before + $total_bayar_input;
$sisa_setelah = $total_invoice - $new_total_bayar;
if ($new_total_bayar > $total_invoice) {
echo json_encode([
'status' => false,
'message' => 'Pembayaran melebihi total invoice'
]);
return;
}
// =========================
// KONFIGURASI AKUN
// =========================
$akun_tabungan = 70;
$akun_barang = 62;
$akun_piutang = 19;
$akun_pendapatan_ditahan = 78;
$akun_hpp = isset($this->akun_hpp) ? $this->akun_hpp : 81;
$persediaan_dijual = isset($this->persediaan_dijual) ? $this->persediaan_dijual : 50;
// =========================
// HITUNG CICILAN
// =========================
$cicilan = $this->db->query("
SELECT ii.*
FROM invoice_installments ii
JOIN (
SELECT invoice_detail_id, MIN(angsuran_ke) as angsuran_ke
FROM invoice_installments
WHERE customer_id = ?
AND status != 'paid'
GROUP BY invoice_detail_id
) x
ON x.invoice_detail_id = ii.invoice_detail_id
AND x.angsuran_ke = ii.angsuran_ke
", [$customer->id])->result();
$total_cicilan = 0;
foreach ($cicilan as $c) {
$total_cicilan += ($c->nominal - $c->paid_amount);
}
// =========================
// TOTAL PER KATEGORI
// =========================
$total_tabungan = (float)$this->db
->select('SUM(subtotal) as total')
->where('invoice_id', $invoice_id)
->where('account_id', $akun_tabungan)
->get('invoice_details')->row()->total ?? 0;
$total_barang = (float)$this->db
->select('SUM(subtotal) as total')
->where('invoice_id', $invoice_id)
->where('account_id', $akun_barang)
->get('invoice_details')->row()->total ?? 0;
$total_other = (float)$this->db
->select('SUM(subtotal) as total')
->where('invoice_id', $invoice_id)
->where_not_in('account_id', [$akun_tabungan, $akun_barang])
->get('invoice_details')->row()->total ?? 0;
// =========================
// SISA PER KATEGORI
// =========================
$sisa_other = max(0, $total_other - $paid_before);
$sisa_tabungan = max(0, $total_tabungan - max(0, $paid_before - $total_other));
$sisa_barang = max(0, $total_barang - max(0, $paid_before - $total_other - $total_tabungan));
// =========================
// ALOKASI PEMBAYARAN
// =========================
$sisa_bayar = $total_bayar_input;
$bayar_cicilan = min($sisa_bayar, $total_cicilan);
$sisa_bayar -= $bayar_cicilan;
$bayar_other = min($sisa_bayar, $sisa_other);
$sisa_bayar -= $bayar_other;
$bayar_tabungan = min($sisa_bayar, $sisa_tabungan);
$sisa_bayar -= $bayar_tabungan;
$bayar_barang = min($sisa_bayar, $sisa_barang);
$sisa_bayar -= $bayar_barang;
// =========================
// AMBIL INVOICE DETAIL
// =========================
$inv_detail_items = $this->db
->select('
invoice_details.id,
invoice_details.account_id,
invoice_details.subtotal,
invoice_details.recognized_amount,
invoice_details.total_hpp_barang,
invoice_details.recognized_hpp,
accounts.priority
')
->from('invoice_details')
->join('accounts', 'accounts.id = invoice_details.account_id', 'left')
->where('invoice_details.invoice_id', $invoice_id)
->where('invoice_details.account_id !=', $akun_tabungan)
->order_by('accounts.priority', 'ASC')
->order_by('invoice_details.id', 'ASC')
->get()
->result();
// =========================
// DEBIT PENDAPATAN DITAHAN
// =========================
// Tidak termasuk tabungan!
$debit_pendapatan_ditahan = $bayar_cicilan + $bayar_other + $bayar_barang;
// =========================
// MULAI TRANSAKSI
// =========================
$this->db->trans_start();
// UPDATE cicilan
if ($bayar_cicilan > 0) {
$sisa = $bayar_cicilan;
foreach ($cicilan as $c) {
if ($sisa <= 0) break;
$kurang = $c->nominal - $c->paid_amount;
$bayar = min($sisa, $kurang);
$sisa -= $bayar;
$new_paid = $c->paid_amount + $bayar;
$status = ($new_paid >= $c->nominal) ? 'paid' : 'partial';
$this->db->where('id', $c->id)->update('invoice_installments', [
'paid_amount' => $new_paid,
'status' => $status
]);
}
}
$no_ref = 'PAY-' . date('YmdHis');
$this->db->insert('journals', [
'tanggal' => $tanggal_bayar,
'no_ref' => $no_ref,
'keterangan'=> 'Pembayaran Invoice ' . $invoice->no_invoice . ' (' . $customer->nama . ')',
'ref_type' => 'invoice_payment',
'ref_id' => $invoice_id,
'created_by'=> $this->session->userdata('user_id')
]);
$journal_id = $this->db->insert_id();
// =========================
// DEBIT SIDE
// =========================
$total_debit = 0;
$jurnal_debit = [];
// 1. Kas/Bank (dari detail)
foreach ($detail as $d) {
if (empty($d['account_id']) || $d['amount'] <= 0) continue;
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $d['account_id'],
'debit' => $d['amount'],
'kredit' => 0
]);
$total_debit += (float)$d['amount'];
$jurnal_debit[] = [
'account_id' => $d['account_id'],
'debit' => (float)$d['amount'],
'jenis' => 'kas'
];
}
// =========================
// KREDIT SIDE
// =========================
$total_kredit = 0;
$jurnal_kredit = [];
// 1. Piutang (KREDIT)
$kredit_piutang = $total_bayar_input - $bayar_tabungan;
if ($kredit_piutang > 0) {
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $akun_piutang,
'debit' => 0,
'kredit' => $kredit_piutang
]);
$total_kredit += $kredit_piutang;
$jurnal_kredit[] = [
'account_id' => $akun_piutang,
'kredit' => $kredit_piutang,
'jenis' => 'piutang'
];
}
// 2. Tabungan (KREDIT)
if ($bayar_tabungan > 0) {
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $akun_tabungan,
'debit' => 0,
'kredit' => $bayar_tabungan
]);
$total_kredit += $bayar_tabungan;
$jurnal_kredit[] = [
'account_id' => $akun_tabungan,
'kredit' => $bayar_tabungan,
'jenis' => 'tabungan'
];
}
// =========================
// PENGAKUAN PENDAPATAN & HPP
// =========================
$sisa_pengakuan = $bayar_cicilan + $bayar_other + $bayar_barang;
$account_recognition = [];
$total_hpp = 0;
foreach ($inv_detail_items as $i) {
if ($sisa_pengakuan <= 0) break;
$belum_diakui = (float)$i->subtotal - (float)$i->recognized_amount;
if ($belum_diakui <= 0) continue;
$nominal = min($sisa_pengakuan, $belum_diakui);
if (!isset($account_recognition[$i->account_id])) {
$account_recognition[$i->account_id] = 0;
}
$account_recognition[$i->account_id] += $nominal;
// Update recognized amount
$this->db->query("
UPDATE invoice_details
SET recognized_amount = recognized_amount + ?
WHERE id = ?
", [$nominal, $i->id]);
if ($i->account_id == $akun_barang ) {
$persentase = $nominal / (float)$i->subtotal;
$hpp_item = (float)$i->total_hpp_barang * $persentase;
$hpp_item = round($hpp_item, 2);
$sisa_hpp = max(0, (float)$i->total_hpp_barang - (float)$i->recognized_hpp);
$hpp_item = min($hpp_item, $sisa_hpp);
if ($hpp_item > 0) {
// Cara 1: Pakai update() method
$this->db->where('id', $i->id);
$this->db->update('invoice_details', [
'recognized_hpp' => $i->recognized_hpp + $hpp_item
]);
$total_hpp += $hpp_item;
}
}
$sisa_pengakuan -= $nominal;
}
// =========================
// ALOKASI SISA KE AKUN LAIN
// =========================
$total_diaku = array_sum($account_recognition);
$sisa_yg_harus_diaku = $bayar_cicilan + $bayar_other + $bayar_barang;
$selisih_alokasi = $sisa_yg_harus_diaku - $total_diaku;
if ($selisih_alokasi > 0) {
$akun_other_list = $this->db
->select('account_id, SUM(subtotal) as total')
->from('invoice_details')
->where('invoice_id', $invoice_id)
->where_not_in('account_id', [$akun_tabungan, $akun_barang])
->group_by('account_id')
->get()
->result();
foreach ($akun_other_list as $ao) {
if ($selisih_alokasi <= 0) break;
$belum_diaku_akun = (float)$ao->total;
$alokasi = min($selisih_alokasi, $belum_diaku_akun);
if (!isset($account_recognition[$ao->account_id])) {
$account_recognition[$ao->account_id] = 0;
}
$account_recognition[$ao->account_id] += $alokasi;
$selisih_alokasi -= $alokasi;
}
}
// =========================
// Insert DEBIT pendapatan
// =========================
if ($debit_pendapatan_ditahan > 0) {
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $akun_pendapatan_ditahan,
'debit' => $debit_pendapatan_ditahan,
'kredit' => 0
]);
$total_debit += $debit_pendapatan_ditahan;
$jurnal_debit[] = [
'account_id' => $akun_pendapatan_ditahan,
'debit' => $debit_pendapatan_ditahan,
'jenis' => 'pendapatan_ditahan'
];
}
// =========================
// Insert KREDIT pendapatan
// =========================
foreach ($account_recognition as $account_id => $nominal) {
if ($nominal <= 0) continue;
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $account_id,
'debit' => 0,
'kredit' => $nominal
]);
$total_kredit += $nominal;
$jurnal_kredit[] = [
'account_id' => $account_id,
'kredit' => $nominal,
'jenis' => 'pendapatan'
];
}
// =========================
// JURNAL HPP
// =========================
if ($total_hpp > 0) {
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $akun_hpp,
'debit' => $total_hpp,
'kredit' => 0
]);
$total_debit += $total_hpp;
$jurnal_debit[] = [
'account_id' => $akun_hpp,
'debit' => $total_hpp,
'jenis' => 'hpp_barang'
];
$this->db->insert('journal_details', [
'journal_id' => $journal_id,
'account_id' => $persediaan_dijual,
'debit' => 0,
'kredit' => $total_hpp
]);
$total_kredit += $total_hpp;
$jurnal_kredit[] = [
'account_id' => $persediaan_dijual,
'kredit' => $total_hpp,
'jenis' => 'persediaan_dijual'
];
}
// =========================
// VALIDASI JURNAL
// =========================
if (round($total_debit, 2) !== round($total_kredit, 2)) {
$this->db->trans_rollback();
echo json_encode([
'status' => false,
'message' => 'Jurnal tidak balance!'
]);
return;
}
// =========================
// INSERT PAYMENTS
// =========================
$this->db->insert('payments', [
'invoice_id' => $invoice_id,
'tanggal' => $tanggal_bayar,
'jumlah' => $total_bayar_input,
'metode' => 'manual',
'journal_id'=> $journal_id
]);
// =========================
// UPDATE STATUS INVOICE
// =========================
if ($new_total_bayar >= $total_invoice) {
$status = 'paid';
$sisa_setelah = 0;
} elseif ($new_total_bayar > 0) {
$status = 'partial';
} else {
$status = 'unpaid';
}
$this->db->where('id', $invoice_id)->update('invoices', [
'total_bayar' => $new_total_bayar,
'sisa_piutang' => $sisa_setelah,
'status' => $status,
'updated_at' => date('Y-m-d H:i:s'),
'updated_by' => $this->session->userdata('user_id')
]);
// =========================
// SELESAIKAN TRANSAKSI
// =========================
$this->db->trans_complete();
// =========================
// RESPONSE
// =========================
if (!$this->db->trans_status()) {
echo json_encode([
'status' => false,
'message' => 'Gagal menyelesaikan pembayaran'
]);
return;
}
// Logging
log_activity(
'invoice',
'create',
'Pembayaran invoice ' . $invoice->no_invoice . ' - ' . $customer->nama,
'success'
);
echo json_encode([
'status' => true,
'message' => 'Pembayaran berhasil'
]);
}
} }
@@ -0,0 +1,431 @@
<style>
.calendar-grid{
display:grid;
grid-template-columns:repeat(7,1fr);
gap:8px;
}
.calendar-card{
min-height:60px;
font-size:12px;
}
.calendar-card.empty{
visibility:hidden;
}
.calendar-card .badge{
font-size:10px;
}
.badge-custom {
background-color: #ff8874;
color: #fff;
}
.badge-custom1{
background-color: #27F549;
color: #fff;
}
.badge-custom2 {
background-color: #CB21FF;
color: #fff;
}
.badge-custom3 {
background-color: #94A600;
color: #fff;
}
.badge-custom4 {
background-color: #8200C7;
color: #fff;
}
</style>
<div class="container mt-4">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4">
<div class="d-flex justify-content-end align-items-center mb-3">
<h7 class="mb-0">Tanggal</h7>
</div>
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">
Monitoring Absensi Bulanan
</h5>
<input type="month"
id="filterMonth"
class="form-control"
style="max-width:220px"
value="<?= date('Y-m') ?>">
</div>
<div class="table-responsive">
<table id="tableAttendance"
class="table modern-table align-middle w-100">
<thead class="table-light">
<tr>
<th>No</th>
<th>Kode</th>
<th>Nama</th>
<th>Periode</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
<!-- MODAL DETAIL -->
<div class="modal fade" id="modalDetail">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
<h5>Detail Absensi Bulanan</h5>
<button class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="container">
<div class="row">
<div class="col-6">
<table class="table mb-3">
<tbody>
<tr>
<th width="180">No</th>
<td><span id="d_id"></span></td>
</tr>
<tr>
<th>Kode Karyawan</th>
<td><span id="d_employee_code"></span></td>
</tr>
<tr>
<th>Nama Karyawan</th>
<td><span id="d_full_name"></span></td>
</tr>
<tr>
<th>Periode</th>
<td><span id="d_month"></span></td>
</tr>
<tr>
<th>Status</th>
<td><span id="d_status"></span></td>
</tr>
<tr>
<th>Score</th>
<td><span id="d_score"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="col-3">
<table class="table mb-3">
<tbody>
<tr>
<th width="100"><span class="badge bg-success">Hadir</span></th>
<td id="d_present_count"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-info">Sakit</span></th>
<td id="d_sick_count"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-primary">Izin</span></th>
<td id="d_total_permit"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-danger">Alpha</span></th>
<td id="d_absent_count"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-warning">Terlambat</span></th>
<td id="d_late_count"></td>
<th width="180">Menit</th>
</tr>
<tr>
<th><span class="badge badge-custom">Total Early Leave Minutes</span></th>
<td id="d_total_early_leave_minutes"></td>
<th width="180">Menit</th>
</tr>
</tbody>
</table>
</div>
<div class="col-3">
<table class="table mb-3">
<tbody>
<tr>
<th width="100"><span class="badge badge-custom1">Total Hari Kerja</span></th>
<td id="d_total_hari_kerja"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-dark">Cuti</span></th>
<td id="d_total_annual"></td>
<th width="180">Hari</th>
</tr>
<tr>
<th><span class="badge bg-secondary">Total Jam Kerja</span></th>
<td id="d_total_work_hours"></td>
<th width="180">Jam</th>
</tr>
<tr>
<th><span class="badge badge-custom2">Total Late Minutes</span></th>
<td id="d_total_late_minutes"></td>
<th width="180">Menit</th>
</tr>
<tr>
<th><span class="badge badge-custom4">Total Overtime Hours</span></th>
<td id="d_total_overtime_hours"></td>
<th width="180">Jam</th>
</tr>
<tr>
<th><span class="badge badge-custom3">Total Alpha</span></th>
<td id="d_total_alpha"></td>
<th width="180">Hari</th>
</tr>
</tbody>
</table>
</div>
<div class="border-top my-3"></div>
<div id="detailContent"> // VIEW DETAIL ABSENSI BULANAN (DI-GENERATE DARI JS)
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(function(){
let table = $('#tableAttendance').DataTable({
ajax:{
url:`<?= base_url(); ?>attendancemonthly/get_data`,
data:function(d){
d.date = $('#filterMonth').val();
},
dataSrc:'data'
},
columns:[
{data:0},
{data:1},
{data:2},
{data:3},
{data:4},
{data:5}
]
});
$('#filterMonth').change(function(){
table.ajax.reload();
});
});
$(function () {
$(document).on('click', '.btn-detail', function () {
let id = $(this).data('id');
let month = $(this).data('month');
$.get(
`<?= base_url(); ?>attendancemonthly/detail/${id}/${month}`,
function (res) {
// Header
$('#d_id').text(res.header.id);
$('#d_employee_code').text(res.header.employee_code);
$('#d_full_name').text(res.header.full_name);
$('#d_month').text(res.header.month);
$('#d_status').text(res.header.status);
// Header - perhitungan
$('#d_total_hari_kerja').text(res.header.total_hari_kerja);
$('#d_present_count').text(res.header.total_present);
$('#d_total_hadir').text(res.header.total_hadir);
$('#d_late_count').text(res.header.total_late);
$('#d_total_permit').text(res.header.total_permit);
$('#d_sick_count').text(res.header.total_sick);
$('#d_total_overtime_hours').text(res.header.total_overtime_hours);
$('#d_total_work_hours').text(res.header.total_work_hours);
$('#d_total_annual').text(res.header.total_annual);
$('#d_total_late_minutes').text(res.header.total_late_minutes);
$('#d_total_early_leave_minutes').text(res.header.total_early_leave_minutes);
$('#d_score').text(res.header.score);
$('#d_leave_count').text(res.header.total_leave);
$('#d_absent_count').text(res.header.total_alpha);
$('#d_late_count').text(res.header.total_late);
$('#d_total_alpha').text(res.header.total_alpha);
// Mapping data absensi berdasarkan tanggal
let attendanceMap = {};
$.each(res.detail, function (i, row) {
attendanceMap[row.attendance_date] = row;
});
// Ambil bulan dari header
let monthDate = new Date(res.header.month);
let year = monthDate.getFullYear();
let month = monthDate.getMonth();
// Hari pertama bulan
let firstDay = new Date(year, month, 1);
// Jumlah hari dalam bulan
let daysInMonth = new Date(year, month + 1, 0).getDate();
// Konversi agar minggu dimulai Senin
let startDay = firstDay.getDay();
startDay = startDay === 0 ? 6 : startDay - 1;
let html = `
<div class="calendar-wrapper">
<div class="row g-1 fw-bold text-center mb-2">
<div class="col">Sen</div>
<div class="col">Sel</div>
<div class="col">Rab</div>
<div class="col">Kam</div>
<div class="col">Jum</div>
<div class="col">Sab</div>
<div class="col">Min</div>
</div>
<div class="calendar-grid">
`;
// Kosong sebelum tanggal 1
for (let i = 0; i < startDay; i++) {
html += `<div class="calendar-card empty"></div>`;
}
// Isi tanggal
for (let day = 1; day <= daysInMonth; day++) {
let dateStr =
`${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
let data = attendanceMap[dateStr];
let badge = '';
let cardClass = '';
if (data) {
switch (data.attendance_status) {
case 'present':
badge = '<span class="badge bg-success">Hadir</span>';
cardClass = 'border-success';
break;
case 'late':
badge = '<span class="badge bg-warning">Terlambat</span>';
cardClass = 'border-warning';
break;
case 'alpha':
badge = '<span class="badge bg-danger">Alpha</span>';
cardClass = 'border-danger';
break;
case 'sick':
badge = '<span class="badge bg-info">Sakit</span>';
cardClass = 'border-info';
break;
case 'permit':
badge = '<span class="badge bg-secondary">Izin</span>';
cardClass = 'border-secondary';
break;
case 'annual':
badge = '<span class="badge bg-dark">Cuti</span>';
cardClass = 'border-dark';
break;
}
html += `
<div class="calendar-card card ${cardClass}">
<div class="card-body p-2">
<div class="row gx-0">
<div class="col-auto">
<div class="fw-bold" style="font-size: 16px;">${day}</div>
</div>
<div class="col text-end">
<div class="m-0">${badge}</div>
</div>
</div>
<small class="text-muted d-block mt-1" style="font-size:15px;">
${data.shift_name}
</small>
</div>
</div>
`;
} else {
html += `
<div class="calendar-card card">
<div class="card-body p-2">
<div class="fw-bold" style="font-size: 16px;">${day}</div>
</div>
</div>
`;
}
}
html += `
</div>
</div>
`;
$('#detailContent').html(html);
// Bootstrap 5
let modal = new bootstrap.Modal(document.getElementById('modalDetail'));
modal.show();
},
'json'
);
});
});
</script>
+11 -7
View File
@@ -398,17 +398,21 @@ $('#btnSimpan').click(function(){
return; return;
} }
if(totalDebit === 0 && totalKredit === 0){
Swal.fire('Warning','Harap Isi Semua Kolom!','warning'); // ✅ FIX add all collumn must be filled
return;
}
if($('#keterangan').val().trim() === ''){
Swal.fire('Warning','Keterangan tidak boleh kosong!','warning');
return;
}
if(!$('#tanggal').val()){ if(!$('#tanggal').val()){
Swal.fire('Warning','Tanggal wajib diisi!','warning'); // ✅ FIX tambah validasi tanggal Swal.fire('Warning','Tanggal tidak boleh kosong!','warning');
return; return;
} }
if(!$('#keterangan').val()){
Swal.fire('Warning','Keterangan wajib diisi!','warning'); // ✅ FIX tambah validasi keterangan
return;
}
// lanjut simpan // lanjut simpan
$.post("<?= base_url('jurnal/save'); ?>", { $.post("<?= base_url('jurnal/save'); ?>", {
tanggal: $('#tanggal').val(), tanggal: $('#tanggal').val(),
+10 -3
View File
@@ -258,17 +258,24 @@
</li> </li>
<li> <li>
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monitoring') ? 'active' : '' ?>" <a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monitoring') ? 'active' : '' ?>"
href="<?= base_url('attendancemonitoring'); ?>"> href="<?= base_url('attendancemonitoring'); ?>">
<i class="bi bi-fingerprint me-2"></i> <i class="bi bi-fingerprint me-2"></i>
Monitoring Absensi Monitoring Absensi
</a> </a>
</li> </li>
<li>
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monthly') ? 'active' : '' ?>"
href="<?= base_url('attendancemonthly'); ?>">
<i class="bi bi-calendar2-date me-2"></i>
Absensi Bulanan
</a>
</li>
<hr class="dropdown-divider"> <hr class="dropdown-divider">
<li> <li>