Compare commits

..

27 Commits

Author SHA1 Message Date
sean 0d1f83872c perbaikan di invoices 2026-07-30 09:49:00 +07:00
sean 4c2fefe9c3 tambah kolom server di pdf barang bawaan 2026-07-29 08:01:24 +07:00
sean 7d90548717 update form barang 2026-07-27 08:32:05 +07:00
sean 0a16246138 log barang teknisi satu halaman 2026-07-24 19:02:21 +07:00
sean abbabf4af8 update log barang teknisi 2026-07-24 15:41:19 +07:00
sean f145b9e2ec Merge branch 'main' of https://git.radiq.my.id/admin/accounting_dev 2026-07-24 10:33:29 +07:00
sean ea969beedc update barcode yg baru 2026-07-24 10:32:52 +07:00
Wian Drs f715a8d637 update invoice barcode id 2026-07-24 00:01:55 +07:00
sean aa326838fc form barang bawaan teknisi dan fix bug draft item 2026-07-22 21:05:37 +07:00
sean 8a56e2f485 Update history peralatan di bawah teknisi 2026-07-18 11:44:27 +07:00
Prod-Panel c7ec369234 Update Controller Items karena ada bug 2026-07-12 16:55:48 +00:00
Wian Drs 81e35f56a0 Merge branch 'main' of https://git.radiq.my.id/admin/accounting_dev 2026-07-12 22:12:44 +07:00
Wian Drs 3113d77242 Perubahan barcode dan inventory 2026-07-12 22:10:02 +07:00
Prod-Panel 776f22ae24 Perubahan invoice di server 2026-07-12 15:09:52 +00:00
Wian Drs 3ac6316da4 Barcode Scan + Barcode Print 2026-06-30 11:16:31 +07:00
sean be5a02150e Merge branch 'main' of github.com:yandrs/accounting 2026-06-15 14:43:53 +07:00
sean 36ad02a222 Penambahan tombol PDF di Bukubesar, Neraca, Laba rugi, Neraca saldo, dan Data Barang
Format PDF tersimpan di library semua
2026-06-13 16:03:15 +07:00
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
51 changed files with 10858 additions and 1853 deletions
+5
View File
@@ -0,0 +1,5 @@
# CodeGraph data files — local to each machine, not for committing.
# Ignore everything in .codegraph/ except this file itself, so transient
# files (the database, daemon.pid, sockets, logs) never show up in git.
*
!.gitignore
+12
View File
@@ -1,5 +1,12 @@
.DS_Store
application/config/config.php
application/config/database.php
application/controllers/Dbcompare.php
.htaccess
uploads/temp_qr/*
application/cache/*
!application/cache/index.html
@@ -28,3 +35,8 @@ user_guide_src/cilexer/pycilexer.egg-info/*
*.sublime-project
/tests/tests/
/tests/results/
/dev-docs/
/ai-rules/
*.omo/
-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>
@@ -0,0 +1,10 @@
{
"sessionID": "ses_068c091cfffeSZ25RoesHkRZHA",
"updatedAt": "2026-07-25T03:09:04.625Z",
"sources": {
"background-task": {
"state": "idle",
"updatedAt": "2026-07-25T03:09:04.625Z"
}
}
}
@@ -0,0 +1,10 @@
{
"sessionID": "ses_0690d5681fferVVn0tDzMM9Ow7",
"updatedAt": "2026-07-25T08:09:46.929Z",
"sources": {
"background-task": {
"state": "idle",
"updatedAt": "2026-07-25T08:09:46.929Z"
}
}
}
+19
View File
@@ -0,0 +1,19 @@
# TODO - Update Cronjob depreciation_asset()
- [x] Review dan lock aturan tanggal penyusutan bulanan berdasarkan tanggal_perolehan
- [x] Update logic due date bulanan per aset (min(day_perolehan, last_day_of_month))
- [x] Pastikan penyusutan mulai bulan berikutnya setelah perolehan
- [x] Batasi eksekusi hanya saat runDate == dueDateThisMonth
- [x] Pertahankan idempotency 1x per bulan
- [x] Samakan tanggal pencatatan mutation dan journal ke dueDateThisMonth
- [x] Verifikasi struktur response details (success/skip reason) tetap informatif
## TODO Baru - Catch-up penyusutan multi-bulan
- [x] Refactor logic depreciation_asset agar memproses multi-bulan (catch-up)
- [x] Terapkan loop per bulan dari bulan setelah tanggal patokan sampai bulan berjalan
- [x] Pertahankan idempotent check per bulan berdasarkan asset_mutations
- [x] Generate mutasi + jurnal per bulan yang eligible
- [x] Update nilai buku & akumulasi penyusutan asset setelah proses loop
- [x] Tambahkan detail response per bulan (processed/skipped reason)
- [x] Validasi syntax PHP untuk Cronjob.php
-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
);
+7
View File
@@ -56,3 +56,10 @@ $route['translate_uri_dashes'] = FALSE;
// untuk webhook absensi
$route['iclock/cdata'] = 'AttendanceWebhook/cdata';
// db compare structure
$route['dbcompare/sql'] = 'Dbcompare/sql';
$route['dbcompare/sql_download'] = 'Dbcompare/sql_download';
// teknisi log barang
$route['teknisi/log_barang'] = 'teknisi/log_barang';
+137 -22
View File
@@ -6,6 +6,10 @@ class Asset extends CI_Controller {
public function __construct(){
parent::__construct();
$this->load->database();
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index(){
@@ -18,10 +22,11 @@ class Asset extends CI_Controller {
// ================= GET DATA =================
public function get_data(){
$data = $this->db
->select('assets.*, lokasi_asset.nama as nama_lokasi')
->select('assets.*, lokasi_asset.nama as nama_lokasi, item_barcodes.barcode as barcode, item_barcodes.id as barcode_id')
->from('assets')
->join('lokasi_asset', 'lokasi_asset.id = assets.lokasi_asset_id', 'left')
->order_by('assets.id','DESC')
->join('item_barcodes', 'item_barcodes.id = assets.barcode_id', 'left')
->order_by('assets.tanggal_perolehan','DESC')
->get()
->result();
@@ -33,8 +38,9 @@ class Asset extends CI_Controller {
$no++,
$d->tanggal_perolehan,
$d->kode_asset ?? '-',
$d->nama_asset,
$d->nama_asset . ($d->barcode_id ? '<br><small class="text-muted"><i>'.$d->barcode.'</i></small>' : ''),
$d->nama_lokasi ?? '-',
$d->keterangan ?? '-',
number_format($d->nilai_perolehan),
$d->masa_manfaat . ' bln',
number_format($d->penyusutan_per_bulan),
@@ -101,11 +107,12 @@ class Asset extends CI_Controller {
'nilai_residu' => $residu,
'penyusutan_per_bulan' => $penyusutan,
'nilai_buku' => $nilai_perolehan,
'sumber' => 'manual',
'sumber' => 'pembelian',
'account_debit_id' => $account_debit_id,
'account_kredit_id' => $account_kredit_id,
'lokasi_asset_id' => $lokasi_asset_id,
'keterangan' => $keterangan
'keterangan' => $keterangan,
'qty' => 1
];
$this->db->insert('assets',$data);
@@ -142,11 +149,18 @@ class Asset extends CI_Controller {
'asset_id' => $asset_id,
'tipe' => 'perolehan',
'nilai' => $nilai_perolehan,
'keterangan' => 'Perolehan manual: '.$keterangan
'keterangan' => 'Perolehan manual: '.$keterangan,
'tanggal_dibuat' => $tanggal
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
if ($this->db->trans_status()) {
log_activity('Asset', 'create', 'Menambah asset manual: '.$nama_asset.' (ID: '.$asset_id.')');
echo json_encode(['status'=>true]);
} else {
echo json_encode(['status'=>false]);
}
}
// ================= SAVE FROM STOCK =================
@@ -157,6 +171,12 @@ class Asset extends CI_Controller {
$masa = (int)$this->input->post('s_masa');
$residu = preg_replace('/\D/','',$this->input->post('s_residu'));
$tanggal = $this->input->post('s_tanggal');
$keterangan = $this->input->post('s_keterangan');
$barcode_id = $this->input->post('barcode_id');
$qty = (int)$this->input->post('qty');
if($qty < 1){
$qty = 1;
}
$item = $this->db->get_where('items',['id'=>$item_id])->row();
@@ -165,21 +185,27 @@ class Asset extends CI_Controller {
return;
}
if($item->stok < 1){
if(!$barcode_id){
echo json_encode(['status'=>false,'message'=>'Barcode wajib dipilih']);
return;
}
if($item->stok < $qty){
echo json_encode(['status'=>false,'message'=>'Stok tidak cukup']);
return;
}
$harga = $item->harga_beli;
$nama_asset = $item->nama_barang;
$nilai_perolehan = $harga;
$penyusutan = ($harga - $residu) / ($masa ?: 1);
$nilai_perolehan = $harga * $qty;
$penyusutan = ($nilai_perolehan - $residu) / ($masa ?: 1);
$this->db->trans_start();
// INSERT ASSET
$this->db->insert('assets',[
'item_id' => $item_id,
'barcode_id' => $barcode_id,
'kode_asset' => 'AST-'.time(),
'nama_asset' => $nama_asset,
'harga_per_unit' => $harga,
@@ -192,10 +218,43 @@ class Asset extends CI_Controller {
'sumber' => 'gudang',
'account_debit_id' => $account_debit_id,
'account_kredit_id' => 21, // Persediaan
'lokasi_asset_id' => $lokasi_asset_id
'lokasi_asset_id' => $lokasi_asset_id,
'keterangan' => $keterangan,
'qty' => $qty
]);
$asset_id = $this->db->insert_id();
// START - Item Movement & item barcodes update status
$this->db->where('item_id', $item_id);
$this->db->order_by('created_at', 'DESC');
$this->db->limit(1);
$item_move = $this->db->get('item_movements')->row();
// get item_barcode
$itemBar = $this->db->get_where('item_barcodes', ['id'=>$barcode_id])->row();
if($itemBar->qty_sisa < $qty) {
echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$itemBar->qty_sisa]);
$this->db->trans_rollback();
return;
}
$total_sisa = $itemBar->qty_sisa - $qty;
$status_bar = ($total_sisa == 0) ? 'installed' : 'available';
$this->db->insert('item_movements',[
'item_id' => $item_id,
'barcode_id' => $barcode_id,
'qty' => $qty,
'from_type' => $item_move ? $item_move->to_type : 'warehouse', //enum('supplier','warehouse','technician','customer','invoice','asset')
'to_type' => 'asset', //enum('supplier','warehouse','technician','customer','invoice','asset')
'movement_type' => 'installation', //enum('purchase','transfer','installation','uninstallation','supplier_return','customer_return','adjustment','sold_out','asset')
'notes' => $keterangan,
'created_at' => date('Y-m-d H:i:s')
]);
// Status yang boleh masuk : enum('available','reserved','installed','damaged','lost','returned_supplier','sold_out','asset')
$this->db->where('id', $barcode_id)->update('item_barcodes', ['status' => $status_bar, 'qty_sisa' => $total_sisa]);
// Journal
$this->db->insert('journals',[
@@ -225,9 +284,9 @@ class Asset extends CI_Controller {
'kredit' => $nilai_perolehan
]);
// UPDATE STOK ITEM (kurangi 1)
// UPDATE STOK ITEM (kurangi sesuai qty)
$this->db->where('id', $item_id)
->set('stok', 'stok-1', FALSE)
->set('stok', 'stok-'.$qty, FALSE)
->update('items');
// MUTATION DARI GUDANG
@@ -235,12 +294,18 @@ class Asset extends CI_Controller {
'asset_id' => $asset_id,
'tipe' => 'perolehan',
'nilai' => $nilai_perolehan,
'keterangan' => 'Perolehan dari gudang: '.$nama_asset
'keterangan' => 'Perolehan dari gudang: '.$nama_asset,
'tanggal_dibuat' => $tanggal
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
if ($this->db->trans_status()) {
log_activity('Asset', 'create', 'Menambah asset dari gudang: '.$nama_asset.' (ID: '.$asset_id.', Qty: '.$qty.')');
echo json_encode(['status'=>true]);
} else {
echo json_encode(['status'=>false]);
}
}
// ================= UPDATE =================
@@ -270,7 +335,13 @@ class Asset extends CI_Controller {
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
if ($this->db->trans_status()) {
log_activity('Asset', 'update', 'Update asset ID: '.$id.' Nama: '.$nama_asset);
echo json_encode(['status'=>true]);
} else {
echo json_encode(['status'=>false]);
}
}
// ================= DELETE =================
@@ -302,7 +373,13 @@ class Asset extends CI_Controller {
]);
$this->db->trans_complete();
echo json_encode(['status'=>true]);
if ($this->db->trans_status()) {
log_activity('Asset', 'delete', 'Hapus asset ID: '.$id.' Alasan: '.$alasan);
echo json_encode(['status'=>true]);
} else {
echo json_encode(['status'=>false]);
}
}
// ================= GET DATA SELECT =================
@@ -326,11 +403,49 @@ class Asset extends CI_Controller {
echo json_encode($data);
}
public function get_items(){
$data = $this->db
->where('stok > 0')
->get('items')
->result();
public function get_items()
{
// $data = $this->db
// ->select('items.*, kode_barang.*')
// ->from('items')
// ->join('kode_barang', 'items.kode_id = kode_barang.id', 'left')
// ->where('items.stok >', 0)
// ->get()
// ->result();
$this->db->select('
items.id,
items.nama_barang,
items.harga_jual,
items.harga_beli,
items.kode_detail,
kode_barang.tracking_type,
COALESCE(SUM(
stock_logs.qty * IF(stock_logs.tipe="masuk",1,-1)
),0) as stok
');
$this->db->where('status', 'active');
$this->db->from('items');
$this->db->join('stock_logs',
'stock_logs.item_id = items.id' ,
'left');
$this->db->join('kode_barang',
'items.kode_id = kode_barang.id' ,
'left');
// $this->db->where('items.account_id', $account_id);
$this->db->group_by('items.id');
// ✅ INI KUNCINYA
$this->db->having('stok >', 0);
$data = $this->db->get()->result();
echo json_encode($data);
}
@@ -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
]);
}
}
+536 -4
View File
@@ -9,9 +9,9 @@ class Cronjob extends CI_Controller {
$this->load->database();
// ONLY CLI ACCESS
if (!$this->input->is_cli_request()) {
exit('No direct access allowed');
}
// if (!$this->input->is_cli_request()) {
// exit('No direct access allowed');
// }
}
public function generate_insight()
@@ -137,4 +137,536 @@ class Cronjob extends CI_Controller {
'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()
));
}
}
// =====================================================
// CRONJOB AUTO PENYUSUTAN ASSET BULANAN
// =====================================================
public function depreciation_asset()
{
$period = date('Y-m');
$runDate = date('Y-m-d');
$akun_beban_penyusutan = 54;
$assets = $this->db
->select('id, nama_asset, nilai_perolehan, nilai_buku, nilai_residu, penyusutan_per_bulan, masa_manfaat, tanggal_perolehan, akumulasi_penyusutan, account_debit_id')
->from('assets')
->where('status', 'aktif')
->where('nilai_buku > nilai_residu', null, false)
->where('penyusutan_per_bulan >', 0)
->get()
->result();
$processed = 0;
$success = 0;
$skipped = 0;
$errors = 0;
$details = [];
$this->db->trans_begin();
try {
foreach ($assets as $a) {
$processed++;
$assetDetails = [
'asset_id' => (int)$a->id,
'asset_name' => $a->nama_asset,
'processed_months' => [],
'skipped_months' => []
];
$account_parent = $this->db
->select('id')
->where('parent_id', $a->account_debit_id)
->get('accounts')
->row();
if (!$account_parent || empty($account_parent->id)) {
$skipped++;
$assetDetails['status'] = 'skip';
$assetDetails['reason'] = 'account_parent_not_found';
$details[] = $assetDetails;
continue;
}
$akun_akumulasi_penyusutan = (int)$account_parent->id;
$mutasiPatokan = $this->db
->select('tanggal_dibuat')
->from('asset_mutations')
->where('asset_id', $a->id)
->where_in('tipe', ['perolehan', 'penambahan'])
->order_by('tanggal_dibuat', 'DESC')
->order_by('id', 'DESC')
->limit(1)
->get()
->row();
$tanggalPatokan = null;
if ($mutasiPatokan && !empty($mutasiPatokan->tanggal_dibuat)) {
$tanggalPatokan = $mutasiPatokan->tanggal_dibuat;
}
if (empty($tanggalPatokan)) {
$tanggalPatokan = $a->tanggal_perolehan;
}
if (empty($tanggalPatokan)) {
$skipped++;
$assetDetails['status'] = 'skip';
$assetDetails['reason'] = 'tanggal_patokan_tidak_ditemukan';
$details[] = $assetDetails;
continue;
}
$startMonth = date('Y-m', strtotime(date('Y-m-01', strtotime($tanggalPatokan)) . ' +1 month'));
$currentMonth = $period;
if ($startMonth > $currentMonth) {
$skipped++;
$assetDetails['status'] = 'skip';
$assetDetails['reason'] = 'start_next_month_after_acquisition';
$assetDetails['tanggal_patokan'] = $tanggalPatokan;
$details[] = $assetDetails;
continue;
}
$nilaiPerolehan = (float)$a->nilai_perolehan;
$nilaiResidu = (float)$a->nilai_residu;
$susutPerBulan = (float)$a->penyusutan_per_bulan;
$masaManfaat = (int)$a->masa_manfaat;
$akumulasiMutasi = $this->db
->select('COALESCE(SUM(nilai),0) total', false)
->from('asset_mutations')
->where('asset_id', $a->id)
->where('tipe', 'penyusutan')
->get()
->row();
$akumulasiBerjalan = (float)($akumulasiMutasi ? $akumulasiMutasi->total : 0);
$nilaiBukuBerjalan = $nilaiPerolehan - $akumulasiBerjalan;
if ($nilaiBukuBerjalan < $nilaiResidu) {
$nilaiBukuBerjalan = $nilaiResidu;
$akumulasiBerjalan = $nilaiPerolehan - $nilaiBukuBerjalan;
}
$iterMonth = $startMonth;
$assetHasSuccess = false;
while ($iterMonth <= $currentMonth) {
$monthsUsed = 0;
$start = new DateTime(date('Y-m-01', strtotime($tanggalPatokan)));
$end = new DateTime(date('Y-m-01', strtotime($iterMonth . '-01')));
$diff = $start->diff($end);
$monthsUsed = ($diff->y * 12) + $diff->m;
if ($monthsUsed < 0) {
$skipped++;
$assetDetails['skipped_months'][] = [
'period' => $iterMonth,
'reason' => 'tanggal_patokan_after_period'
];
$iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month'));
continue;
}
if ($masaManfaat > 0 && $monthsUsed >= $masaManfaat) {
$skipped++;
$assetDetails['skipped_months'][] = [
'period' => $iterMonth,
'reason' => 'masa_manfaat_finished'
];
break;
}
$acqDay = (int)date('d', strtotime($tanggalPatokan));
$lastDayOfMonth = (int)date('t', strtotime($iterMonth . '-01'));
$dueDay = min($acqDay, $lastDayOfMonth);
$dueDateThisMonth = $iterMonth . '-' . str_pad($dueDay, 2, '0', STR_PAD_LEFT);
// Untuk bulan berjalan, tetap hormati due date.
// Untuk bulan yang sudah lewat, langsung boleh diproses (catch-up).
if ($iterMonth === $currentMonth && strtotime($runDate) < strtotime($dueDateThisMonth)) {
$skipped++;
$assetDetails['skipped_months'][] = [
'period' => $iterMonth,
'reason' => 'not_due_date_today',
'due_date' => $dueDateThisMonth,
'run_date' => $runDate
];
$iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month'));
continue;
}
$startDate = $iterMonth . '-01';
$endDate = date('Y-m-d', strtotime($startDate . ' +1 month'));
$already = $this->db
->where('asset_id', $a->id)
->where('tipe', 'penyusutan')
->where('tanggal_dibuat >=', $startDate)
->where('tanggal_dibuat <', $endDate)
->count_all_results('asset_mutations');
if ($already > 0) {
$skipped++;
$assetDetails['skipped_months'][] = [
'period' => $iterMonth,
'reason' => 'already_depreciated_this_month'
];
$iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month'));
continue;
}
if ($nilaiBukuBerjalan <= $nilaiResidu) {
$skipped++;
$assetDetails['skipped_months'][] = [
'period' => $iterMonth,
'reason' => 'nothing_to_depreciate'
];
break;
}
$maxSusut = max(0, $nilaiBukuBerjalan - $nilaiResidu);
$susut = min($susutPerBulan, $maxSusut);
if ($susut <= 0) {
$skipped++;
$assetDetails['skipped_months'][] = [
'period' => $iterMonth,
'reason' => 'nothing_to_depreciate'
];
break;
}
$akumulasiBefore = $akumulasiBerjalan;
$nilaiBukuBefore = $nilaiBukuBerjalan;
$akumulasiBerjalan += $susut;
$nilaiBukuBerjalan = $nilaiPerolehan - $akumulasiBerjalan;
if ($nilaiBukuBerjalan < $nilaiResidu) {
$nilaiBukuBerjalan = $nilaiResidu;
$akumulasiBerjalan = $nilaiPerolehan - $nilaiBukuBerjalan;
}
$this->db->insert('asset_mutations', [
'asset_id' => $a->id,
'tipe' => 'penyusutan',
'nilai' => $susut,
'keterangan' => 'Penyusutan otomatis periode ' . $iterMonth . ' (cronjob)',
'tanggal_dibuat' => $dueDateThisMonth
]);
$no_ref = 'DEP-' . date('YmdHis') . '-' . $a->id . '-' . str_replace('-', '', $iterMonth);
$this->db->insert('journals', [
'tanggal' => $dueDateThisMonth,
'no_ref' => $no_ref,
'keterangan' => 'Penyusutan Asset ' . $a->nama_asset . ' periode ' . $iterMonth,
'ref_type' => 'assets',
'ref_id' => $a->id,
'created_by' => 1
]);
$jid = $this->db->insert_id();
$this->db->insert('journal_details', [
'journal_id' => $jid,
'account_id' => $akun_beban_penyusutan,
'debit' => $susut,
'kredit' => 0
]);
$this->db->insert('journal_details', [
'journal_id' => $jid,
'account_id' => $akun_akumulasi_penyusutan,
'debit' => 0,
'kredit' => $susut
]);
$success++;
$assetHasSuccess = true;
$assetDetails['processed_months'][] = [
'period' => $iterMonth,
'depreciation' => (float)$susut,
'due_date' => $dueDateThisMonth,
'akumulasi_before' => (float)$akumulasiBefore,
'akumulasi_after' => (float)$akumulasiBerjalan,
'nilai_buku_before' => (float)$nilaiBukuBefore,
'nilai_buku_after' => (float)$nilaiBukuBerjalan,
'journal_no_ref' => $no_ref
];
$iterMonth = date('Y-m', strtotime($iterMonth . '-01 +1 month'));
}
if ($assetHasSuccess) {
$this->db->where('id', $a->id)->update('assets', [
'akumulasi_penyusutan' => $akumulasiBerjalan,
'nilai_buku' => $nilaiBukuBerjalan
]);
$assetDetails['status'] = 'success';
$assetDetails['akumulasi_final'] = (float)$akumulasiBerjalan;
$assetDetails['nilai_buku_final'] = (float)$nilaiBukuBerjalan;
} else {
$assetDetails['status'] = 'skip';
if (empty($assetDetails['reason'])) {
$assetDetails['reason'] = 'no_eligible_month_to_process';
}
}
$details[] = $assetDetails;
}
if ($this->db->trans_status() === FALSE) {
throw new Exception('Transaction failed');
}
$this->db->trans_commit();
echo json_encode([
'status' => true,
'period' => $period,
'processed' => $processed,
'success' => $success,
'skipped' => $skipped,
'errors' => $errors,
'message' => 'Depreciation cron executed',
'details' => $details
]);
} catch (Exception $e) {
$this->db->trans_rollback();
$errors++;
echo json_encode([
'status' => false,
'period' => $period,
'processed' => $processed,
'success' => $success,
'skipped' => $skipped,
'errors' => $errors,
'message' => $e->getMessage()
]);
}
}
}
+2
View File
@@ -135,6 +135,8 @@ class Customers extends CI_Controller {
// =========================
public function delete($id)
{
$customer = $this->dm->setTable('customers')->get_by_id($id);
$this->dm->setTable('customers')->delete($id);
// ================= LOG ACTIVITY =================
+26
View File
@@ -10,6 +10,10 @@ class Employees extends CI_Controller {
$this->load->database();
$this->load->helper(['url', 'file']);
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
date_default_timezone_set('Asia/Jakarta');
}
@@ -192,6 +196,14 @@ class Employees extends CI_Controller {
$data['photo'] = $photo;
$this->db->insert('k_employees',$data);
$employee_id = $this->db->insert_id();
log_activity(
'employees',
'create',
'Menambah karyawan: '.$data['full_name'].' (ID: '.$employee_id.')',
'success'
);
return $this->json([
'status' => true,
@@ -247,6 +259,13 @@ class Employees extends CI_Controller {
->where('id',$id)
->update('k_employees',$data);
log_activity(
'employees',
'update',
'Update karyawan ID: '.$id.' Nama: '.($data['full_name'] ?? '-'),
'success'
);
return $this->json([
'status' => true,
'message' => 'Berhasil update data'
@@ -292,6 +311,13 @@ class Employees extends CI_Controller {
'id'=>$id
]);
log_activity(
'employees',
'delete',
'Hapus karyawan ID: '.$id.' Nama: '.($employee->full_name ?? '-'),
'success'
);
return $this->json([
'status' => true,
'message' => 'Berhasil hapus data'
+138
View File
@@ -0,0 +1,138 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generatebuku extends CI_Controller
{
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
}
// ----------------------------------------------------------------
// PDF GENERATE — BUKU BESAR
// ----------------------------------------------------------------
public function generatepdf($account_id, $start, $end)
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak', 403);
}
if (ob_get_length()) {
ob_end_clean();
}
// =============================================================
// AMBIL INFO AKUN
// =============================================================
$akun = null;
if (!empty($account_id)) {
$akun = $this->db->get_where('accounts', ['id' => (int) $account_id])->row();
}
// =============================================================
// QUERY DATA
// =============================================================
$this->db->select('
journals.tanggal,
journals.no_ref,
journals.keterangan,
journal_details.debit,
journal_details.kredit,
accounts.tipe
');
$this->db->from('journal_details');
$this->db->join('journals', 'journals.id = journal_details.journal_id');
$this->db->join('accounts', 'accounts.id = journal_details.account_id');
if (!empty($account_id)) {
$this->db->where('journal_details.account_id', (int) $account_id);
}
if (!empty($start) && !empty($end)) {
$this->db->where('journals.tanggal >=', $start);
$this->db->where('journals.tanggal <=', $end);
}
$this->db->group_start();
$this->db->where('journal_details.debit >', 0);
$this->db->or_where('journal_details.kredit >', 0);
$this->db->group_end();
$this->db->order_by('journals.tanggal', 'ASC');
$rows = $this->db->get()->result();
// =============================================================
// HITUNG SALDO BERJALAN
// =============================================================
$saldo = 0;
$result = [];
foreach ($rows as $r) {
if (in_array($r->tipe, ['asset', 'expense'])) {
$saldo += ($r->debit - $r->kredit);
} else {
$saldo += ($r->kredit - $r->debit);
}
$result[] = [
'tanggal' => $r->tanggal,
'no_ref' => $r->no_ref,
'keterangan' => $r->keterangan,
'debit' => $r->debit,
'kredit' => $r->kredit,
'saldo' => $saldo,
];
}
$total_debit = array_sum(array_column($result, 'debit'));
$total_kredit = array_sum(array_column($result, 'kredit'));
// =============================================================
// DATA PERUSAHAAN
// =============================================================
$company = $this->db->get('pengaturan')->row();
// =============================================================
// BUILD STRING INFO LAPORAN
// =============================================================
$namaAkun = $akun
? $akun->kode_akun . ' - ' . $akun->nama_akun
: 'Semua Akun';
$periodeStr = (!empty($start) && !empty($end))
? 'Periode : ' . date('d-m-Y', strtotime($start)) . ' s/d ' . date('d-m-Y', strtotime($end))
: 'Periode : Semua Tanggal';
// =============================================================
// LOAD LIBRARY PDF
// =============================================================
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_BukuBesar.php');
$pdf = new PDF ('P', 'mm', 'A4');
$pdf->SetMargins(10, 10, 10);
$pdf->setHeaderData(
$company->nama_perusahaan,
$company->alamat_perusahaan,
FCPATH . 'uploads/img/logo-ljn.png'
);
$pdf->setInfoLaporan($namaAkun, $periodeStr);
$pdf->AddPage('P', 'A4');
$pdf->BukuBesarTable($result);
$pdf->BukuBesarTotal($total_debit, $total_kredit, $saldo);
$pdf->Pembuat('Tito Ngudiana', 'Finance Department');
// =============================================================
// OUTPUT
// =============================================================
$filename = 'Buku_Besar_' . date('YmdHis') . '.pdf';
$pdf->Output('D', $filename);
exit;
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateitems extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
}
public function generatepdf()
{
if(ob_get_length()){
ob_end_clean();
}
$this->db->select('
items.id,
items.nama_barang,
items.kode_detail,
items.harga_beli,
items.harga_jual,
items.created_at,
items.tanggal_pembelian,
items.stok,
(
SELECT GROUP_CONCAT(DISTINCT w.nama SEPARATOR ", ")
FROM stock_logs sl
LEFT JOIN warehouses w ON sl.warehouse_id = w.id
WHERE sl.item_id = items.id
) as gudang
');
$this->db->where('items.status','active');
$this->db->where('items.stok >', 0); // <-- stok 0 agar tidak muncul
$this->db->order_by('(items.stok = 0)','ASC',false);
$this->db->order_by('items.tanggal_pembelian','ASC');
$this->db->order_by('items.kode_detail','ASC');
$data = $this->db->get('items')->result();
$company = $this->db->get('pengaturan')->row_array();
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH. 'libraries/PDF_Items.php');
$pdf = new PDF();
$pdf->setHeaderData(
$company['nama_perusahaan'],
$company['alamat_perusahaan'],
$company['telepon_perusahaan'],
FCPATH.'uploads/img/logo-ljn.png'
);
$pdf->AddPage('P', 'A4');
$pdf->ItemsTable($data);
$pdf->TotalBarang(count($data));
// $pdf->Pembuat($company['nama_bendahara']);
$pdf->Output('I','Data_Barang_'.date('YmdHis').'.pdf');
}
}
@@ -0,0 +1,197 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generatelabarugi extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "laba_rugi"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/laba_rugi');
$this->load->view('partials/footer');
}
// =========================
// AMBIL DATA LABA RUGI (DIPAKAI BERSAMA OLEH get_data() & generatepdf())
// =========================
private function get_labarugi_data($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
a.id,
a.kode_akun,
a.nama_akun,
a.tipe,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts a');
// ================= JOIN
$this->db->join('journal_details jd','a.id = jd.account_id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
// ================= FILTER AKUN
$this->db->where('a.kategori','laba_rugi');
$this->db->where('a.is_active',1);
// ================= FILTER TANGGAL (AMAN)
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$this->db->group_by('a.id');
$this->db->order_by('a.kode_akun','ASC');
$rows = $this->db->get()->result();
$pendapatan = [];
$beban = [];
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$saldo = $r->kredit - $r->debit;
$revenue += $saldo;
$pendapatan[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
}
if ($r->tipe == 'expense') {
$saldo = $r->debit - $r->kredit;
$expense += $saldo;
$beban[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
}
}
return [
'data' => $rows,
'pendapatan' => $pendapatan,
'beban' => $beban,
'revenue' => $revenue,
'expense' => $expense,
'total_pendapatan'=> $revenue,
'total_beban' => $expense,
'laba' => $revenue - $expense
];
}
// =========================
// GET DATA LABA RUGI (JSON, untuk AJAX/datatable)
// =========================
public function get_data()
{
$tanggal_dari = $this->input->get('tanggal_dari');
$tanggal_sampai = $this->input->get('tanggal_sampai');
$result = $this->get_labarugi_data($tanggal_dari, $tanggal_sampai);
echo json_encode([
'data' => $result['data'],
'revenue' => $result['revenue'],
'expense' => $result['expense'],
'laba' => $result['laba']
]);
}
// =========================
// PDF GENERATE — LABA RUGI
// =========================
public function generatepdf($start = null, $end = null)
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak', 403);
}
if (ob_get_length()) {
ob_end_clean();
}
// Normalisasi parameter URL (0000-00-00 dianggap kosong)
$tanggal_dari = (!empty($start) && $start != '0000-00-00') ? $start : null;
$tanggal_sampai = (!empty($end) && $end != '0000-00-00') ? $end : null;
// =============================================================
// AMBIL DATA
// =============================================================
$labarugi = $this->get_labarugi_data($tanggal_dari, $tanggal_sampai);
// =============================================================
// DATA PERUSAHAAN
// =============================================================
$company = $this->db->get('pengaturan')->row();
// =============================================================
// BUILD STRING PERIODE
// =============================================================
$periodeStr = (!empty($tanggal_dari) && !empty($tanggal_sampai))
? 'Periode : ' . date('d-m-Y', strtotime($tanggal_dari))
. ' s/d ' . date('d-m-Y', strtotime($tanggal_sampai))
: ((!empty($tanggal_sampai))
? 'Per Tanggal : ' . date('d-m-Y', strtotime($tanggal_sampai))
: 'Per Tanggal : ' . date('d-m-Y'));
// =============================================================
// LOAD LIBRARY PDF
// =============================================================
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_Labarugi.php');
$pdf = new PDF ('P', 'mm', 'A4');
$pdf->SetMargins(10, 10, 10);
$pdf->setHeaderData(
$company->nama_perusahaan,
$company->alamat_perusahaan,
FCPATH . 'uploads/img/logo-ljn.png'
);
$pdf->setInfoLaporan($periodeStr);
$pdf->AddPage('P', 'A4');
$pdf->LabaRugiTable(
$labarugi['pendapatan'],
$labarugi['beban'],
$labarugi['total_pendapatan'],
$labarugi['total_beban']
);
$pdf->LabaRugiRingkasan($labarugi['laba']);
$pdf->Pembuat('Tito Ngudiana', 'Finance Department');
// =============================================================
// OUTPUT
// =============================================================
$filename = 'Laba_Rugi_' . date('YmdHis') . '.pdf';
$pdf->Output('D', $filename);
exit;
}
}
+252
View File
@@ -0,0 +1,252 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateneraca extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "neraca"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/neraca');
$this->load->view('partials/footer');
}
// =========================
// LABA BERJALAN (PAKAI TANGGAL JOURNAL)
// =========================
private function get_laba_berjalan($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
accounts.tipe,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details jd','jd.account_id = accounts.id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
$this->db->where_in('accounts.tipe',['revenue','expense']);
// ================= FILTER TANGGAL
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$revenue += ($r->kredit - $r->debit);
} else {
$expense += ($r->debit - $r->kredit);
}
}
return $revenue - $expense;
}
// =========================
// AMBIL DATA NERACA (DIPAKAI BERSAMA OLEH get_data() & generatepdf())
// =========================
private function get_neraca_data($tanggal_dari = null, $tanggal_sampai = null)
{
$this->db->select('
a.id,
a.kode_akun,
a.nama_akun,
a.tipe,
a.posisi,
a.is_kontra,
COALESCE(SUM(jd.debit),0) as debit,
COALESCE(SUM(jd.kredit),0) as kredit
');
$this->db->from('accounts a');
$this->db->join('journal_details jd','jd.account_id = a.id','LEFT');
$this->db->join('journals j','j.id = jd.journal_id','LEFT');
$this->db->where('a.kategori','neraca');
// ================= FILTER TANGGAL
if($tanggal_dari){
$this->db->where('(j.tanggal >= "'.$tanggal_dari.'" OR j.tanggal IS NULL)');
}
if($tanggal_sampai){
$this->db->where('(j.tanggal <= "'.$tanggal_sampai.'" OR j.tanggal IS NULL)');
}
$this->db->group_by('a.id');
$this->db->order_by('a.kode_akun','ASC');
$rows = $this->db->get()->result();
$activa = [];
$pasiva = [];
$total_activa = 0;
$total_pasiva = 0;
// ================= LABA BERJALAN
$laba = $this->get_laba_berjalan($tanggal_dari, $tanggal_sampai);
foreach ($rows as $r) {
// ================= SALDO NORMAL
if ($r->posisi == 'debit') {
$saldo = $r->debit - $r->kredit;
} else {
$saldo = $r->kredit - $r->debit;
}
// ================= KONTRA ACCOUNT
if ($r->is_kontra == 1) {
$saldo = -$saldo;
}
// ================= LABA BERJALAN
if ($r->kode_akun == '302') {
$saldo = $laba;
}
// ================= ACTIVA
if ($r->tipe == 'asset') {
$activa[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
$total_activa += $saldo;
}
// ================= PASIVA
if (in_array($r->tipe, ['liability','equity'])) {
$pasiva[] = [
'kode' => $r->kode_akun,
'nama' => $r->nama_akun,
'saldo' => $saldo
];
$total_pasiva += $saldo;
}
}
return [
'activa' => $activa,
'pasiva' => $pasiva,
'total_activa' => $total_activa,
'total_pasiva' => $total_pasiva,
'laba' => $laba,
'selisih' => $total_activa - $total_pasiva
];
}
// =========================
// GET DATA NERACA (JSON, untuk AJAX/datatable)
// =========================
public function get_data()
{
$tanggal_dari = $this->input->get('tanggal_dari');
$tanggal_sampai = $this->input->get('tanggal_sampai');
$data = $this->get_neraca_data($tanggal_dari, $tanggal_sampai);
echo json_encode($data);
}
// =========================
// PDF GENERATE — NERACA
// =========================
public function generatepdf($start = null, $end = null)
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak', 403);
}
if (ob_get_length()) {
ob_end_clean();
}
// Normalisasi parameter URL (0000-00-00 dianggap kosong)
$tanggal_dari = (!empty($start) && $start != '0000-00-00') ? $start : null;
$tanggal_sampai = (!empty($end) && $end != '0000-00-00') ? $end : null;
// =============================================================
// AMBIL DATA
// =============================================================
$neraca = $this->get_neraca_data($tanggal_dari, $tanggal_sampai);
// =============================================================
// DATA PERUSAHAAN
// =============================================================
$company = $this->db->get('pengaturan')->row();
// =============================================================
// BUILD STRING PERIODE
// =============================================================
$periodeStr = (!empty($tanggal_dari) && !empty($tanggal_sampai))
? 'Per Tanggal : ' . date('d-m-Y', strtotime($tanggal_sampai))
. ' (Sejak ' . date('d-m-Y', strtotime($tanggal_dari)) . ')'
: ((!empty($tanggal_sampai))
? 'Per Tanggal : ' . date('d-m-Y', strtotime($tanggal_sampai))
: 'Per Tanggal : ' . date('d-m-Y'));
// =============================================================
// LOAD LIBRARY PDF
// =============================================================
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_Neraca.php');
$pdf = new PDF ('P', 'mm', 'A4');
$pdf->SetMargins(10, 10, 10);
$pdf->setHeaderData(
$company->nama_perusahaan,
$company->alamat_perusahaan,
FCPATH . 'uploads/img/logo-ljn.png'
);
$pdf->setInfoLaporan($periodeStr);
$pdf->AddPage('P', 'A4');
$pdf->NeracaTable(
$neraca['activa'],
$neraca['pasiva'],
$neraca['total_activa'],
$neraca['total_pasiva']
);
$pdf->NeracaRingkasan($neraca['laba'], $neraca['selisih']);
$pdf->Pembuat('Tito Ngudiana', 'Finance Department');
// =============================================================
// OUTPUT
// =============================================================
$filename = 'Neraca_' . date('YmdHis') . '.pdf';
$pdf->Output('D', $filename);
exit;
}
}
@@ -0,0 +1,188 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Generateneracasaldo extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('DynamicModel', 'dm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
if($this->session->userdata('role') != 'Admin'){
show_error('Akses ditolak', 403);
}
$data = ["active_menu" => "neraca_saldo"];
$this->load->view('partials/header', $data);
$this->load->view('laporan/neraca_saldo');
$this->load->view('partials/footer');
}
// =========================
// AMBIL DATA NERACA SALDO
// DIPAKAI OLEH AJAX & PDF
// =========================
private function get_neracasaldo_data()
{
$tanggal_awal = $this->input->get('tanggal_awal');
$tanggal_akhir = $this->input->get('tanggal_akhir');
$this->db->select("
accounts.id,
accounts.kode_akun,
accounts.nama_akun,
accounts.kategori,
accounts.tipe,
COALESCE(SUM(journal_details.debit),0) AS debit,
COALESCE(SUM(journal_details.kredit),0) AS kredit
");
$this->db->from('accounts');
// ======================================
// FILTER PERIODE
// ======================================
if (!empty($tanggal_awal) && !empty($tanggal_akhir)) {
$this->db->join(
"(SELECT jd.*
FROM journal_details jd
JOIN journals j ON j.id = jd.journal_id
WHERE j.tanggal BETWEEN ".$this->db->escape($tanggal_awal)."
AND ".$this->db->escape($tanggal_akhir)."
) journal_details",
"journal_details.account_id = accounts.id",
"left",
false
);
} else {
$this->db->join(
'journal_details',
'journal_details.account_id = accounts.id',
'left'
);
}
$rows = $this->db
->where('accounts.is_active',1)
->group_by('accounts.id')
->order_by('accounts.kode_akun','ASC')
->get()
->result();
$total_debit = 0;
$total_kredit = 0;
foreach($rows as $row){
$row->debit = (float)$row->debit;
$row->kredit = (float)$row->kredit;
$total_debit += $row->debit;
$total_kredit += $row->kredit;
}
return [
'tanggal_awal' => $tanggal_awal,
'tanggal_akhir' => $tanggal_akhir,
'rows' => $rows,
'total_debit' => $total_debit,
'total_kredit' => $total_kredit
];
}
public function get_data()
{
$result = $this->get_neracasaldo_data();
echo json_encode($result['rows']);
}
public function generatepdf()
{
if ($this->session->userdata('role') != 'Admin') {
show_error('Akses ditolak',403);
}
if (ob_get_length()) {
ob_end_clean();
}
// ==========================================
// DATA
// ==========================================
$neracasaldo = $this->get_neracasaldo_data();
// ==========================================
// PERUSAHAAN
// ==========================================
$company = $this->db->get('pengaturan')->row();
// ==========================================
// PERIODE
// ==========================================
if(!empty($neracasaldo['tanggal_awal']) && !empty($neracasaldo['tanggal_akhir'])){
$periodeStr = 'Periode : '
. date('d-m-Y',strtotime($neracasaldo['tanggal_awal']))
.' s/d '
. date('d-m-Y',strtotime($neracasaldo['tanggal_akhir']));
}else{
$periodeStr = 'Seluruh Periode';
}
// ==========================================
// PDF
// ==========================================
require_once(APPPATH.'third_party/fpdf/fpdf.php');
require_once(APPPATH.'libraries/PDF_Neracasaldo.php');
$pdf = new PDF('P','mm','A4');
$pdf->SetMargins(10,10,10);
$pdf->setHeaderData(
$company->nama_perusahaan,
$company->alamat_perusahaan,
FCPATH.'uploads/img/logo-ljn.png'
);
$pdf->setInfoLaporan($periodeStr);
$pdf->AddPage();
$pdf->NeracaSaldoTable(
$neracasaldo['rows'],
$neracasaldo['total_debit'],
$neracasaldo['total_kredit']
);
$pdf->NeracaSaldoRingkasan(
$neracasaldo['total_debit'],
$neracasaldo['total_kredit']
);
$pdf->Pembuat(
$this->session->userdata('nama') ?: 'Administrator',
'Finance Department'
);
$filename = 'Neraca_Saldo_'.date('YmdHis').'.pdf';
$pdf->Output('I',$filename);
exit;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -141,13 +141,13 @@ class Jurnal extends CI_Controller {
$debit = $this->input->post('debit');
$kredit = $this->input->post('kredit');
if (array_sum($debit) != array_sum($kredit)) {
echo json_encode([
'status'=>false,
'message'=>'Debit & Kredit tidak balance!'
]);
return;
}
if (array_sum($debit) != array_sum($kredit)) {
echo json_encode([
'status'=>false,
'message'=>'Debit & Kredit tidak balance!'
]);
return;
}
$this->db->trans_start();
+97 -24
View File
@@ -25,7 +25,7 @@ class Kodebarang extends CI_Controller {
// =========================
public function get_data()
{
$data = $this->db->get('kode_barang')->result();
$data = $this->db->order_by('id', 'DESC')->get('kode_barang')->result();
$result = [];
$no = 1;
@@ -35,7 +35,10 @@ class Kodebarang extends CI_Controller {
$no++,
$row->kode_barang,
$row->nama,
$row->limit_stock,
(int)$row->limit_stock,
strtoupper($row->tracking_type ?? 'UNIT'),
strtolower($row->need_serial_number ?? 'false') === 'true' ? 'Ya' : 'Tidak',
$row->unit ?? 'PCS',
'
<button class="btn btn-sm btn-secondary btn-editkode" data-id="'.$row->id.'">Edit</button>
<button class="btn btn-sm btn-danger btn-delete" data-id="'.$row->id.'">Delete</button>
@@ -61,29 +64,57 @@ class Kodebarang extends CI_Controller {
// =========================
public function save()
{
$nama = $this->input->post('nama');
$kode = $this->input->post('kode');
$limit_stock = $this->input->post('limit_stock');
$nama = trim($this->input->post('nama'));
$kode = trim($this->input->post('kode'));
$limit_stock = (int)$this->input->post('limit_stock');
$tracking_type = strtoupper(trim($this->input->post('tracking_type')));
$need_serial_number = strtolower(trim($this->input->post('need_serial_number')));
$unit = trim($this->input->post('unit'));
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
if(!$nama || !$kode){
echo json_encode(['status'=>false,'message'=>'Kode dan nama wajib diisi']);
return;
}
if(!in_array($tracking_type, ['UNIT','QTY'])){
echo json_encode(['status'=>false,'message'=>'Tracking type tidak valid']);
return;
}
if(!in_array($need_serial_number, ['true','false'])){
echo json_encode(['status'=>false,'message'=>'Need serial number tidak valid']);
return;
}
if(!in_array($unit, ['PCS','Meter','Lembar'])){
echo json_encode(['status'=>false,'message'=>'Unit tidak valid']);
return;
}
$exists = $this->db->get_where('kode_barang', ['kode_barang' => $kode])->num_rows();
if($exists > 0){
echo json_encode(['status'=>false,'message'=>'Kode barang sudah digunakan']);
return;
}
$this->db->insert('kode_barang', [
'kode_barang' => $kode,
'nama' => $nama,
'limit_stock' => $limit_stock
'created_at' => date('Y-m-d H:i:s'),
'limit_stock' => $limit_stock,
'tracking_type' => $tracking_type,
'need_serial_number' => $need_serial_number,
'unit' => $unit
]);
log_activity(
'kode_barang',
'create',
'Menambahkan kode barang: ' . $nama,
'Menambahkan kode barang: ' . $kode . ' - ' . $nama,
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil ditambahkan']);
echo json_encode(['status'=>true,'message'=>'Kode barang berhasil ditambahkan']);
}
// =========================
@@ -91,30 +122,62 @@ class Kodebarang extends CI_Controller {
// =========================
public function update()
{
$id = $this->input->post('id');
$kode = $this->input->post('kode');
$nama = $this->input->post('nama');
$limit_stock = $this->input->post('limit_stock');
$id = (int)$this->input->post('id');
$kode = trim($this->input->post('kode'));
$nama = trim($this->input->post('nama'));
$limit_stock = (int)$this->input->post('limit_stock');
$tracking_type = strtoupper(trim($this->input->post('tracking_type')));
$need_serial_number = strtolower(trim($this->input->post('need_serial_number')));
$unit = trim($this->input->post('unit'));
if(!$nama){
echo json_encode(['status'=>false,'message'=>'Nama wajib diisi']);
if(!$id || !$nama || !$kode){
echo json_encode(['status'=>false,'message'=>'ID, kode dan nama wajib diisi']);
return;
}
if(!in_array($tracking_type, ['UNIT','QTY'])){
echo json_encode(['status'=>false,'message'=>'Tracking type tidak valid']);
return;
}
if(!in_array($need_serial_number, ['true','false'])){
echo json_encode(['status'=>false,'message'=>'Need serial number tidak valid']);
return;
}
if(!in_array($unit, ['PCS','Meter','Lembar'])){
echo json_encode(['status'=>false,'message'=>'Unit tidak valid']);
return;
}
$exists = $this->db
->where('kode_barang', $kode)
->where('id !=', $id)
->get('kode_barang')
->num_rows();
if($exists > 0){
echo json_encode(['status'=>false,'message'=>'Kode barang sudah digunakan']);
return;
}
$this->db->where('id',$id)->update('kode_barang', [
'kode_barang' => $kode,
'nama' => $nama,
'limit_stock' => $limit_stock
'limit_stock' => $limit_stock,
'tracking_type' => $tracking_type,
'need_serial_number' => $need_serial_number,
'unit' => $unit
]);
log_activity(
'kode_barang',
'update',
'Update kode barang ID: ' . $id . ' nama: ' . $nama,
'Update kode barang ID: ' . $id . ' (' . $kode . ' - ' . $nama . ')',
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil diupdate']);
echo json_encode(['status'=>true,'message'=>'Kode barang berhasil diupdate']);
}
// =========================
@@ -122,13 +185,23 @@ class Kodebarang extends CI_Controller {
// =========================
public function delete($id)
{
// optional: cek relasi ke items
$cek = $this->db->get_where('items',['warehouse_id'=>$id])->num_rows();
$row = $this->db->get_where('kode_barang', ['id'=>$id])->row();
if(!$row){
echo json_encode([
'status'=>false,
'message'=>'Data kode barang tidak ditemukan'
]);
return;
}
// cek relasi ke items (mengacu ke kode_id)
$cek = $this->db->get_where('items',['kode_id'=>$id])->num_rows();
if($cek > 0){
echo json_encode([
'status'=>false,
'message'=>'Gudang tidak bisa dihapus karena masih dipakai item'
'message'=>'Kode barang tidak bisa dihapus karena masih dipakai item'
]);
return;
}
@@ -138,11 +211,11 @@ class Kodebarang extends CI_Controller {
log_activity(
'kode_barang',
'delete',
'Hapus gudang ID: ' . $id,
'Hapus kode barang ID: ' . $id . ' (' . $row->kode_barang . ' - ' . $row->nama . ')',
'success'
);
echo json_encode(['status'=>true,'message'=>'Gudang berhasil dihapus']);
echo json_encode(['status'=>true,'message'=>'Kode barang berhasil dihapus']);
}
+35 -68
View File
@@ -26,87 +26,54 @@ class Neracasaldo extends CI_Controller {
$this->load->view('partials/footer');
}
// =========================
// HITUNG LABA BERJALAN (REALTIME)
// =========================
private function get_laba_berjalan()
{
$this->db->select('
accounts.tipe,
COALESCE(SUM(journal_details.debit),0) as debit,
COALESCE(SUM(journal_details.kredit),0) as kredit
');
$this->db->from('accounts');
$this->db->join('journal_details','journal_details.account_id = accounts.id','LEFT');
$this->db->where_in('accounts.tipe',['revenue','expense']);
$rows = $this->db->get()->result();
$revenue = 0;
$expense = 0;
foreach ($rows as $r) {
if ($r->tipe == 'revenue') {
$revenue += ($r->kredit - $r->debit);
} else {
$expense += ($r->debit - $r->kredit);
}
}
return $revenue - $expense;
}
public function get_data()
{
$this->db->select('
$tanggal_awal = $this->input->get('tanggal_awal');
$tanggal_akhir = $this->input->get('tanggal_akhir');
$this->db->select("
accounts.id,
accounts.kode_akun,
accounts.nama_akun,
accounts.kategori,
accounts.tipe,
accounts.posisi,
COALESCE(SUM(journal_details.debit),0) as debit,
COALESCE(SUM(journal_details.kredit),0) as kredit
');
COALESCE(SUM(journal_details.debit),0) AS debit,
COALESCE(SUM(journal_details.kredit),0) AS kredit
");
$this->db->from('accounts');
$this->db->join('journal_details','journal_details.account_id = accounts.id','LEFT');
// OPTIONAL (best practice)
$this->db->where('accounts.is_active',1);
$this->db->where('accounts.kategori','neraca');
if (!empty($tanggal_awal) && !empty($tanggal_akhir)) {
$this->db->group_by('accounts.id');
$this->db->order_by('accounts.kode_akun','ASC');
$this->db->join(
"(SELECT jd.*
FROM journal_details jd
JOIN journals j ON j.id = jd.journal_id
WHERE j.tanggal BETWEEN ".$this->db->escape($tanggal_awal)."
AND ".$this->db->escape($tanggal_akhir)."
) journal_details",
"journal_details.account_id = accounts.id",
"left",
false
);
$rows = $this->db->get()->result();
} else {
// =========================
// HITUNG LABA BERJALAN
// =========================
$laba = $this->get_laba_berjalan();
$this->db->join(
'journal_details',
'journal_details.account_id = accounts.id',
'left'
);
foreach ($rows as $r) {
// ================= SALDO NORMAL
if ($r->posisi == 'debit') {
$saldo = $r->debit - $r->kredit;
} else {
$saldo = $r->kredit - $r->debit;
}
// ================= OVERRIDE LABA BERJALAN
if ($r->kode_akun == '302') {
$saldo = $laba;
}
// ================= NORMALISASI KE KOLOM
if ($saldo >= 0 AND $r->posisi == 'debit') {
$r->saldo_debit = $saldo;
$r->saldo_kredit = 0;
} else {
$r->saldo_debit = 0;
$r->saldo_kredit = abs($saldo);
}
}
$rows = $this->db
->where('accounts.is_active', 1)
->group_by('accounts.id')
->order_by('accounts.kode_akun', 'ASC')
->get()
->result();
echo json_encode($rows);
}
}
+518
View File
@@ -0,0 +1,518 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Teknisi extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->load->model('PeralatanTeknisi_model', 'ptm');
if (!$this->session->userdata('logged_in')) {
redirect('auth');
}
}
public function index()
{
$data = [
"active_menu" => "peralatan_teknisi",
"teknisi_list" => $this->ptm->get_teknisi_list()
];
$this->load->view('partials/header', $data);
$this->load->view('peralatan_teknisi/layout', $data);
$this->load->view('partials/footer');
}
public function get_data()
{
$user_id = $this->input->get('user_id');
$status = $this->input->get('status') ?: 'active';
log_message('debug', '[get_data] user_id: ' . var_export($user_id, true) . ', status: ' . $status);
$data = $this->ptm->get_barang_dibawa($user_id, $status);
log_message('debug', '[get_data] query returned ' . count($data) . ' rows');
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->serial_number, ENT_QUOTES, 'UTF-8'),
];
}
log_message('debug', '[get_data] response: ' . json_encode(['data' => $result]));
echo json_encode(['data' => $result]);
}
public function scan_item()
{
$barcode = trim($this->input->get('barcode'));
if (empty($barcode)) {
echo json_encode(['status' => false, 'message' => 'QR Code tidak boleh kosong']);
return;
}
$item = $this->ptm->get_item_by_barcode($barcode);
if (!$item) {
echo json_encode(['status' => false, 'message' => 'Barcode tidak ditemukan']);
return;
}
$teknisi = $this->ptm->get_current_teknisi($item->barcode);
echo json_encode([
'status' => true,
'message' => 'Barang ditemukan',
'data' => [
'barcode' => $item->barcode,
'nama_barang' => $item->nama_barang,
'serial_number' => $item->serial_number,
'barcode_id' => $item->barcode_id,
'item_id' => $item->item_id,
'barcode_status' => $item->barcode_status,
'teknisi_id' => $teknisi ? $teknisi->user_id : null,
'teknisi_name' => $teknisi ? $teknisi->full_name : null,
'teknisi_code' => $teknisi ? $teknisi->employee_code : null
]
]);
}
function get_date_by_employee_id()
{
$user_id = (int)$this->input->get('user_id');
if (!$user_id) {
echo json_encode(['status' => false, 'message' => 'ID teknisi tidak valid']);
return;
}
$dates = $this->ptm->get_dates_by_employee_id($user_id);
echo json_encode(['status' => true, 'data' => $dates]);
}
public function batch_kembalikan()
{
$barcodes = $this->input->post('barcodes');
if (empty($barcodes) || !is_array($barcodes)) {
echo json_encode(['status' => false, 'message' => 'Tidak ada barcode yang dipilih']);
return;
}
$result = $this->ptm->batch_kembalikan($barcodes);
echo json_encode($result);
}
public function batch_dibawa()
{
$barcodes = $this->input->post('barcodes');
$user_id = (int)$this->input->post('user_id');
if (empty($barcodes) || !is_array($barcodes)) {
echo json_encode(['status' => false, 'message' => 'Tidak ada barcode yang dipilih']);
return;
}
if (empty($user_id)) {
echo json_encode(['status' => false, 'message' => 'Pilih teknisi terlebih dahulu']);
return;
}
$result = $this->ptm->batch_dibawa($barcodes, $user_id);
echo json_encode($result);
}
public function get_teknisi_table()
{
$draw = (int)$this->input->get('draw');
$start = (int)$this->input->get('start');
$length = (int)$this->input->get('length');
$search = $this->input->get('search')['value'] ?? '';
$order_col = (int)$this->input->get('order')[0]['column'] ?? 1;
$order_dir = $this->input->get('order')[0]['dir'] ?? 'asc';
$data = $this->ptm->get_teknisi_table_data($start, $length, $search, $order_col, $order_dir);
$total_filtered = $this->ptm->count_filtered_teknisi($search);
$total_all = $this->ptm->count_all_teknisi();
$result = [];
$no = $start + 1;
foreach ($data as $row) {
$item_count = (int)$row->item_count;
$item_text = $item_count . ' Item';
$aksi = '<button class="btn btn-info btn-sm btn-detail-teknisi" data-id="'.$row->id.'" title="Detail">
<i class="bi bi-eye"></i>
</button>
<button class="btn btn-warning btn-sm btn-form-barang-bawaan" data-id="'.$row->id.'" data-nama="'.htmlspecialchars($row->full_name, ENT_QUOTES, 'UTF-8').'" title="Form Barang Bawaan">
<i class="bi bi-file-earmark-text"></i>
</button>';
$result[] = [
$no++,
htmlspecialchars($row->full_name, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->employee_code, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->department_name, ENT_QUOTES, 'UTF-8') ?: '-',
$item_text,
$aksi
];
}
echo json_encode([
'draw' => $draw,
'recordsTotal' => $total_all,
'recordsFiltered' => $total_filtered,
'data' => $result
]);
}
public function teknisi_detail()
{
$user_id = (int)$this->input->get('user_id');
if (!$user_id) {
echo json_encode(null);
return;
}
$info = $this->ptm->get_teknisi_info($user_id);
echo json_encode($info);
}
public function get_teknisi_items()
{
$user_id = (int)$this->input->get('user_id');
$draw = (int)$this->input->get('draw');
$start = (int)$this->input->get('start');
$length = (int)$this->input->get('length');
$search = $this->input->get('search')['value'] ?? '';
$order_col = (int)$this->input->get('order')[0]['column'] ?? 2;
$order_dir = $this->input->get('order')[0]['dir'] ?? 'asc';
if (!$user_id) {
echo json_encode(['draw' => $draw, 'recordsTotal' => 0, 'recordsFiltered' => 0, 'data' => []]);
return;
}
$data = $this->ptm->get_teknisi_items_data($user_id, $start, $length, $search, $order_col, $order_dir);
$total_filtered = $this->ptm->count_filtered_teknisi_items($user_id, $search);
$total_all = $this->ptm->count_all_teknisi_items($user_id);
$result = [];
$no = $start + 1;
foreach ($data as $row) {
$badge = $row->status == 'active'
? '<span class="badge bg-success">Dibawa</span>'
: '<span class="badge bg-secondary">' . htmlspecialchars($row->status, ENT_QUOTES, 'UTF-8') . '</span>';
$result[] = [
$no++,
$row->tanggal ?? date('d/m/Y'),
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->serial_number, ENT_QUOTES, 'UTF-8') ?: '-',
htmlspecialchars($row->qty_sisa, ENT_QUOTES, 'UTF-8') ?: '0',
$badge
];
}
echo json_encode([
'draw' => $draw,
'recordsTotal' => $total_all,
'recordsFiltered' => $total_filtered,
'data' => $result
]);
}
public function get_form_barang_bawaan_data()
{
$user_id = (int)$this->input->get('user_id');
if (!$user_id) {
echo json_encode(['status' => false, 'message' => 'ID teknisi tidak valid']);
return;
}
$info = $this->ptm->get_teknisi_info($user_id);
if (!$info) {
echo json_encode(['status' => false, 'message' => 'Teknisi tidak ditemukan']);
return;
}
$tanggal_param = $this->input->get('tanggal');
$items = [];
if ($tanggal_param) {
$items = $this->ptm->get_teknisi_items_full($user_id, $tanggal_param);
}
if ($tanggal_param) {
if ($tanggal_param === 'all') {
$noFaktur = 'GRJN/K/ALL' . date('/m/Y');
$tanggal = date('d F Y');
} else {
$ts = strtotime($tanggal_param);
$noFaktur = 'GRJN/K/' . date('d/m/Y', $ts);
$tanggal = date('d F Y', $ts);
}
} else {
$noFaktur = 'GRJN/K/' . date('d/m/Y');
$tanggal = date('d F Y');
}
$itemsData = [];
$no = 1;
foreach ($items as $item) {
$itemsData[] = [
'no' => $no++,
'tanggal' => $item->tanggal ?? date('d/m/Y'),
'barcode' => $item->barcode ?? '-',
'nama_barang' => $item->nama_barang ?? '-',
'serial_number' => $item->serial_number ?? '-',
'jumlah' => isset($item->qty_sisa) ? (int)$item->qty_sisa : 1,
'keterangan' => ''
];
}
echo json_encode([
'status' => true,
'data' => [
'no_faktur' => $noFaktur,
'tanggal' => $tanggal,
'teknisi' => $info->full_name,
'teknisi_id' => $info->id,
'employee_code' => $info->employee_code,
'position_name' => $info->position_name,
'department_name' => $info->department_name,
'total_item' => count($itemsData),
'items' => $itemsData
]
]);
}
public function generate_pdf_barang_bawaan($user_id = null)
{
$user_id = (int)$user_id;
if (!$user_id) {
show_error('ID teknisi tidak valid', 400);
}
if (ob_get_length()) {
ob_end_clean();
}
$info = $this->ptm->get_teknisi_info($user_id);
if (!$info) {
show_error('Teknisi tidak ditemukan', 404);
}
$tanggal_param = $this->input->get('tanggal');
if ($tanggal_param === 'all') {
$items = $this->ptm->get_teknisi_items_full($user_id);
$noFaktur = 'GRJN/K/ALL' . date('/m/Y');
$tanggal = 'Semua Tanggal';
} elseif ($tanggal_param) {
$ts = strtotime($tanggal_param);
$items = $this->ptm->get_teknisi_items_full($user_id, $tanggal_param);
$noFaktur = 'GRJN/K/' . date('d/m/Y', $ts);
$tanggal = date('d/m/Y', $ts);
} else {
$items = $this->ptm->get_teknisi_items_full($user_id);
$noFaktur = 'GRJN/K/' . date('d/m/Y');
$tanggal = date('d/m/Y');
}
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
require_once(APPPATH . 'libraries/PDF_BarangBawaan.php');
$pdf = new PDF_BarangBawaan('L', 'mm', 'A5');
$pdf->AliasNbPages();
$pdf->SetMargins(5, 5, 5);
$pdf->SetAutoPageBreak(true, 10);
$pdf->setFormInfo($noFaktur, $tanggal, $info->full_name);
$pdf->AddPage('L', 'A5');
$pdf->BarangBawaanTable($items);
$pdf->SignatureArea();
$namaFile = 'FORM_BARANG_BAWAAN_' . str_replace(' ', '_', $info->full_name) . '_' . date('Ymd') . '.pdf';
$pdf->Output('I', $namaFile);
exit;
}
public function get_transfer_data()
{
$data = $this->ptm->get_transfer_data();
$result = [];
$no = 1;
foreach ($data as $row) {
$result[] = [
$no++,
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang, ENT_QUOTES, 'UTF-8'),
htmlspecialchars(($row->from_code ?? '') . ' - ' . ($row->from_name ?? ''), ENT_QUOTES, 'UTF-8'),
htmlspecialchars(($row->to_code ?? '') . ' - ' . ($row->to_name ?? ''), ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->created_at, ENT_QUOTES, 'UTF-8'),
'<span class="badge bg-success">Selesai</span>'
];
}
echo json_encode(['data' => $result]);
}
public function batch_transfer()
{
$barcodes = $this->input->post('barcodes');
$from_user_id = (int)$this->input->post('from_user_id');
$to_user_id = (int)$this->input->post('to_user_id');
$technician = $this->ptm->get_teknisi_info($to_user_id);
log_message('debug', 'batch_transfer POST: ' . json_encode([
'barcodes' => $barcodes,
'from_user_id' => $from_user_id,
'to_user_id' => $to_user_id
]));
if (empty($barcodes) || !is_array($barcodes)) {
echo json_encode(['status' => false, 'message' => 'Tidak ada barcode yang dipilih']);
return;
}
if (empty($from_user_id) || empty($to_user_id)) {
echo json_encode(['status' => false, 'message' => 'Pilih teknisi asal dan tujuan']);
return;
}
if ($from_user_id == $to_user_id) {
echo json_encode(['status' => false, 'message' => 'Teknisi tujuan tidak boleh sama dengan teknisi asal']);
return;
}
$now = date('Y-m-d H:i:s');
$errors = [];
$this->db->trans_start();
foreach ($barcodes as $barcode) {
$barcode = trim($barcode);
if ($barcode == '') continue;
$item = $this->db
->select('it.*, ib.id as barcode_id, tec.full_name as teknisi_name')
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('k_employees tec', 'tec.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->where('it.status', 'active')
->where('it.user_id', $from_user_id)
->get()
->row();
if (!$item) {
$errors[] = "Barcode $barcode tidak ditemukan pada teknisi asal";
continue;
}
log_message('debug', 'batch_transfer item: ' . json_encode($item));
$this->db
->where('barcode', $item->barcode)
->where('user_id', $from_user_id)
->where('status', 'active')
->update('item_technician', [
'user_id' => $to_user_id
]);
$this->db->insert('item_movements', [
'item_id' => $item->item_id,
'barcode_id' => $item->barcode_id,
'qty' => 1,
'from_type' => 'technician',
'from_id' => $from_user_id,
'to_type' => 'technician',
'to_id' => $to_user_id,
'movement_type' => 'transfer',
'notes' => 'Transfer Barang dari: ' . $item->teknisi_name . ' ke ' . $technician->full_name,
'created_at' => $now
]);
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
$db_error = $this->db->error();
log_message('error', 'batch_transfer gagal: ' . json_encode($db_error));
echo json_encode([
'status' => false,
'message' => 'Gagal memproses transfer',
'debug' => $db_error['message'] ?? 'Unknown database error'
]);
return;
}
$success_count = count($barcodes) - count($errors);
$msg = "$success_count barang berhasil ditransfer";
if (count($errors) > 0) {
$msg .= '. ' . implode(', ', $errors);
}
echo json_encode(['status' => true, 'message' => $msg, 'errors' => $errors]);
}
public function get_log_data()
{
$data = $this->ptm->get_log_barang();
$result = [];
$no = 1;
foreach ($data as $row) {
$status_badge = $row->status === 'active'
? '<span class="badge bg-success">ACTIVE</span>'
: '<span class="badge bg-secondary">INACTIVE</span>';
$result[] = [
$no++,
htmlspecialchars($row->teknisi_name ?? '-', ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->item_code ?? '-', ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->barcode, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($row->nama_barang ?? '-', ENT_QUOTES, 'UTF-8'),
$row->created_at ? date('d/m/Y H:i', strtotime($row->created_at)) : '-',
$row->status === 'inactive' && $row->deleted_at ? date('d/m/Y H:i', strtotime($row->deleted_at)) : '-',
$status_badge,
];
}
echo json_encode(['data' => $result]);
}
public function log_barang()
{
$data = [
"active_menu" => "peralatan_teknisi",
];
$this->load->view('partials/header', $data);
$this->load->view('peralatan_teknisi/log_barang', $data);
$this->load->view('partials/footer');
}
}
+210
View File
@@ -0,0 +1,210 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF_BarangBawaan extends FPDF
{
private $noFaktur;
private $tanggal;
private $teknisi;
private $colWidths = [8, 18, 27, 42, 24, 10, 46, 15];
public function __construct($orientation = 'L', $unit = 'mm', $size = 'A5')
{
parent::__construct($orientation, $unit, $size);
$this->SetDisplayMode('fullpage');
}
function SetMargins($left, $top, $right = null)
{
parent::SetMargins(10, $top, 10);
}
public function setFormInfo($noFaktur, $tanggal, $teknisi)
{
$this->noFaktur = $noFaktur;
$this->tanggal = $tanggal;
$this->teknisi = $teknisi;
}
function Header()
{
$this->SetFont('Arial', 'B', 11);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 6, 'FORM BARANG BAWAAN TEKNISI', 0, 1, 'L');
$this->SetTextColor(0, 0, 0);
$this->Ln(2);
$this->SetFont('Arial', '', 8);
$this->Cell(0, 5, 'Tanggal : ' . $this->tanggal . ' Teknisi : ' . $this->teknisi . ' No. Faktur : ' . $this->noFaktur, 0, 1, 'C');
$this->Ln(2);
$this->_drawTableHeader();
}
function Footer()
{
$this->SetY(-8);
$this->SetFont('Arial', 'I', 7);
$this->Cell(0, 4, 'Hal ' . $this->PageNo(), 0, 0, 'C');
}
private function _drawTableHeader()
{
$startX = $this->lMargin;
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 7);
$headers = ['No', 'Tanggal', 'Barcode', 'Nama Barang', 'Serial Number', 'Jumlah', 'Keterangan', 'Server'];
foreach ($headers as $i => $h) {
$this->Cell($this->colWidths[$i], 7, $h, 1, 0, 'C', true);
}
$this->Ln();
$this->SetTextColor(0, 0, 0);
}
public function BarangBawaanTable($items)
{
$this->SetFont('Arial', '', 7);
$lineH = 5.5;
$minRowH = 8;
$no = 1;
$colsMultiCell = [false, false, true, true, true, false, true, false];
$cellAligns = ['C', 'C', 'L', 'L', 'L', 'C', 'L', 'L'];
foreach ($items as $item) {
$keterangan = isset($item->keterangan) ? $item->keterangan : '';
$namaBarang = $item->nama_barang ?? '-';
$serial = $item->serial_number ?? '-';
$barcode = $item->barcode ?? '-';
$jumlah = isset($item->qty_sisa) ? (int)$item->qty_sisa : 1;
$server = '';
$cellValues = [
(string)$no,
$item->tanggal ?? '-',
$barcode,
$namaBarang,
$serial,
(string)$jumlah,
$keterangan,
$server
];
$maxLines = 1;
foreach ($cellValues as $i => $val) {
if ($colsMultiCell[$i]) {
$lines = $this->NbLines($this->colWidths[$i], $val);
if ($lines > $maxLines) {
$maxLines = $lines;
}
}
}
$rowH = max($minRowH, $maxLines * $lineH);
if ($this->GetY() + $rowH > $this->PageBreakTrigger) {
$this->AddPage();
}
$startX = $this->lMargin;
$startY = $this->GetY();
$currentX = $startX;
foreach ($this->colWidths as $i => $w) {
$this->Rect($currentX, $startY, $w, $rowH);
$currentX += $w;
}
$currentX = $startX;
foreach ($cellValues as $i => $val) {
$this->SetXY($currentX, $startY);
if ($colsMultiCell[$i]) {
$colLines = $this->NbLines($this->colWidths[$i], $val);
$contentH = $colLines * $lineH;
$offsetY = ($rowH - $contentH) / 2;
$this->SetXY($currentX, $startY + $offsetY);
$this->MultiCell($this->colWidths[$i], $lineH, $val, 0, $cellAligns[$i]);
} else {
$this->Cell($this->colWidths[$i], $rowH, $val, 0, 0, $cellAligns[$i]);
}
$currentX += $this->colWidths[$i];
}
$this->SetXY($startX, $startY + $rowH);
$no++;
}
}
public function SignatureArea()
{
$this->Ln(6);
$sigW = 56;
$gapW = 11;
$blockW = 3 * $sigW + 2 * $gapW;
$startX = $this->lMargin + ($this->w - $this->lMargin - $this->rMargin - $blockW) / 2;
$signatures = [
['title' => 'Admin Gudang', 'name' => 'Gono Soewisto'],
['title' => 'Leader Teknisi', 'name' => 'Chandra Nursani'],
['title' => 'Mengetahui', 'name' => 'Tito Ngudiana'],
];
$sigY = $this->GetY();
foreach ($signatures as $i => $sig) {
$x = $startX + $i * ($sigW + $gapW);
$this->SetXY($x, $sigY);
$this->SetFont('Arial', '', 8);
$this->Cell($sigW, 5, $sig['title'], 0, 1, 'C');
$this->SetX($x);
$this->Ln(10);
$this->SetX($x);
$this->SetFont('Arial', 'U', 8);
$this->Cell($sigW, 5, ' ', 0, 1, 'C');
$this->SetX($x);
$this->SetFont('Arial', 'I', 7);
$this->Cell($sigW, 4, $sig['name'], 0, 1, 'C');
}
$this->SetY($sigY + 5 + 10 + 5 + 4);
}
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") { $nb--; }
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") { $i++; $sep = -1; $j = $i; $l = 0; $nl++; continue; }
if ($c == ' ') { $sep = $i; }
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) { if ($i == $j) $i++; }
else { $i = $sep + 1; }
$sep = -1; $j = $i; $l = 0; $nl++;
} else { $i++; }
}
return $nl;
}
}
+329
View File
@@ -0,0 +1,329 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
// ----------------------------------------------------------------
// PROPERTI HEADER
// ----------------------------------------------------------------
private $nama;
private $alamat;
private $logo;
// ----------------------------------------------------------------
// PROPERTI TABEL
// ----------------------------------------------------------------
private $isTable = false;
private $tableWidth = [];
private $tableHeader = [];
// Konfigurasi tabel A4 Portrait (lebar efektif = 190mm, margin 10+10)
// Tanggal : 22 | No Ref : 28 | Keterangan : 60 | Debit : 26 | Kredit : 26 | Saldo : 28 → TOTAL : 190
private $colWidths = [22, 28, 60, 26, 26, 28];
// ----------------------------------------------------------------
// PROPERTI INFO LAPORAN
// ----------------------------------------------------------------
private $namaAkun = '';
private $periodeStr = '';
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($namaAkun, $periodeStr)
{
$this->namaAkun = $namaAkun;
$this->periodeStr = $periodeStr;
}
public function setTableHeader($header, $width)
{
$this->tableHeader = $header;
$this->tableWidth = $width;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// Lebar halaman A4 Portrait = 210mm (area cetak 10..200)
$pageRight = 200;
// Logo (opsional)
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 6, 18);
$this->SetXY(31, 10);
} else {
$this->SetXY(10, 10);
}
// Nama perusahaan
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 6, $this->nama, 0, 1, 'L');
// Alamat
$this->SetFont('Arial', '', 9);
$x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10;
$this->SetX($x);
$this->Cell(0, 5, $this->alamat, 0, 1, 'L');
// Garis pemisah
$this->Ln(2);
$this->SetLineWidth(0.6);
$this->Line(10, $this->GetY(), $pageRight, $this->GetY());
$this->SetLineWidth(0.2);
$this->Line(10, $this->GetY() + 1, $pageRight, $this->GetY() + 1);
$this->Ln(5);
// Judul
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 7, 'LAPORAN BUKU BESAR', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Periode
$this->SetFont('Arial', '', 10);
$this->Cell(0, 5, $this->periodeStr, 0, 1, 'C');
$this->Ln(3);
// Info akun & tanggal cetak
$colLabel = 35;
$colSep = 5;
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($colLabel, 6, 'Akun');
$this->SetFont('Arial', '', 10);
$this->Cell($colSep, 6, ':');
$this->Cell(0, 6, $this->namaAkun, 0, 1);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($colLabel, 6, 'Di Cetak Pada');
$this->SetFont('Arial', '', 10);
$this->Cell($colSep, 6, ':');
$this->Cell(0, 6, date('d-m-Y') . ' Jam ' . date('H:i:s'), 0, 1);
$this->Ln(3);
if ($this->isTable && !empty($this->tableHeader)) {
$this->_drawTableHeader();
}
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: Gambar baris header tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 9);
foreach ($this->tableHeader as $k => $v) {
$this->Cell($this->tableWidth[$k], 8, $v, 1, 0, 'C', true);
}
$this->SetTextColor(0, 0, 0);
$this->Ln();
}
// ----------------------------------------------------------------
// TABEL BUKU BESAR
// ----------------------------------------------------------------
public function BukuBesarTable($result)
{
$header = ['Tanggal', 'No Ref', 'Keterangan', 'Debit', 'Kredit', 'Saldo'];
$this->isTable = true;
$this->setTableHeader($header, $this->colWidths);
// Header halaman pertama
$this->_drawTableHeader();
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$lineH = 5;
$this->SetFont('Arial', '', 9);
if (empty($result)) {
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell(array_sum($this->colWidths), 8, 'Tidak ada data', 1, 1, 'C');
$this->isTable = false;
return;
}
$fill = false;
// Batas bawah A4 Portrait (297mm) dikurangi area footer
$pageBreakTrigger = 277;
foreach ($result as $row) {
// Hitung tinggi baris berdasar No Ref & Keterangan (keduanya MultiCell)
$nbRef = $this->NbLines($this->colWidths[1], $row['no_ref']);
$nbKet = $this->NbLines($this->colWidths[2], $row['keterangan']);
$nbLines = max(1, $nbRef, $nbKet);
$rowH = $nbLines * $lineH;
// Ganti halaman jika mendekati batas bawah
if ($this->GetY() + $rowH > $pageBreakTrigger) {
$this->AddPage('P', 'A4');
}
if ($fill) {
$this->SetFillColor(245, 245, 245);
}
$x = $startX;
$y = $this->GetY();
// Tanggal
$this->SetX($x);
$this->Cell($this->colWidths[0], $rowH, date('d-m-Y', strtotime($row['tanggal'])), 1, 0, 'C', $fill);
// No Ref (MultiCell)
$xRef = $this->GetX();
$offsetRef = ($rowH - $nbRef * $lineH) / 2;
// gambar border kotak dulu
$this->Rect($xRef, $y, $this->colWidths[1], $rowH, $fill ? 'DF' : 'D');
$this->SetXY($xRef, $y + $offsetRef);
$this->MultiCell($this->colWidths[1], $lineH, $row['no_ref'], 0, 'C', false);
// Keterangan (MultiCell)
$xKet = $xRef + $this->colWidths[1];
$offsetKet = ($rowH - $nbKet * $lineH) / 2;
$this->Rect($xKet, $y, $this->colWidths[2], $rowH, $fill ? 'DF' : 'D');
$this->SetXY($xKet, $y + $offsetKet);
$this->MultiCell($this->colWidths[2], $lineH, $row['keterangan'], 0, 'L', false);
// Kolom angka
$this->SetXY($xKet + $this->colWidths[2], $y);
$this->Cell($this->colWidths[3], $rowH, number_format($row['debit'], 2, ',', '.'), 1, 0, 'R', $fill);
$this->Cell($this->colWidths[4], $rowH, number_format($row['kredit'], 2, ',', '.'), 1, 0, 'R', $fill);
$this->Cell($this->colWidths[5], $rowH, number_format($row['saldo'], 2, ',', '.'), 1, 0, 'R', $fill);
$this->SetXY($x, $y + $rowH);
$fill = !$fill;
}
$this->isTable = false;
}
// ----------------------------------------------------------------
// BARIS TOTAL
// ----------------------------------------------------------------
public function BukuBesarTotal($totalDebit, $totalKredit, $saldo)
{
$startX = ($this->GetPageWidth() - array_sum($this->colWidths)) / 2;
$labelWidth = $this->colWidths[0] + $this->colWidths[1] + $this->colWidths[2];
$this->SetX($startX);
$this->SetFillColor(255, 204, 102);
$this->SetFont('Arial', 'B', 9);
$this->Cell($labelWidth, 8, 'TOTAL', 1, 0, 'C', true);
$this->Cell($this->colWidths[3], 8, number_format($totalDebit, 2, ',', '.'), 1, 0, 'R', true);
$this->Cell($this->colWidths[4], 8, number_format($totalKredit, 2, ',', '.'), 1, 0, 'R', true);
$this->Cell($this->colWidths[5], 8, number_format($saldo, 2, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TANDA TANGAN
// ----------------------------------------------------------------
public function Pembuat($nama, $jabatan = '')
{
$signWidth = 70;
$signX = 125; // disesuaikan untuk A4 Portrait (210mm)
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', '', 10);
$this->Cell($signWidth, 6, 'Mengetahui,', 0, 1, 'C');
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($signWidth, 6, $nama, 0, 1, 'C');
if (!empty($jabatan)) {
$this->SetX($signX);
$this->SetFont('Arial', '', 9);
$this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'C');
}
}
// ----------------------------------------------------------------
// HELPER: Hitung jumlah baris MultiCell
// ----------------------------------------------------------------
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', (string)$txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
$nb--;
}
$sep = -1;
$i = 0; $j = 0; $l = 0; $nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") {
$i++; $sep = -1; $j = $i; $l = 0; $nl++;
continue;
}
if ($c == ' ') {
$sep = $i;
}
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) $i++;
} else {
$i = $sep + 1;
}
$sep = -1; $j = $i; $l = 0; $nl++;
} else {
$i++;
}
}
return $nl;
}
}
+216
View File
@@ -0,0 +1,216 @@
<?php
require_once(APPPATH.'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
private $nama;
private $alamat;
private $telepon;
private $logo;
private $isTable = false;
private $tableWidth = [];
private $tableHeader = [];
// Lebar kertas A4 Portrait = 210mm, margin kiri+kanan = 10+10 = 20mm
// Area cetak efektif = 190mm
private $pageWidth = 190;
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $telepon, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->telepon = $telepon;
$this->logo = $logo;
}
public function setTableHeader($header, $width)
{
$this->tableHeader = $header;
$this->tableWidth = $width;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// Logo (opsional)
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 8, 18);
$this->SetXY(31, 10);
} else {
$this->SetXY(10, 10);
}
// Nama perusahaan
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 6, $this->nama, 0, 1, 'L');
// Alamat
$this->SetFont('Arial', '', 9);
$x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10;
$this->SetX($x);
$this->Cell(0, 5, $this->alamat, 0, 1, 'L');
$this->SetX($x);
$this->Cell(0, 5, 'Telp : ' . $this->telepon, 0, 1, 'L');
// Garis pemisah header (A4 Portrait: dari x=10 s/d x=200)
$this->Ln(2);
$this->SetLineWidth(0.6);
$this->Line(10, $this->GetY(), 200, $this->GetY());
$this->SetLineWidth(0.2);
$this->Line(10, $this->GetY() + 1, 200, $this->GetY() + 1);
$this->Ln(5);
// Judul laporan
$this->SetFont('Arial', 'B', 14);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 8, 'LAPORAN DATA BARANG', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Tanggal cetak
$this->SetFont('Arial', '', 9);
$this->Cell(0, 5, 'DIcetak Pada : ' . date('d-m-Y') . ' - ' . date('H:i:s'), 0, 1, 'C');
$this->Ln(3);
// Header tabel (muncul otomatis saat ganti halaman)
if ($this->isTable && !empty($this->tableHeader)) {
$this->_drawTableHeader();
}
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: Gambar baris header tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 8);
foreach ($this->tableHeader as $k => $v) {
$this->Cell($this->tableWidth[$k], 7, $v, 1, 0, 'C', true);
}
$this->SetTextColor(0, 0, 0);
$this->Ln();
}
// ----------------------------------------------------------------
// TABEL DATA BARANG
// ----------------------------------------------------------------
public function ItemsTable($data)
{
/*
* Total lebar kolom = 190mm (pas A4 Portrait, margin 10mm kiri & kanan)
*
* NO : 8
* TGL BELI : 22
* KODE : 22
* NAMA BARANG : 48 ← kolom terpanjang
* GUDANG : 35
* STOK : 13
* HARGA BELI : 21
* HARGA JUAL : 21
* ----
* TOTAL : 190
*/
$w = [8, 22, 22, 48, 35, 13, 21, 21];
$header = [
'NO',
'TGL BELI',
'KODE',
'NAMA BARANG',
'GUDANG',
'STOK',
'HARGA BELI',
'HARGA JUAL',
];
// Daftarkan SEBELUM AddPage() agar Header() sudah tahu isTable=true
// ketika halaman berikutnya dibuat (termasuk halaman ke-2, 3, dst.)
$this->isTable = true;
$this->setTableHeader($header, $w);
// Gambar header tabel di halaman PERTAMA secara eksplisit,
// karena saat AddPage() awal dipanggil isTable masih false.
$this->_drawTableHeader();
// ---- Baris data ----
$this->SetFont('Arial', '', 8);
$no = 1;
foreach ($data as $row) {
// Ganti halaman sebelum batas bawah (A4 Portrait: tinggi efektif ~267mm)
if ($this->GetY() > 262) {
$this->AddPage('P', 'A4');
// _drawTableHeader() sudah dipanggil otomatis oleh Header()
}
// Warna baris selang-seling
if ($no % 2 === 0) {
$this->SetFillColor(255, 245, 230);
$fill = true;
} else {
$fill = false;
}
$this->Cell($w[0], 6, $no++, 1, 0, 'C', $fill);
$this->Cell($w[1], 6, date('d-m-Y', strtotime($row->tanggal_pembelian)), 1, 0, 'C', $fill);
$this->Cell($w[2], 6, $row->kode_detail, 1, 0, 'C', $fill);
$this->Cell($w[3], 6, mb_substr($row->nama_barang, 0, 45), 1, 0, 'L', $fill);
$this->Cell($w[4], 6, mb_substr($row->gudang ?: '-', 0, 30), 1, 0, 'L', $fill);
$this->Cell($w[5], 6, number_format($row->stok), 1, 0, 'R', $fill);
$this->Cell($w[6], 6, number_format($row->harga_beli, 0, ',', '.'), 1, 0, 'R', $fill);
$this->Cell($w[7], 6, number_format($row->harga_jual, 0, ',', '.'), 1, 1, 'R', $fill);
}
$this->isTable = false;
}
// ----------------------------------------------------------------
// RINGKASAN & TANDA TANGAN
// ----------------------------------------------------------------
public function TotalBarang($jumlah)
{
$this->Ln(4);
$this->SetFont('Arial', 'B', 10);
$this->Cell(0, 7, 'Total Data Barang : ' . $jumlah, 0, 1, 'R');
}
// public function Pembuat($nama)
// {
// $this->Ln(12);
// $this->SetFont('Arial', '', 10);
// $this->Cell(0, 6, 'Mengetahui,', 0, 1, 'R');
// $this->Ln(18);
// $this->SetFont('Arial', 'B', 10);
// $this->Cell(0, 6, $nama, 0, 1, 'R');
// }
}
+345
View File
@@ -0,0 +1,345 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
// ----------------------------------------------------------------
// PROPERTI HEADER
// ----------------------------------------------------------------
private $nama;
private $alamat;
private $logo;
private $periodeStr = '';
// ----------------------------------------------------------------
// KONFIGURASI TABEL
// ----------------------------------------------------------------
// A4 Portrait efektif = 190mm, margin 10+10
// Kolom: Kode 20 | Nama Akun 120 | Saldo 50 → TOTAL 190
private $colWidths = [20, 120, 50];
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($periodeStr)
{
$this->periodeStr = $periodeStr;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// Logo (opsional)
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 6, 18);
$this->SetXY(31, 10);
} else {
$this->SetXY(10, 10);
}
// Nama perusahaan
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 6, $this->nama, 0, 1, 'L');
// Alamat
$this->SetFont('Arial', '', 9);
$x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10;
$this->SetX($x);
$this->Cell(0, 5, $this->alamat, 0, 1, 'L');
// Garis pemisah (A4 Portrait: x=10 s/d x=200)
$this->Ln(2);
$this->SetLineWidth(0.6);
$this->Line(10, $this->GetY(), 200, $this->GetY());
$this->SetLineWidth(0.2);
$this->Line(10, $this->GetY() + 1, 200, $this->GetY() + 1);
$this->Ln(5);
// Judul
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 7, 'LAPORAN LABA RUGI', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Periode
$this->SetFont('Arial', '', 10);
$this->Cell(0, 5, $this->periodeStr, 0, 1, 'C');
// Tanggal cetak
$this->SetFont('Arial', '', 9);
$this->Cell(0, 5, 'Di Cetak Pada : ' . date('d-m-Y') . ' Jam ' . date('H:i:s'), 0, 1, 'C');
$this->Ln(4);
// Header kolom tabel
$this->_drawTableHeader();
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: posisi X awal — TABEL RATA TENGAH KERTAS
// ----------------------------------------------------------------
private function _startX()
{
$tableW = array_sum($this->colWidths);
return ($this->GetPageWidth() - $tableW) / 2;
}
// ----------------------------------------------------------------
// HELPER: Gambar header kolom tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$startX = $this->_startX();
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0], 8, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 8, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 8, 'Saldo', 1, 1, 'C', true);
$this->SetTextColor(0, 0, 0);
}
// ----------------------------------------------------------------
// HELPER: Gambar baris judul section (PENDAPATAN / BEBAN)
// ----------------------------------------------------------------
private function _drawSectionTitle($title)
{
if ($this->GetY() + 8 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$this->SetX($startX);
$this->SetFillColor(255, 224, 178);
$this->SetFont('Arial', 'B', 9);
$this->Cell($tableW, 7, $title, 1, 1, 'L', true);
}
// ----------------------------------------------------------------
// HELPER: Gambar baris detail akun (dengan dukungan multiline Nama Akun)
// ----------------------------------------------------------------
private function _drawRow($kode, $namaAkun, $saldo)
{
$lineH = 6;
$nbLines = $this->NbLines($this->colWidths[1], $namaAkun);
$rowH = max(1, $nbLines) * $lineH;
if ($this->GetY() + $rowH > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$y = $this->GetY();
$this->SetFont('Arial', '', 9);
$this->SetXY($startX, $y);
$this->Cell($this->colWidths[0], $rowH, $kode, 1, 0, 'C');
$xNama = $this->GetX();
$this->MultiCell($this->colWidths[1], $lineH, $namaAkun, 1, 'L');
$this->SetXY($xNama + $this->colWidths[1], $y);
$this->Cell($this->colWidths[2], $rowH, number_format($saldo, 0, ',', '.'), 1, 0, 'R');
$this->SetXY($startX, $y + $rowH);
}
// ----------------------------------------------------------------
// HELPER: Gambar baris total section
// ----------------------------------------------------------------
private function _drawSectionTotal($label, $total)
{
if ($this->GetY() + 7 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$this->SetX($startX);
$this->SetFillColor(255, 224, 178);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 7, $label, 1, 0, 'L', true);
$this->Cell($this->colWidths[2], 7, number_format($total, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TABEL LABA RUGI (PENDAPATAN, BEBAN, masing-masing dengan rincian akun)
// ----------------------------------------------------------------
public function LabaRugiTable($pendapatan, $beban, $totalPendapatan, $totalBeban)
{
// ---- SECTION PENDAPATAN ----
$this->_drawSectionTitle('PENDAPATAN');
if (empty($pendapatan)) {
$this->_drawRow('-', 'Tidak ada data', 0);
} else {
foreach ($pendapatan as $row) {
$this->_drawRow($row['kode'], $row['nama'], $row['saldo']);
}
}
$this->_drawSectionTotal('TOTAL PENDAPATAN', $totalPendapatan);
// ---- SECTION BEBAN ----
$this->AddPage('P', 'A4');
$this->_drawSectionTitle('BEBAN');
if (empty($beban)) {
$this->_drawRow('-', 'Tidak ada data', 0);
} else {
foreach ($beban as $row) {
$this->_drawRow($row['kode'], $row['nama'], $row['saldo']);
}
}
$this->_drawSectionTotal('TOTAL BEBAN', $totalBeban);
}
// ----------------------------------------------------------------
// RINGKASAN: LABA / RUGI BERSIH
// ----------------------------------------------------------------
public function LabaRugiRingkasan($laba)
{
if ($this->GetY() + 9 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$labelW = $tableW - $this->colWidths[2];
$this->Ln(2);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
if ($laba >= 0) {
$this->SetFillColor(198, 239, 206); // hijau, laba
$label = 'LABA BERSIH';
} else {
$this->SetFillColor(255, 199, 206); // merah, rugi
$label = 'RUGI BERSIH';
}
$this->Cell($labelW, 8, $label, 1, 0, 'L', true);
$this->Cell($this->colWidths[2], 8, number_format($laba, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TANDA TANGAN
// ----------------------------------------------------------------
public function Pembuat($nama, $jabatan = '')
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$signWidth = 70;
$signX = $startX + $tableW - $signWidth; // sejajar sisi kanan tabel
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', '', 10);
$this->Cell($signWidth, 6, 'Mengetahui,', 0, 1, 'R');
$this->Ln(10);
$this->SetX($signX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($signWidth, 6, $nama, 0, 1, 'R');
if (!empty($jabatan)) {
$this->SetX($signX);
$this->SetFont('Arial', '', 9);
$this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'R');
}
}
// ----------------------------------------------------------------
// HELPER: Hitung jumlah baris yang dibutuhkan MultiCell
// ----------------------------------------------------------------
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
$nb--;
}
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") {
$i++;
$sep = -1;
$j = $i;
$l = 0;
$nl++;
continue;
}
if ($c == ' ') {
$sep = $i;
}
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) {
$i++;
}
} else {
$i = $sep + 1;
}
$sep = -1;
$j = $i;
$l = 0;
$nl++;
} else {
$i++;
}
}
return $nl;
}
}
+296
View File
@@ -0,0 +1,296 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
private $nama;
private $alamat;
private $logo;
private $periodeStr = '';
private $colWidths = [15, 47, 23];
private $gap = 10;
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($periodeStr)
{
$this->periodeStr = $periodeStr;
}
function Header()
{
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 6, 18);
$this->SetXY(31, 10);
} else {
$this->SetXY(10, 10);
}
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 6, $this->nama, 0, 1, 'L');
$this->SetFont('Arial', '', 9);
$x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10;
$this->SetX($x);
$this->Cell(0, 5, $this->alamat, 0, 1, 'L');
$this->Ln(2);
$this->SetLineWidth(0.6);
$this->Line(10, $this->GetY(), 200, $this->GetY());
$this->SetLineWidth(0.2);
$this->Line(10, $this->GetY() + 1, 200, $this->GetY() + 1);
$this->Ln(5);
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 7, 'LAPORAN NERACA', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
$this->SetFont('Arial', '', 10);
$this->Cell(0, 5, $this->periodeStr, 0, 1, 'C');
$this->SetFont('Arial', '', 9);
$this->Cell(0, 5, 'Di Cetak Pada : ' . date('d-m-Y') . ' - ' . date('H:i:s'), 0, 1, 'C');
$this->Ln(4);
$this->_drawSectionHeader();
}
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
private function _drawSectionHeader()
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 10);
$this->Cell($tableW, 7, 'ACTIVA', 1, 0, 'C', true);
$this->SetX($startX + $tableW + $this->gap);
$this->Cell($tableW, 7, 'PASIVA', 1, 1, 'C', true);
$this->SetFont('Arial', 'B', 8);
$this->SetFillColor(255, 224, 178);
$this->SetTextColor(0, 0, 0);
$this->SetX($startX);
$this->Cell($this->colWidths[0], 6, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 6, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 6, 'Saldo', 1, 0, 'C', true);
$this->SetX($startX + $tableW + $this->gap);
$this->Cell($this->colWidths[0], 6, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 6, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 6, 'Saldo', 1, 1, 'C', true);
}
private function _startX()
{
$totalW = (array_sum($this->colWidths) * 2) + $this->gap;
return ($this->GetPageWidth() - $totalW) / 2;
}
/**
* Hitung jumlah baris yang dibutuhkan MultiCell untuk teks tertentu
* pada lebar kolom $w. Diadaptasi dari contoh resmi FPDF.
*/
private function _nbLines($w, $txt)
{
if ($txt === null || $txt === '') return 1;
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', (string)$txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") $nb--;
$sep = -1; $i = 0; $j = 0; $l = 0; $nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") { $i++; $sep = -1; $j = $i; $l = 0; $nl++; continue; }
if ($c == ' ') $sep = $i;
$l += isset($cw[$c]) ? $cw[$c] : 0;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) $i++;
} else {
$i = $sep + 1;
}
$sep = -1; $j = $i; $l = 0; $nl++;
} else {
$i++;
}
}
return $nl;
}
/**
* Render satu baris (kiri+kanan) dengan tinggi sama,
* memakai MultiCell untuk kolom Nama Akun.
*/
private function _drawRow($activaRow, $pasivaRow, $startX, $rightX, $lineH)
{
$wNama = $this->colWidths[1];
$nlA = $activaRow ? $this->_nbLines($wNama, $activaRow['nama']) : 1;
$nlB = $pasivaRow ? $this->_nbLines($wNama, $pasivaRow['nama']) : 1;
$nl = max($nlA, $nlB, 1);
$h = $lineH * $nl;
// page break manual agar baris tidak terpotong
if ($this->GetY() + $h > $this->PageBreakTrigger) {
$this->AddPage($this->CurOrientation);
}
$y = $this->GetY();
// ---------- ACTIVA ----------
$this->SetXY($startX, $y);
if ($activaRow) {
// Kode (auto vertical-center karena pakai Cell dengan tinggi $h)
$this->Cell($this->colWidths[0], $h, $activaRow['kode'], 1, 0, 'C');
// Nama (MultiCell) — vertical center manual
$xNama = $this->GetX();
$this->Rect($xNama, $y, $wNama, $h);
$nLines = $this->_nbLines($wNama, $activaRow['nama']);
$offsetY = ($h - $lineH * $nLines) / 2;
$this->SetXY($xNama, $y + $offsetY);
$this->MultiCell($wNama, $lineH, $activaRow['nama'], 0, 'L');
// Saldo
$this->SetXY($xNama + $wNama, $y);
$this->Cell($this->colWidths[2], $h, number_format($activaRow['saldo'], 0, ',', '.'), 1, 0, 'R');
} else {
$this->Cell($this->colWidths[0], $h, '', 1, 0);
$this->Cell($wNama, $h, '', 1, 0);
$this->Cell($this->colWidths[2], $h, '', 1, 0);
}
// ---------- PASIVA ----------
$this->SetXY($rightX, $y);
if ($pasivaRow) {
$this->Cell($this->colWidths[0], $h, $pasivaRow['kode'], 1, 0, 'C');
$xNama = $this->GetX();
$this->Rect($xNama, $y, $wNama, $h);
$nLines = $this->_nbLines($wNama, $pasivaRow['nama']);
$offsetY = ($h - $lineH * $nLines) / 2;
$this->SetXY($xNama, $y + $offsetY);
$this->MultiCell($wNama, $lineH, $pasivaRow['nama'], 0, 'L');
$this->SetXY($xNama + $wNama, $y);
$this->Cell($this->colWidths[2], $h, number_format($pasivaRow['saldo'], 0, ',', '.'), 1, 0, 'R');
} else {
$this->Cell($this->colWidths[0], $h, '', 1, 0);
$this->Cell($wNama, $h, '', 1, 0);
$this->Cell($this->colWidths[2], $h, '', 1, 0);
}
// pindah ke baris berikutnya
$this->SetXY($startX, $y + $h);
}
public function NeracaTable($activa, $pasiva, $totalActiva, $totalPasiva)
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$rightX = $startX + $tableW + $this->gap;
$lineH = 5;
$this->SetFont('Arial', '', 8);
$maxRow = max(count($activa), count($pasiva));
for ($i = 0; $i < $maxRow; $i++) {
$this->_drawRow(
isset($activa[$i]) ? $activa[$i] : null,
isset($pasiva[$i]) ? $pasiva[$i] : null,
$startX, $rightX, $lineH
);
}
// ---- BARIS TOTAL ----
if ($this->GetY() + 8 > $this->PageBreakTrigger) {
$this->AddPage('P', 'A4');
}
$y = $this->GetY();
$this->SetFont('Arial', 'B', 8);
$this->SetFillColor(255, 204, 102);
$this->SetXY($startX, $y);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 7, 'TOTAL ACTIVA', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 7, number_format($totalActiva, 0, ',', '.'), 1, 0, 'R', true);
$this->SetXY($rightX, $y);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 7, 'TOTAL PASIVA', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 7, number_format($totalPasiva, 0, ',', '.'), 1, 1, 'R', true);
}
public function NeracaRingkasan($laba, $selisih)
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths) * 2 + $this->gap;
$labelW = $tableW - 40;
$this->Ln(4);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 9);
$this->Cell($labelW, 7, 'Laba Rugi Berjalan', 1, 0, 'L');
$this->SetFont('Arial', '', 9);
$this->Cell(40, 7, number_format($laba, 0, ',', '.'), 1, 1, 'R');
$this->SetX($startX);
$this->SetFont('Arial', 'B', 9);
if (abs($selisih) < 1) {
$this->SetFillColor(198, 239, 206);
$label = 'Selisih (Balance)';
} else {
$this->SetFillColor(255, 199, 206);
$label = 'Selisih (Tidak Balance)';
}
$this->Cell($labelW, 7, $label, 1, 0, 'L', true);
$this->Cell(40, 7, number_format($selisih, 0, ',', '.'), 1, 1, 'R', true);
}
public function Pembuat($nama, $jabatan = '')
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths) * 2 + $this->gap;
$signWidth = 70;
$signX = $startX + $tableW - $signWidth;
$this->Ln(15);
$this->SetX($signX);
$this->SetFont('Arial', '', 10);
$this->Cell($signWidth, 6, 'Mengetahui,', 0, 1, 'R');
$this->Ln(18);
$this->SetX($signX);
$this->SetFont('Arial', 'B', 10);
$this->Cell($signWidth, 6, $nama, 0, 1, 'R');
if (!empty($jabatan)) {
$this->SetX($signX);
$this->SetFont('Arial', '', 9);
$this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'R');
}
}
}
+313
View File
@@ -0,0 +1,313 @@
<?php
require_once(APPPATH . 'third_party/fpdf/fpdf.php');
class PDF extends FPDF
{
// ----------------------------------------------------------------
// PROPERTI HEADER
// ----------------------------------------------------------------
private $nama;
private $alamat;
private $logo;
private $periodeStr = '';
// ----------------------------------------------------------------
// KONFIGURASI TABEL
// ----------------------------------------------------------------
// A4 Portrait efektif = 190mm, margin 10+10
// Kolom: Kode 20 | Nama Akun 90 | Debit 40 | Kredit 40 → TOTAL 190
private $colWidths = [20, 90, 40, 40];
// ----------------------------------------------------------------
// SETTER
// ----------------------------------------------------------------
public function setHeaderData($nama, $alamat, $logo = '')
{
$this->nama = $nama;
$this->alamat = $alamat;
$this->logo = $logo;
}
public function setInfoLaporan($periodeStr)
{
$this->periodeStr = $periodeStr;
}
// ----------------------------------------------------------------
// HEADER HALAMAN
// ----------------------------------------------------------------
function Header()
{
// Header lengkap hanya di halaman pertama
if ($this->PageNo() === 1) {
// Logo (opsional)
if (!empty($this->logo) && file_exists($this->logo)) {
$this->Image($this->logo, 10, 6, 18);
$this->SetXY(31, 10);
} else {
$this->SetXY(10, 10);
}
// Nama perusahaan
$this->SetFont('Arial', 'B', 12);
$this->Cell(0, 6, $this->nama, 0, 1, 'L');
// Alamat
$this->SetFont('Arial', '', 9);
$x = !empty($this->logo) && file_exists($this->logo) ? 31 : 10;
$this->SetX($x);
$this->Cell(0, 5, $this->alamat, 0, 1, 'L');
// Garis pemisah (A4 Portrait: x=10 s/d x=200)
$this->Ln(2);
$this->SetLineWidth(0.6);
$this->Line(10, $this->GetY(), 200, $this->GetY());
$this->SetLineWidth(0.2);
$this->Line(10, $this->GetY() + 1, 200, $this->GetY() + 1);
$this->Ln(5);
// Judul
$this->SetFont('Arial', 'B', 13);
$this->SetTextColor(245, 140, 0);
$this->Cell(0, 7, 'LAPORAN KEUANGAN', 0, 1, 'C');
$this->SetTextColor(0, 0, 0);
// Periode
$this->SetFont('Arial', '', 10);
$this->Cell(0, 5, $this->periodeStr, 0, 1, 'C');
// Tanggal cetak
$this->SetFont('Arial', '', 9);
$this->Cell(0, 5, 'Di Cetak Pada : ' . date('d-m-Y') . ' - ' . date('H:i:s'), 0, 1, 'C');
$this->Ln(4);
} else {
// Halaman berikutnya tanpa header laporan
$this->SetY(10);
}
// Header kolom tabel tetap tampil di setiap halaman
$this->_drawTableHeader();
}
// ----------------------------------------------------------------
// FOOTER HALAMAN
// ----------------------------------------------------------------
function Footer()
{
$this->SetY(-10);
$this->SetFont('Arial', 'I', 8);
$this->Cell(0, 5, 'Halaman ' . $this->PageNo(), 0, 0, 'C');
}
// ----------------------------------------------------------------
// HELPER: posisi X awal — TABEL RATA TENGAH KERTAS
// ----------------------------------------------------------------
private function _startX()
{
$tableW = array_sum($this->colWidths);
return ($this->GetPageWidth() - $tableW) / 2;
}
// ----------------------------------------------------------------
// HELPER: Gambar header kolom tabel
// ----------------------------------------------------------------
private function _drawTableHeader()
{
$startX = $this->_startX();
$this->SetX($startX);
$this->SetFillColor(245, 140, 0);
$this->SetTextColor(255, 255, 255);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0], 8, 'Kode', 1, 0, 'C', true);
$this->Cell($this->colWidths[1], 8, 'Nama Akun', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 8, 'Debit', 1, 0, 'C', true);
$this->Cell($this->colWidths[3], 8, 'Kredit', 1, 1, 'C', true);
$this->SetTextColor(0, 0, 0);
}
// ----------------------------------------------------------------
// TABEL NERACA SALDO (mendukung multiline Nama Akun)
// ----------------------------------------------------------------
public function NeracaSaldoTable($rows, $totalDebit, $totalKredit)
{
$startX = $this->_startX();
$lineH = 6;
$this->SetFont('Arial', '', 9);
if (empty($rows)) {
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
$this->Cell(array_sum($this->colWidths), 8, 'Tidak ada data', 1, 1, 'C');
} else {
foreach ($rows as $row) {
$namaAkun = isset($row->nama_akun) ? $row->nama_akun : (isset($row['nama_akun']) ? $row['nama_akun'] : '');
$kodeAkun = isset($row->kode_akun) ? $row->kode_akun : (isset($row['kode_akun']) ? $row['kode_akun'] : '');
$debit = isset($row->debit) ? (float)$row->debit : (isset($row['debit']) ? (float)$row['debit'] : 0);
$kredit = isset($row->kredit) ? (float)$row->kredit : (isset($row['kredit']) ? (float)$row['kredit'] : 0);
$nbLines = $this->NbLines($this->colWidths[1], $namaAkun);
$rowH = max(1, $nbLines) * $lineH;
if ($this->GetY() + $rowH > 270) {
$this->AddPage('P', 'A4');
}
$y = $this->GetY();
$this->SetFont('Arial', '', 9);
$this->SetXY($startX, $y);
$this->Cell($this->colWidths[0], $rowH, $kodeAkun, 1, 0, 'C');
$xNama = $this->GetX();
$this->MultiCell($this->colWidths[1], $lineH, $namaAkun, 1, 'L');
$this->SetXY($xNama + $this->colWidths[1], $y);
$this->Cell($this->colWidths[2], $rowH, $debit > 0 ? number_format($debit, 0, ',', '.') : '-', 1, 0, 'R');
$this->Cell($this->colWidths[3], $rowH, $kredit > 0 ? number_format($kredit, 0, ',', '.') : '-', 1, 0, 'R');
$this->SetXY($startX, $y + $rowH);
}
}
// ---- BARIS TOTAL ----
if ($this->GetY() + 8 > 270) {
$this->AddPage('P', 'A4');
}
$this->SetX($startX);
$this->SetFillColor(255, 204, 102);
$this->SetFont('Arial', 'B', 9);
$this->Cell($this->colWidths[0] + $this->colWidths[1], 8, 'TOTAL', 1, 0, 'C', true);
$this->Cell($this->colWidths[2], 8, number_format($totalDebit, 0, ',', '.'), 1, 0, 'R', true);
$this->Cell($this->colWidths[3], 8, number_format($totalKredit, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// RINGKASAN: STATUS BALANCE
// ----------------------------------------------------------------
public function NeracaSaldoRingkasan($totalDebit, $totalKredit)
{
if ($this->GetY() + 9 > 270) {
$this->AddPage('P', 'A4');
}
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$labelW = $tableW - $this->colWidths[3];
$selisih = $totalDebit - $totalKredit;
$this->Ln(2);
$this->SetX($startX);
$this->SetFont('Arial', 'B', 10);
if (abs($selisih) < 1) {
$this->SetFillColor(198, 239, 206); // hijau, balance
$label = 'SELISIH (BALANCE)';
} else {
$this->SetFillColor(255, 199, 206); // merah, tidak balance
$label = 'SELISIH (TIDAK BALANCE)';
}
$this->Cell($labelW, 8, $label, 1, 0, 'L', true);
$this->Cell($this->colWidths[3], 8, number_format($selisih, 0, ',', '.'), 1, 1, 'R', true);
}
// ----------------------------------------------------------------
// TANDA TANGAN
// ----------------------------------------------------------------
public function Pembuat($nama, $jabatan = '')
{
$startX = $this->_startX();
$tableW = array_sum($this->colWidths);
$signWidth = 70;
$signX = $startX + $tableW - $signWidth; // sejajar sisi kanan tabel
$this->Ln(3);
// $this->SetX($signX);
$this->SetFont('Arial', 'I', 9);
$this->Cell($signWidth, 6, '*Mengetahui, (Finance Departement)', 0, 1, 'L');
// $this->Cell($signWidth, 6, $nama, 0, 1, 'L');
// if (!empty($jabatan)) {
// $this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'L');
// }
// $this->Ln(0);
// $this->SetX($signX);
// $this->SetFont('Arial', 'B', 10);
// $this->Cell($signWidth, 6, $nama, 0, 1, 'R');
// if (!empty($jabatan)) {
// $this->SetX($signX);
// $this->SetFont('Arial', '', 9);
// $this->Cell($signWidth, 5, '(' . $jabatan . ')', 0, 1, 'R');
// }
}
// ----------------------------------------------------------------
// HELPER: Hitung jumlah baris yang dibutuhkan MultiCell
// ----------------------------------------------------------------
private function NbLines($w, $txt)
{
$cw = &$this->CurrentFont['cw'];
if ($w == 0) {
$w = $this->w - $this->rMargin - $this->x;
}
$wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
$s = str_replace("\r", '', $txt);
$nb = strlen($s);
if ($nb > 0 && $s[$nb - 1] == "\n") {
$nb--;
}
$sep = -1;
$i = 0;
$j = 0;
$l = 0;
$nl = 1;
while ($i < $nb) {
$c = $s[$i];
if ($c == "\n") {
$i++;
$sep = -1;
$j = $i;
$l = 0;
$nl++;
continue;
}
if ($c == ' ') {
$sep = $i;
}
$l += isset($cw[ord($c)]) ? $cw[ord($c)] : 600;
if ($l > $wmax) {
if ($sep == -1) {
if ($i == $j) {
$i++;
}
} else {
$i = $sep + 1;
}
$sep = -1;
$j = $i;
$l = 0;
$nl++;
} else {
$i++;
}
}
return $nl;
}
}
@@ -0,0 +1,556 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class PeralatanTeknisi_model extends CI_Model {
public function __construct()
{
parent::__construct();
}
public function get_teknisi_list()
{
return $this->db
->select('e.id, e.employee_code, e.full_name, p.position_name, d.department_name')
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc'])
->order_by('e.full_name', 'ASC')
->get()
->result();
}
public function get_barang_dibawa($user_id = null, $status = 'active')
{
$this->db
->select("
it.barcode,
it.user_id,
it.item_id,
it.status,
it.created_at,
ib.serial_number,
i.nama_barang,
i.kode_detail,
ke.full_name as teknisi_name,
ke.employee_code
")
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('items i', 'i.id = it.item_id', 'left')
->join('k_employees ke', 'ke.id = it.user_id', 'left');
if ($user_id) {
$this->db->where('it.user_id', $user_id);
}
if ($status && in_array($status, ['active', 'inactive'])) {
$this->db->where('it.status', $status);
}
$this->db->order_by('it.created_at', 'DESC');
return $this->db->get()->result();
}
public function get_item_by_barcode($barcode)
{
$barcode = trim($barcode);
if ($barcode == '') return null;
$row = $this->db
->select('ib.id as barcode_id, ib.item_id, ib.barcode, ib.serial_number, ib.qty_sisa, ib.status as barcode_status, i.nama_barang, i.kode_detail')
->from('item_barcodes ib')
->join('items i', 'i.id = ib.item_id', 'left')
->where('ib.barcode', $barcode)
->where('ib.qty_sisa >', 0)
->get()
->row();
if (!$row) {
$row = $this->db
->select('ib.id as barcode_id, ib.item_id, ib.barcode, ib.serial_number, ib.qty_sisa, ib.status as barcode_status, i.nama_barang, i.kode_detail')
->from('item_barcodes ib')
->join('items i', 'i.id = ib.item_id', 'left')
->where('ib.serial_number', $barcode)
->where('ib.qty_sisa >', 0)
->get()
->row();
}
return $row;
}
public function get_current_teknisi($barcode)
{
return $this->db
->select('it.user_id, ke.full_name, ke.employee_code')
->from('item_technician it')
->join('k_employees ke', 'ke.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->where('it.status', 'active')
->get()
->row();
}
public function batch_kembalikan(array $barcodes)
{
$now = date('Y-m-d H:i:s');
$errors = [];
$this->db->trans_start();
foreach ($barcodes as $barcode) {
$barcode = trim($barcode);
if ($barcode == '') continue;
$item = $this->db
->select('it.*, ib.id as barcode_id, tec.full_name as teknisi_name')
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('k_employees tec', 'tec.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->where('it.status', 'active')
->get()
->row();
if (!$item) {
$errors[] = "Barcode $barcode tidak ditemukan atau sudah dikembalikan";
continue;
}
$this->db->insert('item_movements', [
'item_id' => $item->item_id,
'barcode_id' => $item->barcode_id,
'qty' => 1,
'from_type' => 'technician',
'from_id' => $item->user_id,
'to_type' => 'warehouse',
'to_id' => null,
'movement_type' => 'transfer',
'notes' => 'Pengembalian dari: ' . $item->teknisi_name . ' ke gudang',
'created_at' => $now
]);
$this->db
->where('barcode', $barcode)
->where('status', 'active')
->update('item_technician', [
'status' => 'inactive',
'deleted_at' => $now
]);
if ($item->barcode_id) {
$this->db
->where('id', $item->barcode_id)
->update('item_barcodes', [
'status' => 'available'
]);
}
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
return ['status' => false, 'message' => 'Gagal memproses pengembalian'];
}
$success_count = count($barcodes) - count($errors);
$msg = "$success_count barang berhasil dikembalikan";
if (count($errors) > 0) {
$msg .= '. ' . implode(', ', $errors);
}
return ['status' => true, 'message' => $msg, 'errors' => $errors];
}
public function get_dates_by_employee_id($user_id)
{
return $this->db
->select("DATE(it.created_at) as tanggal")
->from('item_technician it')
->where('it.user_id', $user_id)
->where('it.status', 'active')
->group_by("DATE(it.created_at)")
->order_by("DATE(it.created_at)", "DESC")
->get()
->result();
}
public function batch_dibawa(array $barcodes, $user_id)
{
$now = date('Y-m-d H:i:s');
$errors = [];
$skipped = 0;
$technician = $this->db
->select('id, full_name, employee_code')
->from('k_employees')
->where('id', $user_id)
->get()
->row();
$this->db->trans_start();
foreach ($barcodes as $barcode) {
$barcode = trim($barcode);
if ($barcode == '') continue;
$barcode_row = $this->db
->select('ib.id as barcode_id, ib.item_id, ib.barcode')
->from('item_barcodes ib')
->where('ib.barcode', $barcode)
->where_not_in('ib.status', ['damaged', 'lost', 'returned_supplier', 'sold_out'])
->get()
->row();
if (!$barcode_row) {
$errors[] = "Barcode $barcode tidak valid atau tidak tersedia";
continue;
}
$already = $this->db
->where('barcode', $barcode_row->barcode)
->where('status', 'active')
->get('item_technician')
->row();
if ($already) {
if ($already->user_id == $user_id) {
$skipped++;
continue;
}
$errors[] = "Barang $barcode sedang dibawa teknisi lain";
continue;
}
$this->db->insert('item_technician', [
'user_id' => $user_id,
'barcode' => $barcode_row->barcode,
'item_id' => $barcode_row->item_id,
'status' => 'active',
'created_at' => $now
]);
$last_movement = $this->db
->where('barcode_id', $barcode_row->barcode_id)
->order_by('id', 'DESC')
->limit(1)
->get('item_movements')
->row();
$from_type = 'warehouse';
$from_id = null;
if ($last_movement) {
$from_type = $last_movement->to_type;
$from_id = $last_movement->to_id;
}
$this->db->insert('item_movements', [
'item_id' => $barcode_row->item_id,
'barcode_id' => $barcode_row->barcode_id,
'qty' => 1,
'from_type' => $from_type,
'from_id' => $from_id,
'to_type' => 'technician',
'to_id' => $user_id,
'movement_type' => 'transfer',
'notes' => 'Dibawa oleh: ' . $technician->full_name . ' dari gudang',
'created_at' => $now
]);
$this->db
->where('id', $barcode_row->barcode_id)
->update('item_barcodes', [
'status' => 'installed'
]);
}
$this->db->trans_complete();
if ($this->db->trans_status() === FALSE) {
return ['status' => false, 'message' => 'Gagal memproses penugasan barang'];
}
$success_count = count($barcodes) - count($errors) - $skipped;
$msg = "$success_count barang berhasil ditugaskan";
if ($skipped > 0) {
$msg .= ". $skipped barang sudah ditugaskan ke teknisi yang sama";
}
if (count($errors) > 0) {
$msg .= '. ' . implode(', ', $errors);
}
return ['status' => true, 'message' => $msg, 'errors' => $errors];
}
public function get_teknisi_table_data($start, $length, $search, $order_col, $order_dir)
{
$sort_cols = [
1 => 'e.full_name',
2 => 'e.employee_code',
3 => 'd.department_name',
4 => 'item_count'
];
$this->db
->select("
e.id,
e.full_name,
e.employee_code,
p.position_name,
d.department_name,
(SELECT COUNT(*) FROM item_technician it WHERE it.user_id = e.id AND it.status = 'active') as item_count
")
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
if (!empty($search)) {
$this->db->group_start()
->like('LOWER(e.full_name)', strtolower($search))
->or_like('LOWER(e.employee_code)', strtolower($search))
->or_like('LOWER(d.department_name)', strtolower($search))
->group_end();
}
if (isset($sort_cols[$order_col])) {
$this->db->order_by($sort_cols[$order_col], $order_dir);
} else {
$this->db->order_by('e.full_name', 'ASC');
}
if ($length > 0) {
$this->db->limit($length, $start);
}
return $this->db->get()->result();
}
public function count_all_teknisi()
{
$this->db
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
return $this->db->count_all_results();
}
public function count_filtered_teknisi($search)
{
$this->db
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('(e.resign_date IS NULL OR e.resign_date = \'0000-00-00\')')
->where_in('LOWER(p.position_name)', ['teknisi', 'noc']);
if (!empty($search)) {
$this->db->group_start()
->like('LOWER(e.full_name)', strtolower($search))
->or_like('LOWER(e.employee_code)', strtolower($search))
->or_like('LOWER(d.department_name)', strtolower($search))
->group_end();
}
return $this->db->count_all_results();
}
public function get_teknisi_info($user_id)
{
return $this->db
->select('e.id, e.full_name, e.employee_code, p.position_name, d.department_name')
->from('k_employees e')
->join('k_positions p', 'p.id = e.position_id', 'left')
->join('k_departments d', 'd.id = e.department_id', 'left')
->where('e.id', $user_id)
->get()
->row();
}
public function get_teknisi_items_data($user_id, $start, $length, $search, $order_col, $order_dir)
{
$sort_cols = [
1 => 'it.created_at',
2 => 'it.barcode',
3 => 'i.nama_barang',
4 => 'ib.serial_number',
5 => 'it.status'
];
$this->db
->select("
it.barcode,
i.nama_barang,
ib.serial_number,
ib.qty_sisa,
it.status,
DATE_FORMAT(it.created_at, '%d/%m/%Y') as tanggal
")
->from('item_technician it')
->join('items i', 'i.id = it.item_id', 'left')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->where('it.user_id', $user_id)
->where('it.status', 'active');
if (!empty($search)) {
$this->db->group_start()
->like('it.barcode', $search)
->or_like('LOWER(i.nama_barang)', strtolower($search))
->or_like('ib.serial_number', $search)
->group_end();
}
if (isset($sort_cols[$order_col])) {
$this->db->order_by($sort_cols[$order_col], $order_dir);
} else {
$this->db->order_by('it.created_at', 'DESC');
}
if ($length > 0) {
$this->db->limit($length, $start);
}
return $this->db->get()->result();
}
public function count_all_teknisi_items($user_id)
{
$this->db
->from('item_technician it')
->where('it.user_id', $user_id)
->where('it.status', 'active');
return $this->db->count_all_results();
}
public function count_filtered_teknisi_items($user_id, $search)
{
$this->db
->from('item_technician it')
->join('items i', 'i.id = it.item_id', 'left')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->where('it.user_id', $user_id)
->where('it.status', 'active');
if (!empty($search)) {
$this->db->group_start()
->like('it.barcode', $search)
->or_like('LOWER(i.nama_barang)', strtolower($search))
->or_like('ib.serial_number', $search)
->group_end();
}
return $this->db->count_all_results();
}
public function get_transfer_data()
{
return $this->db
->select("
it.barcode,
i.nama_barang,
ib.serial_number,
from_ke.full_name as from_name,
from_ke.employee_code as from_code,
to_ke.full_name as to_name,
to_ke.employee_code as to_code,
im.created_at,
im.movement_type
")
->from('item_movements im')
->join('items i', 'i.id = im.item_id', 'left')
->join('item_barcodes ib', 'ib.id = im.barcode_id', 'left')
->join('k_employees from_ke', 'from_ke.id = im.from_id', 'left')
->join('k_employees to_ke', 'to_ke.id = im.to_id', 'left')
->where('im.movement_type', 'installation')
->where('im.from_type', 'technician')
->where('im.to_type', 'technician')
->order_by('im.created_at', 'DESC')
->limit(200)
->get()
->result();
}
public function detail_by_barcode($barcode)
{
return $this->db
->select("
it.*,
ib.serial_number,
ib.qty_sisa,
ib.status as barcode_status,
i.nama_barang,
i.kode_detail,
ke.full_name as teknisi_name,
ke.employee_code
")
->from('item_technician it')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->join('items i', 'i.id = it.item_id', 'left')
->join('k_employees ke', 'ke.id = it.user_id', 'left')
->where('it.barcode', $barcode)
->get()
->row();
}
public function get_teknisi_items_full($user_id, $tanggal = null)
{
$this->db
->select("
it.barcode,
i.nama_barang,
ib.serial_number,
ib.qty_sisa,
it.status,
DATE_FORMAT(it.created_at, '%d/%m/%Y') as tanggal
")
->from('item_technician it')
->join('items i', 'i.id = it.item_id', 'left')
->join('item_barcodes ib', 'ib.barcode = it.barcode', 'left')
->where('it.user_id', $user_id)
->where('it.status', 'active');
if ($tanggal AND $tanggal != 'all') {
$this->db->where('DATE(it.created_at)', $tanggal);
}
return $this->db
->order_by('it.created_at', 'ASC')
->get()
->result();
}
public function generate_no_faktur()
{
return 'GRJN/K/' . date('d/m/Y');
}
public function get_log_barang()
{
return $this->db
->select("
it.barcode,
it.item_id,
it.user_id,
it.status,
it.created_at,
it.deleted_at,
ke.full_name as teknisi_name,
i.kode_detail as item_code,
i.nama_barang
")
->from('item_technician it')
->join('k_employees ke', 'ke.id = it.user_id', 'left')
->join('items i', 'i.id = it.item_id', 'left')
->order_by('it.created_at', 'DESC')
->get()
->result();
}
}
+308 -38
View File
@@ -16,9 +16,10 @@
<tr>
<th>No</th>
<th>Tanggal</th>
<th>Kode</th>
<th>Nama</th>
<th style="min-width: 80px;">Kode</th>
<th style="min-width: 80px;">Nama</th>
<th>Lokasi Asset</th>
<th style="max-width: 150px;">Keterangan</th>
<th>Nilai</th>
<th>Masa</th>
<th>Susut/Bulan</th>
@@ -56,9 +57,10 @@
<input type="text" id="nama_asset" class="form-control">
<label class="mt-2">Keterangan</label>
<input type="text" id="keterangan" class="form-control">
<textarea rows="3" id="keterangan" class="form-control"></textarea>
<!-- <input type="text" id="keterangan" class="form-control"> -->
<label class="mt-2">Harga Per Unit</label>
<label class="mt-2">Harga</label>
<input type="text" id="harga" class="form-control format-rupiah">
<label class="mt-2">Tanggal</label>
@@ -99,7 +101,8 @@
<select id="edit_lokasi_asset" class="form-control"></select>
<label class="mt-2">Keterangan</label>
<input type="text" id="edit_keterangan" class="form-control">
<!-- <input type="text" id="edit_keterangan" class="form-control"> -->
<textarea rows="3" id="edit_keterangan" class="form-control"></textarea>
</div>
<div class="modal-footer">
<button class="btn btn-primary" id="btnUpdate">Update</button>
@@ -146,7 +149,7 @@
</div>
<div class="modal-body">
<input type="hidden" id="s_item_id">
<input type="hidden" id="s_harga">
<!-- <input type="hidden" id="s_harga"> -->
<label class="mt-2">Akun Asset (Debit)</label>
<select id="s_account_asset" class="form-control"></select>
@@ -155,14 +158,33 @@
<select id="s_lokasi_asset" class="form-control"></select>
<label>Pilih Barang</label>
<select id="s_item" class="form-control"></select>
<select id="s_item" class="form-control select-search"></select>
<label class="mt-2">Stok Tersedia</label>
<input type="text" id="s_stok" class="form-control" readonly>
<div class="mb-2 d-none" id="wrap_barcode_select">
<label for="barcode_select" class="form-label">Barcode / QR ID</label>
<select id="barcode_select" class="form-control select-search"></select>
</div>
<div id="stokSisaQty"></div>
<label class="mt-2">Harga Barang</label>
<input type="text" id="s_harga" class="form-control format-rupiah" disabled>
<div class="mb-0 d-none" id="wrap_qty">
<label class="mt-2">Qty</label>
<input type="number" id="s_qty" placeholder="1" class="form-control">
<label class="mt-2">Total Harga</label>
<input type="text" id="s_total_harga" class="form-control format-rupiah" disabled>
</div>
<label class="mt-2">Tanggal</label>
<input type="date" id="s_tanggal" class="form-control">
<label class="mt-2">Keterangan</label>
<textarea rows="3" id="s_keterangan" class="form-control"></textarea>
<!-- <input type="text" id="s_keterangan" class="form-control"> -->
<hr>
<label>Masa Manfaat (bulan)</label>
@@ -224,6 +246,10 @@ function formatRupiah(angka){
return angka.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
function parseNumber(val){
return parseInt((val || '').toString().replace(/\D/g,'')) || 0;
}
function setSelectValue(selector, value){
setTimeout(()=>{
$(selector).val(value).trigger('change');
@@ -269,7 +295,7 @@ function loadItems(){
$.get("<?= base_url('asset/get_items'); ?>",res=>{
let opt='<option value="">-- PILIH BARANG --</option>';
res.forEach(i=>{
opt += `<option value="${i.id}" data-harga="${i.harga_beli}" data-stok="${i.stok}" data-nama="${i.nama_barang}">
opt += `<option value="${i.id}" data-id="${i.id}" data-harga="${i.harga_beli}" data-stok="${i.stok}" data-nama="${i.nama_barang}" data-type="${i.tracking_type}">
${i.kode_detail} - ${i.nama_barang} - ( Stok: ${i.stok})
</option>`;
});
@@ -282,20 +308,81 @@ function hitungSusut(){
let harga = parseInt($('#harga').val().replace(/\D/g,'')) || 0;
let residu = parseInt($('#residu').val().replace(/\D/g,'')) || 0;
let masa = parseInt($('#masa').val()) || 1;
let maxResidu = harga / 100 * 80;
if (residu > maxResidu) {
residu = maxResidu;
$('#residu').val(maxResidu).trigger('input');
}
let susut = (harga - residu) / masa;
$('#susut').val(formatRupiah(Math.round(susut)));
susut = Math.max(0, susut);
$('#susut').val(susut);
}
// ================= LOAD BARCODE ITEMS =================
// function loadItemBarcodes(item_id){
// return $.get("<?= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
// if(res && res.length){
// let opt = '<option value="">Pilih Item</option>';
// res.forEach(i=>{
// opt += `<option value="${i.barcode_id}">
// ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
// </option>`;
// });
// $('#barcode_select').html(opt);
// $('#wrap_barcode_select').removeClass('d-none');
// } else {
// $('#wrap_barcode_select').addClass('d-none');
// }
// }, 'json');
// }
function loadItemBarcodes(item_id, selectedBarcode = null){
return $.get("<?= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
if(res && res.length){
let opt = '<option value="">Pilih Barcode</option>';
res.forEach(i=>{
opt += `
<option value="${i.barcode_id}" data-qty_sisa="${i.qty_sisa}">
${i.nama_barang} - ${i.barcode} - ${i.serial_number}
</option>
`;
});
$('#barcode_select').html(opt);
if(selectedBarcode){
$('#barcode_select').val(selectedBarcode);
}
$('#wrap_barcode_select').removeClass('d-none');
// setMode('item');
}else{
$('#wrap_barcode_select').addClass('d-none');
// setMode('manual');
}
},'json');
}
$('#masa, #residu, #harga').on('keyup change',hitungSusut);
// ================= HITUNG SUSUT STOCK =================
$('#s_masa, #s_residu').on('keyup change',function(){
let harga = parseInt($('#s_harga').val()) || 0;
let residu = parseInt($('#s_residu').val().replace(/\D/g,'')) || 0;
function hitungSusutStock(){
let trackingType = ($('#s_item option:selected').data('type') || '').toString().trim().toUpperCase();
let hargaDasar = trackingType === 'QTY' ? parseNumber($('#s_total_harga').val()) : parseNumber($('#s_harga').val());
let residu = parseNumber($('#s_residu').val());
let masa = parseInt($('#s_masa').val()) || 1;
let susut = (harga - residu) / masa;
$('#s_susut').val(formatRupiah(Math.round(susut)));
});
let maxResidu = hargaDasar / 100 * 80;
if (residu > maxResidu) {
residu = maxResidu;
$('#s_residu').val(maxResidu).trigger('input');
}
let susut = (hargaDasar - residu) / masa;
susut = Math.max(0, susut);
$('#s_susut').val(susut);
}
$('#s_masa, #s_residu').on('keyup change', hitungSusutStock);
// ================= TAMBAH ASSET MANUAL =================
$('.btn-add-asset').click(function(){
@@ -303,26 +390,77 @@ $('.btn-add-asset').click(function(){
$('#modalTitleAsset').text('Tambah Asset');
$('#modalAsset input[type="text"], #modalAsset input[type="date"], #modalAsset input[type="number"], #modalAsset select').val('');
$('#id_asset').val('');
$('#stokSisaQty').html('');
loadKas(); loadAsset(); loadLokasiAsset();
$('#modalAsset').modal('show');
});
// ================= DETAIL & HISTORY =================
$(document).on('click','.btn-detail',function(){
let id = $(this).data('id');
$.get("<?= base_url('asset/detail/'); ?>"+id, function(asset){
$('#detailContent').html(`
<table class="table table-borderless">
<tr><td><strong>Kode Asset</strong></td><td>: <strong>${asset.kode_asset || '-'}</strong></td></tr>
<tr><td><strong>Nama Asset</strong></td><td>: <strong>${asset.nama_asset}</strong></td></tr>
<tr><td><strong>Lokasi</strong></td><td>: <strong>${asset.nama_lokasi || '-'}</strong></td></tr>
<tr><td><strong>Harga/Unit</strong></td><td>: <strong>Rp ${formatRupiah(asset.harga_per_unit)}</strong></td></tr>
<tr><td><strong>Nilai Buku</strong></td><td>: <strong>Rp ${formatRupiah(asset.nilai_buku)}</strong></td></tr>
<tr><td><strong>Masa Manfaat</strong></td><td>: <strong>${asset.masa_manfaat} bulan</strong></td></tr>
<tr><td><strong>Sumber</strong></td><td>: <strong>${asset.sumber}</strong></td></tr>
<tr><td><strong>Keterangan</strong></td><td>: <strong>${asset.keterangan || '-'}</strong></td></tr>
</table>
<div class="row">
<div class="col-md-6">
<table class="table table-borderless table-sm">
<tr>
<td width="45%"><strong>Kode Asset</strong></td>
<td>: <strong>${asset.kode_asset || '-'}</strong></td>
</tr>
<tr>
<td><strong>Nama Asset</strong></td>
<td>: <strong>${asset.nama_asset}</strong></td>
</tr>
<tr>
<td><strong>Lokasi</strong></td>
<td>: <strong>${asset.nama_lokasi || '-'}</strong></td>
</tr>
<tr>
<td><strong>Harga/Unit</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.harga_per_unit)}</strong></td>
</tr>
<tr>
<td><strong>Qty</strong></td>
<td>: <strong>${asset.qty}</strong></td>
</tr>
<tr>
<td><strong>Nilai Perolehan</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.nilai_perolehan)}</strong></td>
</tr>
</table>
</div>
<div class="col-md-6">
<table class="table table-borderless table-sm">
<tr>
<td width="45%"><strong>Akumulasi Penyusutan</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.akumulasi_penyusutan || 0)}</strong></td>
</tr>
<tr>
<td><strong>Nilai Buku</strong></td>
<td>: <strong>Rp ${formatRupiah(asset.nilai_buku)}</strong></td>
</tr>
<tr>
<td><strong>Masa Manfaat</strong></td>
<td>: <strong>${asset.masa_manfaat} bulan</strong></td>
</tr>
<tr>
<td><strong>Sumber</strong></td>
<td>: <strong>${asset.sumber}</strong></td>
</tr>
<tr>
<td><strong>Keterangan</strong></td>
<td>: <strong>${asset.keterangan || '-'}</strong></td>
</tr>
</table>
</div>
</div>
`);
// Load history
@@ -336,7 +474,7 @@ $(document).on('click','.btn-detail',function(){
h.tipe=='penambahan' ? 'bg-info' :
h.tipe=='pengurangan' ? 'bg-warning' : 'bg-secondary';
rows += `<tr>
<td>${h.created_at}</td>
<td>${h.tanggal_dibuat}</td>
<td><span class="badge ${badgeClass}">${h.tipe}</span></td>
<td>Rp ${formatRupiah(h.nilai || 0)}</td>
<td>${h.keterangan}</td>
@@ -465,35 +603,157 @@ $('#btnSave').click(function(){
});
// ================= FROM STOCK =================
$('.btn-from-stock').click(function(){
function resetStockForm(){
$('#modalAssetStock input[type="text"], #modalAssetStock input[type="date"], #modalAssetStock input[type="number"], #modalAssetStock select').val('');
$('#s_item_id, #s_harga').val('');
$('#s_item_id, #s_harga, #s_total_harga, #s_susut, #s_keterangan').val('');
$('#s_qty').val(1);
$('#stokSisaQty').html('');
$('#wrap_qty').addClass('d-none');
$('#wrap_barcode_select').removeClass('d-none');
$('#barcode_select').html('<option value="">Loading...</option>');
}
$('.btn-from-stock').click(function(){
resetStockForm();
loadItems();
loadAsset();
loadLokasiAsset();
$('#modalAssetStock').modal('show');
});
// ================= PILIH ITEM STOCK =================
$(document).on('change','#s_item',function(){
$('#barcode_select').on('change', function(){
let selectedItem = $('#s_item option:selected');
let type = (selectedItem.data('type') || '').toString().trim().toUpperCase();
let selected = $(this).find(':selected');
let harga = selected.data('harga') || 0;
let stok = selected.data('stok') || 0;
$('#s_item_id').val($(this).val());
let qty_sisa = parseFloat(selected.data('qty_sisa')) || 0;
if($(this).val()){
$('#stokSisaQty').html('<h6 class="text-success ml-4">Stok : ' + qty_sisa + '</h6><hr>');
}else{
$('#stokSisaQty').html('');
}
// perlakuan qty setelah barcode dipilih, berdasarkan tracking_type
if(type === 'UNIT'){
$('#stokSisaQty').html('');
$('#wrap_qty').addClass('d-none');
$('#s_qty').removeAttr('max').val(1);
$('#s_total_harga').val(parseNumber($('#s_harga').val()));
hitungSusutStock();
}else{
$('#wrap_qty').removeClass('d-none');
$('#s_qty').attr('min', 1).attr('max', qty_sisa > 0 ? qty_sisa : 1).val('');
$('#s_total_harga').val('');
}
});
$('#s_item').on('change', function(){
let selected = $(this).find(':selected');
let item_id = selected.data('id');
let type = selected.data('type');
let harga = parseInt(selected.data('harga')) || 0;
let stok = parseFloat(selected.data('stok')) || 0;
$('#s_item_id').val(item_id || '');
$('#s_harga').val(harga);
$('#s_stok').val(stok);
$('#s_total_harga').val(harga);
$('#stokSisaQty').html('');
$('#s_qty').val(1).removeAttr('max');
$('#wrap_qty').addClass('d-none');
$('#wrap_barcode_select').addClass('d-none');
$('#barcode_select').html('<option value="">Pilih Barcode</option>');
if(!item_id){
return;
}
let normalizedType = (type || '').toString().trim().toUpperCase();
// barcode tetap harus muncul untuk ITEM maupun QTY
loadItemBarcodes(item_id, null).done(function(res){
if(res && res.length){
$('#wrap_barcode_select').removeClass('d-none');
}else{
$('#wrap_barcode_select').addClass('d-none');
$('#barcode_select').html('<option value="">Barcode tidak tersedia</option>');
}
// sebelum pilih barcode: qty & total belum ditentukan
$('#wrap_qty').addClass('d-none');
$('#s_qty').removeAttr('max').val('');
$('#s_total_harga').val('');
}).fail(function(){
$('#wrap_barcode_select').addClass('d-none');
$('#barcode_select').html('<option value="">Gagal load barcode</option>');
$('#wrap_qty').addClass('d-none');
$('#s_qty').removeAttr('max').val('');
$('#s_total_harga').val('');
});
});
$('#s_qty').on('keyup change',function(){
let selectedItem = $('#s_item option:selected');
let type = (selectedItem.data('type') || '').toString().trim().toUpperCase();
let qtyRaw = $('#s_qty').val();
let qty = parseInt(qtyRaw);
let harga = parseNumber($('#s_harga').val());
let max = parseFloat($('#s_qty').attr('max')) || 0;
if(type === 'UNIT'){
$('#wrap_qty').addClass('d-none');
$('#s_qty').val(1);
$('#s_total_harga').val(harga);
return;
}
if(!qtyRaw || isNaN(qty) || qty < 1){
$('#s_total_harga').val('');
return;
}
if(max > 0 && qty > max){
qty = max;
Swal.fire('Warning','Stok tidak cukup! Max: ' + max,'warning');
}
let total = harga * qty;
$('#s_qty').val(qty);
$('#s_total_harga').val(total);
hitungSusutStock();
});
// ================= SAVE FROM STOCK =================
$('#btnSaveStock').click(function(){
let btn = $(this);
let trackingType = ($('#s_item option:selected').data('type') || '').toString().trim().toUpperCase();
let maxQty = parseFloat($('#s_qty').attr('max')) || 0;
let qtyRaw = $('#s_qty').val();
let qty = parseInt(qtyRaw);
if(trackingType === 'UNIT'){
qty = 1;
}else{
if(!qtyRaw || isNaN(qty) || qty < 1){
Swal.fire('Error','Qty wajib diisi untuk item tracking QTY','error');
return;
}
if(maxQty > 0 && qty > maxQty){
Swal.fire('Error','Qty melebihi stok tersedia','error');
return;
}
}
let data = {
item_id: $('#s_item').val(),
s_tanggal: $('#s_tanggal').val(),
s_masa: $('#s_masa').val(),
s_residu: $('#s_residu').val(),
s_lokasi_asset: $('#s_lokasi_asset').val(),
account_asset: $('#s_account_asset').val()
account_asset: $('#s_account_asset').val(),
s_keterangan: $('#s_keterangan').val(),
barcode_id: $('#barcode_select').val(),
qty: qty
};
// Validasi
@@ -502,6 +762,15 @@ $('#btnSaveStock').click(function(){
return;
}
if(!data.barcode_id){
Swal.fire('Error','Barcode wajib dipilih','error');
return;
}
if(trackingType === 'UNIT'){
data.qty = 1;
}
btn.prop('disabled', true).html('Menyimpan...');
$.post("<?= base_url('asset/save_from_stock'); ?>",data,function(res){
@@ -523,5 +792,6 @@ $('#btnSaveStock').click(function(){
});
});
});
</script>
+58 -3
View File
@@ -13,19 +13,23 @@
<select id="account_id" class="form-control select-search"></select>
</div>
<div class="col-md-3">
<div class="col-md-2">
<label>Dari</label>
<input type="date" id="start_date" class="form-control">
</div>
<div class="col-md-3">
<div class="col-md-2">
<label>Sampai</label>
<input type="date" id="end_date" class="form-control">
</div>
<div class="col-md-2 d-flex align-items-end">
<div class="col-md-4 d-flex align-items-end gap-2">
<button class="btn btn-primary w-100" id="btnFilter">Filter</button>
<button type="button" class="btn btn-danger w-100" id="generate_pdf">
Generate PDF</button>
</div>
</div>
<div class="table-responsive">
@@ -92,10 +96,61 @@ $(function(){
},'json');
}
// FILTER
$('#btnFilter').click(function(){
loadData();
});
// Prevent double submit
$(document).on('submit', 'form', function(e){
e.preventDefault();
});
// GENERATE PDF
$('#generate_pdf').on('click', function () {
const now = new Date();
const today = now.getFullYear() + '-'
+ String(now.getMonth() + 1).padStart(2, '0') + '-'
+ String(now.getDate()).padStart(2, '0');
const account_id = $('#account_id').val();
const start_date = $('#start_date').val() || '0000-00-00';
const end_date = $('#end_date').val() || today;
let btn = $(this);
btn.prop('disabled', true).html('Generate PDF...');
$.ajax({
url: "<?= base_url('Generatebuku/generatepdf'); ?>"
+ '/' + account_id
+ '/' + start_date
+ '/' + end_date,
type: "GET",
xhrFields: {
responseType: 'blob' // penting untuk PDF
},
success: function (response) {
let blob = new Blob([response], { type: 'application/pdf' });
let url = window.URL.createObjectURL(blob);
// buka di tab baru
window.open(url, '_blank');
},
error: function () {
Swal.fire('Error', 'Gagal generate PDF', 'error');
},
complete: function(){
btn.prop('disabled', false).html('Generate PDF');
}
});
});
});
</script>
@@ -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>
+125 -76
View File
@@ -89,7 +89,7 @@
<!-- MODAL (TIDAK DIUBAH STYLE) -->
<div class="modal fade" id="modalItem">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
<h5 class="modal-title" id="modalTitle">Item Invoice</h5>
@@ -98,9 +98,6 @@
<div class="modal-body">
<input type="hidden" id="item_id">
<div id="item_mode_label" class="alert alert-secondary py-1 small mb-3">
Mode: Manual Input
</div>
<div class="mb-2">
<label for="account_id" class="form-label">Tujuan Neraca (Account)</label>
@@ -124,7 +121,14 @@
<select id="item_select" class="form-control select-search"></select>
</div>
<div class="mb-2">
<div class="mb-2 d-none" id="wrap_barcode_select">
<label for="barcode_select" class="form-label">Barcode / QR ID</label>
<select id="barcode_select" class="form-control select-search"></select>
</div>
<div id="stokSisaQty"></div>
<div class="mb-2" id="wrap_qty_item">
<label for="qty" class="form-label">Qty</label>
<input type="number" id="qty" class="form-control" min="0">
</div>
@@ -225,9 +229,12 @@ $(function(){
// ================= RESET UI =================
function resetForm(){
// $('#form_scan_barang').addClass('d-none');
$('#wrap_warehouse').addClass('d-none');
$('#wrap_item_select').addClass('d-none');
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
$('#wrap_barcode_select').addClass('d-none');
$('#warehouse_id').html('<option value="">Pilih Gudang</option>');
$('#item_select').html('<option value="">Pilih Item</option>');
@@ -237,10 +244,12 @@ $(function(){
$('#qty').val('');
$('#harga').val('');
$('#keterangan').val('');
$('#barcode_select').val('');
$('#is_cicilan').prop('checked', false);
$('#wrap_cicilan').addClass('d-none');
$('#tenor').val('');
$('#stokSisaQty').html('');
setMode('manual');
}
@@ -270,12 +279,15 @@ $(function(){
$.get("<?= base_url('invoices/check_items_by_account/'); ?>" + account_id, function(res){
if(res.has_item){
// $('#form_scan_barang').removeClass('d-none');
$('#wrap_warehouse').removeClass('d-none');
$('#wrap_nama_item').addClass('d-none');
$('#wrap_qty_item').addClass('d-none');
loadWarehouse();
setMode('warehouse');
} else {
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
setMode('manual');
}
}, 'json').fail(function(){
@@ -305,6 +317,7 @@ $(function(){
if(!warehouse_id){
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
setMode('manual');
return;
}
@@ -328,15 +341,116 @@ $(function(){
$('#item_select').html(opt);
$('#wrap_item_select').removeClass('d-none');
$('#wrap_nama_item').addClass('d-none');
$('#wrap_qty_item').addClass('d-none');
setMode('item');
} else {
$('#wrap_item_select').addClass('d-none');
$('#wrap_nama_item').removeClass('d-none');
$('#wrap_qty_item').removeClass('d-none');
setMode('manual');
}
}, 'json');
}
$('#item_select').on('change', function(){
let item_id = $(this).val();
$('#wrap_barcode_select').addClass('d-none');
$('#barcode_select').html('<option value="">Loading...</option>');
if(!item_id){
$('#wrap_barcode_select').removeClass('d-none');
setMode('manual');
return;
}
// loadItemBarcodes(item_id);
loadItemBarcodes(
item_id,
isEditing ? editData.barcode_id : null
);
});
$('#barcode_select').on('change', function(){
let barcode_id = $(this).val();
let selected = $(this).find(':selected');
let qty_sisa = parseFloat(selected.data('qty_sisa')) || 0;
// console.log("QTY : " + qty_sisa);
if (qty_sisa > 1 ) {
$('#stokSisaQty').html('<h6 class="text-success ml-4">Stok : ' + qty_sisa + '</h6><hr>');
$('#qty').val('');
$('#wrap_qty_item').removeClass('d-none');
}
});
// $(document).on('keyup','#qty',function(){
// hitungTotal();
// });
// function hitungTotal(){
// let qty_sisa = 1000;
// $('#stokSisaQty').html('<h6 class="text-success ml-4">Stok : ' + qty_sisa + '</h6><hr>');
// }
// ================= LOAD BARCODE ITEMS =================
// function loadItemBarcodes(item_id){
// return $.get("<?= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
// if(res && res.length){
// let opt = '<option value="">Pilih Item</option>';
// res.forEach(i=>{
// opt += `<option value="${i.barcode_id}">
// ${i.nama_barang} - ${i.barcode} - ${i.serial_number}
// </option>`;
// });
// $('#barcode_select').html(opt);
// $('#wrap_barcode_select').removeClass('d-none');
// // set QTY jadi 1
// let qty = Number($('#qty').val());
// $('#qty').val(qty + 1);
// setMode('item');
// } else {
// $('#wrap_barcode_select').addClass('d-none');
// // $('#wrap_barcode_select').removeClass('d-none');
// setMode('manual');
// }
// }, 'json');
// }
function loadItemBarcodes(item_id, selectedBarcode = null){
return $.get("<?= base_url('items/get_list_barcode_id/'); ?>" + item_id, function(res){
if(res && res.length){
let opt = '<option value="">Pilih Barcode</option>';
res.forEach(i=>{
opt += `
<option value="${i.barcode_id}" data-qty_sisa="${i.qty_sisa}">
${i.nama_barang} - ${i.barcode} - ${i.serial_number}
</option>
`;
});
$('#barcode_select').html(opt);
if(selectedBarcode){
$('#barcode_select').val(selectedBarcode);
}
$('#wrap_barcode_select').removeClass('d-none');
if(!isEditing){
$('#qty').val(1);
}
setMode('item');
}else{
$('#wrap_barcode_select').addClass('d-none');
setMode('manual');
}
},'json');
}
// ================= AUTO HARGA =================
$('#item_select').on('change', function(){
let selected = $(this).find(':selected');
@@ -347,8 +461,8 @@ $(function(){
// ================= STOK CHECK =================
$('#qty').on('input', function(){
let qty = parseFloat($(this).val()) || 0;
let selected = $('#item_select option:selected');
let stok = selected.length ? parseFloat(selected.data('stok')) || 0 : 0;
let selected = $('#barcode_select option:selected');
let stok = selected.length ? parseFloat(selected.data('qty_sisa')) || 0 : 0;
if(stok > 0 && qty > stok){
Swal.fire('Warning','Stok tidak cukup! Max: ' + stok,'warning');
@@ -476,25 +590,7 @@ $(function(){
let btnAction = '';
if(v.status == 'draft'){
let btnEditTable = '';
if(v.is_cicilan == 0) {
btnEditTable = `
<button class="btn btn-sm btn-secondary btn-editItem"
data-id="${v.id}"
data-tanggal_input="${v.tanggal}"
data-account="${v.account_id}"
data-item="${v.nama_item}"
data-qty="${v.qty}"
data-harga="${v.harga}"
data-warehouse="${v.warehouse_id || ''}"
data-item_id="${v.items_id || ''}"
data-keterangan="${v.keterangan || ''}"
data-is_cicilan="${v.is_cicilan || 0}">
Edit
</button>`;
}
btnAction = `
${btnEditTable}
<button class="btn btn-sm btn-danger btn-delete" data-id="${v.id}">
Hapus
</button>
@@ -509,7 +605,7 @@ $(function(){
</td>
<td>${no++}</td>
<td>${formatTglIndo(v.tanggal)}</td>
<td>${v.nama_item || '-'}</td>
<td>${v.nama_item || '-'}<br><small class="text-secondary"><small></small><i>${v.barcode || ''}</i></small></td>
<td>${v.qty || 0}</td>
<td>${formatNumber(v.harga)}</td>
<td>${formatNumber(subtotal)}</td>
@@ -563,53 +659,6 @@ $(function(){
modal.show();
});
// ================= EDIT =================
$(document).on('click','.btn-editItem', function(){
action = "edit";
edit_id = $(this).data('id');
isEditing = true;
editData = $(this).data();
$('#modalTitle').text('Edit Item');
resetForm();
// Set basic fields first
$('#account_id').val(editData.account).trigger('change');
// Wait for account change to complete, then set other fields
setTimeout(() => {
if(editData.warehouse){
$('#warehouse_id').val(editData.warehouse).trigger('change');
// gak ada checkbox cicilan
$('#is_cicilan_group').addClass('d-none');
// Wait for warehouse items to load
setTimeout(() => {
$('#item_select').val(editData.item_id).trigger('change');
}, 500);
} else {
$('#nama_item').val(editData.item);
}
$('#qty').val(editData.qty);
$('#harga').val(editData.harga);
$('#tanggal_input').val(editData.tanggal_input);
$('#keterangan').val(editData.keterangan);
}, 500);
// if(editData.is_cicilan == 1){
// $('#qty').prop('disabled', true);
// $('#harga').prop('disabled', true);
// $('#is_cicilan').prop('disabled', true);
// $('#tenor').prop('disabled', true);
// }
modal.show();
});
// ================= SAVE =================
// ================= SAVE - FIXED SESUAI STYLE ANDA =================
@@ -642,6 +691,7 @@ $(function(){
qty: parseFloat($('#qty').val()) || 0,
harga: parseFloat($('#harga').val()) || 0,
keterangan: $('#keterangan').val().trim(),
barcode_id: $('#barcode_select').val() || 0,
is_cicilan: $('#is_cicilan').is(':checked') ? 1 : 0,
tenor: parseInt($('#tenor').val()) || 0,
@@ -706,7 +756,7 @@ $(function(){
btn.prop('disabled', true).html('Menghapus...');
$.get({
url: "<?= base_url('invoices/delete_item/'); ?>" + id,
url: "<?= base_url('invoices/delete_item/'); ?>" + id + "/" + `<?= $no_invoice ?>`,
dataType: 'json' // ✅ FIXED
})
.done(function(res){
@@ -1159,6 +1209,5 @@ $(function(){
$(`tr[data-group="${group}"]`).removeClass('table-secondary');
$(this).addClass('table-secondary');
});
</script>
+1 -1
View File
@@ -365,7 +365,7 @@ $('.btn-add-bayar').click(function(){
$('#tableBayar tbody').append(`
<tr>
<td><select class="form-control account_id">${accountOptionsAssets}</select></td>
<td style="width: 60%;"><select class="form-control account_id select-search">${accountOptionsAssets}</select></td>
<td><input type="text" class="form-control debit text-end format-rupiah"></td>
<td><button class="btn btn-danger btn-remove">X</button></td>
</tr>
+641
View File
@@ -0,0 +1,641 @@
<style>
body {
background: #f5f7fb;
}
/* ===========================
CARD
=========================== */
.main-card,
.scanner-card,
.table-card {
border: none;
border-radius: 18px;
overflow: hidden;
/*box-shadow: 0 8px 25px rgba(0, 0, 0, .08);*/
}
/* ===========================
HEADER
=========================== */
.header-gradient {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: #fff;
padding: 22px 28px;
}
.info-box {
background: rgba(255,255,255,.18);
border-radius: 12px;
padding: 12px 16px;
}
.progress {
height: 8px;
border-radius: 20px;
}
/* ===========================
SCANNER
=========================== */
.barcode-avatar {
width: 110px;
height: 110px;
margin: auto;
border-radius: 50%;
background: #fff7e6;
color: #f59e0b;
display: flex;
justify-content: center;
align-items: center;
font-size: 45px;
}
.barcode-code {
font-size: 22px;
font-weight: 700;
letter-spacing: 2px;
}
.form-control-lg {
height: 60px;
font-size: 22px;
border-radius: 12px;
}
.form-control:focus {
border-color: #f59e0b;
box-shadow: 0 0 0 .2rem rgba(245, 158, 11, .2);
}
.scan-note {
color: #6c757d;
font-size: 13px;
}
.camera-btn {
height: 55px;
border-radius: 12px;
font-size: 17px;
}
/* ===========================
TABLE
=========================== */
.table thead th {
background: #d97706;
color: #fff;
border: none;
vertical-align: middle;
}
.table tbody td {
vertical-align: middle;
}
.table tbody tr:hover {
background: #fffaf0;
}
/* ===========================
BADGE
=========================== */
.badge-success-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #d1fae5;
color: #065f46;
font-weight: 600;
}
.badge-danger-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fee2e2;
color: #991b1b;
font-weight: 600;
}
.badge-warning-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fef3c7;
color: #92400e;
font-weight: 600;
}
#reader{
width:100%;
min-height:280px;
border-radius:12px;
overflow:hidden;
}
</style>
<div class="container mt-4">
<div class="card main-card">
<!-- HEADER -->
<div class="header-gradient">
<div class="row align-items-center">
<div class="col-md-8">
<h3 class="mb-1">
<i class="fa fa-barcode"></i>
Input Serial Number
</h3>
<div class="small">
Silahkan scan Serial Number perangkat.
Data akan otomatis tersimpan.
</div>
</div>
<div class="col-md-4 text-right">
<div class="info-box">
<div class="small">
Progress
</div>
<h5 class="font-weight-bold mb-2" id="progress_text">
0 / 0 Barcode
</h5>
<div class="progress">
<div class="progress-bar bg-success" style="width:35%"></div>
</div>
</div>
</div>
</div>
</div>
<!-- BODY -->
<div class="card-body">
<div class="row">
<!-- LEFT -->
<div class="col-lg-4 mb-4">
<div class="card scanner-card">
<div class="card-body">
<div class="barcode-avatar">
<i class="bi bi-qr-code-scan"></i>
</div>
<div class="text-center mt-3">
<div class="barcode-code">
ONU-250626-0007
</div>
<div class="scan-note mt-2">
Barcode yang sedang menunggu Serial Number
</div>
</div>
<hr>
<center>
<label class="font-weight-bold">
Serial Number
</label>
<!-- <input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan Serial Number"
autocomplete="off"
autofocus
>
<small class="scan-note">
Scanner akan otomatis menyimpan.
</small>
<button class="btn btn-warning btn-block camera-btn mt-4">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button> -->
<div id="scannerArea">
<input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan Serial Number"
autocomplete="off"
autofocus>
<small class="scan-note">
Scanner akan otomatis menyimpan.
</small>
<button
type="button"
class="btn btn-warning btn-block camera-btn mt-4"
id="btnCamera">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button>
</div>
<div id="cameraArea" style="display:none;">
<div id="reader"></div>
<small class="scan-note d-block mt-2">
Arahkan QR Code ke kamera.
</small>
<button
type="button"
class="btn btn-secondary btn-block mt-3"
id="btnCloseCamera">
Tutup Kamera
</button>
</div>
</center>
<div class="alert alert-warning mt-3 mb-0 text-center">
<i class="fa fa-check-circle"></i>
Auto Save Aktif
</div>
<input type="hidden" id="barcode_id">
</div>
</div>
</div>
<!-- RIGHT -->
<div class="col-lg-8">
<div class="card table-card">
<div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
Daftar Barcode
</h5>
<span class="badge badge-warning px-3 py-2">
20 Data
</span>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0" id="tableBarcode">
<thead>
<tr>
<th width="60">No</th>
<th>Barcode</th>
<th>Serial Number</th>
<th width="120">Status</th>
<th width="80" class="text-center">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>
<strong></strong>
</td>
<td></td>
<td>
<span class="badge-success-soft">
<i class="fa fa-check mr-1"></i>
</span>
</td>
<td class="text-center">
<button class="btn btn-sm btn-outline-danger">
<i class="fa fa-trash"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
let ITEM_ID = `<?= $item_id; ?>` ;
let isSaving = false;
$(document).ready(function () {
loadData();
$("#serial_number").focus();
$("#serial_number").on("keypress", function (e) {
if (e.which == 13) {
e.preventDefault();
if (!isSaving) {
saveSerial();
}
}
});
});
function loadData() {
$.getJSON( "<?= base_url(); ?>" + "items/load_data_barcode/" + ITEM_ID)
.done(function (r) {
render(r);
})
.fail(function () {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: 'Gagal mengambil data barcode.'
});
});
}
function saveSerial() {
let serial = $("#serial_number").val().trim();
if (serial == "") {
$("#serial_number").focus();
return;
}
isSaving = true;
$("#serial_number")
.prop("disabled", true);
$.ajax({
url: "<?= base_url(); ?>" + "items/save_serial_number",
type: "POST",
dataType: "json",
data: {
barcode_id: $("#barcode_id").val(),
serial_number: serial
},
success: function (r) {
isSaving = false;
render(r);
},
error: function () {
isSaving = false;
$("#serial_number")
.prop("disabled", false)
.focus();
Swal.fire({
icon: "error",
title: "Error",
text: "Terjadi kesalahan pada server."
});
}
});
}
function render(r) {
if (!r.status) {
$("#serial_number")
.val("")
.prop("disabled", false)
.focus();
Swal.fire({
icon: "error",
title: "Oops...",
text: r.message
});
return;
}
// =======================
// Semua SN sudah selesai
// =======================
if (r.next == null) {
$("#barcode_id").val("");
$(".barcode-code").html("SELESAI");
$(".progress-bar").css("width", "100%");
$("#progress_text").html(r.done + " / " + r.total);
if (r.item_status == "draft") {
$("#serial_number")
.val("")
.prop("disabled", false)
.attr("placeholder", "Semua SN selesai. Edit melalui daftar di bawah jika ada yang salah.")
.blur();
Swal.fire({
icon: "success",
title: "Selesai",
text: "Semua Serial Number telah diinput. Karena status barang masih Draft, Anda masih dapat mengubah Serial Number melalui daftar di bawah."
});
} else {
$("#serial_number")
.val("")
.prop("disabled", true);
Swal.fire({
icon: "success",
title: "Selesai",
text: "Semua Serial Number berhasil diinput."
});
}
} else {
$("#barcode_id").val(r.next.id);
$(".barcode-code").text(r.next.barcode);
$("#progress_text").text(r.done + " / " + r.total);
$(".progress-bar").css("width", r.percent + "%");
$("#serial_number")
.val("")
.prop("disabled", false)
.attr("placeholder", "Scan Serial Number")
.focus();
}
// =======================
// Table
// =======================
let html = "";
$.each(r.list, function (i, v) {
html += "<tr>";
html += "<td>" + (i + 1) + "</td>";
html += "<td><strong>" + v.barcode + "</strong></td>";
html += "<td>" + (v.serial_number ? v.serial_number : "-") + "</td>";
html += "<td>";
if (v.serial_number) {
html += '<span class="badge-success-soft"><i class="fa fa-check mr-1"></i>Selesai</span>';
} else {
html += '<span class="badge-warning-soft">Menunggu</span>';
}
html += "</td>";
html += '<td class="text-center">';
if (r.item_status == "draft") {
html += '<button class="btn btn-sm btn-outline-danger btnDelete" data-id="' + v.id + '">Delete</button>';
} else {
html += '<button class="btn btn-sm btn-outline-secondary" disabled>Locked</button>';
}
html += "</td>";
html += "</tr>";
});
$("#tableBarcode tbody").html(html);
}
// function render(r) {
// if (!r.status) {
// $("#serial_number")
// .val("")
// .prop("disabled", false)
// .focus();
// Swal.fire({
// icon: "error",
// title: "Oops...",
// text: r.message
// });
// return;
// }
// if (r.next == null) {
// $("#barcode_id").val("");
// $(".barcode-code").html("SELESAI");
// $("#serial_number")
// .val("")
// .prop("disabled", true);
// $(".progress-bar")
// .css("width", "100%");
// $("#progress_text").html(r.done + " / " + r.total);
// Swal.fire({
// icon: "success",
// title: "Selesai",
// text: "Semua Serial Number berhasil diinput."
// });
// return;
// }
// // Barcode Aktif
// $("#barcode_id").val(r.next.id);
// $(".barcode-code").text(r.next.barcode);
// // Progress
// $("#progress_text").text(r.done + " / " + r.total);
// $(".progress-bar")
// .css("width", r.percent + "%");
// // Table
// let html = "";
// $.each(r.list, function (i, v) {
// html += "<tr>";
// html += "<td>" + (i + 1) + "</td>";
// html += "<td><strong>" + v.barcode + "</strong></td>";
// html += "<td>" + (v.serial_number ? v.serial_number : "-") + "</td>";
// html += "<td>";
// if (v.serial_number) {
// html += '<span class="badge-success-soft"><i class="fa fa-check mr-1"></i>Selesai</span>';
// } else {
// html += '<span class="badge-warning-soft">Menunggu</span>';
// }
// html += "</td>";
// html += '<td class="text-center">';
// html += '<button class="btn btn-sm btn-outline-danger btnDelete" data-id="' + v.id + '">';
// html += 'Delete';
// html += '</button>';
// html += '</td>';
// html += "</tr>";
// });
// $("#tableBarcode tbody").html(html);
// // Reset Input
// $("#serial_number")
// .val("")
// .prop("disabled", false)
// .focus();
// }
$(document).on("click", ".btnDelete", function () {
let id = $(this).data("id");
Swal.fire({
title: "Hapus Serial Number?",
icon: "warning",
showCancelButton: true,
confirmButtonText: "Ya",
cancelButtonText: "Batal"
}).then(function (result) {
if (!result.isConfirmed) return;
$.post(
"<?= base_url(); ?>" + "items/delete_serial_number",
{
barcode_id: id
},
function (r) {
render(r);
},
"json"
);
});
});
let html5QrCode=null;
$("#btnCamera").click(function(){
$("#scannerArea").hide();
$("#cameraArea").show();
html5QrCode=new Html5Qrcode("reader");
html5QrCode.start(
{
facingMode:"environment"
},
{
fps:10,
qrbox:220
},
function(decodedText){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number")
.val(decodedText);
saveSerial();
});
}
);
});
$("#btnCloseCamera").click(function(){
if(html5QrCode){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number").focus();
});
}
});
</script>
+421 -297
View File
@@ -34,7 +34,7 @@
<!-- MODAL -->
<div class="modal fade" id="modalItem">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-xl">
<div class="modal-content">
<div class="modal-header bg-warning text-white">
@@ -45,39 +45,146 @@
<input type="hidden" id="id">
<div id="group_tanggal">
<label>Tanggal Pembelian</label>
<input type="date" id="tanggal_beli" class="form-control">
</div>
<div id="group_kode">
<label class="mt-2">Kode Barang</label>
<select id="kode_barang" class="form-control select-search"></select>
<div class="row">
<div class="col-md-6">
<div id="group_tanggal">
<label>Tanggal Pembelian</label>
<input type="date" id="tanggal_beli" class="form-control">
</div>
</div>
<div class="col-md-6">
<div id="group_kode">
<label class="mt-2">Kode Barang</label>
<select id="kode_barang" class="form-control select-search"></select>
</div>
</div>
</div>
<label>Nama Barang</label>
<input type="text" id="nama_barang" class="form-control">
<div id="group_qty">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty" class="form-control">
<div class="row">
<div class="col-md-2">
<div id="group_qty">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty" class="form-control">
</div>
</div>
<div class="col-md-2">
<div id="group_tracking_qty">
<label class="mt-2">Jml / (Roll, Box, Dus)</label>
<input type="number" id="tracking_qty" class="form-control">
</div>
</div>
<div class="col-md-4">
<label class="mt-2">Harga Beli</label>
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="text" id="harga_beli" class="form-control format-rupiah">
</div>
</div>
<div class="col-md-4">
<label class="mt-2">Harga Jual</label>
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="text" id="harga_jual" class="form-control format-rupiah">
</div>
</div>
</div>
<label class="mt-2">Harga Beli</label>
<input type="text" id="harga_beli" class="form-control format-rupiah">
<label class="mt-2">Harga Jual</label>
<input type="text" id="harga_jual" class="form-control format-rupiah">
<div id="group_warehouse">
<label class="mt-2">Gudang</label>
<select id="warehouse_id" class="form-control"></select>
</div>
<div id="group_account">
<!-- <div class="row">
<label class="mt-2">Akun Kas</label>
<select id="account_kas" class="form-control"></select>
<div class="col-md-7">
<div id="group_account">
<select id="account_kas" class="form-control"></select>
</div>
</div>
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text">Rp</span>
<input type="text" id="account[]" class="form-control format-rupiah">
</div>
</div>
</div>
<button class="btn btn-warning btn-sm mt-4">Tambah Account</button> -->
<hr>
<label class="font-weight-bold">
Pembayaran
</label>
<div id="accountContainer">
<div class="row account-row mb-2">
<div class="col-md-6">
<select
name="account_kas[]"
class="form-control account_kas">
</select>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text">Rp</span>
<input
type="text"
name="nominal[]"
class="form-control nominal format-rupiah">
</div>
</div>
<div class="col-md-2">
<button
type="button"
class="btn btn-sm btn-danger btn-remove-account w-100"
disabled>
Hapus
</button>
</div>
</div>
</div>
<div class="mt-2">
<button
type="button"
id="btnTambahAccount"
class="btn btn-warning btn-sm">
+ Tambah Account
</button>
</div>
<div class="mt-3">
<div class="row">
<div class="col-md-4">
<!-- total qty * harga beli -->
<h5>
Total :
<span id="totalHppBarang">
Rp 0
</span>
</h5>
</div>
<div class="col-md-4">
<!-- total accounting -->
<h5>
Total :
<span id="totalAccount">
Rp 0
</span>
</h5>
</div>
<div class="col-md-4">
<!-- balance accounting + total hpp barang -->
<h5>
Total :
<span id="totalAll">
Rp 0
</span>
</h5>
</div>
</div>
</div>
</div>
@@ -89,59 +196,6 @@
</div>
</div>
<!-- MODAL BARANAG KELUAR -->
<div class="modal fade" id="modalKeluar">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5>Barang Keluar</h5>
</div>
<div class="modal-body">
<div id="group_account_biaya">
<label class="mt-2">Akun Biaya</label>
<select id="account_biaya" class="form-control"></select>
</div>
<div>
<label>Tanggal</label>
<input type="date" id="tanggal_keluar" class="form-control">
</div>
<div>
<label class="mt-2">Gudang</label>
<select id="warehouse_id_keluar" class="form-control"></select>
</div>
<div class="d-none" id="group_barang_keluar">
<label class="mt-2">Barang</label>
<select id="barang_id" class="form-control select-search"></select>
</div>
<div class="d-none" id="group_qty_keluar">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty_keluar" class="form-control">
</div>
<div class="mb-2 d-none" id="group_keterangan">
<label for="keterangan" class="form-label">Keterangan</label>
<textarea class="form-control" id="keterangan_keluar" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger" id="btnKeluar">Keluarkan</button>
</div>
</div>
</div>
</div>
<!-- MODAL DETAIL -->
<div class="modal fade" id="modalDetail">
<div class="modal-dialog modal-lg">
@@ -158,46 +212,13 @@
</div>
<div class="modal fade" id="modalAdjust">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-warning">
<h5>Penyesuaian Stok</h5>
</div>
<div class="modal-body">
<input type="hidden" id="adjust_item_id">
<label>Qty Adjustment</label>
<input type="number" id="adjust_qty" class="form-control">
<small>Gunakan minus (-) jika mengurangi</small>
<label class="mt-2">Gudang</label>
<select id="adjust_warehouse" class="form-control"></select>
<label class="mt-2">Keterangan</label>
<input type="text" id="adjust_ket" class="form-control">
</div>
<div class="modal-footer">
<button class="btn btn-warning" id="btnAdjust">Simpan</button>
</div>
</div>
</div>
</div>
<script>
$(function(){
let action = 'add';
let selectedItemId = null;
let accountOption = '';
// ================= DATATABLE =================
let table = $('#tableItems').DataTable({
@@ -210,24 +231,26 @@ let table = $('#tableItems').DataTable({
});
// ================= LOAD AKUN KAS =================
// function loadKas(){
// $.get("<?= base_url('items/get_accounts'); ?>",res=>{
// let opt='<option value="">-- PILIH KAS --</option>';
// res.forEach(a=>{
// opt += `<option value="${a.id}">${a.nama_akun}</option>`;
// });
// $('#account_kas').html(opt);
// },'json');
// }
function loadKas(){
$.get("<?= base_url('items/get_accounts'); ?>",res=>{
let opt='<option value="">-- PILIH KAS --</option>';
res.forEach(a=>{
opt += `<option value="${a.id}">${a.nama_akun}</option>`;
$.get("<?= base_url('items/get_accounts'); ?>",function(res){
accountOption = '<option value="">-- PILIH KAS --</option>';
res.forEach(function(a){
accountOption += `
<option value="${a.id}">
${a.nama_akun}
</option>
`;
});
$('#account_kas').html(opt);
},'json');
}
// ================= LOAD AKUN KAS =================
function loadKasOut(){
$.get("<?= base_url('items/get_accounts_biaya'); ?>",res=>{
let opt='<option value="">-- PILIH KAS --</option>';
res.forEach(a=>{
opt += `<option value="${a.id}">${a.nama_akun}</option>`;
});
$('#account_biaya').html(opt);
$('.account_kas').html(accountOption);
},'json');
}
@@ -241,27 +264,105 @@ function loadGudang(){
$('#warehouse_id, #adjust_warehouse').html(opt);
},'json');
}
// ================= LOAD GUDANG =================
function loadGudangList(){
$.get("<?= base_url('warehouses/list'); ?>",res=>{
let opt='<option value="">-- PILIH GUDANG --</option>';
res.forEach(w=>{
opt += `<option value="${w.id}">${w.nama}</option>`;
function loadKode() {
$.get("<?= base_url('kodebarang/list'); ?>", function(res) {
let opt = '<option value="">-- PILIH KODE --</option>';
res.forEach(function(a){
opt += `
<option value="${a.id}"
data-type="${a.tracking_type}">
${a.kode_barang} - ${a.nama}
</option>
`;
});
$('#warehouse_id_keluar').html(opt);
},'json');
$('#kode_barang').html(opt).trigger('change');
}, 'json');
}
function loadKode(){
$.get("<?= base_url('kodebarang/list'); ?>",res=>{
let opt='<option value="">-- PILIH KODE --</option>';
res.forEach(a=>{
opt += `<option value="${a.id}">${a.kode_barang} - ${a.nama}</option>`;
});
$('#kode_barang').html(opt);
},'json');
function addAccountRow(){
let html = `
<div class="row account-row mb-2">
<div class="col-md-6">
<select
name="account_kas[]"
class="form-control account_kas">
${accountOption}
</select>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text">
Rp
</span>
<input
type="text"
name="nominal[]"
class="form-control nominal format-rupiah">
</div>
</div>
<div class="col-md-2">
<button
type="button"
class="btn btn-sm btn-danger btn-remove-account w-100">
Hapus
</button>
</div>
</div>
`;
$('#accountContainer').append(html);
hitungTotal();
}
$(document).on('change', '#kode_barang', function () {
let type = $(this).find(':selected').data('type');
if(type == 'QTY'){
$('#group_tracking_qty').show();
}else{
$('#group_tracking_qty').hide();
$('#tracking_qty').val('');
}
});
$('#btnTambahAccount').click(function(){
addAccountRow();
});
$(document).on('click','.btn-remove-account',function(){
$(this).closest('.account-row').remove();
hitungTotal();
});
// function hitungTotal(){
// let total = 0;
// $('.nominal').each(function(){
// let angka = $(this)
// .val()
// .replace(/\./g,'')
// .replace(/,/g,'');
// total += Number(angka || 0);
// });
// $('#totalAccount').text(formatRupiah(total.toString()));
// }
$(document).on('keyup','.nominal',function(){
hitungTotal();
});
$(document).on('keyup','#harga_beli',function(){
hitungTotal();
});
$(document).on('keyup change','#qty',function(){
hitungTotal();
});
// ================= RESET FORM =================
function resetForm(){
$('#id').val('');
@@ -270,6 +371,44 @@ function resetForm(){
$('#harga_beli').val('');
$('#harga_jual').val('');
$('#tanggal_beli').val('');
$('#tracking_qty').val('');
$('#group_tracking_qty').hide();
$('#accountContainer').html(`
<div class="row account-row mb-2">
<div class="col-md-6">
<select
name="account_kas[]"
class="form-control account_kas">
${accountOption}
</select>
</div>
<div class="col-md-4">
<div class="input-group">
<span class="input-group-text">Rp</span>
<input
type="text"
name="nominal[]"
class="form-control nominal format-rupiah">
</div>
</div>
<div class="col-md-2">
<button
class="btn btn-sm btn-danger btn-remove-account w-100"
disabled>
Hapus
</button>
</div>
</div>
`);
$('#totalAccount').text('Rp 0');
$('#totalHppBarang').text('Rp 0');
$('#totalAll')
.text('Balance')
.removeClass('text-danger')
.addClass('text-success');
$('#btnSimpan').prop('disabled',true);
}
// ================= INIT LOAD =================
@@ -324,6 +463,40 @@ $('#btnSimpan').click(function(){
let btn = $(this);
let qtys = parseFloat($('#qty').val()) || 0;
let totalHpp = qtys * rupiahToNumber($('#harga_beli').val());
let totalAccount = 0;
$('.nominal').each(function(){
totalAccount += rupiahToNumber($(this).val());
});
if(totalHpp != totalAccount){
Swal.fire({
icon:'warning',
title:'Belum Balance',
text:'Total pembayaran harus sama dengan Total Harga Beli.'
});
return;
}
let account = [];
$('.account-row').each(function(){
account.push({
account_id: $(this).find('.account_kas').val(),
nominal: $(this)
.find('.nominal')
.val()
});
});
// data.accounts = account;
let data = {
id: $('#id').val(),
kode_id: $('#kode_barang').val(),
@@ -332,8 +505,10 @@ $('#btnSimpan').click(function(){
harga_beli: $('#harga_beli').val(),
harga_jual: $('#harga_jual').val(),
warehouse_id: $('#warehouse_id').val(),
account_kas: $('#account_kas').val(),
// account_kas: $('#account_kas').val(),
account_kas: account,
tanggal_beli: $('#tanggal_beli').val(),
tracking_qty: $('#tracking_qty').val(),
status: `draft`
};
@@ -384,122 +559,74 @@ $('#btnSimpan').click(function(){
});
// =====================================================
// ================= START URUSAN KELUAR ===============
// =====================================================
// ================= RESET FORM KELUAR =================
function resetFormKeluar(){
$('#tanggal_keluar').val('');
$('#qty_keluar').val('');
$('#keterangan_keluar').val('');
}
// ================= PRINT BARCODE =================
$(document).on('click', '.btn-print', function () {
loadKasOut();
loadGudangList();
// ================= BARANG KELUAR ================= account_biaya warehouse_id barang_id
$('.btn-out').click(function(){
// action='add';
resetFormKeluar();
let id = $(this).data('id');
$('#warehouse_id').closest('div').show();
$('#account_biaya').closest('div').show();
$('#modalKeluar').modal('show');
loadGudangList();
loadKasOut();
});
Swal.fire({
title: 'Cetak Barcode',
html: `
<div class="text-left">
$('#warehouse_id_keluar').on('change', function(){
let warehouse_id = $(this).val();
// $('#wrap_item_select').addClass('d-none');
$('#barang_id').html('<option value="">Loading...</option>');
loadItems(warehouse_id);
});
<div class="form-group">
<label for="print_column" class="font-weight-bold">
Jumlah Kolom
</label>
<select id="print_column" class="form-control">
<option value="1">1 Kolom</option>
<option value="2">2 Kolom</option>
<option value="3" selected>3 Kolom (Rekomendasi)</option>
</select>
</div>
// ================= ITEMS =================
function loadItems(warehouse_id){
return $.get("<?= base_url('items/get_items_by_wh_id/'); ?>" + warehouse_id, function(res){
if(res && res.length){
let opt = '<option value="">-- PILIH BARANG --</option>';
res.forEach(i=>{
opt += `<option value="${i.id}">
${i.kode_detail} - ${i.nama_barang} (Stok: ${i.stok})
</option>`;
});
$('#barang_id').html(opt);
$('#group_barang_keluar').removeClass('d-none');
$('#group_qty_keluar').removeClass('d-none');
$('#group_keterangan').removeClass('d-none');
} else {
$('#barang_id').html(opt);
}
}, 'json');
}
<div class="form-group mb-0">
<label for="duplicat" class="font-weight-bold">
Jumlah Cetak per Barcode
</label>
<select id="duplicat" class="form-control">
<option value="1" selected>1 Label</option>
<option value="2">2 Label</option>
</select>
</div>
// ================= SAVE =================
$('#btnKeluar').click(function(){
</div>
`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Cetak',
cancelButtonText: 'Batal',
confirmButtonColor: '#f0ad4e',
preConfirm: () => {
let btn = $(this);
let column = $('#print_column').val();
let duplicat = $('#duplicat').val();
let data = {
account_biaya: $('#account_biaya').val(),
tanggal_keluar: $('#tanggal_keluar').val(),
warehouse_id_keluar: $('#warehouse_id_keluar').val(),
barang_id: $('#barang_id').val(),
qty_keluar: $('#qty_keluar').val(),
keterangan_keluar: $('#keterangan_keluar').val()
};
return {
column: column,
duplicat: duplicat
};
let url = "<?= base_url('items/keluarkan'); ?>";
// 🔥 loading state
btn.prop('disabled', true).html('Menyimpan...');
$.post(url, data, function(res){
if(res.status){
$('#modalItem').modal('hide');
table.ajax.reload(null,false);
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: res.message || 'Data berhasil disimpan',
timer: 1500,
showConfirmButton: false
});
}else{
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Terjadi kesalahan'
});
}
},'json')
.fail(function(){
}).then((result) => {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Koneksi ke server gagal'
});
if (!result.isConfirmed) {
return;
}
window.open(
"<?= base_url('items/print_barcode/'); ?>" +
id +
"?column=" + result.value.column +
"&duplicat=" + result.value.duplicat,
"_blank"
);
})
.always(function(){
btn.prop('disabled', false).html('Keluarkan');
});
});
// =====================================================
// ================= END URUSAN KELUAR ===============
// =====================================================
// ================= DELETE =================
$(document).on('click','.btn-delete',function(){
@@ -680,78 +807,75 @@ $(document).on('click','.btn-detail',function(){
});
// ================= OPEN ADJUST =================
$(document).on('click','.btn-adjust',function(){
function rupiahToNumber(value){
let id = $(this).data('id');
if(!value) return 0;
if(!id){
alert('Pilih item dari tabel (gunakan tombol Adjust di baris)');
return;
}
let s = value.toString().trim();
s = s.replace(/\.(?=\d{3}(\.|$))/g, '');
s = s.replace(/,/g, '');
return parseFloat(s) || 0;
selectedItemId = id;
$('#adjust_item_id').val(id);
$('#adjust_qty').val('');
$('#adjust_ket').val('');
}
$('#modalAdjust').modal('show');
});
function hitungTotal(){
// ================= SAVE ADJUST =================
$('#btnAdjust').click(function(){
// ==========================
// HPP BARANG
// ==========================
let btn = $(this);
let qty = parseFloat($('#qty').val()) || 0;
let hargaBeli = rupiahToNumber($('#harga_beli').val());
let data = {
item_id: $('#adjust_item_id').val(),
qty: $('#adjust_qty').val(),
warehouse_id: $('#adjust_warehouse').val(),
keterangan: $('#adjust_ket').val()
};
let totalHpp = qty * hargaBeli;
// 🔥 loading state
btn.prop('disabled', true).html('Menyimpan...');
$.post("<?= base_url('items/adjust'); ?>", data, function(res){
// ==========================
// ACCOUNTING
// ==========================
if(res.status){
let totalAccount = 0;
$('#modalAdjust').modal('hide');
table.ajax.reload(null,false);
$('.nominal').each(function(){
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: res.message || 'Stok berhasil disesuaikan',
timer: 1500,
showConfirmButton: false
});
totalAccount += rupiahToNumber($(this).val());
}else{
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Gagal melakukan penyesuaian'
});
}
},'json')
.fail(function(){
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Koneksi ke server gagal'
});
})
.always(function(){
btn.prop('disabled', false).html('Simpan');
});
});
// ==========================
// BALANCE
// ==========================
let balance = totalAccount - totalHpp;
$('#totalHppBarang').text(formatRupiah(totalHpp.toString()));
$('#totalAccount').text(formatRupiah(totalAccount.toString()));
if(balance == 0){
$('#totalAll')
.removeClass('text-danger')
.addClass('text-success')
.text('Balance');
$('#btnSimpan').prop('disabled',false);
}else{
let prefix = balance > 0 ? '+' : '-';
$('#totalAll')
.removeClass('text-success')
.addClass('text-danger')
.text(prefix + ' ' + formatRupiah(Math.abs(balance).toString()));
$('#btnSimpan').prop('disabled',true);
}
}
});
</script>
+406
View File
@@ -0,0 +1,406 @@
<style>
body {
background: #f5f7fb;
}
/* ===========================
CARD
=========================== */
.main-card,
.scanner-card,
.table-card {
border: none;
border-radius: 18px;
overflow: hidden;
/*box-shadow: 0 8px 25px rgba(0, 0, 0, .08);*/
}
/* ===========================
HEADER
=========================== */
.header-gradient {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: #fff;
padding: 22px 28px;
}
.info-box {
background: rgba(255,255,255,.18);
border-radius: 12px;
padding: 12px 16px;
}
.progress {
height: 8px;
border-radius: 20px;
}
/* ===========================
SCANNER
=========================== */
.barcode-avatar {
width: 110px;
height: 110px;
margin: auto;
border-radius: 50%;
background: #fff7e6;
color: #f59e0b;
display: flex;
justify-content: center;
align-items: center;
font-size: 45px;
}
.barcode-code {
font-size: 22px;
font-weight: 700;
letter-spacing: 2px;
}
.form-control-lg {
height: 60px;
font-size: 22px;
border-radius: 12px;
}
.form-control:focus {
border-color: #f59e0b;
box-shadow: 0 0 0 .2rem rgba(245, 158, 11, .2);
}
.scan-note {
color: #6c757d;
font-size: 13px;
}
.camera-btn {
height: 55px;
border-radius: 12px;
font-size: 17px;
}
/* ===========================
TABLE
=========================== */
.table thead th {
background: #d97706;
color: #fff;
border: none;
vertical-align: middle;
}
.table tbody td {
vertical-align: middle;
}
.table tbody tr:hover {
background: #fffaf0;
}
/* ===========================
BADGE
=========================== */
.badge-success-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #d1fae5;
color: #065f46;
font-weight: 600;
}
.badge-danger-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fee2e2;
color: #991b1b;
font-weight: 600;
}
.badge-warning-soft {
display: inline-block;
padding: 6px 14px;
border-radius: 30px;
background: #fef3c7;
color: #92400e;
font-weight: 600;
}
#reader{
width:100%;
min-height:280px;
border-radius:12px;
overflow:hidden;
}
</style>
<div class="container mt-4">
<div class="card main-card">
<!-- HEADER -->
<div class="header-gradient">
<div class="row align-items-center">
<div class="col-md-8">
<h3 class="mb-1">
<i class="fa fa-barcode"></i>
Riwayat Perpindahan Barang
</h3>
<div class="small">
Silahkan scan / input QR id Barang
</div>
</div>
<div class="col-md-4 text-right">
</div>
</div>
</div>
<!-- BODY -->
<div class="card-body">
<div class="row">
<!-- LEFT -->
<div class="col-lg-4 mb-4">
<div class="card scanner-card">
<div class="card-body">
<div class="barcode-avatar">
<i class="bi bi-qr-code-scan"></i>
</div>
<div class="text-center mt-3">
<div class="scan-note mt-2">
Scan QR ID
</div>
</div>
<hr>
<center>
<div id="scannerArea">
<input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan / Input QR ID"
autocomplete="off"
autofocus>
<small class="scan-note">
Scanner akan otomatis memanggil riwayat.
</small>
<button
type="button"
class="btn btn-warning btn-block camera-btn mt-4"
id="btnCamera">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button>
</div>
<div id="cameraArea" style="display:none;">
<div id="reader"></div>
<small class="scan-note d-block mt-2">
Arahkan QR Code ke kamera.
</small>
<button
type="button"
class="btn btn-secondary btn-block mt-3"
id="btnCloseCamera">
Tutup Kamera
</button>
</div>
</center>
<input type="hidden" id="barcode_id">
</div>
</div>
</div>
<!-- RIGHT -->
<div class="col-lg-8">
<div class="card table-card">
<div class="card-header bg-white">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
Riwayat Perpindahan Barang
</h5>
</div>
</div>
<div class="card-body border-bottom" id="itemInfo">
Belum ada data
</div>
<div class="table-responsive">
<table class="table table-hover mb-0" id="tableBarcode">
<thead>
<tr>
<th width="60">No</th>
<th>Date</th>
<th>Type</th>
<th>From</th>
<th>To</th>
<th>NOtes</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$("#serial_number").keypress(function(e){
if(e.which==13){
searchHistory();
}
});
function searchHistory(){
let barcode=$("#serial_number").val().trim();
if(barcode=="") return;
$.ajax({
url: `<?= base_url("items/scan_history"); ?>`,
type:"POST",
dataType:"json",
data:{
barcode:barcode
},
beforeSend:function(){
$("#serial_number").prop("disabled",true);
},
success:function(r){
$("#serial_number")
.prop("disabled",false)
.val("")
.focus();
if(!r.status){
Swal.fire({
icon:"error",
text:r.message
});
return;
}
renderItem(r.item);
renderHistory(r.history);
}
});
}
function renderItem(item){
let html='';
html+='<div class="row">';
html+=' <div class="col-md-6">';
html+=' <h4 class="mb-3"><b>'+item.nama_barang+'</b></h4>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Kode</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.kode_detail+'</div>';
html+=' </div>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Barcode</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.barcode+'</div>';
html+=' </div>';
html+=' </div>';
html+=' <div class="col-md-6">';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Serial Number</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+(item.serial_number ? item.serial_number : '-')+'</div>';
html+=' </div>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Status</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.status+'</div>';
html+=' </div>';
html+=' <div class="row mb-2">';
html+=' <div class="col-4 font-weight-bold">Qty</div>';
html+=' <div class="col-1">:</div>';
html+=' <div class="col-7">'+item.qty_sisa+' / '+item.qty_awal+'</div>';
html+=' </div>';
html+=' </div>';
html+='</div>';
$("#itemInfo").html(html);
}
function renderHistory(list){
let html='';
$.each(list,function(i,v){
html+="<tr>";
html+="<td>"+(i+1)+"</td>";
html+="<td>"+v.created_at+"</td>";
html+="<td>"+v.movement_type+"</td>";
html+="<td>"+(v.from_warehouse??v.from_type)+"</td>";
html+="<td>"+(v.to_warehouse??v.to_type)+"</td>";
html+="<td>"+v.notes+"</td>";
html+="</tr>";
});
$("#tableBarcode tbody").html(html);
}
let html5QrCode=null;
$("#btnCamera").click(function(){
$("#scannerArea").hide();
$("#cameraArea").show();
html5QrCode=new Html5Qrcode("reader");
html5QrCode.start(
{
facingMode:"environment"
},
{
fps:10,
qrbox:220
},
function(decodedText){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number")
.val(decodedText);
saveSerial();
});
}
);
});
$("#btnCloseCamera").click(function(){
if(html5QrCode){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number").focus();
});
}
});
</script>
+497 -75
View File
@@ -6,7 +6,7 @@
<h5>Data Barang</h5>
<div class="text-end">
<button class="btn btn-add bg-danger btn-out mr-2">Pengeluaran</button>
<button class="btn btn-warning btn-add btn-add-item mr-2">Tambah Barang</button>
<!-- <button class="btn btn-warning btn-add btn-add-item mr-2">Tambah Barang</button> -->
</div>
</div>
@@ -92,7 +92,7 @@
<!-- MODAL BARANAG KELUAR -->
<div class="modal fade" id="modalKeluar">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
@@ -101,6 +101,57 @@
<div class="modal-body">
<center>
<div id="scannerArea">
<input
type="text"
id="serial_number"
class="form-control form-control-lg text-center"
placeholder="Scan Barcode / Serial Number / Nama Barang"
autocomplete="off"
autofocus>
<small class="scan-note">
Scan atau ketik Barcode / Serial Number / Nama Barang, lalu Enter.
</small><br>
<button
type="button"
class="btn btn-warning btn-block camera-btn mt-4"
id="btnCamera">
<i class="fa fa-camera mr-2"></i>
Scan dengan Kamera
</button>
</div>
<div id="cameraArea" style="display:none;">
<div id="reader"></div>
<small class="scan-note d-block mt-2">
Arahkan QR Code ke kamera.
</small>
<button
type="button"
class="btn btn-secondary btn-block mt-3"
id="btnCloseCamera">
Tutup Kamera
</button>
</div>
</center>
<div style="max-height: 200px; overflow: auto;">
<div class="mt-2" id="tableBarcode"></div>
</div>
<hr>
<div id="group_account_biaya">
<label class="mt-2">Akun Biaya</label>
<select id="account_biaya" class="form-control"></select>
@@ -111,7 +162,7 @@
<input type="date" id="tanggal_keluar" class="form-control">
</div>
<div>
<!-- <div>
<label class="mt-2">Gudang</label>
<select id="warehouse_id_keluar" class="form-control"></select>
</div>
@@ -124,9 +175,8 @@
<div class="d-none" id="group_qty_keluar">
<label class="mt-2">Jumlah Barang</label>
<input type="number" id="qty_keluar" class="form-control">
</div>
<div class="mb-2 d-none" id="group_keterangan">
</div> -->
<div class="mb-2" id="group_keterangan">
<label for="keterangan" class="form-label">Keterangan</label>
<textarea class="form-control" id="keterangan_keluar" rows="3"></textarea>
</div>
@@ -195,6 +245,290 @@
<script>
// URUSAN BARCODE INPUT DAN CAMSCANNER
let daftarBarang = [];
let isSaving = false;
let searchTimer = null;
$(document).ready(function () {
$("#serial_number").focus();
$("#serial_number").on("keypress", function(e){
if(e.which==13){
e.preventDefault();
if(searchTimer){
clearTimeout(searchTimer);
}
if(!isSaving){
getBarcodeId(true);
}
}
});
$("#serial_number").on("input", function(){
let keyword = ($(this).val() || "").trim();
if(searchTimer){
clearTimeout(searchTimer);
}
if(keyword === ""){
daftarBarang = [];
renderTable();
return;
}
searchTimer = setTimeout(function(){
if(!isSaving){
getBarcodeId(false);
}
}, 300);
});
});
function getBarcodeId(isScanner = false){
let barcode = ($("#serial_number").val() || "").trim();
if(barcode==""){
return;
}
$.post(
"<?=base_url('items/get_barcode_item');?>",
{ barcode: barcode },
function(r){
if(!r.status){
Swal.fire({
icon:"error",
title:"Oops...",
text:r.message
});
$("#serial_number").val("").focus();
return;
}
let rows = Array.isArray(r.data) ? r.data : [];
if(rows.length === 0){
Swal.fire({
icon:"warning",
title:"Data kosong",
text:"Tidak ada barang yang cocok."
});
$("#serial_number").val("").focus();
return;
}
daftarBarang = rows;
renderTable();
if(isScanner){
$("#serial_number").val("").focus();
}
},
"json"
);
}
function renderTable() {
let html = '';
html += '<div class="table-responsive">';
html += '<table class="table table-bordered table-hover table-sm mb-0">';
html += '<thead>';
html += '<tr>';
html += '<th width="50">No</th>';
html += '<th width="180">Barcode</th>';
html += '<th width="120">Kode Barang</th>';
html += '<th>Nama Barang</th>';
html += '<th width="180">Serial Number</th>';
html += '<th width="70" class="text-center">Pilih</th>';
html += '</tr>';
html += '</thead>';
html += '<tbody>';
if (daftarBarang.length == 0) {
html += `
<tr>
<td colspan="6" class="text-center text-muted py-4">
Belum ada barang yang discan.
</td>
</tr>
`;
} else {
$.each(daftarBarang, function (i, v) {
html += `
<tr>
<td>${i + 1}</td>
<td><strong>${v.barcode}</strong></td>
<td>${v.kode_detail}</td>
<td>${v.nama_barang}</td>
<td>${v.serial_number ? v.serial_number : '-'}</td>
<td class="text-center align-middle">
<div class="form-check d-flex justify-content-center mb-0">
<input
class="form-check-input pilihBarang"
type="checkbox"
value="${v.barcode_id}"
data-barcode="${v.barcode}">
</div>
</td>
</tr>
`;
});
}
html += '</tbody>';
html += '</table>';
html += '</div>';
$('#tableBarcode').html(html);
if (daftarBarang.length === 1) {
$(".pilihBarang").prop("checked", false);
$(".pilihBarang").first().prop("checked", true);
}
}
// function renderTable(){
// let html='';
// html+='<div class="table-responsive">';
// html+='<table class="table table-bordered table-hover table-sm mb-0">';
// html+='<thead>';
// html+='<tr>';
// html+='<th width="50">No</th>';
// html+='<th width="180">Barcode</th>';
// html+='<th width="150">Kode Barang</th>';
// html+='<th>Nama Barang</th>';
// html+='<th width="180">Serial Number</th>';
// html+='<th width="80" class="text-center">Action</th>';
// html+='</tr>';
// html+='</thead>';
// html+='<tbody>';
// if(daftarBarang.length==0){
// html+='<tr>';
// html+='<td colspan="6" class="text-center text-muted py-4">';
// html+='Belum ada barcode yang discan.';
// html+='</td>';
// html+='</tr>';
// }else{
// $.each(daftarBarang,function(i,v){
// html+='<tr>';
// html+='<td>'+(i+1)+'</td>';
// html+='<td><strong>'+v.barcode+'</strong></td>';
// html+='<td>'+v.kode_detail+'</td>';
// html+='<td>'+v.nama_barang+'</td>';
// html+='<td>'+(v.serial_number?v.serial_number:'-')+'</td>';
// html+='<td class="text-center">';
// html+='<button class="btn btn-sm btn-outline-danger btnRemove" data-id="'+v.barcode_id+'">';
// html+='<i class="bi bi-trash"></i>';
// html+='</button>';
// html+='</td>';
// html+='</tr>';
// });
// }
// html+='</tbody>';
// html+='</table>';
// html+='</div>';
// $("#tableBarcode").html(html);
// }
$(document).on("change", ".pilihBarang", function(){
if($(this).is(":checked")){
$(".pilihBarang").not(this).prop("checked", false);
}
});
function getSelectedBarcodeRows(){
let selectedIds = $(".pilihBarang:checked").map(function(){
return String($(this).val());
}).get();
if(selectedIds.length === 0){
return [];
}
return daftarBarang.filter(function(v){
return selectedIds.indexOf(String(v.barcode_id)) !== -1;
});
}
let html5QrCode=null;
$("#btnCamera").click(function(){
$("#scannerArea").hide();
$("#cameraArea").show();
html5QrCode=new Html5Qrcode("reader");
html5QrCode.start(
{
facingMode:"environment"
},
{
fps:10,
qrbox:220
},
function(decodedText){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number")
.val(decodedText);
getBarcodeId();
});
}
);
});
$("#btnCloseCamera").click(function(){
if(html5QrCode){
html5QrCode.stop().then(function(){
$("#cameraArea").hide();
$("#scannerArea").show();
$("#serial_number").focus();
});
}
});
// URUSAN BARCODE INPUT DAN CAMSCANNER
$(function(){
let action = 'add';
@@ -278,20 +612,20 @@ loadGudang();
loadKas();
loadKode();
// ================= ADD =================
$('.btn-add-item').click(function(){
action='add';
resetForm();
// // ================= ADD =================
// $('.btn-add-item').click(function(){
// action='add';
// resetForm();
$('#qty').closest('div').show();
$('#kode_barang').closest('div').show();
$('#warehouse_id').closest('div').show();
$('#account_kas').closest('div').show();
$('#modalItem').modal('show');
loadGudang();
loadKas();
loadKode();
});
// $('#qty').closest('div').show();
// $('#kode_barang').closest('div').show();
// $('#warehouse_id').closest('div').show();
// $('#account_kas').closest('div').show();
// $('#modalItem').modal('show');
// loadGudang();
// loadKas();
// loadKode();
// });
// ================= EDIT =================
$(document).on('click','.btn-editItem',function(){
@@ -392,8 +726,9 @@ $('#btnSimpan').click(function(){
// ================= RESET FORM KELUAR =================
function resetFormKeluar(){
$('#tanggal_keluar').val('');
$('#qty_keluar').val('');
$('#keterangan_keluar').val('');
daftarBarang = [];
renderTable();
}
loadKasOut();
@@ -411,6 +746,12 @@ $('.btn-out').click(function(){
loadKasOut();
});
$('#modalKeluar').on('shown.bs.modal', function () {
setTimeout(function () {
$('#serial_number').trigger('focus').select();
}, 100);
});
$('#warehouse_id_keluar').on('change', function(){
let warehouse_id = $(this).val();
@@ -444,28 +785,41 @@ function loadItems(warehouse_id){
$('#btnKeluar').click(function(){
let btn = $(this);
let selectedRows = getSelectedBarcodeRows();
if(selectedRows.length !== 1){
Swal.fire({
icon: 'warning',
title: 'Pilih 1 barang',
text: 'Pengeluaran hanya boleh 1 barcode item per proses.'
});
return;
}
let selected = selectedRows[0];
let data = {
account_biaya: $('#account_biaya').val(),
tanggal_keluar: $('#tanggal_keluar').val(),
warehouse_id_keluar: $('#warehouse_id_keluar').val(),
barang_id: $('#barang_id').val(),
qty_keluar: $('#qty_keluar').val(),
keterangan_keluar: $('#keterangan_keluar').val()
account_biaya: $('#account_biaya').val(),
tanggal_keluar: $('#tanggal_keluar').val(),
keterangan_keluar: $('#keterangan_keluar').val(),
selected_barcode: selected.barcode_id
};
let url = "<?= base_url('items/keluarkan'); ?>";
// 🔥 loading state
btn.prop('disabled', true).html('Menyimpan...');
$.post(url, data, function(res){
if(res.status){
$('#modalItem').modal('hide');
$('#modalKeluar').modal('hide');
table.ajax.reload(null,false);
daftarBarang = [];
renderTable();
$("#serial_number").val("");
Swal.fire({
icon: 'success',
title: 'Berhasil',
@@ -502,68 +856,68 @@ $('#btnKeluar').click(function(){
// ================= END URUSAN KELUAR ===============
// =====================================================
// ================= DELETE =================
$(document).on('click','.btn-delete',function(){
// // ================= DELETE =================
// $(document).on('click','.btn-delete',function(){
let id = $(this).data('id');
let btn = $(this);
// let id = $(this).data('id');
// let btn = $(this);
Swal.fire({
title: 'Hapus data?',
text: "Data tidak bisa dikembalikan!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Ya, hapus!',
cancelButtonText: 'Batal'
}).then((result) => {
// Swal.fire({
// title: 'Hapus data?',
// text: "Data tidak bisa dikembalikan!",
// icon: 'warning',
// showCancelButton: true,
// confirmButtonColor: '#d33',
// cancelButtonColor: '#3085d6',
// confirmButtonText: 'Ya, hapus!',
// cancelButtonText: 'Batal'
// }).then((result) => {
if(result.isConfirmed){
// if(result.isConfirmed){
btn.prop('disabled', true).html('Menghapus...');
// btn.prop('disabled', true).html('Menghapus...');
$.get("<?= base_url('items/delete/'); ?>"+id,function(res){
// $.get("<?= base_url('items/delete/'); ?>"+id,function(res){
if(res.status){
// if(res.status){
table.ajax.reload(null,false);
// table.ajax.reload(null,false);
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: res.message || 'Data berhasil dihapus',
timer: 1500,
showConfirmButton: false
});
// Swal.fire({
// icon: 'success',
// title: 'Berhasil',
// text: res.message || 'Data berhasil dihapus',
// timer: 1500,
// showConfirmButton: false
// });
}else{
// }else{
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Gagal menghapus data'
});
}
// Swal.fire({
// icon: 'error',
// title: 'Gagal',
// text: res.message || 'Gagal menghapus data'
// });
// }
},'json')
.fail(function(){
// },'json')
// .fail(function(){
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Koneksi ke server gagal'
});
// Swal.fire({
// icon: 'error',
// title: 'Error',
// text: 'Koneksi ke server gagal'
// });
})
.always(function(){
btn.prop('disabled', false).html('Hapus');
});
// })
// .always(function(){
// btn.prop('disabled', false).html('Hapus');
// });
}
});
// }
// });
});
// });
// ================= DETAIL =================
$(document).on('click','.btn-detail',function(){
@@ -681,5 +1035,73 @@ $('#btnAdjust').click(function(){
});
// ================= PRINT BARCODE =================
$(document).on('click', '.btn-print', function () {
let id = $(this).data('id');
Swal.fire({
title: 'Cetak Barcode',
html: `
<div class="text-left">
<div class="form-group">
<label for="print_column" class="font-weight-bold">
Jumlah Kolom
</label>
<select id="print_column" class="form-control">
<option value="1">1 Kolom</option>
<option value="2">2 Kolom</option>
<option value="3" selected>3 Kolom (Rekomendasi)</option>
</select>
</div>
<div class="form-group mb-0">
<label for="duplicat" class="font-weight-bold">
Jumlah Cetak per Barcode
</label>
<select id="duplicat" class="form-control">
<option value="1" selected>1 Label</option>
<option value="2">2 Label</option>
</select>
</div>
</div>
`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Cetak',
cancelButtonText: 'Batal',
confirmButtonColor: '#f0ad4e',
preConfirm: () => {
let column = $('#print_column').val();
let duplicat = $('#duplicat').val();
return {
column: column,
duplicat: duplicat
};
}
}).then((result) => {
if (!result.isConfirmed) {
return;
}
window.open(
"<?= base_url('items/print_barcode/'); ?>" +
id +
"?column=" + result.value.column +
"&duplicat=" + result.value.duplicat,
"_blank"
);
});
});
});
</script>
+11 -7
View File
@@ -398,16 +398,20 @@ $('#btnSimpan').click(function(){
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()){
Swal.fire('Warning','Tanggal wajib diisi!','warning'); // ✅ FIX tambah validasi tanggal
Swal.fire('Warning','Tanggal tidak boleh kosong!','warning');
return;
}
if(!$('#keterangan').val()){
Swal.fire('Warning','Keterangan wajib diisi!','warning'); // ✅ FIX tambah validasi keterangan
return;
}
// lanjut simpan
$.post("<?= base_url('jurnal/save'); ?>", {
+40 -4
View File
@@ -16,6 +16,9 @@
<th>Kode</th>
<th>Nama</th>
<th>Limit Stok</th>
<th>Tracking Type</th>
<th>Need Serial</th>
<th>Unit</th>
<th>Aksi</th>
</tr>
</thead>
@@ -49,6 +52,25 @@
<label>Limit Stok</label>
<input type="number" id="limit_stock" class="form-control">
<label class="mt-2">Tracking Type</label>
<select id="tracking_type" class="form-control">
<option value="UNIT">UNIT</option>
<option value="QTY">QTY</option>
</select>
<label class="mt-2">Need Serial Number</label>
<select id="need_serial_number" class="form-control">
<option value="false">Tidak</option>
<option value="true">Ya</option>
</select>
<label class="mt-2">Unit</label>
<select id="unit" class="form-control">
<option value="PCS">PCS</option>
<option value="Meter">Meter</option>
<option value="Lembar">Lembar</option>
</select>
</div>
<div class="modal-footer">
@@ -77,6 +99,9 @@ $(function(){
$('#kode').val('');
$('#nama').val('');
$('#limit_stock').val('');
$('#tracking_type').val('UNIT');
$('#need_serial_number').val('false');
$('#unit').val('PCS');
}
// ADD
@@ -96,6 +121,9 @@ $(function(){
$('#kode').val(res.kode_barang);
$('#nama').val(res.nama);
$('#limit_stock').val(res.limit_stock);
$('#tracking_type').val((res.tracking_type || 'UNIT').toUpperCase());
$('#need_serial_number').val((res.need_serial_number || 'false').toLowerCase());
$('#unit').val(res.unit || 'PCS');
action = 'edit';
$('#modalKodebarang').modal('show');
@@ -110,11 +138,19 @@ $(function(){
id: $('#id').val(),
kode: $('#kode').val(),
nama: $('#nama').val(),
limit_stock: $('#limit_stock').val()
limit_stock: $('#limit_stock').val(),
tracking_type: $('#tracking_type').val(),
need_serial_number: $('#need_serial_number').val(),
unit: $('#unit').val()
};
if(!data.kode || !data.nama || !data.limit_stock){
Swal.fire('Warning','Nama gudang wajib diisi','warning');
if(!data.kode || !data.nama){
Swal.fire('Warning','Kode dan nama barang wajib diisi','warning');
return;
}
if(data.limit_stock === '' || parseInt(data.limit_stock) < 0){
Swal.fire('Warning','Limit stok wajib diisi dan tidak boleh negatif','warning');
return;
}
@@ -141,7 +177,7 @@ $(function(){
let id = $(this).data('id');
Swal.fire({
title:'Hapus gudang?',
title:'Hapus kode barang?',
text:'Pastikan tidak dipakai item',
icon:'warning',
showCancelButton:true
+41 -1
View File
@@ -16,10 +16,13 @@
<input type="date" id="tanggal_sampai" class="form-control">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="col-md-4 d-flex align-items-end gap-2">
<button type="submit" class="btn btn-warning w-100">
Tampilkan
</button>
<button type="button" class="btn btn-danger w-100" id="generate_pdf">
Generate PDF
</button>
</div>
</form>
@@ -168,5 +171,42 @@ $(function(){
loadData(dari, sampai);
});
$('#generate_pdf').on('click', function () {
const tanggal_dari = $('#tanggal_dari').val() || '0000-00-00';
const tanggal_sampai = $('#tanggal_sampai').val() || '0000-00-00';
let btn = $(this);
btn.prop('disabled', true).html('Generate PDF...');
$.ajax({
url: "<?= base_url('generatelabarugi/generatepdf'); ?>"
+ '/' + tanggal_dari
+ '/' + tanggal_sampai,
type: "GET",
xhrFields: {
responseType: 'blob' // penting untuk PDF
},
success: function (response) {
let blob = new Blob([response], { type: 'application/pdf' });
let url = window.URL.createObjectURL(blob);
// buka di tab baru
window.open(url, '_blank');
},
error: function () {
Swal.fire('Error', 'Gagal generate PDF', 'error');
},
complete: function(){
btn.prop('disabled', false).html('Generate PDF');
}
});
});
});
</script>
+53 -3
View File
@@ -18,10 +18,12 @@
<input type="date" name="tanggal_sampai" id="tanggal_sampai" class="form-control">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="col-md-4 d-flex align-items-end gap-2">
<button type="submit" class="btn btn-warning w-100">
Tampilkan
</button>
</button><br>
<button type="button" class="btn btn-danger w-100" id="generate_pdf">
<i class="fa fa-print"></i>Generate PDF</button>
</div>
</form>
@@ -76,6 +78,7 @@
</div>
<script>
$(function(){
function loadData(tanggal_dari = '', tanggal_sampai = ''){
@@ -85,6 +88,7 @@ $(function(){
tanggal_sampai: tanggal_sampai
}, function(res){
let act = '', pas = '';
// ================= ACTIVA
@@ -134,18 +138,64 @@ $(function(){
},'json');
}
// ================= LOAD AWAL
loadData();
// ================= SUBMIT FILTER
$('#filterForm').on('submit', function(e){
e.preventDefault();
let dari = $('#tanggal_dari').val();
let sampai = $('#tanggal_sampai').val();
loadData(dari, sampai);
});
// Prevent double submit
$(document).on('submit', 'form', function(e){
e.preventDefault();
});
$('#generate_pdf').on('click', function () {
const tanggal_dari = $('#tanggal_dari').val() || '0000-00-00';
const tanggal_sampai = $('#tanggal_sampai').val() || '0000-00-00';
let btn = $(this);
btn.prop('disabled', true).html('Generate PDF...');
$.ajax({
url: "<?= base_url('generateneraca/generatepdf'); ?>"
+ '/' + tanggal_dari
+ '/' + tanggal_sampai,
type: "GET",
xhrFields: {
responseType: 'blob' // penting untuk PDF
},
success: function (response) {
let blob = new Blob([response], { type: 'application/pdf' });
let url = window.URL.createObjectURL(blob);
// buka di tab baru
window.open(url, '_blank');
},
error: function () {
Swal.fire('Error', 'Gagal generate PDF', 'error');
},
complete: function(){
btn.prop('disabled', false).html('Generate PDF');
}
});
});
</script>
});
</script>
+89 -23
View File
@@ -3,20 +3,49 @@
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0 fw-bold">Neraca Saldo</h4>
<h4 class="mb-0 fw-bold">Laporan Keuangan</h4>
</div>
<div class="row mb-3">
<div class="col-md-3">
<label class="form-label">Tanggal Awal</label>
<input type="date" id="tanggal_awal" class="form-control"
value="<?= date('Y-m-01'); ?>">
</div>
<div class="col-md-3">
<label class="form-label">Tanggal Akhir</label>
<input type="date" id="tanggal_akhir" class="form-control"
value="<?= date('Y-m-t'); ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-2">
<button class="btn btn-warning w-100" id="btnFilter">
Tampilkan
</button>
<button type="button" class="btn btn-danger" id="generate_pdf">
PDF
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-sm table-striped table-hover align-middle">
<thead class="table-light">
<tr>
<th style="width: 120px;">Kode</th>
<th width="120">Kode</th>
<th>Akun</th>
<th class="text-end">Debit</th>
<th class="text-end">Kredit</th>
</tr>
</thead>
<tbody id="tb"></tbody>
<tfoot class="table-light">
<tr class="fw-bold">
<th colspan="2" class="text-end">Total</th>
@@ -24,6 +53,7 @@
<th class="text-end text-danger" id="tk"></th>
</tr>
</tfoot>
</table>
</div>
@@ -32,35 +62,71 @@
</div>
<script>
$(function(){
$(function () {
$.get("<?= base_url('neracasaldo/get_data'); ?>", function(res){
loadData();
let html = '';
let td = 0, tk = 0;
$('#btnFilter').click(function () {
loadData();
});
res.forEach(r=>{
function loadData() {
let debit = parseFloat(r.saldo_debit) || 0;
let kredit = parseFloat(r.saldo_kredit) || 0;
$.get("<?= base_url('neracasaldo/get_data'); ?>", {
td += debit;
tk += kredit;
tanggal_awal: $('#tanggal_awal').val(),
tanggal_akhir: $('#tanggal_akhir').val()
html += `
<tr>
<td class="fw-semibold text-muted">${r.kode_akun}</td>
<td>${r.nama_akun}</td>
<td class="text-end">${debit.toLocaleString()}</td>
<td class="text-end">${kredit.toLocaleString()}</td>
</tr>`;
});
}, function (res) {
$('#tb').html(html);
$('#td').text(td.toLocaleString());
$('#tk').text(tk.toLocaleString());
let html = '';
let td = 0;
let tk = 0;
}, 'json');
res.forEach(r => {
let debit = parseFloat(r.debit) || 0;
let kredit = parseFloat(r.kredit) || 0;
td += debit;
tk += kredit;
html += `
<tr>
<td class="fw-semibold text-muted">${r.kode_akun}</td>
<td>${r.nama_akun}</td>
<td class="text-end">${debit.toLocaleString()}</td>
<td class="text-end">${kredit.toLocaleString()}</td>
</tr>
`;
});
$('#tb').html(html);
$('#td').text(td.toLocaleString());
$('#tk').text(tk.toLocaleString());
}, 'json');
}
$('#generate_pdf').click(function () {
let btn = $(this);
btn.prop('disabled', true).text('Generate PDF...');
window.open(
"<?= base_url('generateneracasaldo/generatepdf'); ?>" +
"?tanggal_awal=" + $('#tanggal_awal').val() +
"&tanggal_akhir=" + $('#tanggal_akhir').val(),
"_blank"
);
setTimeout(function () {
btn.prop('disabled', false).text('Generate PDF');
}, 1000);
});
});
</script>
+28 -7
View File
@@ -38,6 +38,8 @@
<link href="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.0.13/dist/js/select2.min.js"></script>
<!-- Camera Scanner -->
<script src="https://unpkg.com/html5-qrcode"></script>
</head>
<body>
@@ -82,15 +84,15 @@
<i class="bi bi-graph-up"></i> Laporan
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item <?= ($active_menu == 'neraca_saldo') ? 'active' : '' ?>" href="<?= base_url('neracasaldo'); ?>">
<i class="bi bi-clipboard-data me-2"></i> Neraca Saldo</a>
</li>
<li><a class="dropdown-item <?= ($active_menu == 'laba_rugi') ? 'active' : '' ?>" href="<?= base_url('labarugi'); ?>">
<i class="bi bi-graph-up-arrow me-2"></i> Laba Rugi</a>
</li>
<li><a class="dropdown-item <?= ($active_menu == 'neraca') ? 'active' : '' ?>" href="<?= base_url('neraca'); ?>">
<i class="bi bi-bar-chart-line me-2"></i> Neraca</a>
</li>
<li><a class="dropdown-item <?= ($active_menu == 'neraca_saldo') ? 'active' : '' ?>" href="<?= base_url('neracasaldo'); ?>">
<i class="bi bi-clipboard-data me-2"></i> Laporan Harian</a>
</li>
</ul>
</li>
@@ -156,7 +158,7 @@
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle <?= in_array($active_menu, ['warehouses','items', 'kode_barang', 'draft_items']) ? 'active' : '' ?>" href="#" data-bs-toggle="dropdown">
<a class="nav-link dropdown-toggle <?= in_array($active_menu, ['warehouses','items', 'kode_barang', 'draft_items', 'peralatan_teknisi']) ? 'active' : '' ?>" href="#" data-bs-toggle="dropdown">
<i class="bi bi-box-seam"></i> Gudang
</a>
<ul class="dropdown-menu">
@@ -178,11 +180,23 @@
<i class="bi bi-box me-2"></i> Daftar Barang
</a>
</li><hr class="dropdown-divider">
<li>
<a class="dropdown-item <?= ($active_menu == 'item_historys') ? 'active' : '' ?>"
href="<?= base_url('items/item_barcodes_history'); ?>">
<i class="bi bi-clock me-2"></i> History Barang
</a>
</li>
<li>
<a class="dropdown-item <?= ($active_menu == 'kode_barang') ? 'active' : '' ?>"
href="<?= base_url('kodebarang'); ?>">
<i class="bi bi-code me-2"></i> Daftar Kode Barang
</a>
</li><hr class="dropdown-divider">
<li>
<a class="dropdown-item <?= ($active_menu == 'peralatan_teknisi') ? 'active' : '' ?>"
href="<?= base_url('teknisi'); ?>">
<i class="bi bi-tools me-2"></i> Peralatan Teknisi
</a>
</li>
</ul>
</li>
@@ -258,17 +272,24 @@
</li>
<li>
<a class="dropdown-item rounded-3 <?= ($active_menu == 'attendance_monitoring') ? 'active' : '' ?>"
href="<?= base_url('attendancemonitoring'); ?>">
<i class="bi bi-fingerprint me-2"></i>
Monitoring Absensi
</a>
</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">
<li>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,155 @@
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.4.1/css/responsive.bootstrap5.min.css">
<style>
:root {
--tech-primary: #ff9800;
--tech-primary-light: #fff3e0;
--tech-primary-dark: #ff9800;
}
body {
background: #f5f7fb;
}
.tech-card {
border: none;
border-radius: 14px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
background: #fff;
}
.tech-card .card-header-custom {
background: linear-gradient(135deg, var(--tech-primary), var(--tech-primary-dark));
color: #fff;
padding: 14px 20px;
font-weight: 600;
font-size: 15px;
}
.tech-card .card-body {
padding: 18px;
}
.table-tech thead th {
background: #f8f9fa;
font-weight: 600;
font-size: 12px;
text-transform: uppercase;
letter-spacing: .4px;
color: #495057;
border-bottom: 2px solid #ffcc80;
padding: 10px 8px;
white-space: nowrap;
}
.table-tech tbody td {
padding: 8px;
vertical-align: middle;
font-size: 13px;
}
.table-tech td .badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 65px;
padding: 0.35em 0.65em;
margin: 0 auto;
}
.table-tech tbody tr:hover {
background: #fff3e0;
}
</style>
<div class="container-fluid px-3 mt-3">
<div class="d-flex align-items-center gap-2 mb-3">
<div class="bg-warning text-white p-2 rounded-3 d-flex align-items-center justify-content-center" style="width:38px;height:38px;">
<i class="bi bi-journal-text fs-5"></i>
</div>
<div class="flex-grow-1">
<h5 class="mb-0 fw-bold" style="color:#1a1a2e;">Log Barang Teknisi</h5>
<small class="text-muted">Riwayat barang yang pernah dibawa oleh teknisi</small>
</div>
<div>
<a href="<?= base_url('teknisi'); ?>" class="btn btn-secondary">
<i class="bi bi-arrow-left me-1"></i> Kembali
</a>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card tech-card">
<div class="card-body">
<div class="table-responsive">
<table class="table table-tech table-striped w-100" id="tblLogBarang">
<thead>
<tr>
<th>No</th>
<th>Nama Teknisi</th>
<th>Kode Barang</th>
<th>Barcode</th>
<th>Nama Barang</th>
<th>Tanggal Dibawa</th>
<th>Tanggal Kembali</th>
<th>Status</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(function(){
$('#tblLogBarang').DataTable({
processing: true,
serverSide: false,
ajax: {
url: "<?= base_url('teknisi/get_log_data'); ?>",
dataSrc: 'data'
},
columns: [
{ data: 0 },
{ data: 1 },
{ data: 2 },
{ data: 3 },
{ data: 4 },
{ data: 5 },
{ data: 6 },
{ data: 7 }
],
pageLength: 25,
lengthMenu: [
[25, 50, 100, -1],
[25, 50, 100, 'All']
],
responsive: true,
searching: true,
paging: true,
ordering: true,
info: true,
language: {
search: "Cari:",
lengthMenu: "Tampilkan _MENU_ data",
info: "Menampilkan _START_ - _END_ dari _TOTAL_ data",
zeroRecords: "Tidak ada data",
paginate: {
first: "Awal",
last: "Akhir",
next: "&raquo;",
previous: "&laquo;"
}
}
});
});
</script>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB