Compare commits

...

9 Commits

Author SHA1 Message Date
0314e87c3f remove patch file 2026-06-17 20:46:15 +05:00
32331d4cf8 Merge branch 'master' of ssh://gitea.rareobj.com:222/dab/avtoambor 2026-06-17 20:41:13 +05:00
a85979731f use tajik timezone for displaying dates 2026-06-17 20:40:43 +05:00
45ef55e13e fix favicon 2026-06-17 18:43:05 +05:00
0000748fe0 add favicon 2026-06-17 13:40:19 +00:00
a287d26b93 fix gitignore and install bat 2026-06-17 18:06:29 +05:00
d4cba18017 Merge branch 'master' of https://gitea.rareobj.com/dab/avtoambor 2026-06-17 18:05:04 +05:00
66e15dee1f change default unit to liter
Since the inventory is mostly containing enginge oils, which are sold per liter, the default unit for a new part is set to liter
2026-05-23 16:43:39 +05:00
aac71becfc remove the invetory sale value from report
Since product prices can be negotiated with every client, the inventory value at sale cannot be determined. Removing the number from the report but keeping the placeholder.
2026-05-23 16:16:49 +05:00
12 changed files with 35 additions and 41 deletions

3
.gitignore vendored
View File

@ -12,4 +12,5 @@ backups/
*.sw? *.sw?
.session.vim .session.vim
.claude/settings.local.json .claude/settings.local.json
~* *~
/*.bat

View File

@ -6,16 +6,6 @@ chcp 65001 >nul
setlocal setlocal
cd /d "%~dp0" cd /d "%~dp0"
if exist "%ProgramFiles%\nodejs\node.exe" (
echo Node.js уже установлен в %ProgramFiles%\nodejs.
echo Пропускаем установку.
goto :done
)
if exist "%ProgramFiles(x86)%\nodejs\node.exe" (
echo Node.js уже установлен в %ProgramFiles(x86)%\nodejs.
echo Пропускаем установку.
goto :done
)
set "MSI=node-v16.20.2-x64.msi" set "MSI=node-v16.20.2-x64.msi"
if /i "%PROCESSOR_ARCHITECTURE%"=="x86" if not defined PROCESSOR_ARCHITEW6432 set "MSI=node-v16.20.2-x86.msi" if /i "%PROCESSOR_ARCHITECTURE%"=="x86" if not defined PROCESSOR_ARCHITEW6432 set "MSI=node-v16.20.2-x86.msi"

View File

@ -146,7 +146,6 @@
"active_skus": "Active parts", "active_skus": "Active parts",
"units_on_hand": "Units on hand", "units_on_hand": "Units on hand",
"cost_value": "Value (at cost)", "cost_value": "Value (at cost)",
"sale_value": "Value (at sale)",
"low_stock": "Low stock", "low_stock": "Low stock",
"out_of_stock": "Out of stock", "out_of_stock": "Out of stock",
"top_parts": "Top selling parts", "top_parts": "Top selling parts",

View File

@ -85,3 +85,20 @@ export function formatMoney(dirams, lang = 'en') {
const s = n.toFixed(2); const s = n.toFixed(2);
return lang === 'tg' ? s.replace('.', ',') : s; return lang === 'tg' ? s.replace('.', ',') : s;
} }
export function formatTs(utcStr) {
if (!utcStr) return '';
const normalized = String(utcStr).trim().replace(' ', 'T');
const utcDate = new Date(`${normalized}Z`);
if (Number.isNaN(utcDate.getTime())) return utcStr;
const tajikDate = new Date(utcDate.getTime() + 5 * 60 * 60 * 1000);
const pad = (n) => String(n).padStart(2, '0');
return [
pad(tajikDate.getUTCDate()),
pad(tajikDate.getUTCMonth() + 1),
tajikDate.getUTCFullYear()
].join('.') + ` ${pad(tajikDate.getUTCHours())}:${pad(tajikDate.getUTCMinutes())}`;
}

View File

@ -146,7 +146,6 @@
"active_skus": "Қисмҳои фаъол", "active_skus": "Қисмҳои фаъол",
"units_on_hand": "Дар анбор", "units_on_hand": "Дар анбор",
"cost_value": "Арзиш (бо нархи харид)", "cost_value": "Арзиш (бо нархи харид)",
"sale_value": "Арзиш (бо нархи фурӯш)",
"low_stock": "Захираи кам", "low_stock": "Захираи кам",
"out_of_stock": "Тамом шуд", "out_of_stock": "Тамом шуд",
"top_parts": "Қисмҳои серфурӯш", "top_parts": "Қисмҳои серфурӯш",

View File

@ -1,6 +1,6 @@
import { getDb } from './db.js'; import { getDb } from './db.js';
// All time windows are computed in local time using SQLite's `datetime('now', 'localtime')`. // All time windows are computed in Tajikistan time (UTC+5) while timestamps are stored as UTC.
// Cost of goods (COG) and profit are computed against each part's current cost_price — // Cost of goods (COG) and profit are computed against each part's current cost_price —
// the schema does not snapshot cost at sale time, so historical cost changes are not // the schema does not snapshot cost at sale time, so historical cost changes are not
// reflected. Custom (non-inventory) lines contribute to sale revenue but have zero COG. // reflected. Custom (non-inventory) lines contribute to sale revenue but have zero COG.
@ -29,9 +29,9 @@ function windowStats(dateClause) {
export function salesSummary() { export function salesSummary() {
return { return {
all_time: windowStats(''), all_time: windowStats(''),
today: windowStats(`date(saved_at, 'localtime') = date('now', 'localtime')`), today: windowStats(`date(saved_at, '+5 hours') = date('now', '+5 hours')`),
week: windowStats(`date(saved_at, 'localtime') >= date('now', 'localtime', '-6 days')`), week: windowStats(`date(saved_at, '+5 hours') >= date('now', '+5 hours', '-6 days')`),
month: windowStats(`strftime('%Y-%m', saved_at, 'localtime') = strftime('%Y-%m', 'now', 'localtime')`) month: windowStats(`strftime('%Y-%m', saved_at, '+5 hours') = strftime('%Y-%m', 'now', '+5 hours')`)
}; };
} }

View File

@ -1,5 +1,5 @@
<script> <script>
import { locale, t, localized } from '$lib/i18n/store.js'; import { locale, t, localized, formatTs } from '$lib/i18n/store.js';
export let data; export let data;
$: lang = $locale; $: lang = $locale;
@ -48,7 +48,7 @@
<tbody> <tbody>
{#each movements as m} {#each movements as m}
<tr> <tr>
<td>{m.created_at}</td> <td>{formatTs(m.created_at)}</td>
<td><span class="pill">{$t('movements.type_' + m.movement_type)}</span></td> <td><span class="pill">{$t('movements.type_' + m.movement_type)}</span></td>
<td><a href="/parts/{m.part_id}">{localized(m, 'name', lang)}</a></td> <td><a href="/parts/{m.part_id}">{localized(m, 'name', lang)}</a></td>
<td class="num">{m.quantity}</td> <td class="num">{m.quantity}</td>

View File

@ -1,16 +1,10 @@
<script> <script>
import { locale, t, localized, formatMoney } from '$lib/i18n/store.js'; import { locale, t, localized, formatMoney, formatTs } from '$lib/i18n/store.js';
export let data; export let data;
$: lang = $locale; $: lang = $locale;
$: ({ sales, topParts, inventory, recentSales } = data); $: ({ sales, topParts, inventory, recentSales } = data);
function formatWhen(iso) {
if (!iso) return '';
const d = new Date(iso.replace(' ', 'T') + 'Z');
const pad = (n) => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
</script> </script>
<h2>{$t('reports.sales_heading')}</h2> <h2>{$t('reports.sales_heading')}</h2>
@ -56,13 +50,7 @@
<span class="cur">{$t('common.currency_short')}</span> <span class="cur">{$t('common.currency_short')}</span>
</div> </div>
</div> </div>
<div class="card stat"> <div></div>
<div class="label">{$t('reports.sale_value')}</div>
<div class="value">
{formatMoney(inventory.sale_value_dirams, lang)}
<span class="cur">{$t('common.currency_short')}</span>
</div>
</div>
<div class="card stat"> <div class="card stat">
<div class="label">{$t('reports.low_stock')}</div> <div class="label">{$t('reports.low_stock')}</div>
<div class="value" class:warn={inventory.lowStockCount > 0}>{inventory.lowStockCount}</div> <div class="value" class:warn={inventory.lowStockCount > 0}>{inventory.lowStockCount}</div>
@ -128,7 +116,7 @@
<tbody> <tbody>
{#each recentSales as s} {#each recentSales as s}
<tr> <tr>
<td>{formatWhen(s.saved_at)}</td> <td>{formatTs(s.saved_at)}</td>
<td class="num">{s.line_count}</td> <td class="num">{s.line_count}</td>
<td class="num"> <td class="num">
{formatMoney(s.sale_dirams, lang)} {formatMoney(s.sale_dirams, lang)}

View File

@ -1,5 +1,5 @@
<script> <script>
import { locale, t, localized, formatMoney } from '$lib/i18n/store.js'; import { locale, t, localized, formatMoney, formatTs } from '$lib/i18n/store.js';
export let data; export let data;
$: lang = $locale; $: lang = $locale;
@ -15,7 +15,7 @@
<header class="head"> <header class="head">
<div> <div>
<h1>{$t('invoices.saved_title')} #{invoice.id}</h1> <h1>{$t('invoices.saved_title')} #{invoice.id}</h1>
<p class="muted">{invoice.saved_at}</p> <p class="muted">{formatTs(invoice.saved_at)}</p>
</div> </div>
<a href="/invoices/new" class="print-hide back">{$t('invoices.new_another')}</a> <a href="/invoices/new" class="print-hide back">{$t('invoices.new_another')}</a>
</header> </header>

View File

@ -1,6 +1,6 @@
<script> <script>
import { enhance } from '$app/forms'; import { enhance } from '$app/forms';
import { locale, t, localized, formatMoney } from '$lib/i18n/store.js'; import { locale, t, localized, formatMoney, formatTs } from '$lib/i18n/store.js';
export let data; export let data;
export let form; export let form;
@ -117,8 +117,8 @@
{$t('parts.reorder_level')}: {part.reorder_level} {$t('parts.reorder_level')}: {part.reorder_level}
</div> </div>
<hr /> <hr />
<div class="muted small">{$t('common.created')}: {part.created_at}</div> <div class="muted small">{$t('common.created')}: {formatTs(part.created_at)}</div>
<div class="muted small">{$t('common.updated')}: {part.updated_at}</div> <div class="muted small">{$t('common.updated')}: {formatTs(part.updated_at)}</div>
</div> </div>
<h2>{$t('parts.recent_movements')}</h2> <h2>{$t('parts.recent_movements')}</h2>
@ -137,7 +137,7 @@
<tbody> <tbody>
{#each movements as m} {#each movements as m}
<tr> <tr>
<td>{m.created_at}</td> <td>{formatTs(m.created_at)}</td>
<td><span class="pill">{$t('movements.type_' + m.movement_type)}</span></td> <td><span class="pill">{$t('movements.type_' + m.movement_type)}</span></td>
<td class="num">{m.quantity > 0 ? '+' : ''}{m.quantity}</td> <td class="num">{m.quantity > 0 ? '+' : ''}{m.quantity}</td>
<td class="num">{m.unit_price != null ? formatMoney(m.unit_price, lang) : $t('common.none')}</td> <td class="num">{m.unit_price != null ? formatMoney(m.unit_price, lang) : $t('common.none')}</td>

View File

@ -54,7 +54,7 @@
<div class="row"> <div class="row">
<label> <label>
{$t('parts.unit')} {$t('parts.unit')}
<input name="unit" value={values.unit ?? 'pcs'} /> <input name="unit" value={values.unit ?? 'liter'} />
</label> </label>
<label> <label>
{$t('parts.reorder_level')} {$t('parts.reorder_level')}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB