Files
hedena_gold/apps/api/src/modules/pricing/wage.controller.ts
T
s.esmaeiliandClaude Opus 5.5 699657e483 feat(pricing): the server prices app orders with legacy's making-charge formula (MON-08)
Legacy: final % = variant % + customer % − the largest active promotion
on the product, its category or its brand; rial charge 0. The apps sent
the variant's own % only, so the new app undercharged by the customer's
percent (usually 3) and ignored promotions, and the server trusted
whatever the app sent.

- pricing/wage-formula.ts: one formula, legacy by default, with
  coefficients, promotion pick, constant, floor/cap and rounding so the
  admin screen can change it; validated
- an app order is priced by it; the app's wage, discount and rial charge
  are ignored; an operator's manual numbers are kept
- POST customer/me/wage-quote; both apps show that figure
- admin/pricing/wage-formula (config.wage): read, preview, approve with
  the sample it was checked on, last 20 versions kept; the setting is
  kept off the generic const routes

Owner approval: 2026-10-08.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:26:55 +03:30

59 lines
2.0 KiB
TypeScript

import { Body, Controller, Get, HttpCode, Post, Put } from '@nestjs/common';
import { ArrayMaxSize, IsArray, IsString } from 'class-validator';
import { CurrentAdmin, RequirePermission } from '../auth/guards';
import type { AuthenticatedAdmin } from '../auth/jwt.strategy';
import { CurrentCustomer, CustomerAuth } from '../customer-auth/customer.guards';
import type { AuthenticatedCustomer } from '../customer-auth/customer-jwt.strategy';
import { WageService } from './wage.service';
/**
* «اجرت» admin screen — the making-charge formula, a worked sample, and the
* approval that puts a new formula in force. Behind its own rid.
*/
@Controller('admin/pricing/wage-formula')
@RequirePermission('config.wage')
export class WageAdminController {
constructor(private readonly wage: WageService) {}
@Get()
get() {
return this.wage.getConfig();
}
/** Nothing is saved: the draft and the formula in force on the same sample. */
@Post('preview')
@HttpCode(200)
preview(@Body() body: { formula?: unknown; sample?: unknown }) {
return this.wage.preview(body?.formula, body?.sample);
}
/** Puts the formula in force, recorded with the sample it was checked on. */
@Put()
approve(@Body() body: { formula?: unknown; sample?: unknown }, @CurrentAdmin() admin: AuthenticatedAdmin) {
return this.wage.approve(body?.formula, body?.sample, admin);
}
}
class WageQuoteDto {
@IsArray()
@ArrayMaxSize(200)
@IsString({ each: true })
variantIds!: string[];
}
/**
* The making charge the app shows on a product and in the cart — computed by
* the same code that prices the order, for the signed-in customer.
*/
@Controller('customer/me/wage-quote')
export class WageCustomerController {
constructor(private readonly wage: WageService) {}
@CustomerAuth()
@Post()
@HttpCode(200)
async quote(@Body() dto: WageQuoteDto, @CurrentCustomer() me: AuthenticatedCustomer) {
return [...(await this.wage.quote(me.id, dto.variantIds)).values()];
}
}